Disks: one toggle opens SMART for every drive, remembered per device

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Mitchell-Johnson
2026-09-18 13:41:38 -07:00
co-authored by Claude Fable 5.1
parent 4b8a2b15ed
commit b6502ba362
+8 -4
View File
@@ -73,8 +73,10 @@ function SmartPanel({ dev, rotational }: { dev: string; rotational: boolean }) {
} }
// ---- device card ---- // ---- device card ----
function DeviceCard({ d, live }: { d: StorageDevice; live?: { read_bps: number; write_bps: number; util_pct: number } }) { function DeviceCard({ d, live, forceOpen }: { d: StorageDevice; live?: { read_bps: number; write_bps: number; util_pct: number }; forceOpen: boolean | null }) {
const [open, setOpen] = useState(false) const [own, setOwn] = useState(false)
const open = forceOpen ?? own
const setOpen = (v: boolean) => setOwn(v)
return ( return (
<section className="card disk"> <section className="card disk">
<div className="sub-head"> <div className="sub-head">
@@ -93,7 +95,7 @@ function DeviceCard({ d, live }: { d: StorageDevice; live?: { read_bps: number;
</div> </div>
))} ))}
{d.parts.length === 0 && <div className="muted">No partitions.</div>} {d.parts.length === 0 && <div className="muted">No partitions.</div>}
<div className="svc-actions"><button className="btn" onClick={() => setOpen(!open)}>{open ? 'Hide SMART' : 'SMART'}</button></div> {forceOpen === null && <div className="svc-actions"><button className="btn" onClick={() => setOpen(!open)}>{open ? 'Hide SMART' : 'SMART'}</button></div>}
{open && <SmartPanel dev={d.name} rotational={d.rotational} />} {open && <SmartPanel dev={d.name} rotational={d.rotational} />}
</section> </section>
) )
@@ -179,6 +181,7 @@ export default function Disks() {
const [live, setLive] = useState<Sample | null>(null) const [live, setLive] = useState<Sample | null>(null)
const [err, setErr] = useState('') const [err, setErr] = useState('')
const [task, setTask] = useState<string | null>(null) const [task, setTask] = useState<string | null>(null)
const [allSmart, setAllSmart] = useState<boolean>(() => localStorage.getItem('hamlab.disks.smart') === '1')
const rate = useRefreshMs() const rate = useRefreshMs()
const load = useCallback(async () => { const load = useCallback(async () => {
@@ -205,13 +208,14 @@ export default function Disks() {
<div className="dash-bar"> <div className="dash-bar">
<h2 className="section">Devices · {devs.length}</h2> <h2 className="section">Devices · {devs.length}</h2>
<span className="spacer" /> <span className="spacer" />
{hasSmart && <button className={'chip' + (allSmart ? ' on' : '')} title="show SMART for every drive" onClick={() => { const v = !allSmart; setAllSmart(v); localStorage.setItem('hamlab.disks.smart', v ? '1' : '0') }}>{allSmart ? 'SMART: all open' : 'SMART: show all'}</button>}
{!hasSmart && <Btn className="btn" onClick={async () => { try { const t = await api.installPackages(['smartmontools']); setTask(t.id) } catch (e) { toast(e instanceof Error ? e.message : 'failed', 'err') } }}>Install smartmontools</Btn>} {!hasSmart && <Btn className="btn" onClick={async () => { try { const t = await api.installPackages(['smartmontools']); setTask(t.id) } catch (e) { toast(e instanceof Error ? e.message : 'failed', 'err') } }}>Install smartmontools</Btn>}
<Btn className="btn" title="reload" onClick={load}>↻</Btn> <Btn className="btn" title="reload" onClick={load}>↻</Btn>
</div> </div>
{!hasSmart && <div className="notice">SMART health needs smartmontools on this host. Install it with the button, then reload.</div>} {!hasSmart && <div className="notice">SMART health needs smartmontools on this host. Install it with the button, then reload.</div>}
{task && <TaskOutput id={task} onDone={(t) => { toast(`${t.title}: ${t.exit_code === 0 ? 'finished' : `exited ${t.exit_code}`}`, t.exit_code === 0 ? 'ok' : 'err'); load() }} />} {task && <TaskOutput id={task} onDone={(t) => { toast(`${t.title}: ${t.exit_code === 0 ? 'finished' : `exited ${t.exit_code}`}`, t.exit_code === 0 ? 'ok' : 'err'); load() }} />}
<div className="cards"> <div className="cards">
{devs.map((d) => <DeviceCard key={d.name} d={d} live={liveFor(d.name)} />)} {devs.map((d) => <DeviceCard key={d.name} d={d} live={liveFor(d.name)} forceOpen={allSmart ? true : null} />)}
</div> </div>
<div className="dash-bar"><h2 className="section">ZFS</h2></div> <div className="dash-bar"><h2 className="section">ZFS</h2></div>