import { useState, useEffect } from 'react'; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { toast } from "sonner"; import { useTheme } from "next-themes"; import { Loader2, CheckCircle, XCircle, Wand2, Moon, Sun, ShieldCheck, History, ArrowRight, ArrowLeft, Download, Database, Server, Cloud } from "lucide-react"; import { Checkbox } from '@/components/ui/checkbox'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Badge } from "@/components/ui/badge"; // It's a good practice to define types for complex objects type BackupRecord = { id: string; dbName: string; status: 'COMPLETED' | 'FAILED' | 'PROCESSING' | 'QUEUED'; createdAt: string; fileName?: string; error?: string; downloadUrl?: string; }; // Define a type for the form data to ensure type safety type BackupFormData = { dbType: string; dbHost: string; dbPort: string; dbUser: string; dbPassword: string; dbName: string; dbRequireSsl: boolean; s3Endpoint: string; s3BucketName: string; s3AccessKey: string; s3SecretKey: string; s3Region: string; cronExpression: string; }; type View = 'form' | 'history'; type Step = 'database' | 's3' | 'schedule'; type ConnectionStatus = 'idle' | 'success' | 'error' | 'testing'; /** * Main component for the Backup System UI */ export default function HomePage() { const { setTheme, theme } = useTheme(); const [view, setView] = useState('form'); // State for the main backup form const [formData, setFormData] = useState({ dbType: 'postgresql', dbHost: '', dbPort: '5432', dbUser: '', dbPassword: '', dbName: '', dbRequireSsl: true, s3Endpoint: '', s3BucketName: '', s3AccessKey: '', s3SecretKey: '', s3Region: 'us-east-1', cronExpression: '', }); // UI/UX State const [currentStep, setCurrentStep] = useState('database'); const [isLoading, setIsLoading] = useState(false); const [connectionStatus, setConnectionStatus] = useState('idle'); // State for the real-time backup process modal const [isBackupInProgress, setIsBackupInProgress] = useState(false); const [backupLogs, setBackupLogs] = useState([]); const [backupStatus, setBackupStatus] = useState<'pending' | 'completed' | 'failed'>('pending'); // --- Event Handlers --- const handleChange = (e: React.ChangeEvent) => { const { name, value } = e.target; setFormData(prev => ({ ...prev, [name]: value })); // Reset connection status if database details change if (name.startsWith('db')) { setConnectionStatus('idle'); } }; const handleSelectChange = (name: string, value: string) => { setFormData(prev => ({ ...prev, [name]: value })); if (name === 'dbType') { const ports: { [key: string]: string } = { postgresql: '5432' }; setFormData(prev => ({ ...prev, dbPort: ports[value] || '' })); setConnectionStatus('idle'); } }; const handleCheckboxChange = (name: string, checked: boolean) => { setFormData(prev => ({ ...prev, [name]: checked })); if (name.startsWith('db')) { setConnectionStatus('idle'); } }; // --- API Calls --- const handleTestConnection = async () => { setConnectionStatus('testing'); try { const response = await fetch('/api/test-connection', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData), }); const result = await response.json(); if (response.ok) { setConnectionStatus('success'); toast.success("Connection successful!"); } else { setConnectionStatus('error'); toast.error("Connection failed.", { description: result.error || "Please check credentials and network." }); } } catch (error: any) { setConnectionStatus('error'); toast.error("Connection failed.", { description: error?.message || "An unknown error occurred." }); } }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); // If cron expression exists, it's a schedule job, not a real-time one if (formData.cronExpression) { setIsLoading(true); try { const response = await fetch('/api/backup', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData), }); const result = await response.json(); if (response.ok) { toast.success("Success! 🎉", { description: "Backup job has been scheduled." }); setView('history'); } else { toast.error("Uh oh! Something went wrong.", { description: result.message || result.error }); } } catch (error: any) { toast.error("Failed to schedule backup.", { description: error?.message }); } finally { setIsLoading(false); } return; } // --- Start Real-time Backup via Server-Sent Events --- setIsBackupInProgress(true); setBackupLogs([]); setBackupStatus('pending'); const queryParams = new URLSearchParams(); // A more type-safe way to append params Object.entries(formData).forEach(([key, value]) => { queryParams.append(key, String(value)); }); const eventSource = new EventSource(`/api/backup-stream?${queryParams.toString()}`); eventSource.onopen = () => { setBackupLogs(prev => [...prev, "Connection to server established. Starting backup..."]); }; eventSource.onmessage = (event) => { const data = JSON.parse(event.data); if (data.message) { setBackupLogs((prevLogs) => [...prevLogs, data.message]); } if (data.status) { setBackupStatus(data.status); if (['completed', 'failed', 'closed'].includes(data.status)) { eventSource.close(); if (data.status === 'completed') { toast.success("Backup completed successfully!"); } else if (data.status === 'failed') { toast.error("Backup failed.", { description: "Check logs for more details."}); } } } }; eventSource.onerror = (err) => { toast.error("Connection to backup stream failed."); setBackupStatus('failed'); setBackupLogs((prev) => [...prev, "Stream connection closed unexpectedly. The server might have terminated the process."]); eventSource.close(); }; }; // --- Step Navigation --- const nextStep = () => { if (currentStep === 'database') setCurrentStep('s3'); if (currentStep === 's3') setCurrentStep('schedule'); }; const prevStep = () => { if (currentStep === 'schedule') setCurrentStep('s3'); if (currentStep === 's3') setCurrentStep('database'); }; // --- Render Logic --- const renderStepContent = () => { switch(currentStep) { case 'database': return handleSelectChange('dbType', v)} onCheckboxChange={(c: boolean) => handleCheckboxChange('dbRequireSsl', c)} />; case 's3': return ; case 'schedule': return ; default: return null; } }; return (
{view === 'form' ? (
Secure Backup System Configure and run a secure, one-time or scheduled database backup. {renderStepContent()} {currentStep !== 'schedule' ? ( ) : ( )}
) : ( )} {/* Real-time Backup Progress Modal */} Backup in Progress... Please keep this window open until the process is complete. Logs are streamed from the server.
{backupLogs.map((log, index) => (

{log}

))}
{backupStatus === 'completed' && <>

Backup completed successfully!

} {backupStatus === 'failed' && <>

Backup failed. Check logs for details.

} {backupStatus === 'pending' && <>

Processing...

}
); } // --- Sub-components for Form Steps with defined Prop Types --- type DatabaseStepProps = { formData: BackupFormData; connectionStatus: ConnectionStatus; onTestConnection: () => void; onChange: (e: React.ChangeEvent) => void; onSelectChange: (value: string) => void; onCheckboxChange: (checked: boolean) => void; }; const DatabaseStepContent = ({ formData, connectionStatus, onTestConnection, onChange, onSelectChange, onCheckboxChange }: DatabaseStepProps) => (
{connectionStatus === 'success' && } {connectionStatus === 'error' && }
); type S3StepProps = { formData: BackupFormData; onChange: (e: React.ChangeEvent) => void; }; const S3StepContent = ({ formData, onChange }: S3StepProps) => (
); type ScheduleStepProps = { formData: BackupFormData; onChange: (e: React.ChangeEvent) => void; }; const ScheduleStepContent = ({ formData, onChange }: ScheduleStepProps) => (

Leave empty to run backup once immediately. Use crontab.guru to build expressions.

); /** * Component to display the history of backup jobs */ function HistoryView() { const [records, setRecords] = useState([]); const [isLoading, setIsLoading] = useState(true); useEffect(() => { const fetchRecords = async () => { try { const response = await fetch('/api/backups'); if(response.ok) { const data = await response.json(); setRecords(data); } else { toast.error("Could not fetch backup history."); } } catch (error) { console.error("Failed to fetch backup records:", error); toast.error("Could not fetch backup history."); } finally { setIsLoading(false); } }; fetchRecords(); const interval = setInterval(fetchRecords, 5000); // Refresh every 5 seconds return () => clearInterval(interval); }, []); return ( Backup History A list of all scheduled and completed backup jobs. Refreshes automatically. {isLoading && records.length === 0 ? (

Loading history...

) : records.length === 0 ? (

No backup jobs found yet.

) : (
Database Status Created At Filename / Error Actions {records.map((record) => ( {record.dbName} {(record.status === 'QUEUED' || record.status === 'PROCESSING') && } {record.status} {new Date(record.createdAt).toLocaleString()} {record.fileName || record.error} {record.status === 'COMPLETED' && record.downloadUrl && ( )} ))}
)}
); }