fix(web): keep health and status live everywhere they are shown
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled
ci / web (push) Has been cancelled
Desktop App / Build Linux (amd64) (push) Has been cancelled
Desktop App / Attach to Release (push) Has been cancelled

The SSE stream already carried health.changed, health.stale, signal.raised,
signal.resolved and coverage.unmonitored, but two of the places that render
health never listened for them.

- The Fleet table refreshed only on entity.*, so its Health column sat at
  whatever it was when the page mounted while the map view beside it — which
  did listen — updated live. Health arrives on its own events, not entity.*.
  Coalesced on a 400ms timer because health.stale fires once per entity during
  a sweep, and refetching the whole fleet per event would mean a burst of
  identical requests.
- The entity detail window loaded health, signals and monitoring once on open
  and never again, so a window left on screen kept showing the health it had
  at mount. That is the same staleness this whole change set has been about,
  reproduced one window at a time. Now scoped by entity_id, and re-reads only
  what a health or signal event can actually change rather than re-running the
  full 11-request load().

Verified against live prod: flipping lxc:apps healthy -> degraded -> healthy
updated the Fleet table and an open detail window together, without a reload.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-28 15:05:06 +02:00
parent 50e899e5ee
commit 4f706fa65f
2 changed files with 62 additions and 11 deletions

View File

@@ -177,10 +177,28 @@
return unsubscribe
})
// Health arrives on its own events, not entity.*, so the table's Health
// column used to sit at whatever it was when the page mounted while the
// graph view beside it updated live. Coalesced because health.stale fires
// once per entity during a sweep, and refetching the whole fleet for each
// would mean a burst of identical requests.
let fleetRefresh: ReturnType<typeof setTimeout> | null = null
function refreshFleetSoon() {
if (fleetRefresh) return
fleetRefresh = setTimeout(() => {
fleetRefresh = null
loadEntities()
}, 400)
}
$effect(() => {
const ev = $liveEvents[0]
if (!ev || !ev.type.startsWith('entity.')) return
loadEntities()
if (!ev) return
if (ev.type.startsWith('entity.')) {
loadEntities()
return
}
if (ev.type === 'health.changed' || ev.type === 'health.stale') refreshFleetSoon()
})
const filteredEntities = $derived.by(() => {