mirror of
https://github.com/henrygd/beszel.git
synced 2026-03-21 21:26:16 +01:00
* SMART: add eMMC health via sysfs Read eMMC wear/EOL indicators from /sys/class/block/mmcblk*/device and expose in SMART device list. Includes mocked sysfs tests and UI tweaks for unknown temps. * small optimizations for emmc scan and parsing * smart: keep smartctl optional only for Linux hosts with eMMC * update smart alerts to handle warning state * refactor: rename binPath to smartctlPath and replace hasSmartctl with smartctlPath checks --------- Co-authored-by: henrygd <hank@henrygd.me>
108 lines
2.6 KiB
Go
108 lines
2.6 KiB
Go
package alerts
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/pocketbase/pocketbase/core"
|
|
)
|
|
|
|
// handleSmartDeviceAlert sends alerts when a SMART device state worsens into WARNING/FAILED.
|
|
// This is automatic and does not require user opt-in.
|
|
func (am *AlertManager) handleSmartDeviceAlert(e *core.RecordEvent) error {
|
|
oldState := e.Record.Original().GetString("state")
|
|
newState := e.Record.GetString("state")
|
|
|
|
if !shouldSendSmartDeviceAlert(oldState, newState) {
|
|
return e.Next()
|
|
}
|
|
|
|
systemID := e.Record.GetString("system")
|
|
if systemID == "" {
|
|
return e.Next()
|
|
}
|
|
|
|
// Fetch the system record to get the name and users
|
|
systemRecord, err := e.App.FindRecordById("systems", systemID)
|
|
if err != nil {
|
|
e.App.Logger().Error("Failed to find system for SMART alert", "err", err, "systemID", systemID)
|
|
return e.Next()
|
|
}
|
|
|
|
systemName := systemRecord.GetString("name")
|
|
deviceName := e.Record.GetString("name")
|
|
model := e.Record.GetString("model")
|
|
statusLabel := smartStateLabel(newState)
|
|
|
|
// Build alert message
|
|
title := fmt.Sprintf("SMART %s on %s: %s %s", statusLabel, systemName, deviceName, smartStateEmoji(newState))
|
|
var message string
|
|
if model != "" {
|
|
message = fmt.Sprintf("Disk %s (%s) SMART status changed to %s", deviceName, model, newState)
|
|
} else {
|
|
message = fmt.Sprintf("Disk %s SMART status changed to %s", deviceName, newState)
|
|
}
|
|
|
|
// Get users associated with the system
|
|
userIDs := systemRecord.GetStringSlice("users")
|
|
if len(userIDs) == 0 {
|
|
return e.Next()
|
|
}
|
|
|
|
// Send alert to each user
|
|
for _, userID := range userIDs {
|
|
if err := am.SendAlert(AlertMessageData{
|
|
UserID: userID,
|
|
SystemID: systemID,
|
|
Title: title,
|
|
Message: message,
|
|
Link: am.hub.MakeLink("system", systemID),
|
|
LinkText: "View " + systemName,
|
|
}); err != nil {
|
|
e.App.Logger().Error("Failed to send SMART alert", "err", err, "userID", userID)
|
|
}
|
|
}
|
|
|
|
return e.Next()
|
|
}
|
|
|
|
func shouldSendSmartDeviceAlert(oldState, newState string) bool {
|
|
oldSeverity := smartStateSeverity(oldState)
|
|
newSeverity := smartStateSeverity(newState)
|
|
|
|
// Ignore unknown states and recoveries; only alert on worsening transitions
|
|
// from known-good/degraded states into WARNING/FAILED.
|
|
return oldSeverity >= 1 && newSeverity > oldSeverity
|
|
}
|
|
|
|
func smartStateSeverity(state string) int {
|
|
switch state {
|
|
case "PASSED":
|
|
return 1
|
|
case "WARNING":
|
|
return 2
|
|
case "FAILED":
|
|
return 3
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
func smartStateEmoji(state string) string {
|
|
switch state {
|
|
case "WARNING":
|
|
return "\U0001F7E0"
|
|
default:
|
|
return "\U0001F534"
|
|
}
|
|
}
|
|
|
|
func smartStateLabel(state string) string {
|
|
switch state {
|
|
case "FAILED":
|
|
return "failure"
|
|
default:
|
|
return strings.ToLower(state)
|
|
}
|
|
}
|