feat(webui): break the topology PSU row into one card per supply

Each PSU is its own card, coloured by its own status — a failed unit
goes red on its own instead of dragging a single grouped card down —
and shows input voltage + draw (measured output/input, else nameplate
rating). Cards click through to the PSU detail modal and carry data-psu
so the shared topoLiveScript refreshes their wattage from
/api/metrics/latest on the same 5s poll as the fan tiles.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019VHG21rgTUiR1G3qFHTVmN
This commit is contained in:
Mikhail Chusavitin
2026-09-04 12:32:56 +03:00
co-authored by Claude Sonnet 5
parent a4853a0a4f
commit 59271fa674
2 changed files with 125 additions and 54 deletions
+21 -8
View File
@@ -90,15 +90,22 @@ func TestTopoPageRendersArbitraryPSUAndFirmwareCountsAsFlexRows(t *testing.T) {
path := filepath.Join(dir, "audit.json")
okStatus := "OK"
failStatus := "Critical"
watt := 3000
volt := 230.0
var psus []schema.HardwarePowerSupply
for i := 0; i < 6; i++ {
slot := strconv.Itoa(i)
st := okStatus
if i == 3 {
st = failStatus
}
psus = append(psus, schema.HardwarePowerSupply{
HardwareComponentStatus: schema.HardwareComponentStatus{Status: &okStatus},
HardwareComponentStatus: schema.HardwareComponentStatus{Status: &st},
Slot: &slot,
WattageW: &watt,
InputVoltage: &volt,
})
}
@@ -132,14 +139,20 @@ func TestTopoPageRendersArbitraryPSUAndFirmwareCountsAsFlexRows(t *testing.T) {
if !strings.Contains(body, "BIOS") || !strings.Contains(body, "BMC") {
t.Fatalf("topo page missing BIOS/BMC firmware boxes: %s", body)
}
// All 6 PSUs must be represented, grouped into one stacked card with a
// count rather than 6 separate boxes.
if !strings.Contains(body, "Power Supplies ×6") {
t.Fatalf("topo page missing grouped Power Supplies x6 card: %s", body)
// One card per PSU (6), each clickable, each showing voltage + power.
if n := strings.Count(body, `class="topo-psu-tile"`); n != 6 {
t.Fatalf("expected 6 per-PSU cards, got %d", n)
}
if strings.Count(body, `onclick="openComponentDetail(&#39;psu&#39;)"`) != 1 &&
strings.Count(body, `onclick="openComponentDetail('psu')"`) != 1 {
t.Fatalf("expected exactly one clickable PSU group card, not one per PSU: %s", body)
if n := strings.Count(body, `onclick="openComponentDetail('psu')"`) +
strings.Count(body, `onclick="openComponentDetail(&#39;psu&#39;)"`); n != 6 {
t.Fatalf("expected one clickable card per PSU (6), got %d", n)
}
if !strings.Contains(body, "230 V · 3000 W rated") {
t.Fatalf("PSU card missing voltage/power line: %s", body)
}
// The one failed PSU is coloured red on its own (crit token) and labelled.
if !strings.Contains(body, "var(--crit-bg)") || !strings.Contains(body, "CRITICAL") {
t.Fatalf("failed PSU should render individually as critical: %s", body)
}
// Firmware/PSU rows must be flex-wrap HTML (arbitrary count, no overlap),
// not absolutely-positioned SVG rects sharing fixed x/y coordinates.