The volume prune gets the same treatment as the image prune in 0.61.0:
the candidates are listed and removed one by one in the background, and
the Volumes page shows a progress bar (x / y, the volume being removed,
failures) followed by a toast with the space freed.
The button also used to delete without asking, and a bare docker volume
prune only removes anonymous volumes on Engine 23+ with no way to include
named ones. It now opens a confirmation dialog with an explicit -a option
that warns it also takes the data of every stack that is down.
- Candidates: volumes no container (running or stopped) mounts, local
scope only, and anonymous (com.docker.volume.anonymous) unless -a.
Volumes from pre-23 daemons carry no such label, so they only go with
-a, which errs on the side of keeping data.
- Removed without force, so a volume mounted since the listing is
refused; one refusal does not stop the rest.
- No closing daemon prune: nothing is left over, and on a pre-23 daemon an
unfiltered one would delete named volumes too.
- Freed space is the sum of the deleted volumes' sizes (volumes share no
data), from the size cache, which is invalidated afterwards.
- POST /api/volumes/prune takes all= and background=; without background
it still waits and returns {VolumesDeleted, SpaceReclaimed}.
GET /api/volumes/prune-status is admin-only.
- volume_sizes() ("Compute sizes") now uses the long-timeout client; it
walks every volume and could hit the same 60s limit.
The progress machinery moved into services/prune_job.py (PruneJob,
PrunePlan) and, on the frontend, the usePruneJob hook and PruneProgress
component, shared by both pages. The status now carries a phase
(preparing / removing / finishing) so the parts without a count say what
they are doing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
33 lines
1.2 KiB
TypeScript
33 lines
1.2 KiB
TypeScript
import api from "./client";
|
|
import type { HostPathResult, PruneStatus, VolumeInfo } from "@/types";
|
|
|
|
const base = "/api/volumes";
|
|
|
|
export const volumesApi = {
|
|
list: () => api.get<VolumeInfo[]>(base).then((r) => r.data),
|
|
sizes: (force = false) =>
|
|
api
|
|
.get<Record<string, number | null>>(`${base}/sizes`, { params: { force } })
|
|
.then((r) => r.data),
|
|
remove: (name: string, force = false) =>
|
|
api.delete(`${base}/${name}?force=${force}`).then((r) => r.data),
|
|
// Runs in the background on the server; poll pruneStatus() for progress.
|
|
startPrune: (allUnused: boolean) =>
|
|
api
|
|
.post<PruneStatus>(`${base}/prune`, null, {
|
|
params: { all: allUnused, background: true },
|
|
})
|
|
.then((r) => r.data),
|
|
pruneStatus: () => api.get<PruneStatus>(`${base}/prune-status`).then((r) => r.data),
|
|
generateYaml: (spec: Record<string, unknown>) =>
|
|
api
|
|
.post<{ yaml: string }>("/api/volumes/generate-yaml", spec)
|
|
.then((r) => r.data.yaml),
|
|
hostPaths: (path: string, showHidden = false) =>
|
|
api
|
|
.get<HostPathResult>(
|
|
`/api/host/paths?path=${encodeURIComponent(path)}&show_hidden=${showHidden}`
|
|
)
|
|
.then((r) => r.data),
|
|
};
|