'use client'; import React, { useState, useRef, useEffect } from 'react'; import { Card } from 'primereact/card'; import { Button } from 'primereact/button'; import { DataTable } from 'primereact/datatable'; import { Column } from 'primereact/column'; import { Toast } from 'primereact/toast'; import { Toolbar } from 'primereact/toolbar'; import { Tag } from 'primereact/tag'; import { Dialog } from 'primereact/dialog'; import { Calendar } from 'primereact/calendar'; import { Dropdown } from 'primereact/dropdown'; import { InputSwitch } from 'primereact/inputswitch'; import { InputNumber } from 'primereact/inputnumber'; import { ProgressBar } from 'primereact/progressbar'; import { FileUpload } from 'primereact/fileupload'; import { Checkbox } from 'primereact/checkbox'; import { RadioButton } from 'primereact/radiobutton'; import { Timeline } from 'primereact/timeline'; import { ConfirmDialog, confirmDialog } from 'primereact/confirmdialog'; import { Divider } from 'primereact/divider'; import { Panel } from 'primereact/panel'; import { Badge } from 'primereact/badge'; import { formatDate, formatDateTime } from '../../../../utils/formatters'; interface Backup { id: string; nom: string; type: 'MANUEL' | 'AUTOMATIQUE' | 'PLANIFIE'; statut: 'EN_COURS' | 'COMPLETE' | 'ECHOUE' | 'PARTIEL'; dateCreation: Date; taille: number; duree: number; fichiers: number; destination: string; chiffre: boolean; compresse: boolean; notes?: string; utilisateur: string; prochaineSauvegarde?: Date; } interface BackupSchedule { id: string; nom: string; actif: boolean; frequence: 'HORAIRE' | 'QUOTIDIEN' | 'HEBDOMADAIRE' | 'MENSUEL'; heure: Date; joursSelection?: number[]; retention: number; destination: string; chiffrement: boolean; compression: boolean; inclureBase: boolean; inclureFichiers: boolean; inclureConfig: boolean; } interface RestorePoint { id: string; backupId: string; nom: string; date: Date; type: string; statut: 'DISPONIBLE' | 'CORROMPU' | 'EXPIRE'; taille: number; } const SauvegardePage = () => { const [backups, setBackups] = useState([]); const [schedules, setSchedules] = useState([]); const [restorePoints, setRestorePoints] = useState([]); const [loading, setLoading] = useState(false); const [backupInProgress, setBackupInProgress] = useState(false); const [progress, setProgress] = useState(0); const [globalFilter, setGlobalFilter] = useState(''); const [scheduleDialog, setScheduleDialog] = useState(false); const [restoreDialog, setRestoreDialog] = useState(false); const [selectedBackup, setSelectedBackup] = useState(null); const [selectedSchedule, setSelectedSchedule] = useState(null); const [activeIndex, setActiveIndex] = useState(0); const toast = useRef(null); const [newSchedule, setNewSchedule] = useState({ id: '', nom: '', actif: true, frequence: 'QUOTIDIEN', heure: new Date(), retention: 30, destination: 'local', chiffrement: true, compression: true, inclureBase: true, inclureFichiers: true, inclureConfig: true }); const frequenceOptions = [ { label: 'Toutes les heures', value: 'HORAIRE' }, { label: 'Quotidien', value: 'QUOTIDIEN' }, { label: 'Hebdomadaire', value: 'HEBDOMADAIRE' }, { label: 'Mensuel', value: 'MENSUEL' } ]; const destinationOptions = [ { label: 'Stockage local', value: 'local' }, { label: 'Google Drive', value: 'google' }, { label: 'Dropbox', value: 'dropbox' }, { label: 'AWS S3', value: 's3' }, { label: 'Serveur FTP', value: 'ftp' } ]; const joursSemaine = [ { label: 'Lundi', value: 1 }, { label: 'Mardi', value: 2 }, { label: 'Mercredi', value: 3 }, { label: 'Jeudi', value: 4 }, { label: 'Vendredi', value: 5 }, { label: 'Samedi', value: 6 }, { label: 'Dimanche', value: 0 } ]; useEffect(() => { loadBackupData(); }, []); const loadBackupData = () => { setLoading(true); // TODO: Remplacer par un appel API réel pour charger l'historique des sauvegardes // Exemple: const response = await fetch('/api/admin/backups'); // const backups = await response.json(); const mockBackups: Backup[] = []; // TODO: Remplacer par un appel API réel pour charger les planifications de sauvegarde // Exemple: const response = await fetch('/api/admin/backup-schedules'); // const schedules = await response.json(); const mockSchedules: BackupSchedule[] = []; // TODO: Remplacer par un appel API réel pour charger les points de restauration // Exemple: const response = await fetch('/api/admin/restore-points'); // const restorePoints = await response.json(); const mockRestorePoints: RestorePoint[] = []; setTimeout(() => { setBackups(mockBackups); setSchedules(mockSchedules); setRestorePoints(mockRestorePoints); setLoading(false); }, 1000); }; const startManualBackup = () => { confirmDialog({ message: 'Voulez-vous lancer une sauvegarde manuelle maintenant ?', header: 'Confirmation de sauvegarde', icon: 'pi pi-save', acceptLabel: 'Lancer', rejectLabel: 'Annuler', accept: () => { performBackup(); } }); }; const performBackup = () => { setBackupInProgress(true); setProgress(0); const interval = setInterval(() => { setProgress(prev => { if (prev >= 100) { clearInterval(interval); setBackupInProgress(false); const newBackup: Backup = { id: Date.now().toString(), nom: `Sauvegarde manuelle ${new Date().toLocaleString()}`, type: 'MANUEL', statut: 'COMPLETE', dateCreation: new Date(), taille: Math.floor(Math.random() * 2000000000) + 500000000, duree: Math.floor(Math.random() * 300) + 60, fichiers: Math.floor(Math.random() * 10000) + 1000, destination: 'local', chiffre: true, compresse: true, utilisateur: 'admin' }; setBackups([newBackup, ...backups]); toast.current?.show({ severity: 'success', summary: 'Sauvegarde terminée', detail: 'La sauvegarde a été effectuée avec succès', life: 5000 }); return 100; } return prev + 5; }); }, 200); }; const deleteBackup = (backup: Backup) => { confirmDialog({ message: `Êtes-vous sûr de vouloir supprimer la sauvegarde "${backup.nom}" ?`, header: 'Confirmation de suppression', icon: 'pi pi-exclamation-triangle', acceptLabel: 'Supprimer', rejectLabel: 'Annuler', acceptClassName: 'p-button-danger', accept: () => { setBackups(backups.filter(b => b.id !== backup.id)); toast.current?.show({ severity: 'success', summary: 'Suppression réussie', detail: 'La sauvegarde a été supprimée', life: 3000 }); } }); }; const restoreBackup = (backup: Backup) => { setSelectedBackup(backup); setRestoreDialog(true); }; const performRestore = () => { setRestoreDialog(false); toast.current?.show({ severity: 'info', summary: 'Restauration en cours', detail: 'La restauration a été lancée en arrière-plan', life: 5000 }); }; const saveSchedule = () => { if (newSchedule.nom) { const schedule = { ...newSchedule, id: Date.now().toString() }; setSchedules([...schedules, schedule]); setScheduleDialog(false); toast.current?.show({ severity: 'success', summary: 'Planification créée', detail: 'La planification de sauvegarde a été créée', life: 3000 }); } }; const toggleSchedule = (schedule: BackupSchedule) => { const updated = schedules.map(s => s.id === schedule.id ? { ...s, actif: !s.actif } : s ); setSchedules(updated); toast.current?.show({ severity: schedule.actif ? 'warn' : 'success', summary: schedule.actif ? 'Planification désactivée' : 'Planification activée', detail: `La planification "${schedule.nom}" a été ${schedule.actif ? 'désactivée' : 'activée'}`, life: 3000 }); }; const formatFileSize = (bytes: number) => { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', '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]; }; const formatDuration = (seconds: number) => { const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); const secs = seconds % 60; if (hours > 0) { return `${hours}h ${minutes}m ${secs}s`; } else if (minutes > 0) { return `${minutes}m ${secs}s`; } return `${secs}s`; }; const leftToolbarTemplate = () => { return (
); }; const rightToolbarTemplate = () => { return (
); }; const statusBodyTemplate = (rowData: Backup) => { const getSeverity = (status: string) => { switch (status) { case 'COMPLETE': return 'success'; case 'EN_COURS': return 'info'; case 'PARTIEL': return 'warning'; case 'ECHOUE': return 'danger'; default: return 'secondary'; } }; const getLabel = (status: string) => { switch (status) { case 'COMPLETE': return 'Complète'; case 'EN_COURS': return 'En cours'; case 'PARTIEL': return 'Partielle'; case 'ECHOUE': return 'Échouée'; default: return status; } }; return ; }; const typeBodyTemplate = (rowData: Backup) => { const getIcon = (type: string) => { switch (type) { case 'MANUEL': return 'pi-user'; case 'AUTOMATIQUE': return 'pi-clock'; case 'PLANIFIE': return 'pi-calendar'; default: return 'pi-save'; } }; const getLabel = (type: string) => { switch (type) { case 'MANUEL': return 'Manuelle'; case 'AUTOMATIQUE': return 'Automatique'; case 'PLANIFIE': return 'Planifiée'; default: return type; } }; return (
{getLabel(rowData.type)}
); }; const actionBodyTemplate = (rowData: Backup) => { return (
); }; const scheduleActionTemplate = (rowData: BackupSchedule) => { return (
toggleSchedule(rowData)} />
); }; const renderBackupList = () => ( {backupInProgress && (
Sauvegarde en cours...
)}
Historique des sauvegardes
setGlobalFilter(e.currentTarget.value)} /> } > formatDateTime(rowData.dateCreation)} sortable /> formatFileSize(rowData.taille)} sortable /> formatDuration(rowData.duree)} /> (
{rowData.chiffre && } {rowData.compresse && }
)} />
); const renderSchedules = () => ( Planifications de sauvegarde} > { const freq = frequenceOptions.find(f => f.value === rowData.frequence); return freq ? freq.label : rowData.frequence; }} /> rowData.heure.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' })} /> `${rowData.retention} jours`} /> ( )} /> ); const renderStatistics = () => (
{formatFileSize(backups.reduce((sum, b) => sum + b.taille, 0))}
Espace total utilisé
{backups.length}
Sauvegardes totales
{backups.filter(b => b.statut === 'COMPLETE').length}
Sauvegardes réussies
{schedules.filter(s => s.actif).length}
Planifications actives
); const scheduleDialogFooter = (
); const restoreDialogFooter = (
); return (
{renderStatistics()}
{renderBackupList()}
{renderSchedules()}
{/* Dialog pour nouvelle planification */} setScheduleDialog(false)} >
setNewSchedule({...newSchedule, nom: e.target.value})} className="w-full" placeholder="Ex: Sauvegarde quotidienne" />
setNewSchedule({...newSchedule, frequence: e.value})} className="w-full" />
setNewSchedule({...newSchedule, heure: e.value || new Date()})} timeOnly hourFormat="24" className="w-full" />
{newSchedule.frequence === 'HEBDOMADAIRE' && (
{joursSemaine.map(jour => (
{ const jours = newSchedule.joursSelection || []; if (e.checked) { setNewSchedule({...newSchedule, joursSelection: [...jours, jour.value]}); } else { setNewSchedule({...newSchedule, joursSelection: jours.filter(j => j !== jour.value)}); } }} checked={newSchedule.joursSelection?.includes(jour.value) || false} />
))}
)}
setNewSchedule({...newSchedule, destination: e.value})} className="w-full" />
setNewSchedule({...newSchedule, retention: e.value || 30})} min={1} max={365} className="w-full" />
Options de sauvegarde
setNewSchedule({...newSchedule, chiffrement: e.checked || false})} />
setNewSchedule({...newSchedule, compression: e.checked || false})} />
Éléments à sauvegarder
setNewSchedule({...newSchedule, inclureBase: e.checked || false})} />
setNewSchedule({...newSchedule, inclureFichiers: e.checked || false})} />
setNewSchedule({...newSchedule, inclureConfig: e.checked || false})} />
{/* Dialog pour restauration */} setRestoreDialog(false)} > {selectedBackup && (
Sauvegarde: {selectedBackup.nom}
Date: {formatDateTime(selectedBackup.dateCreation)}
Taille: {formatFileSize(selectedBackup.taille)}
La restauration remplacera toutes les données actuelles. Cette action est irréversible.
Options de restauration
)}
); }; export default SauvegardePage;