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 */}
);
};