Files
felhom-controller/controller/internal/agentapi/diskverdict.go
T
admin c97975c1df v0.169.0: disk-health card + degradation notification (Lemezek állapota)
Consumes the agent v0.94.0 smart payload (MinAgent floor unchanged; feature-detect
by presence). One pure verdict fn agentapi.DiskVerdictFor shared by the dashboard
card and the 6h check. Card via a 60s /disks TTL cache (anti-smartctl-storm);
unreachable agent -> Nincs adat, page never blocks. disk-health-check (6h) emits
disk_health_degraded on a degradation only vs an in-memory baseline (first run
silent, recovery/UNKNOWN never notify, multi-attr -> one event). No global banner
(deliberate). Pairs with the hub allowlist bump.

Tests: verdict table (>=90 red-proof), notifier emit, check first-run-silent
(red-proof), degradation-once, recovery-silent, UNKNOWN-excluded, FAILING-critical,
nil-smart card, TTL cache.
2026-07-24 21:27:16 +02:00

88 lines
3.3 KiB
Go

package agentapi
// DiskVerdict is the customer-facing disk-health verdict derived from a SmartSummary (v0.169.0).
// It is the SHARED source of truth for both the "Lemezek állapota" dashboard card and the 6-hourly
// degradation check — one pure function so the chip and the alert can never disagree.
type DiskVerdict int
const (
// DiskVerdictUnknown — no SMART data (nil / UNKNOWN / old agent). Renders "Nincs adat"; NEVER
// alarms and NEVER participates in degradation transitions (excluded both directions).
DiskVerdictUnknown DiskVerdict = iota
DiskVerdictOK // "Rendben" — PASSED, all counters clean
DiskVerdictWarn // "Figyelmeztetés" — PASSED but a wear/relocation counter is non-zero (or NVMe ≥90%)
DiskVerdictFail // "Hiba" — FAILING
)
// percentageUsedWarn is the NVMe wear threshold (inclusive) at which a still-PASSED disk warns.
const percentageUsedWarn = 90
// DiskVerdictFor maps a SmartSummary to a verdict per the v0.169.0 rules:
//
// FAILING → Hiba
// PASSED + any(reallocated>0, pending>0, offline_unc>0,
// critical_warning>0, media_errors>0,
// percentage_used >= 90) → Figyelmeztetés
// PASSED otherwise → Rendben
// nil / UNKNOWN / empty → Nincs adat
func DiskVerdictFor(s *SmartSummary) DiskVerdict {
if s == nil || s.Health == "" || s.Health == SmartUnknown {
return DiskVerdictUnknown
}
if s.Health == SmartFailing {
return DiskVerdictFail
}
// Health == PASSED (or any non-empty non-FAILING value we treat as passing): inspect the counters.
if positive(s.ReallocatedSectors) || positive(s.PendingSectors) || positive(s.OfflineUncorrectable) ||
positive(s.CriticalWarning) || positive(s.MediaErrors) || atLeast(s.PercentageUsed, percentageUsedWarn) {
return DiskVerdictWarn
}
return DiskVerdictOK
}
// Label is the exact Hungarian customer copy for the verdict (shared by the card chip and the email).
func (v DiskVerdict) Label() string {
switch v {
case DiskVerdictOK:
return "Rendben"
case DiskVerdictWarn:
return "Figyelmeztetés"
case DiskVerdictFail:
return "Hiba"
default:
return "Nincs adat"
}
}
// DegradedAttributes returns the human-readable Hungarian names of the attribute(s) that pushed a
// PASSED disk to Figyelmeztetés (empty for OK/Fail/Unknown) — for the alert body. FAILING is a
// whole-disk verdict with no single triggering counter, so it returns nil there.
func DegradedAttributes(s *SmartSummary) []string {
if s == nil {
return nil
}
var out []string
if positive(s.ReallocatedSectors) {
out = append(out, "áthelyezett szektorok")
}
if positive(s.PendingSectors) {
out = append(out, "függőben lévő szektorok")
}
if positive(s.OfflineUncorrectable) {
out = append(out, "javíthatatlan szektorok")
}
if positive(s.CriticalWarning) {
out = append(out, "kritikus figyelmeztetés")
}
if positive(s.MediaErrors) {
out = append(out, "adathordozó-hibák")
}
if atLeast(s.PercentageUsed, percentageUsedWarn) {
out = append(out, "elhasználódás")
}
return out
}
func positive(p *int) bool { return p != nil && *p > 0 }
func atLeast(p *int, n int) bool { return p != nil && *p >= n }