ux(system): one unified Storage donut — apps + Docker together

Put everything back in a single donut instead of an app-only one: a slice
per app plus Docker's images and build cache, with one legend listing them
all. Colours run continuously so app and Docker slices stay distinct, and
the Docker cards below reuse the same colours. The per-folder app
drill-down table stays. This is the "all the data, with app usage added"
view that was asked for.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
This commit is contained in:
librelad 2026-05-28 21:28:19 +01:00
parent beed825778
commit e6cc84271c

View File

@ -202,22 +202,41 @@ class SystemStoragePage {
const palette = SystemStoragePage.PALETTE;
const colorFor = i => palette[i % palette.length];
// ---- Headline: on-disk usage by app (the disk question that matters) ----
// ---- Headline: ONE donut covering everything — a slice per app, then
// Docker's own categories (images, build cache). Colours run continuously
// across the whole list so app and Docker slices stay distinct, and the
// legend lists them all. This is the unified "where's my disk going" view.
const appTotal = (as && as.total) || 0;
const appSegs = appRows.map((a, i) => ({ color: colorFor(i), data: { size: a.bytes || 0 } }));
const headline = appRows.length
const appSegs = appRows.map((a, i) => ({ label: a.app, color: colorFor(i), data: { size: a.bytes || 0 } }));
const dockerCats = d ? SystemStoragePage.segmentsFrom(d).map((s, j) => ({ ...s, color: colorFor(appSegs.length + j) })) : [];
const allSegs = [...appSegs, ...dockerCats];
const grandTotal = allSegs.reduce((t, s) => t + ((s.data && s.data.size) || 0), 0);
const legend = `
<ul class="sys-storage-legend">
${allSegs.map(s => `
<li>
<span class="sys-storage-swatch" style="background: var(--${s.color})"></span>
<span class="sys-storage-leg-k">${fmt.escape(s.label)}</span>
<span></span>
<span class="sys-storage-leg-v">${fmt.bytes((s.data && s.data.size) || 0)}</span>
</li>`).join('')}
</ul>`;
const headline = allSegs.length
? `<div class="sys-storage-headline">
<div class="sys-storage-head-card">${SystemStoragePage.donutSvg(appSegs, appTotal, 'app data')}</div>
<div class="sys-storage-head-card">
${SystemStoragePage.donutSvg(allSegs, grandTotal, 'in use')}
${legend}
</div>
<div class="sys-storage-head-stats">
<div class="sys-storage-stat">
<span class="sys-storage-stat-k">App data on disk</span>
<strong class="sys-storage-stat-v">${fmt.bytes(appTotal)}</strong>
<span class="sys-storage-stat-sub">${appRows.length} app${appRows.length === 1 ? '' : 's'}${as && as.total_external ? ` · ${fmt.bytes(as.total_external)} on external drives` : ''}</span>
<span class="sys-storage-stat-sub">${appRows.length} app${appRows.length === 1 ? '' : 's'}${as && as.total_external ? ` · ${fmt.bytes(as.total_external)} external` : ''}</span>
</div>
${d ? `<div class="sys-storage-stat">
<span class="sys-storage-stat-k">Docker engine</span>
<strong class="sys-storage-stat-v">${fmt.bytes(d.total || 0)}</strong>
<span class="sys-storage-stat-sub">${fmt.bytes(d.reclaimable || 0)} reclaimable</span>
${d ? `<div class="sys-storage-stat sys-storage-stat-recl">
<span class="sys-storage-stat-k">Docker reclaimable</span>
<strong class="sys-storage-stat-v">${fmt.bytes(d.reclaimable || 0)}</strong>
<span class="sys-storage-stat-sub">of ${fmt.bytes(d.total || 0)} engine overhead</span>
</div>` : ''}
</div>
</div>`
@ -262,10 +281,9 @@ class SystemStoragePage {
// plus the largest images. The cleanup view, clearly separate from data.
let dockerSection = '';
if (d) {
const segments = SystemStoragePage.segmentsFrom(d);
const total = d.total || 0;
const recl = d.reclaimable || 0;
const cards = segments.map(s => {
const cards = dockerCats.map(s => {
const v = s.data || {};
const pct = v.size && total ? (v.size / total) * 100 : 0;
const rPct = v.size ? (v.reclaimable / v.size) * 100 : 0;