dashboard: subscribe to /dashboard/stream for live updates (#406 step 3)
Pre-step-2, the SSE subscription on /index.html was conditionally created by the broker-terminal IIFE that only ran on /flow.html (via the `if (!flow || !window.HiveTerminal) return;` guard) — so the dashboard only updated on cold load + after async-form submits. The split in step 2 made the gap more obvious; close it. Bare `EventSource` (the dashboard doesn't render broker rows, so none of the HiveTerminal infrastructure is wanted here). Each event's `kind` looks up against a `MUTATION_HANDLERS` table that fans out to the existing `applyXxx` handlers. Unknown kinds (broker `sent` / `delivered`, anything the backend adds later) silently no-op. On (re)connect we kick `refreshState()` to recover events lost during the disconnect window — same pattern as flow.js's `onStreamOpen`. EventSource handles auto-reconnect. #408 will give /index.html its own stream that omits broker traffic; for now both pages subscribe to `/dashboard/stream` and filter client-side. Closes the #406 architectural roadmap: - step 1 (#410): common.js extraction ✓ - step 2 (#412): split into app.js + flow.js entry points ✓ - step 3 (this PR): /index.html re-acquires its SSE subscription
This commit is contained in:
parent
0e17459943
commit
1819bee43c
1 changed files with 57 additions and 0 deletions
|
|
@ -1828,6 +1828,63 @@ window.marked = marked;
|
||||||
NOTIF.bind();
|
NOTIF.bind();
|
||||||
Panel.bind();
|
Panel.bind();
|
||||||
|
|
||||||
|
// ─── live updates: dashboard event stream (#406 step 3) ────────────────
|
||||||
|
// The dashboard subscribes to /dashboard/stream for live mutation
|
||||||
|
// events so the SW4RM / Y3R C4LL / SYST3M panes update without an
|
||||||
|
// operator action triggering a refreshState. Pre-step-2 this wiring
|
||||||
|
// lived inside the broker-terminal IIFE which only fired on /flow.html
|
||||||
|
// — meaning /index.html only updated on cold load + after async-form
|
||||||
|
// submits.
|
||||||
|
//
|
||||||
|
// Bare `EventSource` (no terminal infrastructure needed — the
|
||||||
|
// dashboard doesn't render broker rows). Each event's `kind` is
|
||||||
|
// looked up against `MUTATION_HANDLERS`; unknown kinds (broker
|
||||||
|
// `sent` / `delivered`, anything new the backend adds) silently
|
||||||
|
// no-op. On (re)connect we kick a refreshState() to recover events
|
||||||
|
// lost during the disconnect window (same pattern as flow.js's
|
||||||
|
// onStreamOpen).
|
||||||
|
//
|
||||||
|
// `#408` will give /index.html its own stream that omits the
|
||||||
|
// broker traffic the dashboard never uses; for now both pages
|
||||||
|
// subscribe to `/dashboard/stream` and filter client-side.
|
||||||
|
const MUTATION_HANDLERS = {
|
||||||
|
approval_added: applyApprovalAdded,
|
||||||
|
approval_resolved: applyApprovalResolved,
|
||||||
|
question_added: applyQuestionAdded,
|
||||||
|
question_resolved: applyQuestionResolved,
|
||||||
|
transient_set: applyTransientSet,
|
||||||
|
transient_cleared: applyTransientCleared,
|
||||||
|
container_state_changed: applyContainerStateChanged,
|
||||||
|
container_removed: applyContainerRemoved,
|
||||||
|
tombstones_changed: applyTombstonesChanged,
|
||||||
|
meta_inputs_changed: applyMetaInputsChanged,
|
||||||
|
meta_update_running: applyMetaUpdateRunning,
|
||||||
|
rebuild_queue_changed: applyRebuildQueueChanged,
|
||||||
|
};
|
||||||
|
(function bindDashboardStream() {
|
||||||
|
const es = new EventSource('/dashboard/stream');
|
||||||
|
es.onmessage = (e) => {
|
||||||
|
let ev;
|
||||||
|
try { ev = JSON.parse(e.data); } catch { return; }
|
||||||
|
const h = MUTATION_HANDLERS[ev.kind];
|
||||||
|
if (!h) return; // broker rows + future kinds — dashboard doesn't care
|
||||||
|
try { h(ev); }
|
||||||
|
catch (err) { console.error('dashboard SSE handler', ev.kind, err); }
|
||||||
|
};
|
||||||
|
es.onopen = () => {
|
||||||
|
// Re-sync to recover events that fired during a disconnect
|
||||||
|
// window (issue #163). Initial connect also fires onopen — the
|
||||||
|
// first refreshState() above and this one race, but refreshState
|
||||||
|
// is idempotent so the second call just overwrites with the
|
||||||
|
// freshest snapshot. Cheap on a quiescent server, fine to repeat.
|
||||||
|
refreshState();
|
||||||
|
};
|
||||||
|
es.onerror = () => {
|
||||||
|
// EventSource auto-reconnects; nothing to do beyond logging.
|
||||||
|
console.debug('dashboard SSE error, will retry');
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
// ─── tab routing (#369) ────────────────────────────────────────────────
|
// ─── tab routing (#369) ────────────────────────────────────────────────
|
||||||
// Hash-based: `#swarm` / `#call` / `#system` activate the matching
|
// Hash-based: `#swarm` / `#call` / `#system` activate the matching
|
||||||
// pane on the dashboard. Empty hash defaults to SW4RM. FL0W is NOT
|
// pane on the dashboard. Empty hash defaults to SW4RM. FL0W is NOT
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue