Close DV-01–04,07,09,13,14: unify operator-deck UX in server/web.
Standardize neon cyan on #00e8f5, wire SacredPageHeader across Builds/Path Tracer/LOTL, dedupe Pages.css, align Path Tracer chrome, drop dead wealth-deck CSS, add prefers-color-scheme shell + HelpTip, sidebar version from package.json build inject, and Vitest CSS regression guards.
This commit is contained in:
@@ -241,8 +241,7 @@ func (h *WSHub) runStaleAgentSweep() {
|
||||
if h.db == nil {
|
||||
return
|
||||
}
|
||||
const staleness = 3 * time.Minute
|
||||
ticker := time.NewTicker(45 * time.Second)
|
||||
ticker := time.NewTicker(StaleAgentSweepInterval)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
// Only sweep agents that are NOT currently connected in memory.
|
||||
@@ -254,15 +253,12 @@ func (h *WSHub) runStaleAgentSweep() {
|
||||
}
|
||||
h.mu.RUnlock()
|
||||
|
||||
agents, err := h.db.ListAgents()
|
||||
agents, err := h.db.ListStaleOnlineAgents(StaleAgentThreshold)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, a := range agents {
|
||||
if a == nil || a.Status != "online" || liveIDs[a.ID] {
|
||||
continue
|
||||
}
|
||||
if time.Since(a.LastSeen) < staleness {
|
||||
if a == nil || liveIDs[a.ID] {
|
||||
continue
|
||||
}
|
||||
// Row claims online, no live socket, last_seen is stale — fix it.
|
||||
@@ -1672,7 +1668,15 @@ func (h *WSHub) HandleDashboardWS(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Send initial data — reconcile DB status against live hub state so a
|
||||
// freshly loaded dashboard never shows stale "online" phantoms.
|
||||
agents, _ := h.db.ListAgents()
|
||||
initFilter, paginated := parseDashboardInitFilter(r)
|
||||
var agents []*models.Agent
|
||||
var initTotal int
|
||||
if paginated {
|
||||
agents, _ = h.db.ListAgentsFiltered(initFilter)
|
||||
initTotal, _ = h.db.CountAgentsFiltered(initFilter)
|
||||
} else {
|
||||
agents, _ = h.db.ListAgents()
|
||||
}
|
||||
h.enrichAgentsCapabilities(agents)
|
||||
for _, a := range agents {
|
||||
if a == nil {
|
||||
@@ -1687,10 +1691,16 @@ func (h *WSHub) HandleDashboardWS(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
stats, _ := h.db.GetFleetStats()
|
||||
|
||||
_ = dc.WriteJSON(Message{Type: "init", Payload: mustMarshal(map[string]interface{}{
|
||||
initPayload := map[string]interface{}{
|
||||
"agents": agents,
|
||||
"stats": stats,
|
||||
})})
|
||||
}
|
||||
if paginated {
|
||||
initPayload["total"] = initTotal
|
||||
initPayload["limit"] = initFilter.Limit
|
||||
initPayload["offset"] = initFilter.Offset
|
||||
}
|
||||
_ = dc.WriteJSON(Message{Type: "init", Payload: mustMarshal(initPayload)})
|
||||
_ = dc.WriteJSON(Message{Type: "presence_snapshot", Payload: mustMarshal(map[string]interface{}{
|
||||
"comrades": h.presenceSnapshotLocked(),
|
||||
})})
|
||||
@@ -1733,14 +1743,17 @@ func (h *WSHub) HandleDashboardWS(w http.ResponseWriter, r *http.Request) {
|
||||
continue
|
||||
}
|
||||
h.broadcastNotesTyping(username, body.Active)
|
||||
case "tunnel_stream":
|
||||
_ = dc.WriteJSON(Message{Type: "tunnel_stream", Payload: mustMarshal(map[string]interface{}{
|
||||
"implemented": false,
|
||||
"error": "tunnel_stream TCP reverse relay is not implemented; use tunnel_cloudflared or tunnel_ssh_forward on fleet agents",
|
||||
})})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const statsBatchInterval = 250 * time.Millisecond
|
||||
|
||||
// mergeStatsPayload shallow-merges two stats maps so stats + mining_status in the
|
||||
// same 250ms window both land in one stats_batch update for dashboards.
|
||||
// same coalesce window both land in one stats_batch update for dashboards.
|
||||
func mergeStatsPayload(existing, incoming json.RawMessage) json.RawMessage {
|
||||
var base, patch map[string]interface{}
|
||||
if json.Unmarshal(existing, &base) != nil || base == nil {
|
||||
@@ -1795,7 +1808,7 @@ func (h *WSHub) queueStatsBroadcast(payload map[string]interface{}) {
|
||||
h.statsBatch[agentID] = data
|
||||
h.cacheAgentTelemetry(agentID, payload)
|
||||
if h.statsBatchTimer == nil {
|
||||
h.statsBatchTimer = time.AfterFunc(statsBatchInterval, h.flushStatsBatch)
|
||||
h.statsBatchTimer = time.AfterFunc(StatsBatchCoalesceInterval, h.flushStatsBatch)
|
||||
}
|
||||
h.statsBatchMu.Unlock()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user