Compare commits
No commits in common. "f792cf55f63d6036461a585d6a78041cbd96b9f0" and "816b96fe97c0c121254c3145ef299021241450a4" have entirely different histories.
f792cf55f6
...
816b96fe97
@ -334,21 +334,12 @@ router.get('/storage', async (req, res) => {
|
|||||||
const df = await storageCache.get('df', () => dockerRequest('GET', '/system/df'));
|
const df = await storageCache.get('df', () => dockerRequest('GET', '/system/df'));
|
||||||
if (!df) return res.status(500).json({ error: 'no_data' });
|
if (!df) return res.status(500).json({ error: 'no_data' });
|
||||||
// Roll the verbose response up into headline numbers per category.
|
// 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.
|
|
||||||
const isDangling = (im) => {
|
|
||||||
const tags = im.RepoTags || [];
|
|
||||||
return tags.length === 0 || tags.every(t => t.includes('<none>'));
|
|
||||||
};
|
|
||||||
const sumImages = (df.Images || []).reduce(
|
const sumImages = (df.Images || []).reduce(
|
||||||
(a, im) => {
|
(a, im) => {
|
||||||
a.count++;
|
a.count++;
|
||||||
a.size += im.Size || 0;
|
a.size += im.Size || 0;
|
||||||
a.shared += im.SharedSize || 0;
|
a.shared += im.SharedSize || 0;
|
||||||
if (isDangling(im)) a.reclaimable += im.Size || 0;
|
if (!im.Containers || im.Containers <= 0) a.reclaimable += im.Size || 0;
|
||||||
return a;
|
return a;
|
||||||
},
|
},
|
||||||
{ count: 0, size: 0, shared: 0, reclaimable: 0 }
|
{ count: 0, size: 0, shared: 0, reclaimable: 0 }
|
||||||
@ -357,14 +348,18 @@ router.get('/storage', async (req, res) => {
|
|||||||
(a, c) => {
|
(a, c) => {
|
||||||
a.count++;
|
a.count++;
|
||||||
a.size += c.SizeRw || 0;
|
a.size += c.SizeRw || 0;
|
||||||
|
if (c.State && c.State !== 'running') a.reclaimable += c.SizeRw || 0;
|
||||||
return a;
|
return a;
|
||||||
},
|
},
|
||||||
{ count: 0, size: 0, reclaimable: 0 }
|
{ count: 0, size: 0, reclaimable: 0 }
|
||||||
);
|
);
|
||||||
const sumVolumes = (df.Volumes || []).reduce(
|
const sumVolumes = (df.Volumes || []).reduce(
|
||||||
(a, v) => {
|
(a, v) => {
|
||||||
|
const sz = (v.UsageData && v.UsageData.Size) || 0;
|
||||||
|
const refs = (v.UsageData && v.UsageData.RefCount) || 0;
|
||||||
a.count++;
|
a.count++;
|
||||||
a.size += (v.UsageData && v.UsageData.Size) || 0;
|
a.size += sz;
|
||||||
|
if (refs <= 0) a.reclaimable += sz;
|
||||||
return a;
|
return a;
|
||||||
},
|
},
|
||||||
{ count: 0, size: 0, reclaimable: 0 }
|
{ count: 0, size: 0, reclaimable: 0 }
|
||||||
|
|||||||
@ -1010,22 +1010,17 @@ table.sys-apps tr:hover td { background: rgba(var(--text-rgb), 0.03); }
|
|||||||
}
|
}
|
||||||
.sys-storage-stat-recl .sys-storage-stat-v { color: var(--status-warning); }
|
.sys-storage-stat-recl .sys-storage-stat-v { color: var(--status-warning); }
|
||||||
|
|
||||||
/* Storage header: title left, "Reclaim space" action top-right (matches the
|
/* "Reclaim space" action — orange to match the Reclaimable stat it sits
|
||||||
metric page's header layout). */
|
under. Safe prune only (build cache + dangling images). */
|
||||||
.sys-storage-page .page-header {
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
/* "Reclaim space" action — orange. Safe prune only (build cache + dangling
|
|
||||||
images); never volumes or in-use images. */
|
|
||||||
.sys-storage-reclaim {
|
.sys-storage-reclaim {
|
||||||
|
align-self: flex-start;
|
||||||
|
margin-top: 14px;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 9px 16px;
|
padding: 9px 16px;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
white-space: nowrap;
|
|
||||||
color: var(--status-warning);
|
color: var(--status-warning);
|
||||||
background: rgba(var(--status-warning-rgb), 0.12);
|
background: rgba(var(--status-warning-rgb), 0.12);
|
||||||
border: 1px solid rgba(var(--status-warning-rgb), 0.4);
|
border: 1px solid rgba(var(--status-warning-rgb), 0.4);
|
||||||
@ -1046,6 +1041,11 @@ table.sys-apps tr:hover td { background: rgba(var(--text-rgb), 0.03); }
|
|||||||
}
|
}
|
||||||
.sys-storage-reclaim svg { flex: 0 0 auto; }
|
.sys-storage-reclaim svg { flex: 0 0 auto; }
|
||||||
.sys-storage-reclaim.is-running svg { animation: sysReclaimSpin 0.8s linear infinite; }
|
.sys-storage-reclaim.is-running svg { animation: sysReclaimSpin 0.8s linear infinite; }
|
||||||
|
.sys-storage-reclaim-note {
|
||||||
|
margin-top: 6px;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
color: rgba(var(--text-rgb), 0.5);
|
||||||
|
}
|
||||||
@keyframes sysReclaimSpin { to { transform: rotate(360deg); } }
|
@keyframes sysReclaimSpin { to { transform: rotate(360deg); } }
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
.sys-storage-reclaim.is-running svg { animation: none; }
|
.sys-storage-reclaim.is-running svg { animation: none; }
|
||||||
|
|||||||
@ -55,10 +55,7 @@ class SystemStoragePage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Confirm, then run the safe reclaim task and re-read usage as it lands.
|
// Confirm, then run the safe reclaim task and re-read usage as it lands.
|
||||||
// The button lives in the header (not the body), so it survives the
|
|
||||||
// _render() refreshes below — re-enable it explicitly when done.
|
|
||||||
_reclaim(btn) {
|
_reclaim(btn) {
|
||||||
const done = () => { btn.disabled = false; btn.classList.remove('is-running'); };
|
|
||||||
const run = async () => {
|
const run = async () => {
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
btn.classList.add('is-running');
|
btn.classList.add('is-running');
|
||||||
@ -67,21 +64,22 @@ class SystemStoragePage {
|
|||||||
throw new Error('Task system not ready');
|
throw new Error('Task system not ready');
|
||||||
}
|
}
|
||||||
await window.tasksManager.router.routeAction('system_reclaim');
|
await window.tasksManager.router.routeAction('system_reclaim');
|
||||||
window.notificationSystem?.show?.('Reclaiming space…', 'info');
|
window.notificationSystem?.show?.('Reclaiming Docker space…', 'info');
|
||||||
// The prune runs in the background task processor; re-read
|
// The prune runs in the background task processor; re-read
|
||||||
// usage a couple of times so the numbers settle without a
|
// usage a couple of times so the numbers settle without a
|
||||||
// manual refresh.
|
// manual refresh. Each _render rebuilds the button fresh.
|
||||||
setTimeout(() => this._load().then(() => this._render()), 3000);
|
setTimeout(() => this._load().then(() => this._render()), 3000);
|
||||||
setTimeout(() => { this._load().then(() => this._render()); done(); }, 8000);
|
setTimeout(() => this._load().then(() => this._render()), 8000);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
window.notificationSystem?.show?.(`Reclaim failed: ${err.message || err}`, 'error');
|
window.notificationSystem?.show?.(`Reclaim failed: ${err.message || err}`, 'error');
|
||||||
done();
|
btn.disabled = false;
|
||||||
|
btn.classList.remove('is-running');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (window.showConfirmation) {
|
if (window.showConfirmation) {
|
||||||
window.showConfirmation(
|
window.showConfirmation(
|
||||||
'Reclaim space',
|
'Reclaim Docker space',
|
||||||
'Clear the build cache and dangling images? Volumes and images in use are left untouched.',
|
'Remove the build cache and dangling (untagged) images? Volumes, app data, and images currently in use are left untouched.',
|
||||||
run,
|
run,
|
||||||
'Reclaim space',
|
'Reclaim space',
|
||||||
'Cancel',
|
'Cancel',
|
||||||
@ -115,10 +113,6 @@ class SystemStoragePage {
|
|||||||
<h1>Storage</h1>
|
<h1>Storage</h1>
|
||||||
<p class="sys-storage-sub">Docker disk usage — images, containers, volumes, and build cache.</p>
|
<p class="sys-storage-sub">Docker disk usage — images, containers, volumes, and build cache.</p>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="sys-storage-reclaim" data-storage-reclaim title="Clear build cache and dangling images (volumes and images in use are kept)">
|
|
||||||
<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>
|
|
||||||
Reclaim space
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="sys-storage-body" data-storage-body>
|
<div class="sys-storage-body" data-storage-body>
|
||||||
<div class="sys-storage-loading">Reading Docker daemon…</div>
|
<div class="sys-storage-loading">Reading Docker daemon…</div>
|
||||||
@ -209,8 +203,13 @@ class SystemStoragePage {
|
|||||||
<div class="sys-storage-stat sys-storage-stat-recl">
|
<div class="sys-storage-stat sys-storage-stat-recl">
|
||||||
<span class="sys-storage-stat-k">Reclaimable</span>
|
<span class="sys-storage-stat-k">Reclaimable</span>
|
||||||
<strong class="sys-storage-stat-v">${fmt.bytes(recl)}</strong>
|
<strong class="sys-storage-stat-v">${fmt.bytes(recl)}</strong>
|
||||||
<span class="sys-storage-stat-sub">${reclPct.toFixed(0)}% of total · build cache & dangling images</span>
|
<span class="sys-storage-stat-sub">${reclPct.toFixed(0)}% of total · stopped containers, dangling images, unused volumes & cache</span>
|
||||||
</div>
|
</div>
|
||||||
|
<button type="button" class="sys-storage-reclaim" data-storage-reclaim title="Remove build cache and dangling images (volumes and in-use images are kept)">
|
||||||
|
<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>
|
||||||
|
Reclaim space
|
||||||
|
</button>
|
||||||
|
<span class="sys-storage-reclaim-note">Frees build cache & dangling images · volumes and in-use images are kept</span>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
|
|||||||
@ -3,20 +3,25 @@
|
|||||||
# System Commands Handler
|
# System Commands Handler
|
||||||
# Handles all system subcommands by calling core functions
|
# Handles all system subcommands by calling core functions
|
||||||
|
|
||||||
# Safe disk reclaim: clear the whole build cache (-a; it's pure cache, always
|
# Reclaim Docker disk space — SAFE scope only: build cache + dangling
|
||||||
# safe to drop) and remove dangling images. Never touches volumes or in-use
|
# (untagged) images. Never prunes volumes (app data) or tagged/in-use images,
|
||||||
# images. runFileOp hits the right daemon (rootless: as the install user).
|
# so nothing an app relies on is removed. runFileOp targets the correct daemon
|
||||||
|
# (rootless: as the install user with DOCKER_HOST set).
|
||||||
reclaimDockerSpace()
|
reclaimDockerSpace()
|
||||||
{
|
{
|
||||||
isHeader "Reclaiming Space"
|
isHeader "Reclaiming Docker Space"
|
||||||
|
isNotice "Safe scope: build cache + dangling images only (no volumes, no in-use images)."
|
||||||
|
|
||||||
runFileOp docker builder prune -af >/dev/null 2>&1
|
local cache_out image_out
|
||||||
checkSuccess "Cleared build cache"
|
cache_out=$(runFileOp docker builder prune -f 2>&1)
|
||||||
|
checkSuccess "Pruned build cache"
|
||||||
|
echo "$cache_out" | grep -i "Total:" | sed 's/^/ /'
|
||||||
|
|
||||||
runFileOp docker image prune -f >/dev/null 2>&1
|
image_out=$(runFileOp docker image prune -f 2>&1)
|
||||||
checkSuccess "Removed dangling images"
|
checkSuccess "Pruned dangling images"
|
||||||
|
echo "$image_out" | grep -i "Total reclaimed space" | sed 's/^/ /'
|
||||||
|
|
||||||
isSuccessful "Done"
|
isSuccessful "Reclaim complete"
|
||||||
}
|
}
|
||||||
|
|
||||||
cliHandleSystemCommands()
|
cliHandleSystemCommands()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user