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
+1 -49
View File
@@ -136,16 +136,6 @@ type Sample struct {
fsT, upsT int64 // when the slow sections were really read; sensors report these, not T
}
// Series is the compact history the UI seeds its sparklines from.
type Series struct {
T []int64 `json:"t"`
CPU []float64 `json:"cpu"`
Mem []uint64 `json:"mem"`
Net map[string]map[string][]float64 `json:"net"` // iface -> {rx,tx}
Disk map[string]map[string][]float64 `json:"disk"` // name -> {r,w,u}
Temp map[string][]float64 `json:"temp"` // "chip/label" -> °C
}
type Collector struct {
mu sync.RWMutex
ring []Sample
@@ -236,44 +226,6 @@ func (c *Collector) Latest() (Sample, bool) {
return c.ring[len(c.ring)-1], true
}
func (c *Collector) History() Series {
c.mu.RLock()
defer c.mu.RUnlock()
n := len(c.ring)
s := Series{T: make([]int64, n), CPU: make([]float64, n), Mem: make([]uint64, n),
Net: map[string]map[string][]float64{}, Disk: map[string]map[string][]float64{}, Temp: map[string][]float64{}}
series := func(m map[string]map[string][]float64, key string, keys ...string) map[string][]float64 {
v, ok := m[key]
if !ok {
v = map[string][]float64{}
for _, k := range keys {
v[k] = make([]float64, n)
}
m[key] = v
}
return v
}
for i, smp := range c.ring {
s.T[i], s.CPU[i], s.Mem[i] = smp.T, smp.CPU, smp.Mem.Used
for _, x := range smp.Net {
v := series(s.Net, x.Iface, "rx", "tx")
v["rx"][i], v["tx"][i] = x.RxBps, x.TxBps
}
for _, d := range smp.Disks {
v := series(s.Disk, d.Name, "r", "w", "u")
v["r"][i], v["w"][i], v["u"][i] = d.ReadBps, d.WriteBps, d.UtilPct
}
for _, t := range smp.Temps {
k := t.Chip + "/" + t.Label
if s.Temp[k] == nil {
s.Temp[k] = make([]float64, n)
}
s.Temp[k][i] = t.C
}
}
return s
}
// ---- sampling ----
func (c *Collector) sample() {
@@ -853,7 +805,7 @@ func upsNUT() *UPS {
u.InV = leadingNum(kv["input.voltage"])
u.OutV = leadingNum(kv["output.voltage"])
u.BatteryPct = leadingNum(kv["battery.charge"])
u.RuntimeMin = leadingNum(kv["battery.runtime"]) / 60
u.RuntimeMin = round1(leadingNum(kv["battery.runtime"]) / 60)
u.LoadPct = leadingNum(kv["ups.load"])
u.LoadW = leadingNum(kv["ups.realpower"])
if u.LoadW == 0 {