The full switch: Monitors and Disks read only the sensor API; /api/metrics retired

- stock Monitors modules fed by one sensor stream, the catalog and one batch of
  histories (GET /api/sensors/history?ids=…), through sampleAdapter.ts
- the stream closes while the page is hidden and refills its curves on return
- Disks tab live rates from the stream
- new sensors: hamlab.sample_interval, ups.last_event
- stream: asking for about the sampling rate means every sample
- NUT runtime rounded at the source

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Mitchell-Johnson
2026-09-19 07:52:14 -07:00
co-authored by Claude Fable 5.1
parent f01175ceca
commit 4ddf10be51
13 changed files with 283 additions and 173 deletions
+18 -12
View File
@@ -8,7 +8,7 @@ func TestFlatten(t *testing.T) {
Fans: []Fan{{Chip: "nct6775", Label: "fan2", RPM: 1100}},
FS: []FS{{Mount: "/", Size: 100, Used: 25}}, fsT: 900}
got := map[string]any{}
for _, x := range flatten(s, "") {
for _, x := range flatten(s, nil) {
if _, dup := got[x.ID]; dup {
t.Errorf("duplicate id %s", x.ID)
}
@@ -20,31 +20,37 @@ func TestFlatten(t *testing.T) {
t.Errorf("%s = %v, want %v", id, got[id], want)
}
}
one := flatten(s, "temp.nvme.composite_2")
one := flatten(s, func(id string) bool { return id == "temp.nvme.composite_2" })
if len(one) != 1 || one[0].Value != 45.0 {
t.Errorf("only: got %+v", one)
}
for _, x := range flatten(s, "fs.root.used") {
for _, x := range flatten(s, func(id string) bool { return id == "fs.root.used" }) {
if x.T != 900 {
t.Errorf("fs sensor should carry the fs read time, got %d", x.T)
}
}
}
func TestSensorHistory(t *testing.T) {
func TestSensorHistories(t *testing.T) {
c := NewCollector()
c.ring = []Sample{{T: 1, CPU: 10}, {T: 2, CPU: 20, Net: []Net{{Iface: "eth0", Up: true, RxBps: 5}}}}
h, ok := c.SensorHistory("cpu.usage")
if !ok || len(h.V) != 2 || h.V[1] != 20 || h.Unit != "%" {
t.Fatalf("cpu: %+v %v", h, ok)
got := map[string][]float64{}
for _, h := range c.SensorHistories(func(id string) bool { return id == "cpu.usage" || id == "net.eth0.rx" || id == "net.eth0.up" }) {
if len(h.T) != len(h.V) {
t.Errorf("%s: t and v differ in length", h.ID)
}
got[h.ID] = h.V
}
if h, ok := c.SensorHistory("net.eth0.rx"); !ok || len(h.V) != 1 || h.T[0] != 2 {
t.Fatalf("net: a sensor that appeared later has a shorter history, got %+v %v", h, ok)
if len(got["cpu.usage"]) != 2 || got["cpu.usage"][1] != 20 {
t.Errorf("cpu: %v", got["cpu.usage"])
}
if _, ok := c.SensorHistory("net.eth0.up"); ok {
if len(got["net.eth0.rx"]) != 1 {
t.Errorf("a sensor that appeared later has a shorter history, got %v", got["net.eth0.rx"])
}
if _, ok := got["net.eth0.up"]; ok {
t.Error("a state has no history")
}
if _, ok := c.SensorHistory("nope"); ok {
t.Error("unknown id")
if len(c.SensorHistories(func(string) bool { return false })) != 0 {
t.Error("nothing matched, nothing returned")
}
}