hub v0.45.0: floor-UI separation + effective-floor source + per-box MinAgent conditional floor

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
2026-07-11 15:33:14 +02:00
parent 37e60b46d2
commit bbecf0592e
15 changed files with 730 additions and 44 deletions
+21
View File
@@ -2,6 +2,7 @@ package web
import (
"encoding/json"
"fmt"
"net/http"
"sort"
"time"
@@ -9,6 +10,15 @@ import (
"gitea.dooplex.hu/admin/felhom-hub/internal/store"
)
// agentOrUnknown renders an agent version for operator text, mapping the empty (never-reported)
// value to a readable token.
func agentOrUnknown(v string) string {
if v == "" {
return "unknown"
}
return v
}
// hostStatus computes the host's liveness state from its last-report recency, using the
// SAME thresholds as the HostStalenessChecker (s.staleThreshold; "down" at 2×). This keeps
// the GUI badge in agreement with the alerting — there is no second definition of "stale".
@@ -177,6 +187,10 @@ type hostListRow struct {
WorstFillPct float64
WorstFillName string
HasStorage bool
// FloorHeld (Part D): the managed controller-version floor is being WITHHELD because this box's
// agent is below the current golden's MinAgent. HeldReason carries the operator-facing text.
FloorHeld bool
HeldReason string
}
// customerName resolves a display name for a customer id (config first, then the last
@@ -224,6 +238,13 @@ func (s *Server) handleHostsList(w http.ResponseWriter, r *http.Request) {
HasReport: h.LastReportAt != nil,
}
// Part D: surface a held managed floor (agent below the golden's MinAgent) so a held box is
// never silently stale.
if fd := s.store.ResolveManagedFloor(h.CustomerID); fd.Held {
row.FloorHeld = true
row.HeldReason = fmt.Sprintf("held: agent %s < MinAgent %s", agentOrUnknown(fd.AgentVersion), fd.MinAgent)
}
// Guest counts from the reality table (per-host accurate).
guests, _ := s.store.ListGuestsForHost(h.HostID)
row.GuestTotal = len(guests)