The CSS-rule-per-canonical-name approach was wrong: any name the producer publishes that wasn't in the hardcoded list (mlp_realistic, cnn_oracle, knn_semi, anything new tomorrow) rendered grey because no .model-fill.<name> rule matched. Replace with a deterministic FNV-1a hash of the model string → hue, applied inline as an OKLCH gradient when the row is created. Every model string gets a stable, distinct color regardless of suffix or case. Inline style beats any CSS rule, so this works whatever's in dashboard.css. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| static | ||
| __init__.py | ||
| __main__.py | ||
| app.py | ||
| client.py | ||
| dashboard.caddy | ||
| events.py | ||
| feeder.py | ||
| PRODUCERS.md | ||
| README.md | ||
training/dashboard/
Live web display served at https://dashboard.wg. A Starlette app
on 127.0.0.1:8447 behind Caddy; messages from Python are pushed to
connected browsers over a WebSocket.
This is intentionally a blank slate — the default page just
appends every received JSON message to a scrolling log. Build the
real widgets on top of window.dashboard.onMessage.
Run locally
uv run python -m training.dashboard
# → open http://127.0.0.1:8447
Push live data from Python
Same process (e.g. a notebook driving the page):
from training.dashboard.app import broadcaster
await broadcaster.publish({"type": "metric", "name": "loss", "value": 0.42})
Different process (orchestrator, receiver, ad-hoc shell):
curl -s http://127.0.0.1:8447/publish \
-H 'content-type: application/json' \
-d '{"type":"hello","msg":"from cron"}'
The /publish endpoint is loopback-only (403 otherwise) and is not
reverse-proxied by Caddy, so it cannot be hit from the WG mesh.
Producing events from your own code
If you're writing code that should drive a dashboard widget (model
inference, training-loop metrics, profile envelopes), see
PRODUCERS.md — it documents every event type
the widgets subscribe to, the loopback-only /publish contract, the
reconnect gotcha, and the systemd hardening that constrains
producers running on the Pi. There's a stdlib-only Python helper at
client.py:
from training.dashboard.client import Publisher
Publisher().publish({"type": "model_metric", "model": "lstm", "accuracy": 0.928})
Customizing the page
The default static/index.html exposes window.dashboard:
window.dashboard.onMessage = (msg) => {
if (msg.type === 'metric') updateChart(msg.name, msg.value);
};
window.dashboard.send({type: 'request-snapshot'}); // browser → server
Override onMessage to dispatch to your own widgets. The blank-slate
log renderer is just there so a fresh deploy is observably alive.
Deploying to the Pi
sudo cp etc/cis490-dashboard.service /etc/systemd/system/sudo cp training/dashboard/dashboard.caddy /etc/caddy/Caddyfile.d/sudo systemctl daemon-reload && sudo systemctl enable --now cis490-dashboard.servicesudo systemctl reload caddy