ensure resource store is populated before WebSocket broadcasts

- Added PopulateFromSnapshot method to resources.Store
- Extended ResourceStoreInterface to include PopulateFromSnapshot
- Monitor now calls updateResourceStore before broadcasts
- This ensures resources are fresh on every WebSocket broadcast

Without this, the store would only be populated when /api/resources or
/api/state endpoints are hit, leaving WebSocket broadcasts empty.
This commit is contained in:
rcourtman 2025-12-07 23:15:02 +00:00
parent 55f10ce36c
commit 881f00a5d9
2 changed files with 74 additions and 2 deletions

View file

@ -93,6 +93,8 @@ type ResourceStoreInterface interface {
GetPollingRecommendations() map[string]float64
// GetAll returns all resources in the store (for WebSocket broadcasts)
GetAll() []resources.Resource
// PopulateFromSnapshot updates the store with data from a StateSnapshot
PopulateFromSnapshot(snapshot models.StateSnapshot)
}
func getNodeDisplayName(instance *config.PVEInstance, nodeName string) string {
@ -3290,7 +3292,8 @@ func (m *Monitor) Start(ctx context.Context, wsHub *websocket.Hub) {
Msg("Broadcasting state update (ticker)")
// Convert to frontend format before broadcasting (converts time.Time to int64, etc.)
frontendState := state.ToFrontend()
// Inject unified resources if resource store is available
// Update and inject unified resources if resource store is available
m.updateResourceStore(state)
frontendState.Resources = m.getResourcesForBroadcast()
wsHub.BroadcastState(frontendState)
@ -6901,7 +6904,9 @@ func (m *Monitor) SetMockMode(enable bool) {
m.mu.RUnlock()
if hub != nil {
frontendState := m.GetState().ToFrontend()
state := m.GetState()
frontendState := state.ToFrontend()
m.updateResourceStore(state)
frontendState.Resources = m.getResourcesForBroadcast()
hub.BroadcastState(frontendState)
}
@ -7057,6 +7062,20 @@ func (m *Monitor) shouldSkipNodeMetrics(nodeName string) bool {
return should
}
// updateResourceStore populates the resource store with data from the current state.
// This should be called before broadcasting to ensure fresh data.
func (m *Monitor) updateResourceStore(state models.StateSnapshot) {
m.mu.RLock()
store := m.resourceStore
m.mu.RUnlock()
if store == nil {
return
}
store.PopulateFromSnapshot(state)
}
// getResourcesForBroadcast retrieves all resources from the store and converts them to frontend format.
// Returns nil if no resource store is configured.
func (m *Monitor) getResourcesForBroadcast() []models.ResourceFrontend {

View file

@ -4,6 +4,8 @@ import (
"strings"
"sync"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/models"
)
// Store maintains a unified collection of all resources with deduplication.
@ -1024,3 +1026,54 @@ type PlatformSummary struct {
Count int
}
// PopulateFromSnapshot converts all resources from a StateSnapshot to the unified store.
// This should be called whenever the state is updated (e.g., before WebSocket broadcasts).
func (s *Store) PopulateFromSnapshot(snapshot models.StateSnapshot) {
// Convert nodes
for _, node := range snapshot.Nodes {
r := FromNode(node)
s.Upsert(r)
}
// Convert VMs
for _, vm := range snapshot.VMs {
r := FromVM(vm)
s.Upsert(r)
}
// Convert containers
for _, ct := range snapshot.Containers {
r := FromContainer(ct)
s.Upsert(r)
}
// Convert hosts
for _, host := range snapshot.Hosts {
r := FromHost(host)
s.Upsert(r)
}
// Convert docker hosts and their containers
for _, dh := range snapshot.DockerHosts {
r := FromDockerHost(dh)
s.Upsert(r)
// Convert containers within the docker host
for _, dc := range dh.Containers {
r := FromDockerContainer(dc, dh.ID, dh.Hostname)
s.Upsert(r)
}
}
// Convert PBS instances
for _, pbs := range snapshot.PBSInstances {
r := FromPBSInstance(pbs)
s.Upsert(r)
}
// Convert storage
for _, storage := range snapshot.Storage {
r := FromStorage(storage)
s.Upsert(r)
}
}