Configurable update interval (100 ms – 2 s): drives daemon sampling and UI polling, saved with the dashboard layout
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
eb70daf129
commit
1751afc974
+21
-6
@@ -22,16 +22,22 @@ const MODULES: Module[] = [
|
||||
]
|
||||
const byId = (id: string) => MODULES.find((m) => m.id === id)
|
||||
|
||||
interface Layout { order: string[]; hidden: Set<string>; hiddenItems: Set<string> }
|
||||
const INTERVALS = [100, 250, 500, 1000, 2000] as const
|
||||
const DEFAULT_INTERVAL = 2000
|
||||
|
||||
interface Layout { order: string[]; hidden: Set<string>; hiddenItems: Set<string>; intervalMs: number }
|
||||
|
||||
// Unknown ids are dropped, missing ones appended, so an old saved layout survives new modules.
|
||||
function normalize(p: DashPrefs): Layout {
|
||||
const known = new Set(MODULES.map((m) => m.id))
|
||||
const order = (p.order ?? []).filter((id) => known.has(id))
|
||||
for (const m of MODULES) if (!order.includes(m.id)) order.push(m.id)
|
||||
return { order, hidden: new Set((p.hidden ?? []).filter((id) => known.has(id))), hiddenItems: new Set(p.hiddenItems ?? []) }
|
||||
const iv = p.intervalMs && p.intervalMs >= 100 && p.intervalMs <= 2000 ? p.intervalMs : DEFAULT_INTERVAL
|
||||
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] })
|
||||
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 ----
|
||||
function push(arr: number[], v: number) { arr.push(v); if (arr.length > MAX) arr.shift() }
|
||||
@@ -256,6 +262,8 @@ export default function Dashboard() {
|
||||
const [editing, setEditing] = useState(false)
|
||||
const hist = useRef<Series | null>(null)
|
||||
const saveTimer = useRef<number | undefined>(undefined)
|
||||
const intervalRef = useRef(DEFAULT_INTERVAL)
|
||||
intervalRef.current = layout.intervalMs
|
||||
|
||||
// Persist layout changes server-side (debounced) so every device sees the same dashboard.
|
||||
const update = useCallback((fn: (l: Layout) => Layout) => {
|
||||
@@ -300,9 +308,9 @@ export default function Dashboard() {
|
||||
setLatest(s); setErr('')
|
||||
} catch (e) { setErr(e instanceof Error ? e.message : 'failed') }
|
||||
}
|
||||
timer = window.setTimeout(tick, 2000)
|
||||
timer = window.setTimeout(tick, intervalRef.current)
|
||||
}
|
||||
seed().then(() => { timer = window.setTimeout(tick, 2000) })
|
||||
seed().then(() => { timer = window.setTimeout(tick, intervalRef.current) })
|
||||
const onVis = () => { if (document.visibilityState === 'visible' && !hist.current) seed() }
|
||||
document.addEventListener('visibilitychange', onVis)
|
||||
return () => { stop = true; clearTimeout(timer); document.removeEventListener('visibilitychange', onVis) }
|
||||
@@ -351,7 +359,14 @@ export default function Dashboard() {
|
||||
<div className={'dash' + (editing ? ' editing' : '')}>
|
||||
<div className="dash-bar">
|
||||
{err && <span className="error">{err}</span>}
|
||||
{editing && <span className="hint">Reorder with ▲▼, hide modules or single rows with ✕. Layout is saved as you go.</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>
|
||||
· charts show the last {fmtWindow(layout.intervalMs)} · ▲▼ reorder, ✕ hides a module or row
|
||||
</label>
|
||||
) : <span className="hint">last {fmtWindow(latest.interval_ms || layout.intervalMs)} · {fmtMs(latest.interval_ms || layout.intervalMs)}</span>}
|
||||
<span className="spacer" />
|
||||
{editing && <button className="btn" onClick={reset}>Reset</button>}
|
||||
<button className={'btn' + (editing ? ' on' : '')} onClick={() => setEditing(!editing)}>{editing ? 'Done' : 'Customize'}</button>
|
||||
|
||||
+2
-2
@@ -27,7 +27,7 @@ export interface Disk { name: string; model?: string; size: number; read_bps: nu
|
||||
export interface FS { mount: string; type: string; size: number; used: number }
|
||||
export interface UPS { driver: string; model: string; state: string; source: string; on_battery: boolean; in_v: number; out_v: number; battery_pct: number; runtime_min: number; load_w: number; load_pct: number; last_event?: string }
|
||||
export interface Sample {
|
||||
t: number; cpu: number; cores: number[]; load: [number, number, number]
|
||||
t: number; interval_ms: number; cpu: number; cores: number[]; load: [number, number, number]
|
||||
mem: { total: number; used: number; avail: number }; swap: { total: number; used: number; avail: number }
|
||||
temps: Temp[]; fans: Fan[]; net: NetIf[]; disks: Disk[]; fs: FS[]; ups: UPS | null; uptime: number
|
||||
}
|
||||
@@ -40,7 +40,7 @@ export interface Series {
|
||||
|
||||
// Dashboard layout. `order` lists module ids top-to-bottom; `hidden` are modules switched
|
||||
// off; `hiddenItems` are individual rows ("net:wlp1s0", "fs:/boot/efi", "disk:sda", "temp:chip/label").
|
||||
export interface DashPrefs { order?: string[]; hidden?: string[]; hiddenItems?: string[] }
|
||||
export interface DashPrefs { order?: string[]; hidden?: string[]; hiddenItems?: string[]; intervalMs?: number }
|
||||
|
||||
export const api = {
|
||||
dashPrefs: () => req<DashPrefs>('GET', '/api/prefs/dashboard'),
|
||||
|
||||
+2
-1
@@ -109,7 +109,8 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
||||
|
||||
/* dashboard: customize mode */
|
||||
.dash-bar { display: flex; align-items: center; gap: 8px; min-height: 28px; }
|
||||
.dash-bar .hint { color: var(--muted); font-size: 12px; }
|
||||
.dash-bar .hint { color: var(--muted); font-size: 12px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
|
||||
.dash-bar select.btn { padding: 3px 6px; }
|
||||
.btn { padding: 5px 10px; background: var(--bg3); border: 1px solid var(--border); border-radius: 5px; font-size: 12px; color: var(--fg); }
|
||||
.btn.on { background: var(--accent); color: #111; border-color: var(--accent); }
|
||||
.mod-tools { display: flex; gap: 4px; margin: -4px -4px 6px auto; justify-content: flex-end; }
|
||||
|
||||
Reference in New Issue
Block a user