Disks: the SMART table stays inside its card
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
dbfe163a75
commit
a7b8319974
@@ -520,3 +520,27 @@ table is re-read under it; terminate, on a throwaway `sleep`, asks by name and p
|
|||||||
and the footer stops offering a process that is gone. Sampler values outside 100 ms–60 s are
|
and the footer stops offering a process that is gone. Sampler values outside 100 ms–60 s are
|
||||||
refused, and a rate slower than the collector's 10 s samples at 10 s. All 14 pages at his
|
refused, and a rate slower than the collector's 10 s samples at 10 s. All 14 pages at his
|
||||||
window size and on a phone, in both browsers: no errors, no overflow.
|
window size and on a phone, in both browsers: no errors, no overflow.
|
||||||
|
|
||||||
|
|
||||||
|
# Bug bash 4, later — 2026-09-21: the SMART table outside its card
|
||||||
|
|
||||||
|
Mitchell, from the Disks page of `hamtv` with SMART open: the attribute table's rows ran out
|
||||||
|
through the right-hand side of the card they belong to, the rules and the Thresh column
|
||||||
|
drawn over the page behind.
|
||||||
|
|
||||||
|
12. ✅ **A table cannot be narrower than its widest unbreakable cell, and one of `hamtv`'s
|
||||||
|
was 130px wide.** `.num` sets `white-space: nowrap`, and a SMART raw value is not always a
|
||||||
|
number — the temperature attribute reads `34 (Min/Max 28/39)`. That one cell set the
|
||||||
|
table's minimum width above the card's, so it spilled. Three changes, in this order,
|
||||||
|
because the first two each looked worse than the bug in their own way:
|
||||||
|
- the raw column wraps — but `overflow-wrap: anywhere` broke `4295163906` across two
|
||||||
|
lines as "429516 / 3906", which reads as two numbers. It wraps at spaces only now;
|
||||||
|
- a disk card is given room for six columns of SMART (`minmax(420px, 1fr)` rather than
|
||||||
|
the shared 320px), and the cells' side padding goes 6px → 4px, which together is the
|
||||||
|
~40px it was short by;
|
||||||
|
- if some drive ever reports a raw value long enough to still not fit, the table scrolls
|
||||||
|
inside its card rather than out of it.
|
||||||
|
Verified on all four machines at six widths from 1721px down to a phone, in Chromium and
|
||||||
|
Firefox: nothing leaves its card, no number is broken across lines, and the fallback
|
||||||
|
scrollbar is never reached. The same measurement was run over every other page's cards
|
||||||
|
and boxes — Dashboard, Top, Docker, Alerts, Machines and the rest — and found nothing.
|
||||||
|
|||||||
+4
-2
@@ -58,17 +58,19 @@ function SmartPanel({ dev, rotational }: { dev: string; rotational: boolean }) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{s.attrs.length > 0 && (
|
{s.attrs.length > 0 && (
|
||||||
|
<div className="attrs-wrap">
|
||||||
<table className="attrs">
|
<table className="attrs">
|
||||||
<thead><tr><th>ID</th><th>Attribute</th><th>Raw</th><th>Value</th><th>Worst</th><th>Thresh</th></tr></thead>
|
<thead><tr><th>ID</th><th>Attribute</th><th>Raw</th><th>Value</th><th>Worst</th><th>Thresh</th></tr></thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{s.attrs.map((a) => (
|
{s.attrs.map((a) => (
|
||||||
<tr key={a.id} className={a.warn ? 'warn' : ''}>
|
<tr key={a.id} className={a.warn ? 'warn' : ''}>
|
||||||
<td className="num">{a.id}</td><td>{a.name.replace(/_/g, ' ')}{a.failed && <span className="badge bad">{a.failed}</span>}</td>
|
<td className="num">{a.id}</td><td>{a.name.replace(/_/g, ' ')}{a.failed && <span className="badge bad">{a.failed}</span>}</td>
|
||||||
<td className="num">{a.raw}</td><td className="num">{a.value}</td><td className="num">{a.worst}</td><td className="num">{a.thresh}</td>
|
<td className="num raw">{a.raw}</td><td className="num">{a.value}</td><td className="num">{a.worst}</td><td className="num">{a.thresh}</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
{!s.self_test_supported && <div className="muted small">Self-tests: {s.self_test_note || 'not available on this drive'}.</div>}
|
{!s.self_test_supported && <div className="muted small">Self-tests: {s.self_test_note || 'not available on this drive'}.</div>}
|
||||||
{s.self_test_supported && <div className="svc-actions">
|
{s.self_test_supported && <div className="svc-actions">
|
||||||
@@ -231,7 +233,7 @@ export default function Disks() {
|
|||||||
</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 disk-cards">
|
||||||
{devs.map((d) => <DeviceCard key={d.name} d={d} live={liveFor(d)} forceOpen={allSmart ? true : null} />)}
|
{devs.map((d) => <DeviceCard key={d.name} d={d} live={liveFor(d)} forceOpen={allSmart ? true : null} />)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+12
-2
@@ -262,9 +262,19 @@ h2.section { margin: 0; font-size: 13px; font-weight: 600; color: var(--muted);
|
|||||||
.good { color: var(--ok); } .warn-text { color: var(--s2); }
|
.good { color: var(--ok); } .warn-text { color: var(--s2); }
|
||||||
.smart { display: flex; flex-direction: column; gap: 8px; padding-top: 8px; border-top: 1px solid var(--border); }
|
.smart { display: flex; flex-direction: column; gap: 8px; padding-top: 8px; border-top: 1px solid var(--border); }
|
||||||
.attrs { width: 100%; border-collapse: collapse; font-size: 12px; }
|
.attrs { width: 100%; border-collapse: collapse; font-size: 12px; }
|
||||||
.attrs th { text-align: left; color: var(--muted); font-weight: 500; padding: 4px 6px; border-bottom: 1px solid var(--border); }
|
.attrs th { text-align: left; color: var(--muted); font-weight: 500; padding: 4px 4px; border-bottom: 1px solid var(--border); }
|
||||||
.attrs td { padding: 3px 6px; border-bottom: 1px solid var(--border); } .attrs td.num, .attrs th:not(:nth-child(2)) { text-align: right; }
|
.attrs td { padding: 3px 4px; border-bottom: 1px solid var(--border); } .attrs td.num, .attrs th:not(:nth-child(2)) { text-align: right; }
|
||||||
.attrs tr.warn td { color: var(--s2); }
|
.attrs tr.warn td { color: var(--s2); }
|
||||||
|
/* A SMART raw value is not always a number — a temperature reads "36 (Min/Max 28/39)" — and a
|
||||||
|
table cannot be narrower than its widest unbreakable cell, so one of those pushed the whole
|
||||||
|
table out through the side of its card. The raw column wraps, but only where there is a
|
||||||
|
space: a number broken across two lines reads as two numbers. If some drive ever reports
|
||||||
|
one long enough to still not fit, the table scrolls inside its card rather than out of it. */
|
||||||
|
.attrs td.raw { white-space: normal; }
|
||||||
|
.attrs-wrap { overflow-x: auto; scrollbar-width: thin; }
|
||||||
|
/* Six columns of SMART want about 350px between them; a disk card is given room for that, so
|
||||||
|
the fallback above stays a fallback. */
|
||||||
|
.disk-cards { grid-template-columns: repeat(auto-fill, minmax(min(420px, 100%), 1fr)); }
|
||||||
.datasets { padding: 0; overflow-x: auto; }
|
.datasets { padding: 0; overflow-x: auto; }
|
||||||
.ds-row { display: grid; grid-template-columns: minmax(160px, 1fr) 80px 80px 80px 50px 50px minmax(120px, 1fr); gap: 8px; align-items: center; padding: 6px 12px; border-bottom: 1px solid var(--border); font-size: 13px; min-width: 700px; } /* the columns and gaps add up to 692: at 640 the row ended before its own cells did */
|
.ds-row { display: grid; grid-template-columns: minmax(160px, 1fr) 80px 80px 80px 50px 50px minmax(120px, 1fr); gap: 8px; align-items: center; padding: 6px 12px; border-bottom: 1px solid var(--border); font-size: 13px; min-width: 700px; } /* the columns and gaps add up to 692: at 640 the row ended before its own cells did */
|
||||||
.ds-row.head { color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: .5px; }
|
.ds-row.head { color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: .5px; }
|
||||||
|
|||||||
Reference in New Issue
Block a user