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
+31
View File
@@ -0,0 +1,31 @@
// One widget, alone, for an iframe in someone else's dashboard (Glance, Home Assistant…):
// /embed/widget?type=gauge&sensor=cpu.usage&token=<read-only token>
// &machine=grid &title=… &theme=dark|light|cyberpunk|retro &every=<ms> &bare=1
// It stays live over the sensor stream. The token must be a read-only sensor token; a
// full-control token is refused in a URL by design.
import { useSensorStream, Source } from './sensors'
import { Widget, WidgetData, WidgetSpec, WidgetType, WIDGET_TYPES } from './widgets'
import { applyTheme } from './themes'
const p = new URLSearchParams(location.search)
if (p.get('theme')) applyTheme(p.get('theme')!)
export default function Embed() {
const sensor = p.get('sensor') ?? ''
const asked = p.get('type') as WidgetType | null
const type: WidgetType = /[*?[]/.test(sensor) ? 'list' : asked && WIDGET_TYPES.some((t) => t.id === asked && !t.many) ? asked : 'stat'
const spec: WidgetSpec = { id: 'w', type, sensor, title: p.get('title') ?? undefined }
const src: Source = { machine: p.get('machine') || null, token: p.get('token') ?? undefined }
const every = Number(p.get('every')) || undefined
const live = useSensorStream<{ w: WidgetData }>(sensor ? { w: sensor } : null, src, every)
return (
<div className={'embed' + (p.get('bare') === '1' ? ' bare' : '')}>
{!sensor ? <div className="error">embed needs ?sensor=</div> : (
<div className={'tile w-' + type}>
<Widget spec={spec} data={live.data ? live.data.w : undefined} src={src} />
{live.error && !live.data && <div className="sub">{src.token ? 'waiting for the stream — check the token' : 'no token given'}</div>}
</div>
)}
</div>
)
}