ui+server: P2-18d pending hosts dashboard panel + expiry sweeper

Dashboard handler loads ListPendingHosts(now); template renders a
warn-bordered panel above the host table with hostname, OS/arch,
fingerprint (selectable / copyable), source IP, age, expiry. Each
row carries an inline accept form (repo URL/user/password) plus a
Reject button. cmd/server adds a 60s ticker calling
DeleteExpiredPendingHosts so 1h-stale rows drop off.
This commit is contained in:
2026-05-04 11:11:32 +01:00
parent a3a53e3b87
commit bbdf631a01
3 changed files with 75 additions and 6 deletions
+13 -6
View File
@@ -109,9 +109,10 @@ func (s *Server) version() string {
// dashboardPage is the data the dashboard template renders against.
type dashboardPage struct {
Hosts []dashboardHostRow
HostCount int
Summary store.FleetSummary
Hosts []dashboardHostRow
HostCount int
Summary store.FleetSummary
PendingHosts []store.PendingHost // announce-and-approve queue (P2-18d)
}
// dashboardHostRow carries a host plus the per-row Run-now decision
@@ -220,12 +221,18 @@ func (s *Server) handleUIDashboard(w stdhttp.ResponseWriter, r *stdhttp.Request)
rows = append(rows, row)
}
pending, perr := s.deps.Store.ListPendingHosts(r.Context(), time.Now().UTC())
if perr != nil {
slog.Warn("ui dashboard: list pending hosts", "err", perr)
}
view := s.baseView(u)
view.OpenAlerts = summary.OpenAlerts
view.Page = dashboardPage{
Hosts: rows,
HostCount: len(hosts),
Summary: summary,
Hosts: rows,
HostCount: len(hosts),
Summary: summary,
PendingHosts: pending,
}
if err := s.deps.UI.Render(w, "dashboard", view); err != nil {
slog.Error("ui: render dashboard", "err", err)