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:
@@ -1,6 +1,8 @@
|
|||||||
package web
|
package web
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"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
|
// Version check
|
||||||
var latestVersion string
|
var latestVersion string
|
||||||
var updateAvailable bool
|
var updateAvailable bool
|
||||||
@@ -227,6 +260,10 @@ func (s *Server) handleCustomerUnified(w http.ResponseWriter, r *http.Request, c
|
|||||||
UpdateAvailable bool
|
UpdateAvailable bool
|
||||||
ControllerURL string
|
ControllerURL string
|
||||||
|
|
||||||
|
ConfigSyncStatus string // "in_sync", "mismatch", "unknown"
|
||||||
|
ControllerConfigHash string
|
||||||
|
HubConfigHash string
|
||||||
|
|
||||||
InfraBackup *store.InfraBackupMeta
|
InfraBackup *store.InfraBackupMeta
|
||||||
InfraBackupAge string
|
InfraBackupAge string
|
||||||
NotifPrefs *store.NotificationPrefs
|
NotifPrefs *store.NotificationPrefs
|
||||||
@@ -257,6 +294,10 @@ func (s *Server) handleCustomerUnified(w http.ResponseWriter, r *http.Request, c
|
|||||||
UpdateAvailable: updateAvailable,
|
UpdateAvailable: updateAvailable,
|
||||||
ControllerURL: controllerURL,
|
ControllerURL: controllerURL,
|
||||||
|
|
||||||
|
ConfigSyncStatus: configSyncStatus,
|
||||||
|
ControllerConfigHash: controllerConfigHash,
|
||||||
|
HubConfigHash: hubConfigHash,
|
||||||
|
|
||||||
InfraBackup: infraMeta,
|
InfraBackup: infraMeta,
|
||||||
InfraBackupAge: infraBackupAge,
|
InfraBackupAge: infraBackupAge,
|
||||||
NotifPrefs: notifPrefs,
|
NotifPrefs: notifPrefs,
|
||||||
|
|||||||
@@ -370,6 +370,17 @@
|
|||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</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;">✓ In sync</span>
|
||||||
|
{{else if eq .ConfigSyncStatus "mismatch"}}<span style="color: #f59e0b;">⚠ 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;">
|
<div style="margin-top: 0.75em; display: flex; gap: 0.5rem; flex-wrap: wrap;">
|
||||||
{{if and .ControllerURL .UpdateAvailable}}
|
{{if and .ControllerURL .UpdateAvailable}}
|
||||||
<button class="btn btn-sm" id="btn-trigger-update" onclick="triggerControllerUpdate('{{.CustomerID}}')">
|
<button class="btn btn-sm" id="btn-trigger-update" onclick="triggerControllerUpdate('{{.CustomerID}}')">
|
||||||
|
|||||||
Reference in New Issue
Block a user