diff --git a/cmd/pulse/main.go b/cmd/pulse/main.go index c0d0037..a299ac9 100644 --- a/cmd/pulse/main.go +++ b/cmd/pulse/main.go @@ -172,6 +172,10 @@ func runServer() { return nil } router = api.NewRouter(cfg, reloadableMonitor.GetMonitor(), wsHub, reloadFunc, Version) + + // Inject resource store into monitor for WebSocket broadcasts + // This must be done after router creation since resourceHandlers is created in NewRouter + router.SetMonitor(reloadableMonitor.GetMonitor()) // Create HTTP server with unified configuration // In production, serve everything (frontend + API) on the frontend port diff --git a/frontend-modern/src/stores/websocket.ts b/frontend-modern/src/stores/websocket.ts index 78c4443..e1fd52a 100644 --- a/frontend-modern/src/stores/websocket.ts +++ b/frontend-modern/src/stores/websocket.ts @@ -74,6 +74,8 @@ export function createWebSocketStore(url: string) { activeAlerts: [], recentlyResolved: [], lastUpdate: '', + // Unified resources for cross-platform monitoring + resources: [], }); const [activeAlerts, setActiveAlerts] = createStore>({}); const [recentlyResolved, setRecentlyResolved] = createStore>({}); @@ -580,6 +582,10 @@ export function createWebSocketStore(url: string) { if (message.data.stats !== undefined) setState('stats', message.data.stats); if (message.data.physicalDisks !== undefined) setState('physicalDisks', reconcile(message.data.physicalDisks, { key: 'id' })); + // Handle unified resources + if (message.data.resources !== undefined) { + setState('resources', reconcile(message.data.resources, { key: 'id' })); + } // Sync active alerts from state if (message.data.activeAlerts !== undefined) { const newAlerts: Record = {}; diff --git a/internal/api/router.go b/internal/api/router.go index cf0e6fb..c96bfac 100644 --- a/internal/api/router.go +++ b/internal/api/router.go @@ -1278,9 +1278,12 @@ func (r *Router) SetMonitor(m *monitoring.Monitor) { } // Inject resource store for polling optimization if r.resourceHandlers != nil { + log.Debug().Msg("[Router] Injecting resource store into monitor") m.SetResourceStore(r.resourceHandlers.Store()) // Also set state provider for on-demand resource population r.resourceHandlers.SetStateProvider(m) + } else { + log.Warn().Msg("[Router] resourceHandlers is nil, cannot inject resource store") } } } diff --git a/internal/monitoring/monitor.go b/internal/monitoring/monitor.go index e15191a..3ce4678 100644 --- a/internal/monitoring/monitor.go +++ b/internal/monitoring/monitor.go @@ -7070,9 +7070,18 @@ func (m *Monitor) updateResourceStore(state models.StateSnapshot) { m.mu.RUnlock() if store == nil { + log.Debug().Msg("[Resources] No resource store configured, skipping population") return } + log.Debug(). + Int("nodes", len(state.Nodes)). + Int("vms", len(state.VMs)). + Int("containers", len(state.Containers)). + Int("hosts", len(state.Hosts)). + Int("dockerHosts", len(state.DockerHosts)). + Msg("[Resources] Populating resource store from state snapshot") + store.PopulateFromSnapshot(state) } @@ -7084,10 +7093,12 @@ func (m *Monitor) getResourcesForBroadcast() []models.ResourceFrontend { m.mu.RUnlock() if store == nil { + log.Debug().Msg("[Resources] No store for broadcast") return nil } allResources := store.GetAll() + log.Debug().Int("count", len(allResources)).Msg("[Resources] Got resources for broadcast") if len(allResources) == 0 { return nil }