diff --git a/containers/libreportal/backend/routes/docker-info-routes.js b/containers/libreportal/backend/routes/docker-info-routes.js index 4f1057d..5e33880 100644 --- a/containers/libreportal/backend/routes/docker-info-routes.js +++ b/containers/libreportal/backend/routes/docker-info-routes.js @@ -22,8 +22,11 @@ // // GET /api/system/storage // `docker system df` — total + reclaimable per category (images, -// containers, volumes, build cache). Cached for STORAGE_TTL_MS because -// this is one of the more expensive calls on a busy daemon. +// containers, build cache). Cached for STORAGE_TTL_MS because this is one +// of the more expensive calls on a busy daemon. Named volumes are omitted: +// LibrePortal apps keep data in bind mounts, so volume accounting is always +// ~empty here — per-app on-disk usage is generated separately (see +// webuiSystemAppStorage / /data/system/app_storage.json). // // Mounted at /api/system in routes.js (so paths are /api/system/containers // etc.). Uses the shared docker util (utils/docker.js) which talks to the @@ -336,9 +339,9 @@ router.get('/storage', async (req, res) => { // Roll the verbose response up into headline numbers per category. // "Reclaimable" across this page reflects exactly what the Reclaim // button frees: dangling images + the whole build cache. Tagged-but- - // unused images, stopped containers and volumes are deliberately NOT - // counted — the safe prune leaves them alone — so the headline number - // matches the button's effect instead of overstating it. + // unused images and stopped containers are deliberately NOT counted — + // the safe prune leaves them alone — so the headline number matches the + // button's effect instead of overstating it. const isDangling = (im) => { const tags = im.RepoTags || []; return tags.length === 0 || tags.every(t => t.includes('')); @@ -361,14 +364,6 @@ router.get('/storage', async (req, res) => { }, { count: 0, size: 0, reclaimable: 0 } ); - const sumVolumes = (df.Volumes || []).reduce( - (a, v) => { - a.count++; - a.size += (v.UsageData && v.UsageData.Size) || 0; - return a; - }, - { count: 0, size: 0, reclaimable: 0 } - ); const sumBuild = (df.BuildCache || []).reduce( (a, b) => { a.count++; @@ -392,29 +387,15 @@ router.get('/storage', async (req, res) => { containers: im.Containers || 0, created: im.Created, })); - // Largest volumes — reclaimable or otherwise. Useful when /docker - // is hitting 90%+. - const topVolumes = (df.Volumes || []) - .slice() - .sort((a, b) => ((b.UsageData?.Size) || 0) - ((a.UsageData?.Size) || 0)) - .slice(0, 10) - .map(v => ({ - name: v.Name, - driver: v.Driver, - size: (v.UsageData && v.UsageData.Size) || 0, - ref_count: (v.UsageData && v.UsageData.RefCount) || 0, - })); - const total = sumImages.size + sumContainers.size + sumVolumes.size + sumBuild.size; - const reclaimable = sumImages.reclaimable + sumContainers.reclaimable + sumVolumes.reclaimable + sumBuild.reclaimable; + const total = sumImages.size + sumContainers.size + sumBuild.size; + const reclaimable = sumImages.reclaimable + sumContainers.reclaimable + sumBuild.reclaimable; res.set('Cache-Control', 'no-store'); res.json({ total, reclaimable, images: sumImages, containers: sumContainers, - volumes: sumVolumes, build_cache: sumBuild, top_images: topImages, - top_volumes: topVolumes, updated: new Date().toISOString(), }); } catch (err) { diff --git a/containers/libreportal/frontend/js/components/admin/admin-system.js b/containers/libreportal/frontend/js/components/admin/admin-system.js index b5d6ab9..ff52b43 100644 --- a/containers/libreportal/frontend/js/components/admin/admin-system.js +++ b/containers/libreportal/frontend/js/components/admin/admin-system.js @@ -335,7 +335,7 @@ class AdminSystem { const head = `

Storage

