diff --git a/CHANGELOG.md b/CHANGELOG.md index 0980505..129c1e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ ## Changelog +### v0.171.0 — Disk-health card: device-model label (pairs with agent v0.95.0) (2026-07-25) + +`agentapi.SmartSummary` gains `ModelName` (mirrors the agent v0.95.0 `model_name`); the "Lemezek +állapota" card row label now prefers the device model ("TOSHIBA MQ04ABF100") over the raw storage +name/UUID, falling back to Name (+ speed hint) on an older agent or a modelless disk. With agent +v0.95.0 the system SSD and the USB drive now carry real SMART, so the card shows real verdicts +(Rendben) with human labels instead of "Nincs adat" on a raw UUID. Additive; old-agent payloads render +exactly as before. Test `TestDiskDisplayLabel_PrefersModel` (red-proof: drop the fallback → A4 fails). + ### v0.170.0 — Root → Indítópult; gofmt normalization; stale-note fix (2026-07-25) - **`/` is now the Indítópult** (operator ruling, reversing the v0.163.0 landing choice). `GET /` 302s diff --git a/controller/internal/agentapi/client.go b/controller/internal/agentapi/client.go index c611f08..7968f14 100644 --- a/controller/internal/agentapi/client.go +++ b/controller/internal/agentapi/client.go @@ -1012,9 +1012,10 @@ type ThinPoolFill struct { // The SATA set (reallocated/pending/offline-uncorrectable) and the NVMe set // (critical_warning/media_errors/percentage_used) are both carried; a device populates only its own. type SmartSummary struct { - Health string `json:"health"` // PASSED | FAILING | UNKNOWN - TemperatureC *int `json:"temperature_c"` - PowerOnHours *int `json:"power_on_hours"` + Health string `json:"health"` // PASSED | FAILING | UNKNOWN + ModelName *string `json:"model_name,omitempty"` // smartctl device model (agent v0.95.0+); nil on older agents + TemperatureC *int `json:"temperature_c"` + PowerOnHours *int `json:"power_on_hours"` // SATA attributes. ReallocatedSectors *int `json:"reallocated_sectors"` PendingSectors *int `json:"pending_sectors"` diff --git a/controller/internal/web/disk_health.go b/controller/internal/web/disk_health.go index 5481510..f978a9e 100644 --- a/controller/internal/web/disk_health.go +++ b/controller/internal/web/disk_health.go @@ -75,6 +75,11 @@ func isPhysicalDisk(d agentapi.DiskInfo) bool { // diskDisplayLabel is the customer-facing disk label (PVE name + a Hungarian speed hint when known). func diskDisplayLabel(d agentapi.DiskInfo) string { + // Prefer the device model (agent v0.95.0) over the raw storage name/UUID — a customer reads + // "TOSHIBA MQ04ABF100", not "47a3361a-…". Falls back to Name (+ speed hint) on an older agent. + if d.Smart != nil && d.Smart.ModelName != nil && *d.Smart.ModelName != "" { + return *d.Smart.ModelName + } switch d.Class { case "fast": return d.Name + " (gyors)" diff --git a/controller/internal/web/disk_health_test.go b/controller/internal/web/disk_health_test.go index f735556..fc87241 100644 --- a/controller/internal/web/disk_health_test.go +++ b/controller/internal/web/disk_health_test.go @@ -167,3 +167,27 @@ func TestCachedDisks_TTL(t *testing.T) { t.Errorf("two fetches within the TTL should call the agent once, got %d", calls) } } + +func sptr(s string) *string { return &s } + +// v0.171.0: the card label prefers the device model (agent v0.95.0) over the raw name/UUID; it falls +// back to Name (+ speed hint) on an older agent or a modelless disk. +// Red-proof: drop the fallback (always return ModelName) → the nil-model/old-agent cases return "" and +// TestDiskDisplayLabel_PrefersModel fails (A4 — old-payload tolerance). +func TestDiskDisplayLabel_PrefersModel(t *testing.T) { + withModel := agentapi.DiskInfo{Name: "47a3361a-uuid", Class: "slow", + Smart: &agentapi.SmartSummary{Health: agentapi.SmartPassed, ModelName: sptr("TOSHIBA MQ04ABF100")}} + if got := diskDisplayLabel(withModel); got != "TOSHIBA MQ04ABF100" { + t.Errorf("label = %q, want the model name", got) + } + // A4: an old (v0.94.0) agent carries no model → Name (+ speed hint), byte-identical to before. + oldAgent := agentapi.DiskInfo{Name: "local", Class: "fast", + Smart: &agentapi.SmartSummary{Health: agentapi.SmartUnknown}} + if got := diskDisplayLabel(oldAgent); got != "local (gyors)" { + t.Errorf("old-agent label = %q, want 'local (gyors)'", got) + } + // No SMART at all → bare Name. + if got := diskDisplayLabel(agentapi.DiskInfo{Name: "sdb"}); got != "sdb" { + t.Errorf("nil-smart label = %q, want 'sdb'", got) + } +}