feat(alerts): live-refresh the table every 15s while the tab is visible

The alerts list is the one screen where staleness is genuinely
harmful — an operator can be looking at an Open tab that's already
been resolved by another admin or auto-resolved by the engine, and
take action on a row that no longer exists.

Add an htmx poll on just the table panel:

  hx-get        same URL with current querystring (filters preserved)
  hx-trigger    every 15s, only when document is visible (no idle CPU)
  hx-select     #alerts-table — pull this element out of the response
  hx-swap       outerHTML

Polling lives on the table div, not the page root, so the filter
strip and header don't flash on each tick. Header gains a small
'live ●' label so the polling is discoverable.

RefreshURL is r.URL.RequestURI() on the server side — keeps any
status/severity/host_id/q params intact across refreshes.

Other screens (dashboard, hosts, jobs) deliberately stay manual-
refresh per the project's anti-flicker stance.
This commit is contained in:
2026-05-04 23:30:19 +01:00
parent 85c62741b5
commit 595656ed59
2 changed files with 26 additions and 8 deletions
+11 -5
View File
@@ -14,10 +14,11 @@ import (
)
type alertsPage struct {
Filter store.AlertFilter
Alerts []store.Alert
Counts alertCounts
HostNames map[string]string // host_id → name for table rendering
Filter store.AlertFilter
Alerts []store.Alert
Counts alertCounts
HostNames map[string]string // host_id → name for table rendering
RefreshURL string // self-URL for the live-refresh poll
}
type alertCounts struct {
@@ -51,7 +52,12 @@ func (s *Server) handleUIAlerts(w stdhttp.ResponseWriter, r *stdhttp.Request) {
return
}
page := alertsPage{Filter: f, Alerts: alerts, HostNames: map[string]string{}}
page := alertsPage{
Filter: f,
Alerts: alerts,
HostNames: map[string]string{},
RefreshURL: r.URL.RequestURI(),
}
if hosts, err := s.deps.Store.ListHosts(r.Context()); err == nil {
for _, h := range hosts {
page.HostNames[h.ID] = h.Name