import { useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { Network as NetworkIcon, Plus, Trash2, Eraser } from "lucide-react"; import { toast } from "sonner"; import { Badge, Button, Card, Input, Spinner } from "@/components/ui"; import { ConfirmDialog } from "@/components/ui/ConfirmDialog"; import { networksApi, type NetworkInfo } from "@/api/networks"; import { apiErrorMessage } from "@/api/client"; import { useAuthStore } from "@/store/auth"; const selectClass = "w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm dark:border-slate-600 dark:bg-slate-800"; export function Networks() { const isAdmin = useAuthStore((s) => s.user?.role === "admin"); const qc = useQueryClient(); const { data, isLoading } = useQuery({ queryKey: ["networks"], queryFn: networksApi.list, refetchInterval: 10000, }); const [creating, setCreating] = useState(false); const [toDelete, setToDelete] = useState(null); const invalidate = () => qc.invalidateQueries({ queryKey: ["networks"] }); const prune = useMutation({ mutationFn: networksApi.prune, onSuccess: (r) => { const n = r.NetworksDeleted?.length ?? 0; toast.success(n ? `Pruned ${n} network(s)` : "No unused networks"); invalidate(); }, onError: (e) => toast.error(apiErrorMessage(e)), }); const remove = useMutation({ mutationFn: (id: string) => networksApi.remove(id), onSuccess: () => { toast.success("Network deleted"); setToDelete(null); invalidate(); }, onError: (e) => toast.error(apiErrorMessage(e)), }); if (isLoading) return ; return (
{isAdmin && (
)} {isAdmin && } {data?.map((n) => ( {isAdmin && ( )} ))} {data?.length === 0 && ( )}
Name Driver Scope Subnet In use
{n.name} {n.is_default && default} {n.stack && {n.stack}} {n.internal && internal}
{n.driver} {n.scope} {n.subnet ?? "—"} {n.in_use ? ( {n.containers.length} container{n.containers.length > 1 ? "s" : ""} ) : ( )} {!n.is_default && ( )}
No networks.
{creating && ( { setCreating(false); invalidate(); }} onCancel={() => setCreating(false)} /> )} {toDelete && ( remove.mutate(toDelete.id)} onCancel={() => setToDelete(null)} /> )}
); } function CreateNetworkDialog({ onDone, onCancel }: { onDone: () => void; onCancel: () => void }) { const [form, setForm] = useState({ name: "", driver: "bridge", subnet: "", gateway: "", internal: false, attachable: true, }); const set = (k: string, v: unknown) => setForm((f) => ({ ...f, [k]: v })); const create = useMutation({ mutationFn: () => networksApi.create({ name: form.name, driver: form.driver, subnet: form.subnet.trim() || null, gateway: form.gateway.trim() || null, internal: form.internal, attachable: form.attachable, }), onSuccess: () => { toast.success("Network created"); onDone(); }, onError: (e) => toast.error(apiErrorMessage(e)), }); return (
e.stopPropagation()} >

Create network

); }