feat: enhance backup functionality and logging

- Updated `createBackup` method to accept a `force` parameter, allowing backups to be created even if no changes are detected.
- Added user alerts in `AdvancedSettingsPageClient` and `BackupSettingsPageClient` for backup status feedback.
- Implemented `getBackupLogs` method in `BackupService` to retrieve backup logs, with a new API route for accessing logs.
- Enhanced UI in `BackupSettingsPageClient` to display backup logs and provide a refresh option.
- Updated `BackupManagerCLI` to support forced backups via command line.
This commit is contained in:
Julien Froidefond
2025-09-21 07:27:23 +02:00
parent 618e774a30
commit 43998425e6
10 changed files with 649 additions and 179 deletions

View File

@@ -43,10 +43,16 @@ export function AdvancedSettingsPageClient({
const handleCreateBackup = async () => {
setIsCreatingBackup(true);
try {
await backupClient.createBackup();
const result = await backupClient.createBackup();
if (result === null) {
alert('⏭️ Sauvegarde sautée : aucun changement détecté');
} else {
alert('✅ Sauvegarde créée avec succès');
}
await reloadBackupData();
} catch (error) {
console.error('Failed to create backup:', error);
alert('❌ Erreur lors de la création de la sauvegarde');
} finally {
setIsCreatingBackup(false);
}

View File

@@ -23,11 +23,15 @@ export default function BackupSettingsPageClient({ initialData }: BackupSettings
const [showDeleteConfirm, setShowDeleteConfirm] = useState<string | null>(null);
const [config, setConfig] = useState<BackupConfig | null>(initialData?.config || null);
const [isSavingConfig, setIsSavingConfig] = useState(false);
const [logs, setLogs] = useState<string[]>([]);
const [isLoadingLogs, setIsLoadingLogs] = useState(false);
const [showLogs, setShowLogs] = useState(false);
const [messages, setMessages] = useState<{[key: string]: {type: 'success' | 'error', text: string} | null}>({
verify: null,
config: null,
restore: null,
delete: null,
backup: null,
});
useEffect(() => {
@@ -66,13 +70,30 @@ export default function BackupSettingsPageClient({ initialData }: BackupSettings
}
};
const handleCreateBackup = async () => {
const handleCreateBackup = async (force: boolean = false) => {
setIsCreatingBackup(true);
try {
await backupClient.createBackup();
const result = await backupClient.createBackup(force);
if (result === null) {
setMessage('backup', {
type: 'success',
text: 'Sauvegarde sautée : aucun changement détecté. Utilisez "Forcer" pour créer malgré tout.'
});
} else {
setMessage('backup', {
type: 'success',
text: `Sauvegarde créée : ${result.filename}`
});
}
await loadData();
} catch (error) {
console.error('Failed to create backup:', error);
setMessage('backup', {
type: 'error',
text: 'Erreur lors de la création de la sauvegarde'
});
} finally {
setIsCreatingBackup(false);
}
@@ -144,6 +165,19 @@ export default function BackupSettingsPageClient({ initialData }: BackupSettings
}
};
const loadLogs = async () => {
setIsLoadingLogs(true);
try {
const backupLogs = await backupClient.getBackupLogs(50);
setLogs(backupLogs);
} catch (error) {
console.error('Failed to load backup logs:', error);
setLogs([]);
} finally {
setIsLoadingLogs(false);
}
};
const formatFileSize = (bytes: number): string => {
const units = ['B', 'KB', 'MB', 'GB'];
let size = bytes;
@@ -360,15 +394,30 @@ export default function BackupSettingsPageClient({ initialData }: BackupSettings
</CardHeader>
<CardContent>
<div className="flex flex-wrap gap-3">
<Button
onClick={handleCreateBackup}
disabled={isCreatingBackup}
className="bg-[var(--primary)] hover:bg-[var(--primary)]/90 text-[var(--primary-foreground)]"
>
{isCreatingBackup ? 'Création...' : 'Créer une sauvegarde'}
</Button>
<div className="flex flex-col gap-2">
<div className="flex gap-2">
<Button
onClick={() => handleCreateBackup(false)}
disabled={isCreatingBackup}
className="bg-[var(--primary)] hover:bg-[var(--primary)]/90 text-[var(--primary-foreground)]"
>
{isCreatingBackup ? 'Création...' : 'Créer sauvegarde'}
</Button>
<Button
onClick={() => handleCreateBackup(true)}
disabled={isCreatingBackup}
className="bg-orange-600 hover:bg-orange-700 text-white"
>
{isCreatingBackup ? 'Création...' : 'Forcer'}
</Button>
</div>
<div className="text-xs text-[var(--muted-foreground)]">
<strong>Créer :</strong> Vérifie les changements <strong>Forcer :</strong> Crée toujours
</div>
<InlineMessage messageKey="backup" />
</div>
<div>
<div className="flex gap-2">
<Button
onClick={handleVerifyDatabase}
disabled={isVerifying}
@@ -377,17 +426,17 @@ export default function BackupSettingsPageClient({ initialData }: BackupSettings
{isVerifying ? 'Vérification...' : 'Vérifier l\'intégrité'}
</Button>
<InlineMessage messageKey="verify" />
<Button
onClick={handleToggleScheduler}
className={data.scheduler.isRunning
? 'bg-[var(--destructive)] hover:bg-[var(--destructive)]/90 text-[var(--destructive-foreground)]'
: 'bg-[var(--primary)] hover:bg-[var(--primary)]/90 text-[var(--primary-foreground)]'
}
>
{data.scheduler.isRunning ? 'Arrêter le planificateur' : 'Démarrer le planificateur'}
</Button>
</div>
<Button
onClick={handleToggleScheduler}
className={data.scheduler.isRunning
? 'bg-[var(--destructive)] hover:bg-[var(--destructive)]/90 text-[var(--destructive-foreground)]'
: 'bg-[var(--primary)] hover:bg-[var(--primary)]/90 text-[var(--primary-foreground)]'
}
>
{data.scheduler.isRunning ? 'Arrêter le planificateur' : 'Démarrer le planificateur'}
</Button>
</div>
</CardContent>
</Card>
@@ -514,6 +563,71 @@ export default function BackupSettingsPageClient({ initialData }: BackupSettings
</Card>
</div>
</div>
{/* Section des logs */}
<div className="mt-6">
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<div>
<h2 className="text-xl font-semibold flex items-center gap-2">
<span className="text-blue-600">📋</span>
Logs des sauvegardes
</h2>
<p className="text-sm text-[var(--muted-foreground)]">
Historique des opérations de sauvegarde
</p>
</div>
<Button
onClick={() => {
if (!showLogs) {
loadLogs();
}
setShowLogs(!showLogs);
}}
className="bg-[var(--card)] text-[var(--foreground)] border border-[var(--border)] hover:bg-[var(--muted)]"
>
{showLogs ? 'Masquer' : 'Afficher'} les logs
</Button>
</div>
</CardHeader>
{showLogs && (
<CardContent>
{isLoadingLogs ? (
<div className="text-center py-4">
<p className="text-sm text-[var(--muted-foreground)]">Chargement des logs...</p>
</div>
) : logs.length === 0 ? (
<div className="text-center py-4">
<p className="text-sm text-[var(--muted-foreground)]">Aucun log disponible</p>
</div>
) : (
<div className="space-y-1 max-h-64 overflow-y-auto">
{logs.map((log, index) => (
<div
key={index}
className="text-xs font-mono p-2 bg-[var(--muted)] rounded border"
>
{log}
</div>
))}
</div>
)}
{showLogs && (
<div className="mt-3 pt-3 border-t border-[var(--border)]">
<Button
onClick={loadLogs}
disabled={isLoadingLogs}
className="text-xs bg-[var(--card)] text-[var(--foreground)] border border-[var(--border)] hover:bg-[var(--muted)]"
>
{isLoadingLogs ? 'Actualisation...' : 'Actualiser'}
</Button>
</div>
)}
</CardContent>
)}
</Card>
</div>
</div>
</div>

View File

@@ -50,10 +50,17 @@ export function SettingsIndexPageClient({ initialPreferences, initialSystemInfo
setIsBackupLoading(true);
try {
const backup = await backupClient.createBackup();
setMessages(prev => ({
...prev,
backup: { type: 'success', text: `Sauvegarde créée: ${backup.filename}` }
}));
if (backup) {
setMessages(prev => ({
...prev,
backup: { type: 'success', text: `Sauvegarde créée: ${backup.filename}` }
}));
} else {
setMessages(prev => ({
...prev,
backup: { type: 'success', text: 'Sauvegarde sautée: aucun changement détecté' }
}));
}
// Recharger les infos système pour mettre à jour le nombre de sauvegardes
loadSystemInfo();