import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { RefreshCw, AlertTriangle, CheckCircle, XCircle, Upload, HardDrive, Cloud, Server, Database, Image, FileArchive, Info, ChevronRight, ChevronLeft, Loader2, Shield, Download, Eye, Calendar, Clock, AlertCircle } from 'lucide-react'; import { format } from 'date-fns'; import { toast } from 'react-toastify'; import { useQuery, useMutation } from '@tanstack/react-query'; import { Button, Card, Input, Loading } from '../common'; import { api } from '../../config/api'; export const RestoreWizard = () => { const { t } = useTranslation(); const [currentStep, setCurrentStep] = useState(0); const steps = [ { id: 'source', title: t('backup.restore.steps.selectSource') }, { id: 'backup', title: t('backup.restore.steps.chooseBackup') }, { id: 'options', title: t('backup.restore.steps.restoreOptions') }, { id: 'confirm', title: t('backup.restore.steps.reviewConfirm') }, { id: 'progress', title: t('backup.restore.steps.restoreProgress') } ]; const restoreTypes = [ { id: 'full', name: t('backup.restore.restoreTypes.full.name'), description: t('backup.restore.restoreTypes.full.description'), icon: RefreshCw, warning: t('backup.restore.restoreTypes.full.warning') }, { id: 'database', name: t('backup.restore.restoreTypes.database.name'), description: t('backup.restore.restoreTypes.database.description'), icon: Database, warning: t('backup.restore.restoreTypes.database.warning') }, { id: 'files', name: t('backup.restore.restoreTypes.files.name'), description: t('backup.restore.restoreTypes.files.description'), icon: Image, warning: t('backup.restore.restoreTypes.files.warning') }, { id: 'selective', name: t('backup.restore.restoreTypes.selective.name'), description: t('backup.restore.restoreTypes.selective.description'), icon: CheckCircle, warning: t('backup.restore.restoreTypes.selective.warning') } ]; const [restoreData, setRestoreData] = useState({ source: null, sourceConfig: {}, selectedBackup: null, restoreType: 'full', selectedItems: [], skipPreBackup: false, force: false, encryptionPassphrase: '' }); const [validationResult, setValidationResult] = useState(null); // Fetch restore status const { data: restoreStatus } = useQuery({ queryKey: ['restore-status'], queryFn: async () => { const response = await api.get('/admin/restore/status'); return response.data.data; }, refetchInterval: currentStep === 4 ? 2000 : false // Poll during restore }); // Fetch available backups const { data: availableBackups, isLoading: loadingBackups } = useQuery({ queryKey: ['available-backups', restoreData.source, restoreData.sourceConfig], queryFn: async () => { const response = await api.post('/admin/restore/list-backups', { source: restoreData.source, ...restoreData.sourceConfig }); return response.data.data; }, enabled: currentStep === 1 && !!restoreData.source }); // Validate restore const validateMutation = useMutation({ mutationFn: async () => { const response = await api.post('/admin/restore/validate', { source: restoreData.source, manifestPath: restoreData.selectedBackup.manifest_path, restoreType: restoreData.restoreType, selectedItems: restoreData.selectedItems, ...restoreData.sourceConfig }); return response.data.data; }, onSuccess: (data) => { setValidationResult(data); setCurrentStep(3); }, onError: (error) => { toast.error(error.response?.data?.error || 'Validation failed'); } }); // Start restore const restoreMutation = useMutation({ mutationFn: async () => { const response = await api.post('/admin/restore/start', { source: restoreData.source, manifestPath: restoreData.selectedBackup.manifest_path, restoreType: restoreData.restoreType, selectedItems: restoreData.selectedItems, skipPreBackup: restoreData.skipPreBackup, force: restoreData.force, encryptionPassphrase: restoreData.encryptionPassphrase, ...restoreData.sourceConfig }); return response.data; }, onSuccess: () => { setCurrentStep(4); toast.success('Restore started successfully'); }, onError: (error) => { toast.error(error.response?.data?.error || 'Failed to start restore'); } }); const handleNext = () => { if (currentStep === 2) { // Validate before confirmation validateMutation.mutate(); } else if (currentStep === 3) { // Start restore restoreMutation.mutate(); } else { setCurrentStep(prev => Math.min(prev + 1, steps.length - 1)); } }; const handleBack = () => { setCurrentStep(prev => Math.max(prev - 1, 0)); }; const canProceed = () => { switch (currentStep) { case 0: return !!restoreData.source; case 1: return !!restoreData.selectedBackup; case 2: return !!restoreData.restoreType; case 3: return !!validationResult && !validateMutation.isLoading; default: return false; } }; // Step Components const renderSourceSelection = () => (

{t('backup.restore.source.title')}

{t('backup.restore.source.subtitle')}

{/* Source-specific configuration */} {restoreData.source === 's3' && (

{t('backup.restore.source.configuration.s3')}

setRestoreData(prev => ({ ...prev, sourceConfig: { ...prev.sourceConfig, s3Endpoint: e.target.value } }))} /> setRestoreData(prev => ({ ...prev, sourceConfig: { ...prev.sourceConfig, s3Bucket: e.target.value } }))} /> setRestoreData(prev => ({ ...prev, sourceConfig: { ...prev.sourceConfig, s3AccessKey: e.target.value } }))} /> setRestoreData(prev => ({ ...prev, sourceConfig: { ...prev.sourceConfig, s3SecretKey: e.target.value } }))} />
)} {restoreData.source === 'upload' && (

{t('backup.restore.source.upload.comingSoon')}

)}
); const renderBackupSelection = () => (

{t('backup.restore.backup.title')}

{t('backup.restore.backup.subtitle')}

{loadingBackups ? ( ) : availableBackups?.length === 0 ? (

{t('backup.restore.backup.noBackupsFound')}

) : (
{availableBackups?.map((backup) => ( setRestoreData(prev => ({ ...prev, selectedBackup: backup }))} >
{backup.status === 'completed' ? ( ) : ( )}

{format(new Date(backup.created_at), 'PPP')} {t('backup.restore.backup.at')} {format(new Date(backup.created_at), 'p')}

{t('backup.dashboard.backupType', { type: backup.backup_type })} • {formatBytes(backup.total_size || 0)}

{backup.encrypted && ( )}
))}
)} {restoreData.selectedBackup?.encrypted && (

{t('backup.restore.backup.encrypted')}

{t('backup.restore.backup.encryptedMessage')}

setRestoreData(prev => ({ ...prev, encryptionPassphrase: e.target.value }))} />
)}
); const renderRestoreOptions = () => (

{t('backup.restore.options.title')}

{t('backup.restore.options.subtitle')}

{restoreTypes.map((type) => { const Icon = type.icon; return ( ); })}
{/* Additional Options */}

{t('backup.restore.options.additionalOptions.title')}

); const renderConfirmation = () => (

{t('backup.restore.confirmation.title')}

{t('backup.restore.confirmation.subtitle')}

{validationResult ? ( <> {/* Validation Results */}
{validationResult.validation?.isValid ? ( ) : ( )}

{validationResult.validation?.isValid ? t('backup.restore.confirmation.validation.passed') : t('backup.restore.confirmation.validation.failed')}

{validationResult.validation?.errors?.length > 0 && (
    {validationResult.validation.errors.map((error, idx) => (
  • {error}
  • ))}
)}
{/* Space Check */} {validationResult.spaceCheck && (

{t('backup.restore.confirmation.spaceCheck.title')}

{t('backup.restore.confirmation.spaceCheck.required')}: {formatBytes(validationResult.spaceCheck.required)}
{t('backup.restore.confirmation.spaceCheck.available')}: {formatBytes(validationResult.spaceCheck.available)}
{!validationResult.spaceCheck.sufficient && (

{t('backup.restore.confirmation.spaceCheck.insufficient')}

)}
)} {/* Summary */}

{t('backup.restore.confirmation.summary.title')}

{t('backup.restore.confirmation.summary.source')}:
{restoreData.source}
{t('backup.restore.confirmation.summary.backupDate')}:
{format(new Date(restoreData.selectedBackup.created_at), 'PPp')}
{t('backup.restore.confirmation.summary.restoreType')}:
{restoreData.restoreType}
{t('backup.restore.confirmation.summary.preBackup')}:
{restoreData.skipPreBackup ? t('backup.restore.confirmation.summary.skipped') : t('backup.restore.confirmation.summary.enabled')}
{/* Warning */}

{t('backup.restore.confirmation.warning.title')}

{t('backup.restore.confirmation.warning.message')}

) : (

{t('backup.restore.confirmation.validation.checking')}

)}
); const renderProgress = () => { const progress = restoreStatus?.currentProgress || {}; const isRunning = restoreStatus?.isRunning; return (

{t('backup.restore.progress.title')}

{isRunning ? t('backup.restore.progress.inProgress') : t('backup.restore.progress.completed')}

{/* Progress Bar */}
{t('backup.restore.progress.overallProgress')} {progress.percentage || 0}%
{progress.currentFile && (

{t('backup.restore.progress.current')}: {progress.currentFile}

)}
{/* Status Details */}

{t('backup.restore.progress.statusDetails')}

{progress.steps?.map((step, idx) => (
{step.status === 'completed' ? ( ) : step.status === 'running' ? ( ) : step.status === 'failed' ? ( ) : ( )}

{step.name}

{step.message && (

{step.message}

)}
{step.duration && ( {step.duration} )}
))}
{/* Logs */} {progress.logs && progress.logs.length > 0 && (

{t('backup.restore.progress.restoreLogs')}

                {progress.logs.join('\n')}
              
)} {/* Completion Actions */} {!isRunning && progress.status === 'completed' && (

{t('backup.restore.progress.success.title')}

{t('backup.restore.progress.success.message')}

)}
); }; const formatBytes = (bytes) => { if (!bytes) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`; }; return (
{/* Progress Steps */}
{/* Step Content */} {currentStep === 0 && renderSourceSelection()} {currentStep === 1 && renderBackupSelection()} {currentStep === 2 && renderRestoreOptions()} {currentStep === 3 && renderConfirmation()} {currentStep === 4 && renderProgress()} {/* Navigation Buttons */}
{currentStep < 4 && ( )} {currentStep === 4 && !restoreStatus?.isRunning && ( )}
); };