feat(gui): Phase A — read-only Streamlit dashboard (Status + Audit)
Implements the foundation of the local observation dashboard described in docs/11-gui-streamlit.md: * gui/data_layer.py — read-only wrappers over Repository (system_state, open positions) and audit_log (tail iteration, chain verify). The GUI never imports runtime/ nor calls MCP services. * gui/main.py — Streamlit entry point with sidebar (engine health badge, kill switch banner, last health check age), home overview. * gui/pages/1_📊_Status.py — engine status with colored health banner, kill switch detail, audit anchor, open positions table. * gui/pages/2_🔍_Audit.py — live audit log stream (newest-first), event filters, hash-chain integrity verify button. * cli.py gui — replaces the placeholder with os.execvpe to `python -m streamlit run` bound to 127.0.0.1, --browser.gatherUsageStats false; --db / --audit paths exported via env to the GUI process. * pyproject.toml — N999 ignore for src/cerbero_bite/gui/pages/* (Streamlit auto-discovers pages whose filename contains numbers and emoji icons). Smoke test: GUI launches, HTTP 200 on / and /_stcore/health, data layer correctly reflects current testnet state (engine=running, kill_switch disarmed, 0 open positions, audit chain integra 7 entries). 353/353 tests still pass; ruff clean; mypy strict src clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+65
-3
@@ -10,6 +10,7 @@ without changing the surface.
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
from collections.abc import Callable
|
||||
from datetime import UTC, datetime
|
||||
@@ -580,9 +581,70 @@ async def _ping_all(
|
||||
|
||||
|
||||
@main.command()
|
||||
def gui() -> None:
|
||||
"""Launch the Streamlit dashboard."""
|
||||
_phase0_notice("gui command not yet implemented (will run streamlit on 127.0.0.1:8765).")
|
||||
@click.option(
|
||||
"--db",
|
||||
type=click.Path(path_type=Path),
|
||||
default=_DEFAULT_DB_PATH,
|
||||
show_default=True,
|
||||
help="SQLite state file the dashboard reads.",
|
||||
)
|
||||
@click.option(
|
||||
"--audit",
|
||||
type=click.Path(path_type=Path),
|
||||
default=_DEFAULT_AUDIT_PATH,
|
||||
show_default=True,
|
||||
help="Audit log file the dashboard streams.",
|
||||
)
|
||||
@click.option(
|
||||
"--port",
|
||||
type=int,
|
||||
default=8765,
|
||||
show_default=True,
|
||||
help="Local port to bind (always 127.0.0.1).",
|
||||
)
|
||||
@click.option(
|
||||
"--headless/--no-headless",
|
||||
default=True,
|
||||
show_default=True,
|
||||
help="When true, do not auto-open the browser.",
|
||||
)
|
||||
def gui(db: Path, audit: Path, port: int, headless: bool) -> None:
|
||||
"""Launch the Streamlit dashboard (read-only, localhost only)."""
|
||||
try:
|
||||
import streamlit # noqa: F401, PLC0415
|
||||
except ImportError:
|
||||
click.echo(
|
||||
"streamlit not installed. Run `uv sync --extra gui` first.",
|
||||
err=True,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
main_path = Path(__file__).parent / "gui" / "main.py"
|
||||
if not main_path.is_file():
|
||||
click.echo(f"GUI entry point not found: {main_path}", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
env = os.environ.copy()
|
||||
env["CERBERO_BITE_GUI_DB"] = str(db.resolve())
|
||||
env["CERBERO_BITE_GUI_AUDIT"] = str(audit.resolve())
|
||||
|
||||
cmd = [
|
||||
sys.executable,
|
||||
"-m",
|
||||
"streamlit",
|
||||
"run",
|
||||
str(main_path),
|
||||
"--server.address",
|
||||
"127.0.0.1",
|
||||
"--server.port",
|
||||
str(port),
|
||||
"--server.headless",
|
||||
"true" if headless else "false",
|
||||
"--browser.gatherUsageStats",
|
||||
"false",
|
||||
]
|
||||
click.echo(f"Launching GUI on http://127.0.0.1:{port} …")
|
||||
os.execvpe(cmd[0], cmd, env)
|
||||
|
||||
|
||||
@main.command()
|
||||
|
||||
Reference in New Issue
Block a user