diff --git a/internal/server/handlers.go b/internal/server/handlers.go index a2700f1..40cf4ce 100644 --- a/internal/server/handlers.go +++ b/internal/server/handlers.go @@ -268,7 +268,42 @@ func (s *Server) handleGetSensors(w http.ResponseWriter, r *http.Request) { jsonResponse(w, []interface{}{}) return } - jsonResponse(w, result.Sensors) + sensors := append([]models.SensorReading{}, result.Sensors...) + sensors = append(sensors, synthesizePSUVoltageSensors(result.Hardware)...) + jsonResponse(w, sensors) +} + +func synthesizePSUVoltageSensors(hw *models.HardwareConfig) []models.SensorReading { + if hw == nil || len(hw.PowerSupply) == 0 { + return nil + } + const ( + nominalV = 220.0 + minV = nominalV * 0.9 // 198V + maxV = nominalV * 1.1 // 242V + ) + out := make([]models.SensorReading, 0, len(hw.PowerSupply)) + for _, psu := range hw.PowerSupply { + if psu.InputVoltage <= 0 { + continue + } + name := "PSU " + strings.TrimSpace(psu.Slot) + " input voltage" + if strings.TrimSpace(psu.Slot) == "" { + name = "PSU input voltage" + } + status := "ok" + if psu.InputVoltage < minV || psu.InputVoltage > maxV { + status = "warn" + } + out = append(out, models.SensorReading{ + Name: name, + Type: "voltage", + Value: psu.InputVoltage, + Unit: "V", + Status: status, + }) + } + return out } func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) { diff --git a/web/static/css/style.css b/web/static/css/style.css index 565663b..37362f5 100644 --- a/web/static/css/style.css +++ b/web/static/css/style.css @@ -662,6 +662,11 @@ code { border-left-color: #f39c12; } +.sensor-card.voltage-out-of-range { + border-left-color: #e74c3c; + background: #fff5f5; +} + .sensor-card.ns { border-left-color: #95a5a6; opacity: 0.6; diff --git a/web/static/js/app.js b/web/static/js/app.js index 6f2413e..374cac2 100644 --- a/web/static/js/app.js +++ b/web/static/js/app.js @@ -1100,8 +1100,10 @@ function renderSensors(sensors) { items.forEach(s => { let valueStr = ''; let statusClass = s.status === 'ok' ? 'ok' : (s.status === 'ns' ? 'ns' : 'warn'); + const sensorName = String(s.name || '').toLowerCase(); + const isPSUVoltage = type === 'voltage' && sensorName.includes('psu') && sensorName.includes('voltage'); - if (s.value) { + if (Number.isFinite(s.value)) { valueStr = `${s.value} ${s.unit}`; } else if (s.raw_value) { valueStr = s.raw_value; @@ -1109,7 +1111,13 @@ function renderSensors(sensors) { valueStr = s.status; } - html += `
+ // Highlight PSU mains voltage deviations from nominal 220V (±10%). + let extraClass = ''; + if (isPSUVoltage && Number.isFinite(s.value) && (s.value < 198 || s.value > 242)) { + extraClass = ' voltage-out-of-range'; + } + + html += `
${escapeHtml(s.name)} ${escapeHtml(valueStr)}
`;