feat(docker): admin-gated per-host prune buttons on the image/disk page
CI / lint (push) Successful in 2s
CI / unit (push) Successful in 48s
CI / integration (push) Successful in 2m27s
CI / publish (push) Successful in 1m10s

M78 step 2. The /disk page surfaced reclaimable space + stopped counts but
said "prune deferred to a later milestone" — this wires the cleanup.

Adds POST /plugins/docker/disk/<host_id>/prune (admin only) that resolves
the host's linked AnsibleTarget → steward:target:<id> scope and fires the
bundled maintenance/docker_prune.yml through the ansible.run_playbook
capability (audited runner; the collection agent stays read-only). Mirrors
the host_agent update route. `target` ∈ {containers, images, system}:
- containers → docker container prune -f
- images     → docker image prune -af (prune_all_images)
- system     → docker system prune -f (conservative)

The disk() route now resolves each host group's linked target + capability
availability; disk.html renders three confirm-gated buttons per host, shown
only to admins when Ansible is available and the host has a linked target,
otherwise a clear "link a target" / "runner unavailable" hint (rules 24/26/27).

Tests: the prune_target→extra_vars mapping (the -af-only-for-images rule) is
extracted to a pure helper and unit-tested; disk_prune added to the module
smoke; disk.html covered by the existing template-parse test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CAGR73DUowdVFVvYzLXC5C
This commit is contained in:
2026-07-19 16:22:30 -04:00
co-authored by Claude Opus 4.8
parent e8ac99174a
commit a9b3b11327
3 changed files with 143 additions and 6 deletions
+39 -2
View File
@@ -7,8 +7,8 @@
<h1 class="page-title" style="margin-bottom:0.4rem;">Image &amp; disk usage</h1>
<p style="font-size:0.82rem;color:var(--text-muted);margin-bottom:1.5rem;">
Reclaimable = space held by images no container references. Cleanup actions
(prune) arrive in a later release — these are read-only figures for now.
Reclaimable = space held by images no container references. Admins can prune
per host below; each action runs an audited Ansible playbook on that host.
</p>
{% if host_groups %}
@@ -47,6 +47,43 @@
</div>
</div>
{# ── Cleanup actions (admin only; audited Ansible prune run) ───────────── #}
{% if session.user_role == 'admin' %}
<div style="margin-bottom:1rem;">
{% if ansible_available and g.has_target %}
<div style="display:flex;flex-wrap:wrap;gap:0.5rem;align-items:center;">
<form method="post" action="/plugins/docker/disk/{{ g.host_id }}/prune" style="margin:0;"
data-msg="Remove all STOPPED containers on {{ g.host_name|e }}? This cannot be undone."
onsubmit="return confirm(this.dataset.msg);">
<input type="hidden" name="target" value="containers">
<button type="submit" class="btn btn-sm">Prune stopped containers</button>
</form>
<form method="post" action="/plugins/docker/disk/{{ g.host_id }}/prune" style="margin:0;"
data-msg="Remove ALL unused images on {{ g.host_name|e }} (docker image prune -a)? Any image not used by a container is deleted."
onsubmit="return confirm(this.dataset.msg);">
<input type="hidden" name="target" value="images">
<button type="submit" class="btn btn-sm">Prune unused images</button>
</form>
<form method="post" action="/plugins/docker/disk/{{ g.host_id }}/prune" style="margin:0;"
data-msg="Run a full system prune on {{ g.host_name|e }}? Removes stopped containers, unused networks, dangling images and build cache."
onsubmit="return confirm(this.dataset.msg);">
<input type="hidden" name="target" value="system">
<button type="submit" class="btn btn-sm btn-danger">System prune…</button>
</form>
</div>
<div style="font-size:0.72rem;color:var(--text-muted);margin-top:0.4rem;">
Reclaimed space appears on the next agent sample.
</div>
{% elif not ansible_available %}
<div style="font-size:0.74rem;color:var(--text-muted);">Cleanup needs the Ansible runner (currently unavailable).</div>
{% else %}
<div style="font-size:0.74rem;color:var(--text-muted);">
Link an <a href="/hosts/{{ g.host_id }}">Ansible target</a> to this host to enable prune actions.
</div>
{% endif %}
</div>
{% endif %}
{# ── Per-image table ──────────────────────────────────────────────────── #}
<div class="card-flush">
<table class="table">