mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-20 07:13:05 +00:00
feat(events): broadcast state-invalidate on docker events so dashboard updates live (#768)
Dashboard and sidebar status indicators previously only refreshed on a
5-30 second polling cadence: a container restart, a degraded -> healthy
transition, or a stack update was invisible until the next tick.
Add a lightweight, non-persisted "state-invalidate" envelope on the
existing /ws/notifications WebSocket:
Backend
- NotificationService.broadcastEvent: sibling of dispatchAlert that
pushes an arbitrary {type, ...} envelope to every subscriber WITHOUT
writing to the alerts history (these are pure ephemeral signals).
- DockerEventService.handleEvent: emit the envelope for state-changing
container actions (start/die/kill/destroy/create/restart/pause/
unpause/health_status/rename/update). Carries node id, stack name
(from the compose project label), container id, action, and
timestamp.
Frontend
- EditorLayout's two notification WebSocket handlers (local plus
per-remote-node) branch on type. On state-invalidate they re-emit a
window CustomEvent and trigger a debounced (250ms) refreshStacks so
a burst of events from compose recreating multiple services
collapses to one refetch. The refresh callback is held in a ref so
the long-lived WS effect never closes over a stale function.
- useDashboardData listens for the same window event and refetches
/stats, /system/stats, and /stacks/statuses on every signal.
Historical metrics stay on their 60s polling cadence (10-minute
trend data, not a live indicator).
Tests
- Three new docker-event-service cases assert broadcastEvent fires on
start and health_status events with the correct envelope shape, and
does not fire on non-state actions like exec_create.
- Existing 28 cases updated with the broadcastEvent mock so the
subscriber stub matches the new shape.
Polling stays as a safety net at the same intervals; the WS path is
the fast path. Multi-node fleets benefit on the local node today;
extending the remote forwarder to relay state-invalidate is a
recommended follow-up.
This commit is contained in:
@@ -164,6 +164,34 @@ export function useDashboardData(): DashboardData {
|
||||
return cleanup;
|
||||
}, [nodeId, fetchJson]);
|
||||
|
||||
// React to live `state-invalidate` signals from /ws/notifications: when a
|
||||
// Docker container event fires (start/stop/die/restart/health), the layout
|
||||
// re-broadcasts the envelope as a window CustomEvent. Refetch the cheap
|
||||
// data (stats, system, statuses) immediately so the dashboard header and
|
||||
// sidebar status update in well under a second instead of waiting for the
|
||||
// next polling tick. Historical metrics are intentionally skipped — they
|
||||
// are a 10-minute trend, not a live indicator.
|
||||
useEffect(() => {
|
||||
const currentNodeId = nodeId;
|
||||
const onInvalidate = async () => {
|
||||
if (nodeIdRef.current !== currentNodeId) return;
|
||||
const [statsData, sysData, statusesData] = await Promise.all([
|
||||
fetchJson<Stats>('/stats'),
|
||||
fetchJson<SystemStats>('/system/stats'),
|
||||
fetchJson<Record<string, StackStatusEntry>>('/stacks/statuses'),
|
||||
]);
|
||||
if (nodeIdRef.current !== currentNodeId) return;
|
||||
if (statsData) {
|
||||
setStats(statsData);
|
||||
setLastSyncAt(Date.now());
|
||||
}
|
||||
if (sysData) setSystemStats(sysData);
|
||||
if (statusesData) setStackStatuses(statusesData);
|
||||
};
|
||||
window.addEventListener('sencho:state-invalidate', onInvalidate);
|
||||
return () => window.removeEventListener('sencho:state-invalidate', onInvalidate);
|
||||
}, [nodeId, fetchJson]);
|
||||
|
||||
const stackCpuSeries = useMemo<Record<string, StackCpuSeries>>(() => {
|
||||
if (metrics.length === 0) return {};
|
||||
const grouped = new Map<string, MetricPoint[]>();
|
||||
|
||||
Reference in New Issue
Block a user