import { useState, useEffect, useCallback, useRef } from "react"; import { ArrowLeftRight, Database } from "lucide-react"; import { Button } from "../ui/Button"; import { BackupProgress } from "./BackupProgress"; import { useBackupStore } from "../../stores/backupStore"; import { useConnectionStore } from "../../stores/connectionStore"; import { useNotificationStore } from "../../stores/notificationStore"; import { detectPgTools, dbSync, getSchemas } from "../../lib/commands"; import type { PgToolStatus } from "../../lib/types"; export function SyncPage() { const [sourceConnectionId, setSourceConnectionId] = useState(""); const [targetConnectionId, setTargetConnectionId] = useState(""); const [schema, setSchema] = useState(""); const [confirmed, setConfirmed] = useState(false); const [toolStatus, setToolStatus] = useState(null); const [checkingTools, setCheckingTools] = useState(true); const [availableSchemas, setAvailableSchemas] = useState([]); const connections = useConnectionStore((s) => s.connections); const activeJobId = useBackupStore((s) => s.activeJobId); const jobs = useBackupStore((s) => s.jobs); const startJob = useBackupStore((s) => s.startJob); const notify = useNotificationStore((s) => s.notify); const activeJob = jobs.find((j) => j.id === activeJobId); const isRunning = activeJob?.status === "running"; const pendingJobRef = useRef(null); useEffect(() => { if (!pendingJobRef.current || !activeJob) return; if (activeJob.id !== pendingJobRef.current) return; if (activeJob.status === "completed") { notify("Sync completed successfully", "success"); pendingJobRef.current = null; } else if (activeJob.status === "failed") { notify( `Sync failed: ${activeJob.error_message || "Unknown error"}`, "error", ); pendingJobRef.current = null; } }, [activeJob, notify]); useEffect(() => { setCheckingTools(true); setConfirmed(false); detectPgTools() .then((status) => setToolStatus(status)) .catch(() => setToolStatus({ pg_dump_found: false, pg_restore_found: false, pg_dump_version: null, pg_restore_version: null, pg_dump_source: null, pg_restore_source: null, }), ) .finally(() => setCheckingTools(false)); }, []); // Fetch schemas from the source connection when it changes useEffect(() => { if (!sourceConnectionId) { setAvailableSchemas([]); setSchema(""); return; } getSchemas(sourceConnectionId) .then((schemas) => setAvailableSchemas(schemas)) .catch(() => setAvailableSchemas([])); }, [sourceConnectionId]); const handleStartSync = useCallback(async () => { if (!sourceConnectionId || !targetConnectionId) { notify("Please select both source and target connections", "error"); return; } if (sourceConnectionId === targetConnectionId) { notify("Source and target must be different", "error"); return; } const jobId = `sync-${Date.now()}`; startJob(jobId, "sync"); pendingJobRef.current = jobId; try { await dbSync({ sourceConnectionId, targetConnectionId, schema: schema || undefined, tables: undefined, }); } catch (e) { const msg = e instanceof Error ? e.message : String(e); useBackupStore.getState().failJob(jobId, msg); } }, [sourceConnectionId, targetConnectionId, schema, startJob, notify]); const toolsMissing = toolStatus && (!toolStatus.pg_dump_found || !toolStatus.pg_restore_found); const toolsBundled = toolStatus?.pg_dump_source === "bundled" && toolStatus?.pg_restore_source === "bundled"; const canStart = sourceConnectionId && targetConnectionId && confirmed && !isRunning; const postgresqlConnections = connections.filter( (c) => c.db_type === "postgresql", ); return (
{/* Toolbar header */}
DB Sync Transfer data between PostgreSQL databases via pipe
{/* Content */}
{/* Tool check */} {checkingTools && (

Checking for pg_dump / pg_restore...

)} {toolsMissing && !toolsBundled && (

PostgreSQL tools not found

Both pg_dump and pg_restore are required for database sync.

    {!toolStatus?.pg_dump_found && (
  • pg_dump is missing.
  • )} {!toolStatus?.pg_restore_found && (
  • pg_restore is missing.
  • )}
)} {!checkingTools && !toolsMissing && ( <> {/* Configuration card */}
{/* Source & Target connection pickers */}
{/* Schema (optional) */}
{/* Flow indicator */} {sourceConnectionId && targetConnectionId && (
{postgresqlConnections.find( (c) => c.id === sourceConnectionId, )?.name ?? sourceConnectionId} {postgresqlConnections.find( (c) => c.id === targetConnectionId, )?.name ?? targetConnectionId}
)}
{/* Destructive confirmation */}
{/* Progress */} {activeJob && (
)} {/* Actions */}
)}
); }