import React from "react"; import { useSidebar } from "@/components/ui/sidebar.tsx"; import { Separator } from "@/components/ui/separator.tsx"; import { Tabs, TabsContent, TabsList, TabsTrigger, } from "@/components/ui/tabs.tsx"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import type { SSHHost, DockerContainer, DockerValidation, } from "@/types/index.js"; import { connectDockerSession, disconnectDockerSession, listDockerContainers, validateDockerAvailability, keepaliveDockerSession, } from "@/ui/main-axios.ts"; import { SimpleLoader } from "@/ui/desktop/navigation/animations/SimpleLoader.tsx"; import { AlertCircle } from "lucide-react"; import { Alert, AlertDescription } from "@/components/ui/alert.tsx"; import { ContainerList } from "./components/ContainerList.tsx"; import { LogViewer } from "./components/LogViewer.tsx"; import { ContainerStats } from "./components/ContainerStats.tsx"; import { ConsoleTerminal } from "./components/ConsoleTerminal.tsx"; import { ContainerDetail } from "./components/ContainerDetail.tsx"; interface DockerManagerProps { hostConfig?: SSHHost; title?: string; isVisible?: boolean; isTopbarOpen?: boolean; embedded?: boolean; } export function DockerManager({ hostConfig, title, isVisible = true, isTopbarOpen = true, embedded = false, }: DockerManagerProps): React.ReactElement { const { t } = useTranslation(); const { state: sidebarState } = useSidebar(); const [currentHostConfig, setCurrentHostConfig] = React.useState(hostConfig); const [sessionId, setSessionId] = React.useState(null); const [containers, setContainers] = React.useState([]); const [selectedContainer, setSelectedContainer] = React.useState< string | null >(null); const [isConnecting, setIsConnecting] = React.useState(false); const [activeTab, setActiveTab] = React.useState("containers"); const [dockerValidation, setDockerValidation] = React.useState(null); const [isValidating, setIsValidating] = React.useState(false); const [viewMode, setViewMode] = React.useState<"list" | "detail">("list"); React.useEffect(() => { if (hostConfig?.id !== currentHostConfig?.id) { setCurrentHostConfig(hostConfig); setContainers([]); setSelectedContainer(null); setSessionId(null); setDockerValidation(null); setViewMode("list"); } }, [hostConfig?.id]); React.useEffect(() => { const fetchLatestHostConfig = async () => { if (hostConfig?.id) { try { const { getSSHHosts } = await import("@/ui/main-axios.ts"); const hosts = await getSSHHosts(); const updatedHost = hosts.find((h) => h.id === hostConfig.id); if (updatedHost) { setCurrentHostConfig(updatedHost); } } catch { // Silently handle error } } }; fetchLatestHostConfig(); const handleHostsChanged = async () => { if (hostConfig?.id) { try { const { getSSHHosts } = await import("@/ui/main-axios.ts"); const hosts = await getSSHHosts(); const updatedHost = hosts.find((h) => h.id === hostConfig.id); if (updatedHost) { setCurrentHostConfig(updatedHost); } } catch { // Silently handle error } } }; window.addEventListener("ssh-hosts:changed", handleHostsChanged); return () => window.removeEventListener("ssh-hosts:changed", handleHostsChanged); }, [hostConfig?.id]); // SSH session lifecycle React.useEffect(() => { const initSession = async () => { if (!currentHostConfig?.id || !currentHostConfig.enableDocker) { return; } setIsConnecting(true); const sid = `docker-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; try { await connectDockerSession(sid, currentHostConfig.id); setSessionId(sid); // Validate Docker availability setIsValidating(true); const validation = await validateDockerAvailability(sid); setDockerValidation(validation); setIsValidating(false); if (!validation.available) { toast.error( validation.error || "Docker is not available on this host", ); } } catch (error) { toast.error( error instanceof Error ? error.message : "Failed to connect to host", ); setIsConnecting(false); setIsValidating(false); } finally { setIsConnecting(false); } }; initSession(); return () => { if (sessionId) { disconnectDockerSession(sessionId).catch(() => { // Silently handle disconnect errors }); } }; }, [currentHostConfig?.id, currentHostConfig?.enableDocker]); // Keepalive interval React.useEffect(() => { if (!sessionId || !isVisible) return; const keepalive = setInterval( () => { keepaliveDockerSession(sessionId).catch(() => { // Silently handle keepalive errors }); }, 10 * 60 * 1000, ); // Every 10 minutes return () => clearInterval(keepalive); }, [sessionId, isVisible]); // Refresh containers function const refreshContainers = React.useCallback(async () => { if (!sessionId) return; try { const data = await listDockerContainers(sessionId, true); setContainers(data); } catch (error) { // Silently handle polling errors } }, [sessionId]); // Poll containers React.useEffect(() => { if (!sessionId || !isVisible || !dockerValidation?.available) return; let cancelled = false; const pollContainers = async () => { try { const data = await listDockerContainers(sessionId, true); if (!cancelled) { setContainers(data); } } catch (error) { // Silently handle polling errors } }; pollContainers(); // Initial fetch const interval = setInterval(pollContainers, 5000); // Poll every 5 seconds return () => { cancelled = true; clearInterval(interval); }; }, [sessionId, isVisible, dockerValidation?.available]); const handleBack = React.useCallback(() => { setViewMode("list"); setSelectedContainer(null); }, []); const topMarginPx = isTopbarOpen ? 74 : 16; const leftMarginPx = sidebarState === "collapsed" ? 16 : 8; const bottomMarginPx = 8; const wrapperStyle: React.CSSProperties = embedded ? { opacity: isVisible ? 1 : 0, height: "100%", width: "100%" } : { opacity: isVisible ? 1 : 0, marginLeft: leftMarginPx, marginRight: 17, marginTop: topMarginPx, marginBottom: bottomMarginPx, height: `calc(100vh - ${topMarginPx + bottomMarginPx}px)`, }; const containerClass = embedded ? "h-full w-full text-white overflow-hidden bg-transparent" : "bg-dark-bg text-white rounded-lg border-2 border-dark-border overflow-hidden"; // Check if Docker is enabled if (!currentHostConfig?.enableDocker) { return (

{currentHostConfig?.folder} / {title}

Docker is not enabled for this host. Enable it in Host Settings to use Docker features.
); } // Loading state if (isConnecting || isValidating) { return (

{currentHostConfig?.folder} / {title}

{isValidating ? "Validating Docker..." : "Connecting to host..."}

); } // Docker not available if (dockerValidation && !dockerValidation.available) { return (

{currentHostConfig?.folder} / {title}

Docker Error
{dockerValidation.error}
{dockerValidation.code && (
Error code: {dockerValidation.code}
)}
); } return (

{currentHostConfig?.folder} / {title}

{dockerValidation?.version && (

Docker v{dockerValidation.version}

)}
{viewMode === "list" ? (
{sessionId ? ( { setSelectedContainer(id); setViewMode("detail"); }} selectedContainerId={selectedContainer} onRefresh={refreshContainers} /> ) : (

No session available

)}
) : sessionId && selectedContainer && currentHostConfig ? ( ) : (

Select a container to view details

)}
); }