import { getErrorMessage } from "@/lib/error-message.js"; import React from "react"; import { Card } from "@/components/card.tsx"; import { Button } from "@/components/button.tsx"; import { Box, Play, Square, RotateCw, Pause, PlayCircle, Trash2, RefreshCw, } from "lucide-react"; import { toast } from "sonner"; import { useTranslation } from "react-i18next"; import type { DockerContainer } from "@/types"; import { startDockerContainer, stopDockerContainer, restartDockerContainer, pauseDockerContainer, unpauseDockerContainer, removeDockerContainer, } from "@/main-axios.ts"; import { useConfirmation } from "@/hooks/use-confirmation.ts"; interface ContainerCardProps { container: DockerContainer; sessionId: string; onSelect?: () => void; isSelected?: boolean; onRefresh?: () => void; } export function DockerBadge({ state }: { state: DockerContainer["state"] }) { let colorClass = "border-border text-muted-foreground"; if (state === "running") colorClass = "border-accent-brand/40 text-accent-brand bg-accent-brand/10"; if (state === "paused") colorClass = "border-yellow-500/40 text-yellow-500 bg-yellow-500/10"; if (state === "exited") colorClass = "border-destructive/40 text-destructive bg-destructive/5"; if (state === "restarting") colorClass = "border-blue-400/40 text-blue-400 bg-blue-400/10"; return ( {state} ); } export function ContainerCard({ container, sessionId, onSelect, isSelected = false, onRefresh, }: ContainerCardProps): React.ReactElement { const { t } = useTranslation(); const { confirmWithToast } = useConfirmation(); const [isStarting, setIsStarting] = React.useState(false); const [isStopping, setIsStopping] = React.useState(false); const [isRestarting, setIsRestarting] = React.useState(false); const [isPausing, setIsPausing] = React.useState(false); const [isRemoving, setIsRemoving] = React.useState(false); const isLoading = isStarting || isStopping || isRestarting || isPausing || isRemoving; const containerName = container.name.startsWith("/") ? container.name.slice(1) : container.name; const portsList = (container.ports ?? "") .split(",") .map((p) => p.trim()) .filter(Boolean); const handleStart = async (e: React.MouseEvent) => { e.stopPropagation(); setIsStarting(true); try { await startDockerContainer(sessionId, container.id); toast.success(t("docker.containerStarted", { name: containerName })); onRefresh?.(); } catch (err) { toast.error( t("docker.failedToStartContainer", { error: getErrorMessage(err), }), ); } finally { setIsStarting(false); } }; const handleStop = async (e: React.MouseEvent) => { e.stopPropagation(); setIsStopping(true); try { await stopDockerContainer(sessionId, container.id); toast.success(t("docker.containerStopped", { name: containerName })); onRefresh?.(); } catch (err) { toast.error( t("docker.failedToStopContainer", { error: getErrorMessage(err), }), ); } finally { setIsStopping(false); } }; const handleRestart = async (e: React.MouseEvent) => { e.stopPropagation(); setIsRestarting(true); try { await restartDockerContainer(sessionId, container.id); toast.success(t("docker.containerRestarted", { name: containerName })); onRefresh?.(); } catch (err) { toast.error( t("docker.failedToRestartContainer", { error: getErrorMessage(err), }), ); } finally { setIsRestarting(false); } }; const handlePause = async (e: React.MouseEvent) => { e.stopPropagation(); setIsPausing(true); try { if (container.state === "paused") { await unpauseDockerContainer(sessionId, container.id); toast.success(t("docker.containerUnpaused", { name: containerName })); } else { await pauseDockerContainer(sessionId, container.id); toast.success(t("docker.containerPaused", { name: containerName })); } onRefresh?.(); } catch (err) { toast.error( t("docker.failedToTogglePauseContainer", { action: container.state === "paused" ? "unpause" : "pause", error: getErrorMessage(err), }), ); } finally { setIsPausing(false); } }; const handleRemove = async (e: React.MouseEvent) => { e.stopPropagation(); confirmWithToast( t("docker.confirmRemoveContainer", { name: containerName }) + (container.state === "running" ? " " + t("docker.runningContainerWarning") : ""), async () => { setIsRemoving(true); try { await removeDockerContainer( sessionId, container.id, container.state === "running", ); toast.success(t("docker.containerRemoved", { name: containerName })); onRefresh?.(); } catch (err) { toast.error( t("docker.failedToRemoveContainer", { error: getErrorMessage(err), }), ); } finally { setIsRemoving(false); } }, t("common.remove"), t("common.cancel"), ); }; return ( {containerName} {t("docker.image")} {container.image} {t("docker.ports")} {portsList.length > 0 ? ( portsList.slice(0, 3).map((p) => ( {p} )) ) : ( {t("docker.noPorts")} )} {container.id.substring(0, 12)} {container.state !== "running" && ( {isStarting ? ( ) : ( )} )} {container.state === "running" && ( {isStopping ? ( ) : ( )} )} {(container.state === "running" || container.state === "paused") && ( {isPausing ? ( ) : container.state === "paused" ? ( ) : ( )} )} {isRestarting ? ( ) : ( )} ); }