feat: add config hash comparison in unified customer page

Compare controller's config_hash from reports against Hub-generated
YAML hash. Shows sync status (in sync / mismatch / unknown) on the
unified customer detail page next to the Push Config button.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-20 16:13:40 +01:00
parent a7a5304093
commit 9d0dd658ed
2 changed files with 52 additions and 0 deletions
+41
View File
@@ -1,6 +1,8 @@
package web
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
@@ -180,6 +182,37 @@ func (s *Server) handleCustomerUnified(w http.ResponseWriter, r *http.Request, c
}
}
// Config hash comparison
var controllerConfigHash string
var hubConfigHash string
var configSyncStatus string // "in_sync", "mismatch", "unknown"
if customer != nil && cfg != nil {
var rptHash struct {
ConfigHash string `json:"config_hash"`
}
json.Unmarshal([]byte(customer.ReportJSON), &rptHash)
controllerConfigHash = rptHash.ConfigHash
if controllerConfigHash != "" {
// Generate Hub-side YAML and compute its hash
templateYAML := defaultControllerTemplate
if s.templateFetcher != nil {
templateYAML = s.templateFetcher.Template()
}
if yamlOutput, err := configgen.Generate(templateYAML, cfg); err == nil {
h := sha256.Sum256([]byte(yamlOutput))
hubConfigHash = hex.EncodeToString(h[:])
if hubConfigHash == controllerConfigHash {
configSyncStatus = "in_sync"
} else {
configSyncStatus = "mismatch"
}
}
} else {
configSyncStatus = "unknown"
}
}
// Version check
var latestVersion string
var updateAvailable bool
@@ -227,6 +260,10 @@ func (s *Server) handleCustomerUnified(w http.ResponseWriter, r *http.Request, c
UpdateAvailable bool
ControllerURL string
ConfigSyncStatus string // "in_sync", "mismatch", "unknown"
ControllerConfigHash string
HubConfigHash string
InfraBackup *store.InfraBackupMeta
InfraBackupAge string
NotifPrefs *store.NotificationPrefs
@@ -257,6 +294,10 @@ func (s *Server) handleCustomerUnified(w http.ResponseWriter, r *http.Request, c
UpdateAvailable: updateAvailable,
ControllerURL: controllerURL,
ConfigSyncStatus: configSyncStatus,
ControllerConfigHash: controllerConfigHash,
HubConfigHash: hubConfigHash,
InfraBackup: infraMeta,
InfraBackupAge: infraBackupAge,
NotifPrefs: notifPrefs,
@@ -370,6 +370,17 @@
</div>
{{end}}
</div>
{{if and .HasConfig .ConfigSyncStatus}}
<div class="info-item" style="margin-top: 0.5rem;">
<span class="label">Config Sync</span>
<span class="value">
{{if eq .ConfigSyncStatus "in_sync"}}<span style="color: #22c55e;">&#x2713; In sync</span>
{{else if eq .ConfigSyncStatus "mismatch"}}<span style="color: #f59e0b;">&#x26A0; Config mismatch — Hub config differs from controller</span>
{{else}}<span style="color: #94a3b8;">Unknown — controller not reporting config hash (update controller)</span>
{{end}}
</span>
</div>
{{end}}
<div style="margin-top: 0.75em; display: flex; gap: 0.5rem; flex-wrap: wrap;">
{{if and .ControllerURL .UpdateAvailable}}
<button class="btn btn-sm" id="btn-trigger-update" onclick="triggerControllerUpdate('{{.CustomerID}}')">