Disks: one card per physical drive (NVMe namespaces folded under their controller), partitions summarised on one line instead of listed
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
a372378098
commit
0af24f41c3
+14
-10
@@ -87,14 +87,13 @@ function DeviceCard({ d, live, forceOpen }: { d: StorageDevice; live?: { read_bp
|
||||
<span className="num">{fmtBytes(d.size)}{d.temp ? ` · ${d.temp.toFixed(0)}°C` : ''}</span>
|
||||
</div>
|
||||
{live && <div className="sub-head num muted">read {fmtRate(live.read_bps)} · write {fmtRate(live.write_bps)} · {pct(live.util_pct)} busy</div>}
|
||||
{d.parts.map((p) => (
|
||||
<div className="row" key={p.name}>
|
||||
<span className="k">{p.name}<small>{p.fstype || p.type}{p.label ? ` · ${p.label}` : ''}</small></span>
|
||||
{p.fs_size ? <Meter frac={(p.used ?? 0) / p.fs_size} /> : <span className="muted">{p.mount === '[SWAP]' ? 'swap' : p.mount ? '' : 'not mounted'}</span>}
|
||||
<span className="num">{p.fs_size ? `${fmtBytes(p.used ?? 0)} / ${fmtBytes(p.fs_size)} · ${p.mount}` : fmtBytes(p.size)}</span>
|
||||
</div>
|
||||
))}
|
||||
{d.parts.length === 0 && <div className="muted">No partitions.</div>}
|
||||
<div className="muted small disk-summary">
|
||||
{d.serial && <span>S/N {d.serial}</span>}
|
||||
{d.namespaces && d.namespaces.length > 1 && <span>{d.namespaces.length} namespaces</span>}
|
||||
<span>{d.parts.length} partition{d.parts.length === 1 ? '' : 's'}</span>
|
||||
{d.parts.filter((p) => p.mount && p.mount !== '[SWAP]').length > 0 && <span>mounted at {d.parts.filter((p) => p.mount && p.mount !== '[SWAP]').map((p) => p.mount).join(', ')}</span>}
|
||||
{d.parts.some((p) => p.mount === '[SWAP]') && <span>swap</span>}
|
||||
</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} />}
|
||||
</section>
|
||||
@@ -200,7 +199,12 @@ export default function Disks() {
|
||||
}, [rate])
|
||||
|
||||
if (!devs) return <div className="dash">{err ? <div className="error">{err}</div> : <Loading label="Scanning disks…" />}</div>
|
||||
const liveFor = (n: string) => live?.disks.find((d) => d.name === n)
|
||||
const liveFor = (dev: StorageDevice) => {
|
||||
const names = dev.namespaces?.length ? dev.namespaces : [dev.name]
|
||||
const rows = (live?.disks ?? []).filter((x) => names.includes(x.name))
|
||||
if (rows.length === 0) return undefined
|
||||
return { read_bps: rows.reduce((a, x) => a + x.read_bps, 0), write_bps: rows.reduce((a, x) => a + x.write_bps, 0), util_pct: Math.max(...rows.map((x) => x.util_pct)) }
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="dash disks">
|
||||
@@ -215,7 +219,7 @@ export default function Disks() {
|
||||
{!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() }} />}
|
||||
<div className="cards">
|
||||
{devs.map((d) => <DeviceCard key={d.name} d={d} live={liveFor(d.name)} forceOpen={allSmart ? true : null} />)}
|
||||
{devs.map((d) => <DeviceCard key={d.name} d={d} live={liveFor(d)} forceOpen={allSmart ? true : null} />)}
|
||||
</div>
|
||||
|
||||
<div className="dash-bar"><h2 className="section">ZFS</h2></div>
|
||||
|
||||
Reference in New Issue
Block a user