Compare commits

...

2 Commits

Author SHA1 Message Date
librelad
b4607de950 Merge claude/2 2026-05-28 21:28:19 +01:00
librelad
e6cc84271c 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>
2026-05-28 21:28:19 +01:00

View File

@ -202,22 +202,41 @@ class SystemStoragePage {
const palette = SystemStoragePage.PALETTE; const palette = SystemStoragePage.PALETTE;
const colorFor = i => palette[i % palette.length]; 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 appTotal = (as && as.total) || 0;
const appSegs = appRows.map((a, i) => ({ color: colorFor(i), data: { size: a.bytes || 0 } })); const appSegs = appRows.map((a, i) => ({ label: a.app, color: colorFor(i), data: { size: a.bytes || 0 } }));
const headline = appRows.length 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-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-head-stats">
<div class="sys-storage-stat"> <div class="sys-storage-stat">
<span class="sys-storage-stat-k">App data on disk</span> <span class="sys-storage-stat-k">App data on disk</span>
<strong class="sys-storage-stat-v">${fmt.bytes(appTotal)}</strong> <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> </div>
${d ? `<div class="sys-storage-stat"> ${d ? `<div class="sys-storage-stat sys-storage-stat-recl">
<span class="sys-storage-stat-k">Docker engine</span> <span class="sys-storage-stat-k">Docker reclaimable</span>
<strong class="sys-storage-stat-v">${fmt.bytes(d.total || 0)}</strong> <strong class="sys-storage-stat-v">${fmt.bytes(d.reclaimable || 0)}</strong>
<span class="sys-storage-stat-sub">${fmt.bytes(d.reclaimable || 0)} reclaimable</span> <span class="sys-storage-stat-sub">of ${fmt.bytes(d.total || 0)} engine overhead</span>
</div>` : ''} </div>` : ''}
</div> </div>
</div>` </div>`
@ -262,10 +281,9 @@ class SystemStoragePage {
// plus the largest images. The cleanup view, clearly separate from data. // plus the largest images. The cleanup view, clearly separate from data.
let dockerSection = ''; let dockerSection = '';
if (d) { if (d) {
const segments = SystemStoragePage.segmentsFrom(d);
const total = d.total || 0; const total = d.total || 0;
const recl = d.reclaimable || 0; const recl = d.reclaimable || 0;
const cards = segments.map(s => { const cards = dockerCats.map(s => {
const v = s.data || {}; const v = s.data || {};
const pct = v.size && total ? (v.size / total) * 100 : 0; const pct = v.size && total ? (v.size / total) * 100 : 0;
const rPct = v.size ? (v.reclaimable / v.size) * 100 : 0; const rPct = v.size ? (v.reclaimable / v.size) * 100 : 0;