hub: shared host_detail_body sub-template + customer Host tab (v0.47.0 part 3)

- host_detail_body.html: {{define}}'d body sections extracted from
  host_detail.html; the standalone page is now chrome + the sub-template
- hosts.go: hostDetailData(host, r) view-model builder extracted from
  handleHostDetail (reused by both surfaces)
- store: ListHostsByCustomer (host_id order; the Host tab is a list by
  design - N hosts for a future HA cluster)
- customer Host tab renders one host_detail_body per host + cross-link;
  empty state when no host is enrolled
- tests: TestTemplates_CustomerHostTab(+_Empty), TestListHostsByCustomer

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vvz1NCu22p8dGkRCpeX9re
This commit is contained in:
2026-07-11 21:17:44 +02:00
parent 9f29bf34c0
commit ae950e5933
8 changed files with 400 additions and 242 deletions
+17
View File
@@ -328,6 +328,11 @@ func (s *Server) handleCustomerUnified(w http.ResponseWriter, r *http.Request, c
// ScriptVersion drives the install-command generator's header (GL-7). Display-only.
ScriptVersion string
// Hosts (v0.47.0): the customer's enrolled hosts for the Host tab — a LIST by design
// (1 today, N for a later HA cluster). Each entry is the hostDetailData view-model map
// the shared host_detail_body sub-template renders.
Hosts []map[string]interface{}
}
pendingSet := make(map[string]bool, len(pendingTails))
@@ -335,6 +340,16 @@ func (s *Server) handleCustomerUnified(w http.ResponseWriter, r *http.Request, c
pendingSet[app] = true
}
// Host tab (v0.47.0): per-host view models via the shared hostDetailData builder.
var hostViews []map[string]interface{}
if hosts, err := s.store.ListHostsByCustomer(customerID); err != nil {
s.logger.Printf("[ERROR] ListHostsByCustomer %s: %v", customerID, err)
} else {
for i := range hosts {
hostViews = append(hostViews, s.hostDetailData(&hosts[i], r))
}
}
// DR recipe presence — show the secret-free reconstruction recipe panel + download link when
// either half has landed (host-report and/or controller report).
var hasDR, drHost, drApps bool
@@ -396,6 +411,8 @@ func (s *Server) handleCustomerUnified(w http.ResponseWriter, r *http.Request, c
CSRFToken: s.csrfToken(r),
ScriptVersion: hostInstallVersion,
Hosts: hostViews,
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")