Sensors: read-only tokens, live stream, Prometheus, status sensors, widgets, embeds

- sensors:read tokens open the sensor endpoints (here and on managed machines
  through the hub) and nothing else; full tokens are never accepted from a URL
- /api/sensors/stream: a shaped query that keeps answering, over SSE
- /metrics: Prometheus text format
- services, updates, ZFS and SMART as background status sensors
- widget library over the sensor API; custom widgets on the dashboard
- /embed/widget: one live widget in its own bundle, the only frameable path
- Dashboard tab is now Monitors

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Mitchell-Johnson
2026-09-19 07:37:10 -07:00
co-authored by Claude Fable 5.1
parent 468f45251d
commit b05a053920
20 changed files with 950 additions and 56 deletions
+24
View File
@@ -3,6 +3,7 @@ package sensors
import (
"encoding/json"
"reflect"
"strings"
"testing"
)
@@ -51,3 +52,26 @@ func TestShape(t *testing.T) {
}
}
}
func TestWriteProm(t *testing.T) {
var b strings.Builder
WriteProm(&b, []Sensor{
{ID: "cpu.usage", Kind: "usage", Unit: "%", Value: 12.5},
{ID: "ups.runtime", Kind: "duration", Unit: "min", Value: 2.0},
{ID: "net.eth0.up", Kind: "state", Value: true, Labels: map[string]string{"iface": "eth0"}},
{ID: "ups.state", Kind: "state", Value: `Nor"mal`},
{ID: "updates.pending", Kind: "count", Value: 3.0},
})
got := b.String()
for _, want := range []string{
"# TYPE hamlab_usage_percent gauge\n" + `hamlab_usage_percent{id="cpu.usage"} 12.5`,
`hamlab_duration_seconds{id="ups.runtime"} 120`,
`hamlab_state{id="net.eth0.up",iface="eth0"} 1`,
`hamlab_state{id="ups.state",value="Nor\"mal"} 1`,
`hamlab_updates_pending{id="updates.pending"} 3`,
} {
if !strings.Contains(got, want) {
t.Errorf("missing %q in:\n%s", want, got)
}
}
}