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