Phase 18: image prune (dangling/unused), local + agent (0.24.0)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
d46a6c3576
commit
34c5fffa85
@@ -28,4 +28,12 @@ export const imagesApi = {
|
||||
api.get<Record<string, UpdateStatus>>(`${base(agentId)}/updates`).then((r) => r.data),
|
||||
check: (agentId?: number) =>
|
||||
api.post<Record<string, UpdateStatus>>(`${base(agentId)}/check`).then((r) => r.data),
|
||||
prune: (allUnused: boolean, agentId?: number) =>
|
||||
api
|
||||
.post<{ ImagesDeleted: unknown[]; SpaceReclaimed: number }>(
|
||||
`${base(agentId)}/prune`,
|
||||
null,
|
||||
{ params: { all: allUnused } }
|
||||
)
|
||||
.then((r) => r.data),
|
||||
};
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useState } from "react";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { RefreshCw, ArrowUpCircle, CheckCircle2, HelpCircle } from "lucide-react";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { RefreshCw, ArrowUpCircle, CheckCircle2, HelpCircle, Eraser } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { Button, Card, Spinner } from "@/components/ui";
|
||||
import { ConfirmDialog } from "@/components/ui/ConfirmDialog";
|
||||
import { HostHeader } from "@/components/hosts/HostHeader";
|
||||
import { imagesApi, type ImageRow } from "@/api/images";
|
||||
import { agentsApi } from "@/api/agents";
|
||||
@@ -60,6 +61,8 @@ function ImagesSection({
|
||||
const online = !agent || agent.status === "online";
|
||||
const qc = useQueryClient();
|
||||
const [checking, setChecking] = useState(false);
|
||||
const [pruneOpen, setPruneOpen] = useState(false);
|
||||
const [pruneAll, setPruneAll] = useState(false);
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: ["images", agentId ?? "local"],
|
||||
queryFn: () => imagesApi.list(agentId),
|
||||
@@ -80,14 +83,35 @@ function ImagesSection({
|
||||
}
|
||||
};
|
||||
|
||||
const prune = useMutation({
|
||||
mutationFn: () => imagesApi.prune(pruneAll, agentId),
|
||||
onSuccess: (r) => {
|
||||
const n = r.ImagesDeleted?.length ?? 0;
|
||||
toast.success(
|
||||
n
|
||||
? `Pruned ${n} image layer(s), freed ${formatBytes(r.SpaceReclaimed)}`
|
||||
: "No unused images"
|
||||
);
|
||||
setPruneOpen(false);
|
||||
setPruneAll(false);
|
||||
qc.invalidateQueries({ queryKey: ["images", agentId ?? "local"] });
|
||||
},
|
||||
onError: (e) => toast.error(apiErrorMessage(e)),
|
||||
});
|
||||
|
||||
return (
|
||||
<section>
|
||||
{(showHostLabel || (isAdmin && online)) && (
|
||||
<HostHeader agent={agent}>
|
||||
{isAdmin && online && (
|
||||
<Button onClick={check} loading={checking}>
|
||||
<RefreshCw className="h-4 w-4" /> Check updates
|
||||
</Button>
|
||||
<>
|
||||
<Button variant="outline" onClick={() => setPruneOpen(true)}>
|
||||
<Eraser className="h-4 w-4" /> Prune
|
||||
</Button>
|
||||
<Button onClick={check} loading={checking}>
|
||||
<RefreshCw className="h-4 w-4" /> Check updates
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</HostHeader>
|
||||
)}
|
||||
@@ -137,6 +161,34 @@ function ImagesSection({
|
||||
</table>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{pruneOpen && (
|
||||
<ConfirmDialog
|
||||
title="Prune images"
|
||||
message={
|
||||
pruneAll
|
||||
? "Remove ALL images not used by any container (including tagged images). They will be re-pulled on next deploy."
|
||||
: "Remove dangling (untagged) image layers. Images in use are kept."
|
||||
}
|
||||
confirmLabel="Prune images"
|
||||
danger
|
||||
busy={prune.isPending}
|
||||
onConfirm={() => prune.mutate()}
|
||||
onCancel={() => {
|
||||
setPruneOpen(false);
|
||||
setPruneAll(false);
|
||||
}}
|
||||
>
|
||||
<label className="flex items-center gap-2 text-sm">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={pruneAll}
|
||||
onChange={(e) => setPruneAll(e.target.checked)}
|
||||
/>
|
||||
Also remove unused tagged images (<code>-a</code>)
|
||||
</label>
|
||||
</ConfirmDialog>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user