Files
felhom.eu/hub/internal/web/held_apps.go
T
admin 7e57e824a2
gates / gates (push) Successful in 1m58s
hub(D53): held apps reach the operator — mirror, Held apps list (host + Hosts page), reminder after the grace
app_held / app_hold_cleared allow-listed and operator-only; handleEvent mirrors them in held_apps; HeldAppsChecker
sends ONE app_held_reminder (warning, operator-only, per-app cooldown) after the grace per reason (6 h / 1 d / 3 d),
then weekly; recorded before sending; stale rows not reminded; the hub never clears a hold. Test-only
alerting.held_app_grace_override. TestD53_* in api/monitor/notify/web, red-proved by six mutations. Commit only.
2026-10-26 18:25:00 +01:00

58 lines
2.0 KiB
Go

package web
import (
"time"
"gitea.dooplex.hu/admin/felhom-hub/internal/monitor"
"gitea.dooplex.hu/admin/felhom-hub/internal/store"
)
// heldAppRow is one line of the „Held apps" list (D53, `08` §10) on the host page and the Hosts page.
type heldAppRow struct {
CustomerID string
HostID string // the customer's host, for the link on the Hosts page; "" when unknown
Stack string
Reason string
Since time.Time
Why string // the box's own sentence
WhoClears string
Reminded bool
Stale bool // the box has not repeated it for heldAppStale — it may be down
}
// heldAppStaleAfter mirrors monitor's heldAppStale: a box repeats app_held daily.
const heldAppStaleAfter = 50 * time.Hour
// heldAppRows reads the held-app mirror, one customer's or every one's ("" = all). A read failure is logged and
// shown as an empty list — the page must still render.
func (s *Server) heldAppRows(customerID string, hostOf map[string]string, now time.Time) []heldAppRow {
held, err := s.store.ListHeldApps(customerID)
if err != nil {
s.logger.Printf("[WARN] held apps: list for %q: %v", customerID, err)
return nil
}
return toHeldAppRows(held, hostOf, now)
}
// hostHeldAppRows is one host's list. A host with no customer has none — never the fleet's („" lists every one).
func (s *Server) hostHeldAppRows(customerID string, now time.Time) []heldAppRow {
if customerID == "" {
return nil
}
return s.heldAppRows(customerID, nil, now)
}
func toHeldAppRows(held []store.HeldApp, hostOf map[string]string, now time.Time) []heldAppRow {
out := make([]heldAppRow, 0, len(held))
for _, h := range held {
since := h.Since
if since.IsZero() {
since = h.FirstSeen
}
out = append(out, heldAppRow{CustomerID: h.CustomerID, HostID: hostOf[h.CustomerID], Stack: h.Stack, Reason: h.Reason,
Since: since, Why: h.Why, WhoClears: monitor.HeldAppWhoClears(h.Reason), Reminded: !h.RemindedAt.IsZero(),
Stale: now.Sub(h.LastSeen) > heldAppStaleAfter})
}
return out
}