'use client'; import { useState, useEffect } from 'react'; import { api } from '~/trpc/react'; import { Button } from './ui/button'; import { Badge } from './ui/badge'; import { RefreshCw, ChevronDown, ChevronRight, HardDrive, Database, Server, CheckCircle, AlertCircle } from 'lucide-react'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from './ui/dropdown-menu'; import { ConfirmationModal } from './ConfirmationModal'; import { LoadingModal } from './LoadingModal'; interface Backup { id: number; backup_name: string; backup_path: string; size: bigint | null; created_at: Date | null; storage_name: string; storage_type: string; discovered_at: Date; server_id: number; server_name: string | null; server_color: string | null; } interface ContainerBackups { container_id: string; hostname: string; backups: Backup[]; } export function BackupsTab() { const [expandedContainers, setExpandedContainers] = useState>(new Set()); const [hasAutoDiscovered, setHasAutoDiscovered] = useState(false); const [restoreConfirmOpen, setRestoreConfirmOpen] = useState(false); const [selectedBackup, setSelectedBackup] = useState<{ backup: Backup; containerId: string } | null>(null); const [restoreProgress, setRestoreProgress] = useState([]); const [restoreSuccess, setRestoreSuccess] = useState(false); const [restoreError, setRestoreError] = useState(null); const { data: backupsData, refetch: refetchBackups, isLoading } = api.backups.getAllBackupsGrouped.useQuery(); const discoverMutation = api.backups.discoverBackups.useMutation({ onSuccess: () => { void refetchBackups(); }, }); const restoreMutation = api.backups.restoreBackup.useMutation({ onMutate: () => { // Show progress immediately when mutation starts setRestoreProgress(['Starting restore...']); setRestoreError(null); setRestoreSuccess(false); }, onSuccess: (result) => { if (result.success) { setRestoreSuccess(true); // Update progress with all messages from backend const progressMessages = result.progress?.map(p => p.message) || ['Restore completed successfully']; setRestoreProgress(progressMessages); setRestoreConfirmOpen(false); setSelectedBackup(null); // Clear success message after 5 seconds setTimeout(() => { setRestoreSuccess(false); setRestoreProgress([]); }, 5000); } else { setRestoreError(result.error || 'Restore failed'); setRestoreProgress(result.progress?.map(p => p.message) || []); } }, onError: (error) => { setRestoreError(error.message || 'Restore failed'); setRestoreConfirmOpen(false); setSelectedBackup(null); setRestoreProgress([]); }, }); // Update progress text in modal based on current progress const currentProgressText = restoreProgress.length > 0 ? restoreProgress[restoreProgress.length - 1] : 'Restoring backup...'; // Auto-discover backups when tab is first opened useEffect(() => { if (!hasAutoDiscovered && !isLoading && backupsData) { // Only auto-discover if there are no backups yet if (!backupsData.backups || backupsData.backups.length === 0) { handleDiscoverBackups(); } setHasAutoDiscovered(true); } }, [hasAutoDiscovered, isLoading, backupsData]); const handleDiscoverBackups = () => { discoverMutation.mutate(); }; const handleRestoreClick = (backup: Backup, containerId: string) => { setSelectedBackup({ backup, containerId }); setRestoreConfirmOpen(true); setRestoreError(null); setRestoreSuccess(false); setRestoreProgress([]); }; const handleRestoreConfirm = () => { if (!selectedBackup) return; setRestoreConfirmOpen(false); setRestoreError(null); setRestoreSuccess(false); restoreMutation.mutate({ backupId: selectedBackup.backup.id, containerId: selectedBackup.containerId, serverId: selectedBackup.backup.server_id, }); }; const toggleContainer = (containerId: string) => { const newExpanded = new Set(expandedContainers); if (newExpanded.has(containerId)) { newExpanded.delete(containerId); } else { newExpanded.add(containerId); } setExpandedContainers(newExpanded); }; const formatFileSize = (bytes: bigint | null): string => { if (!bytes) return 'Unknown size'; const b = Number(bytes); if (b === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(b) / Math.log(k)); return `${(b / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`; }; const formatDate = (date: Date | null): string => { if (!date) return 'Unknown date'; return new Date(date).toLocaleString(); }; const getStorageTypeIcon = (type: string) => { switch (type) { case 'pbs': return ; case 'local': return ; default: return ; } }; const getStorageTypeBadgeVariant = (type: string): 'default' | 'secondary' | 'outline' => { switch (type) { case 'pbs': return 'default'; case 'local': return 'secondary'; default: return 'outline'; } }; const backups = backupsData?.success ? backupsData.backups : []; const isDiscovering = discoverMutation.isPending; return (
{/* Header with refresh button */}

Backups

Discovered backups grouped by container ID

{/* Loading state */} {(isLoading || isDiscovering) && backups.length === 0 && (

{isDiscovering ? 'Discovering backups...' : 'Loading backups...'}

)} {/* Empty state */} {!isLoading && !isDiscovering && backups.length === 0 && (

No backups found

Click "Discover Backups" to scan for backups on your servers.

)} {/* Backups list */} {!isLoading && backups.length > 0 && (
{backups.map((container: ContainerBackups) => { const isExpanded = expandedContainers.has(container.container_id); const backupCount = container.backups.length; return (
{/* Container header - collapsible */} {/* Container content - backups list */} {isExpanded && (
{container.backups.map((backup) => (
{backup.backup_name} {getStorageTypeIcon(backup.storage_type)} {backup.storage_name}
{backup.size && ( {formatFileSize(backup.size)} )} {backup.created_at && ( {formatDate(backup.created_at)} )} {backup.server_name && ( {backup.server_name} )}
{backup.backup_path}
handleRestoreClick(backup, container.container_id)} disabled={restoreMutation.isPending} className="text-muted-foreground hover:text-foreground hover:bg-muted/20 focus:bg-muted/20" > Restore Delete
))}
)}
); })}
)} {/* Error state */} {backupsData && !backupsData.success && (

Error loading backups: {backupsData.error || 'Unknown error'}

)} {/* Restore Confirmation Modal */} {selectedBackup && ( { setRestoreConfirmOpen(false); setSelectedBackup(null); }} onConfirm={handleRestoreConfirm} title="Restore Backup" message={`This will destroy the existing container and restore from backup. The container will be stopped during restore. This action cannot be undone and may result in data loss.`} variant="danger" confirmText={selectedBackup.containerId} confirmButtonText="Restore" cancelButtonText="Cancel" /> )} {/* Restore Progress Modal */} {restoreMutation.isPending && ( )} {/* Restore Progress Details - Show during restore */} {restoreMutation.isPending && (
Restoring backup...
{restoreProgress.length > 0 && (
{restoreProgress.map((message, index) => (

{message}

))}
)}
)} {/* Restore Success */} {restoreSuccess && (
Restore completed successfully!
{restoreProgress.length > 0 && (
{restoreProgress.map((message, index) => (

{message}

))}
)}
)} {/* Restore Error */} {restoreError && (
Restore failed

{restoreError}

{restoreProgress.length > 0 && (
{restoreProgress.map((message, index) => (

{message}

))}
)}
)}
); }