import { useState, useEffect, useRef } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Badge } from "@/components/ui/badge"; import { useDockerStatus, useCloneToDocker } from "@/hooks/use-docker"; import { toast } from "sonner"; import { Loader2, CheckCircle2, XCircle, Container, Copy, ChevronDown, ChevronRight, } from "lucide-react"; import type { CloneMode, CloneProgress } from "@/types"; interface Props { open: boolean; onOpenChange: (open: boolean) => void; connectionId: string; database: string; onConnect?: (connectionId: string) => void; } type Step = "config" | "progress" | "done"; function ProcessLog({ entries, open: logOpen, onToggle, endRef, }: { entries: CloneProgress[]; open: boolean; onToggle: () => void; endRef: React.RefObject; }) { if (entries.length === 0) return null; return (
{logOpen && (
{entries.map((entry, i) => (
{entry.percent}% {entry.message} {entry.detail && ( — {entry.detail} )}
))}
)}
); } export function CloneDatabaseDialog({ open, onOpenChange, connectionId, database, onConnect, }: Props) { const [step, setStep] = useState("config"); const [containerName, setContainerName] = useState(""); const [pgVersion, setPgVersion] = useState("16"); const [portMode, setPortMode] = useState<"auto" | "manual">("auto"); const [manualPort, setManualPort] = useState(5433); const [cloneMode, setCloneMode] = useState("schema_only"); const [sampleRows, setSampleRows] = useState(1000); const [logEntries, setLogEntries] = useState([]); const [logOpen, setLogOpen] = useState(false); const logEndRef = useRef(null); const { data: dockerStatus } = useDockerStatus(); const { clone, result, error, isCloning, progress, reset } = useCloneToDocker(); // Reset state when dialog opens useEffect(() => { if (open) { setStep("config"); setContainerName( `tusk-${database.replace(/[^a-zA-Z0-9_-]/g, "-")}-${Date.now().toString(36)}` ); setPgVersion("16"); setPortMode("auto"); setManualPort(5433); setCloneMode("schema_only"); setSampleRows(1000); setLogEntries([]); setLogOpen(false); reset(); } }, [open, database, reset]); // Accumulate progress events into log useEffect(() => { if (progress) { setLogEntries((prev) => { const last = prev[prev.length - 1]; if (last && last.stage === progress.stage && last.message === progress.message) { return prev; } return [...prev, progress]; }); if (progress.stage === "done" || progress.stage === "error") { setStep("done"); } } }, [progress]); // Auto-scroll log to bottom useEffect(() => { if (logOpen && logEndRef.current) { logEndRef.current.scrollIntoView({ behavior: "smooth" }); } }, [logEntries, logOpen]); const handleClone = () => { if (!containerName.trim()) { toast.error("Container name is required"); return; } setStep("progress"); const cloneId = crypto.randomUUID(); clone({ params: { source_connection_id: connectionId, source_database: database, container_name: containerName.trim(), pg_version: pgVersion, host_port: portMode === "manual" ? manualPort : null, clone_mode: cloneMode, sample_rows: cloneMode === "sample_data" ? sampleRows : null, postgres_password: null, }, cloneId, }); }; const handleConnect = () => { if (result?.connection_id && onConnect) { onConnect(result.connection_id); } onOpenChange(false); }; const dockerReady = dockerStatus?.installed && dockerStatus?.daemon_running; const logSection = ( setLogOpen(!logOpen)} endRef={logEndRef} /> ); return ( Clone to Docker {step === "config" && ( <>
{dockerStatus === undefined ? ( <> Checking Docker... ) : dockerReady ? ( <> Docker {dockerStatus.version} ) : ( <> {dockerStatus?.error || "Docker not available"} )}
{database}
setContainerName(e.target.value)} placeholder="tusk-mydb-clone" />
{portMode === "manual" && ( setManualPort(parseInt(e.target.value) || 5433) } min={1024} max={65535} /> )}
{cloneMode === "sample_data" && (
setSampleRows(parseInt(e.target.value) || 1000) } min={1} max={100000} />
)}
)} {step === "progress" && (
{progress?.message || "Starting..."} {progress?.percent ?? 0}%
{isCloning && (
{progress?.stage || "Initializing..."}
)} {logSection}
)} {step === "done" && (
{error ? (

Clone Failed

{error}

) : (

Clone Completed

Database cloned to Docker container successfully.

{result && (
Container {result.container.name}
Port {result.container.host_port}
URL
{result.connection_url}
)}
)} {logSection} {error ? ( <> ) : ( <> {onConnect && result && ( )} )}
)}
); }