- ${dk.containers_running ?? 0}/${dk.containers_total ?? 0} running · ${dk.images ?? 0} images · ${dk.volumes ?? 0} volumes + ${dk.containers_running ?? 0}/${dk.containers_total ?? 0} running · ${dk.images ?? 0} images
`; if (!s || !s.total || !SP) { const msg = (s && !s.total) ? 'No Docker storage in use yet.' : 'Storage usage unavailable.'; @@ -368,7 +368,7 @@ class AdminSystem {
${rows}
${this.bytes(recl)} reclaimable${reclPct ? ` · ${reclPct}% of total` : ''} - +
`; diff --git a/containers/libreportal/frontend/js/components/admin/system-storage-page.js b/containers/libreportal/frontend/js/components/admin/system-storage-page.js index bc3b58c..6e545aa 100644 --- a/containers/libreportal/frontend/js/components/admin/system-storage-page.js +++ b/containers/libreportal/frontend/js/components/admin/system-storage-page.js @@ -1,16 +1,18 @@ -// Admin → System → Storage — Docker disk-usage breakdown. +// Admin → System → Storage — where the disk is going. // -// Mounted at /admin/config/system/storage. Visualises `docker system df`: +// Mounted at /admin/config/system/storage. Two data sources: // -// - Headline total + reclaimable -// - Donut chart split by category (images / containers / volumes / build cache) -// - Per-category cards (count + size + reclaimable) -// - Top 10 images by size -// - Top 10 volumes by size +// - Storage by app — on-disk size of each app's bind-mounted data, from the +// generated /data/system/app_storage.json (du, not docker — see the +// generator). The headline view for LibrePortal, whose data lives in bind +// mounts, not named volumes. +// - Docker engine `docker system df` — headline total + reclaimable, a +// per-category donut (images / containers / build cache), per-category cards, +// and the top images by size. // // One backend call (GET /api/system/storage), cached server-side for 5s. // A "Reclaim space" button runs the safe prune (build cache + dangling -// images, never volumes) via the system_reclaim task, then re-reads usage. +// images, never app data) via the system_reclaim task, then re-reads usage. class SystemStoragePage { constructor(rootId = 'config-section') { @@ -81,7 +83,7 @@ class SystemStoragePage { if (window.showConfirmation) { window.showConfirmation( 'Reclaim space', - 'Clear the build cache and dangling images? Volumes and images in use are left untouched.', + 'Clear the build cache and dangling images? Images in use and your app data are left untouched.', run, 'Reclaim space', 'Cancel', @@ -115,9 +117,9 @@ class SystemStoragePage { Admin · System

Storage

-

Docker disk usage — images, containers, volumes, and build cache.

+

On-disk space by app, plus Docker's images, containers, and build cache.

- @@ -131,7 +133,6 @@ class SystemStoragePage { static segmentsFrom(d) { return [ { key: 'images', label: 'Images', color: 'accent', data: (d && d.images) || {} }, - { key: 'volumes', label: 'Volumes', color: 'status-success', data: (d && d.volumes) || {} }, { key: 'containers', label: 'Containers', color: 'status-info', data: (d && d.containers) || {} }, { key: 'build_cache', label: 'Build cache', color: 'status-warning', data: (d && d.build_cache) || {} }, ]; @@ -242,7 +243,6 @@ class SystemStoragePage { `; const topImages = Array.isArray(d.top_images) ? d.top_images : []; - const topVolumes = Array.isArray(d.top_volumes) ? d.top_volumes : []; const imagesTable = topImages.length ? `

Largest images

top ${topImages.length} by size
@@ -262,23 +262,6 @@ class SystemStoragePage { ` : ''; - const volumesTable = topVolumes.length ? ` -

Largest volumes

top ${topVolumes.length} by size
-
- - - - ${topVolumes.map(v => ` - - - - - - `).join('')} - -
NameDriverSizeRefs
${fmt.escape(v.name)}${fmt.escape(v.driver || '—')}${fmt.bytes(v.size)}${v.ref_count}${v.ref_count === 0 ? ' orphan' : ''}
-
` : ''; - // Storage by app — the number Docker can't give us: the on-disk size of // each app's bind-mounted data, measured by the generator. This is the // useful "what's eating my disk" view for LibrePortal, where app data @@ -315,7 +298,7 @@ class SystemStoragePage { ${appBody}`; - body.innerHTML = `${headline}${appsSection}${catCards}${imagesTable}${volumesTable}`; + body.innerHTML = `${headline}${appsSection}${catCards}${imagesTable}`; } }