"use client"; import { useCallback, useEffect, useState } from "react"; import { AlertTriangle, Boxes, Check, Loader2, RefreshCw, Trash2, X, } from "lucide-react"; import ConfirmActionDialog from "@/components/ConfirmActionDialog"; import { servers as serversApi, type DockerPruneOptions, type Server as ServerType, type ServerConfigSnapshot, } from "@/lib/api"; import { UserBadge } from "@/app/servers/components/server-config/ServerConfigPrimitives"; const cleanupOptions = [ { key: "images", label: "Unused images", description: "Equivalent to docker image prune -a.", }, { key: "containers", label: "Stopped containers", description: "Equivalent to docker container prune.", }, { key: "networks", label: "Unused networks", description: "Equivalent to docker network prune.", }, { key: "volumes", label: "Unused volumes", description: "Equivalent to docker volume prune.", }, { key: "buildCache", label: "Build cache", description: "Equivalent to docker builder prune.", }, ] as const; function formatDockerManagerError(error: unknown): string { const message = error instanceof Error ? error.message : String(error); if (message.includes("All configured authentication methods failed")) { return "SSH authentication failed. Check the server SSH username and password or private key in Server Config, then try again."; } if (message.includes("Connection lost before handshake")) { return "SSH connection was lost before authentication. Check the server address, SSH port, firewall, and network connection, then try again."; } return message || "Docker operation failed."; } export default function DockerManagerModal({ server, onClose, onActionComplete, }: { server: ServerType; onClose: () => void; onActionComplete: (message: string, tone?: "success" | "error") => void; }) { const [snapshot, setSnapshot] = useState(null); const [error, setError] = useState(null); const [loading, setLoading] = useState(true); const [selectedCleanup, setSelectedCleanup] = useState({ images: false, containers: false, networks: false, volumes: false, buildCache: false, }); const [runningAction, setRunningAction] = useState< "install" | "repair" | "reinstall" | "uninstall" | "prune" | null >(null); const [pendingAction, setPendingAction] = useState< "install" | "repair" | "reinstall" | "uninstall" | "prune" | null >(null); const refresh = useCallback(async () => { setLoading(true); setError(null); try { const response = await serversApi.getConfig(server.id); setSnapshot(response.data); } catch (caught: unknown) { setError(formatDockerManagerError(caught)); } finally { setLoading(false); } }, [server.id]); useEffect(() => { let isCurrent = true; serversApi .getConfig(server.id) .then((response) => { if (isCurrent) { setSnapshot(response.data); setError(null); } }) .catch((caught: unknown) => { if (isCurrent) { setError(formatDockerManagerError(caught)); } }) .finally(() => { if (isCurrent) { setLoading(false); } }); return () => { isCurrent = false; }; }, [server.id]); const docker = snapshot?.docker; const selectedCleanupCount = Object.values(selectedCleanup).filter(Boolean).length; const runAction = async ( action: "install" | "repair" | "reinstall" | "uninstall" | "prune", ) => { setRunningAction(action); setPendingAction(null); setError(null); try { const response = action === "install" ? await serversApi.installDocker(server.id) : action === "repair" ? await serversApi.repairDocker(server.id) : action === "reinstall" ? await serversApi.reinstallDocker(server.id) : action === "uninstall" ? await serversApi.uninstallDocker(server.id) : await serversApi.pruneDocker( server.id, selectedCleanup as DockerPruneOptions, ); setSnapshot((current) => current ? { ...current, docker: response.data, } : current, ); const responseMessage = "message" in response && typeof response.message === "string" ? response.message : null; const message = responseMessage ? responseMessage : action === "install" ? `Docker installation completed for ${server.name}.` : action === "repair" ? `Docker repair completed for ${server.name}.` : action === "reinstall" ? `Docker reinstall completed for ${server.name}.` : action === "uninstall" ? `Docker removal completed for ${server.name}.` : `Docker cleanup completed for ${server.name}.`; onActionComplete(message); await refresh(); } catch (caught: unknown) { const message = formatDockerManagerError(caught); setError(message); onActionComplete(message, "error"); } finally { setRunningAction(null); } }; const confirmConfig = pendingAction ? { install: { title: "Install Docker", description: `Install Docker on ${server.name}? This requires non-interactive sudo access when the SSH user is not root.`, confirmLabel: "Install Docker", tone: "warning" as const, note: "This can take several minutes on a fresh VPS. Keep this window open; the server will refresh after installation completes.", }, repair: { title: "Repair Docker", description: `Start and enable the existing Docker daemon on ${server.name}? Docker packages, containers, images, volumes, networks, and configuration will not be removed or replaced.`, confirmLabel: "Repair Docker", tone: "warning" as const, note: "This requires non-interactive sudo access when the SSH user is not root.", }, reinstall: { title: "Reinstall Docker", description: `Reinstall Docker on ${server.name}? Running containers may be affected while Docker restarts.`, confirmLabel: "Reinstall Docker", tone: "warning" as const, note: "Review active workloads before continuing.", }, uninstall: { title: "Remove Docker", description: `Remove Docker from ${server.name}? This can interrupt and remove access to container workloads on the host.`, confirmLabel: "Remove Docker", tone: "danger" as const, note: "This action does not ask for a typed confirmation, so use it only when you intend to remove Docker.", }, prune: { title: "Run Docker Cleanup", description: `Remove ${selectedCleanupCount} selected ${selectedCleanupCount === 1 ? "category" : "categories"} of unused Docker artifacts on ${server.name}?`, confirmLabel: "Run Cleanup", tone: "warning" as const, note: "Only the selected unused artifacts will be removed.", }, }[pendingAction] : null; return (
{confirmConfig && pendingAction ? ( setPendingAction(null)} onConfirm={() => void runAction(pendingAction)} /> ) : null}

Manage Docker

Live Docker status and maintenance controls for {server.name}.

{error ? (
{error}
) : null}
Docker Runtime

{loading ? "Checking Docker on this server..." : docker?.available ? "Docker is installed and the daemon is reachable." : docker?.installed ? "Docker is installed, but the daemon is not reachable." : "Docker is not installed on this server."}

Version: {docker?.version ?? "Not detected"} / Package manager:{" "} {docker?.platform.packageManager ?? "Not detected"}

{docker?.reason ? (

{docker.reason}

) : null}
{!docker?.installed ? ( ) : !docker.available ? ( ) : null} {docker?.installed ? ( <> ) : null}
{docker?.installed ? (
Prune Docker Garbage

Select unused artifacts to remove, then confirm the cleanup.

{cleanupOptions.map((option) => ( ))}
{selectedCleanupCount} cleanup option {selectedCleanupCount === 1 ? "" : "s"} selected
) : null}
); }