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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user