Starlette + WebSocket dashboard run on the Pi as cis490-dashboard.service
(127.0.0.1:8447, Caddy-fronted at dashboard.wg). Tails
/var/lib/cis490/index.jsonl for episode events, snapshots host counts
every 30s, broadcasts to every connected browser. New connections get a
warm snapshot (recent_episodes, total_bytes, host_counts) so reloads
don't see a cold dashboard.
Frontend is a 10-scene scrollytelling deck following the project
outline: intro, collect, hosts, db explorer, baseline, attacks,
chunking, models, knn, perf. Sticky full-bleed canvas with a
right-aligned prose column (matrix-explorable layout). Hotkeys (arrows,
space, j/k, c, Home/End), prev/next chevrons, FAB, and an opt-in
click-to-advance toggle. Demo toggle drives synthetic data for the
five scenes that have no real producer yet (attack envelopes,
chunking, model bars, knn scatter, perf scatter); when off, those
scenes show "awaiting <event_type> events" rather than fake data.
Producers wire in by POSTing typed JSON to 127.0.0.1:8447/publish
(loopback only; Caddy 404s it externally). Event types the widgets
subscribe to: model_metric {model, accuracy}, embedding {x, y, phase},
model_perf {model, latency_us, accuracy}, prediction {episode_id,
window_idx, predicted, actual}, attack_profile {name, shape, curve},
phase {phase}.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
46 lines
1.3 KiB
Python
46 lines
1.3 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
import logging
|
|
import os
|
|
from pathlib import Path
|
|
|
|
import uvicorn
|
|
|
|
from .app import make_app
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(prog="cis490-dashboard")
|
|
parser.add_argument(
|
|
"--host",
|
|
default=os.environ.get("CIS490_DASHBOARD_HOST", "127.0.0.1"),
|
|
help="bind address (default 127.0.0.1; Caddy reverse-proxies dashboard.wg here)",
|
|
)
|
|
parser.add_argument(
|
|
"--port", type=int,
|
|
default=int(os.environ.get("CIS490_DASHBOARD_PORT", "8447")),
|
|
help="bind port (default 8447)",
|
|
)
|
|
parser.add_argument(
|
|
"--data-root", type=Path,
|
|
default=Path(os.environ.get("CIS490_DATA_ROOT", "/var/lib/cis490")),
|
|
help="receiver data root that the feeders read from",
|
|
)
|
|
parser.add_argument(
|
|
"--no-feeders", action="store_true",
|
|
help="don't start the on-disk feeders (useful for local dev)",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format="%(asctime)s %(levelname)s %(name)s %(message)s",
|
|
)
|
|
|
|
app = make_app(enable_feeders=not args.no_feeders, data_root=args.data_root)
|
|
uvicorn.run(app, host=args.host, port=args.port, log_config=None)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|