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:
co-authored by
Claude Fable 5.1
parent
468f45251d
commit
b05a053920
@@ -165,6 +165,9 @@ type Collector struct {
|
||||
procAt time.Time
|
||||
lastTable *ProcTable
|
||||
upsProbe func() *UPS // nil when no UPS daemon is installed
|
||||
|
||||
subMu sync.Mutex
|
||||
subs map[chan struct{}]struct{} // told after every sample; see Subscribe
|
||||
}
|
||||
|
||||
func NewCollector() *Collector {
|
||||
@@ -309,6 +312,28 @@ func (c *Collector) sample() {
|
||||
c.ring = c.ring[len(c.ring)-History:]
|
||||
}
|
||||
c.mu.Unlock()
|
||||
|
||||
c.subMu.Lock()
|
||||
for ch := range c.subs {
|
||||
select {
|
||||
case ch <- struct{}{}:
|
||||
default: // a slow listener misses a tick; it reads the latest on the next one
|
||||
}
|
||||
}
|
||||
c.subMu.Unlock()
|
||||
}
|
||||
|
||||
// Subscribe returns a channel that gets a nudge after every new sample, and the func
|
||||
// that ends the subscription.
|
||||
func (c *Collector) Subscribe() (<-chan struct{}, func()) {
|
||||
ch := make(chan struct{}, 1)
|
||||
c.subMu.Lock()
|
||||
if c.subs == nil {
|
||||
c.subs = map[chan struct{}]struct{}{}
|
||||
}
|
||||
c.subs[ch] = struct{}{}
|
||||
c.subMu.Unlock()
|
||||
return ch, func() { c.subMu.Lock(); delete(c.subs, ch); c.subMu.Unlock() }
|
||||
}
|
||||
|
||||
type cpuTimes struct{ busy, total uint64 }
|
||||
|
||||
Reference in New Issue
Block a user