Global refresh rate in Settings drives every live view and each machine's sampler; per-page pickers removed

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Mitchell-Johnson
2026-09-18 13:27:13 -07:00
co-authored by Claude Fable 5.1
parent a4f39859b6
commit bbf0f2580f
11 changed files with 92 additions and 32 deletions
+13 -11
View File
@@ -2,6 +2,7 @@ import { ReactNode, useCallback, useEffect, useRef, useState } from 'react'
import { api, DashPrefs, Sample, Series, Temp } from './api'
import Sparkline from './Sparkline'
import { Loading, Spinner, toast } from './ui'
import { fmtMs, useRefreshMs } from './prefs'
const MAX = 300
const S1 = 'var(--s1)', S2 = 'var(--s2)'
@@ -23,8 +24,8 @@ const MODULES: Module[] = [
]
const byId = (id: string) => MODULES.find((m) => m.id === id)
const INTERVALS = [100, 250, 500, 1000, 2000] as const
const DEFAULT_INTERVAL = 2000
const clampSampler = (ms: number) => Math.min(2000, Math.max(100, ms))
interface Layout { order: string[]; hidden: Set<string>; hiddenItems: Set<string>; intervalMs: number }
@@ -37,7 +38,6 @@ function normalize(p: DashPrefs): Layout {
return { order, hidden: new Set((p.hidden ?? []).filter((id) => known.has(id))), hiddenItems: new Set(p.hiddenItems ?? []), intervalMs: iv }
}
const toPrefs = (l: Layout): DashPrefs => ({ order: l.order, hidden: [...l.hidden], hiddenItems: [...l.hiddenItems], intervalMs: l.intervalMs })
const fmtMs = (ms: number) => (ms >= 1000 ? `${ms / 1000} s` : `${ms} ms`)
const fmtWindow = (ms: number) => { const s = Math.round((MAX * ms) / 1000); return s >= 60 ? `${Math.round(s / 60)} min` : `${s} s` }
// ---- formatting ----
@@ -266,8 +266,9 @@ export default function Dashboard() {
const savedTimer = useRef<number | undefined>(undefined)
const hist = useRef<Series | null>(null)
const saveTimer = useRef<number | undefined>(undefined)
const refreshMs = useRefreshMs()
const intervalRef = useRef(DEFAULT_INTERVAL)
intervalRef.current = layout.intervalMs
intervalRef.current = refreshMs
// Persist layout changes server-side (debounced) so every device sees the same dashboard.
const update = useCallback((fn: (l: Layout) => Layout) => {
@@ -328,6 +329,13 @@ export default function Dashboard() {
return () => { stop = true; clearTimeout(timer); document.removeEventListener('visibilitychange', onVis) }
}, [])
// The sampler on this machine follows the global rate (clamped to what it supports).
useEffect(() => {
if (!loaded) return
const want = clampSampler(refreshMs)
if (layout.intervalMs !== want) update((l) => ({ ...l, intervalMs: want }))
}, [refreshMs, loaded]) // eslint-disable-line react-hooks/exhaustive-deps
if (!latest) return <div className="dash">{err ? <div className="error">{err}</div> : <Loading label="Collecting metrics…" />}</div>
const edit: Edit = {
@@ -371,14 +379,8 @@ export default function Dashboard() {
<div className={'dash' + (editing ? ' editing' : '')}>
<div className="dash-bar">
{err && <span className="error">{err}</span>}
{editing ? (
<label className="hint">Update every
<select className="btn" value={layout.intervalMs} onChange={(e) => update((l) => ({ ...l, intervalMs: Number(e.target.value) }))}>
{INTERVALS.map((ms) => <option key={ms} value={ms}>{fmtMs(ms)}</option>)}
</select>
· last {fmtWindow(layout.intervalMs)} · ▲▼ reorder · ✕ hide
</label>
) : <span className="hint">last {fmtWindow(latest.interval_ms || layout.intervalMs)} · {fmtMs(latest.interval_ms || layout.intervalMs)}</span>}
{editing ? <span className="hint">last {fmtWindow(latest.interval_ms || layout.intervalMs)} · ▲▼ reorder · ✕ hide · refresh rate is in Settings</span>
: <span className="hint">last {fmtWindow(latest.interval_ms || layout.intervalMs)} · {fmtMs(latest.interval_ms || layout.intervalMs)}</span>}
<span className="spacer" />
{saving === 'saving' && <span className="saved"><Spinner /> saving</span>}
{saving === 'saved' && <span className="saved">saved ✓</span>}