import type { BackendEventPayload, BrowserEvent, FrontendEventPayload, MaglevdStatusPayload, VPPStatusPayload, } from "../types"; import { fetchAllState } from "./rest"; import { applyBackendTransition, applyFrontendTransition, applyMaglevdStatus, applyVPPStatus, replaceAll, } from "../stores/state"; import { pushEvent } from "../stores/events"; // openEventStream wires the SPA to /view/api/events. EventSource auto- // reconnects with the Last-Event-ID header set, which the Go broker uses // to replay events from its 30s ring buffer. A "resync" event tells us to // refetch full state and redraw. export function openEventStream(): EventSource { const es = new EventSource("/view/api/events"); es.onmessage = (msg) => { try { const ev = JSON.parse(msg.data) as BrowserEvent; dispatch(ev); } catch (err) { console.error("sse parse error", err, msg.data); } }; // "resync" is emitted as a named event so we can listen for it // without it going through the default onmessage dispatch. es.addEventListener("resync", async () => { try { const snaps = await fetchAllState(); replaceAll(snaps); } catch (err) { console.error("resync refetch failed", err); } }); es.onerror = (err) => { // EventSource handles reconnection on its own — just log. console.debug("sse error, browser will reconnect", err); }; return es; } function dispatch(ev: BrowserEvent) { pushEvent(ev); switch (ev.type) { case "backend": // The reducer also recomputes effective weights across every // frontend so pool-failover transitions (primary ⇌ fallback) // reflect instantly, without waiting for the 30s refresh. applyBackendTransition(ev.maglevd, ev.payload as BackendEventPayload); break; case "frontend": applyFrontendTransition(ev.maglevd, ev.payload as FrontendEventPayload); break; case "maglevd-status": applyMaglevdStatus(ev.maglevd, ev.payload as MaglevdStatusPayload); break; case "vpp-status": applyVPPStatus(ev.maglevd, (ev.payload as VPPStatusPayload).state); break; case "log": // Log events are displayed in the DebugPanel but no longer // mutate the state tree. The previous vpp-lb-sync-as-* // routing was removed because a VIP-scoped event was being // naively written into every frontend that shared the // backend, corrupting effective_weight for frontends with // different per-pool configured weights. Backend state // changes (arriving via "backend" events above) are a // sufficient trigger for recomputing effective weights // locally, and the set-weight kebab action updates the // store directly via applyConfiguredWeight on its POST // success path. break; } }