feat: enhance backup management in AdvancedSettingsPage

- Added backup management functionality to `AdvancedSettingsPageClient`, including creating and verifying backups.
- Updated `package.json` with new backup-related scripts.
- Improved UI to display backup status and next scheduled backup time.
- Updated `.gitignore` to exclude backup files.
- Enhanced server-side data fetching to include backup data and database statistics.
This commit is contained in:
Julien Froidefond
2025-09-18 17:19:37 +02:00
parent 2fbfab1b9e
commit 9c2c719384
12 changed files with 2157 additions and 131 deletions

View File

@@ -1,16 +1,115 @@
'use client';
import { useState } from 'react';
import { UserPreferences } from '@/lib/types';
import { Header } from '@/components/ui/Header';
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
import { Button } from '@/components/ui/Button';
import { UserPreferencesProvider } from '@/contexts/UserPreferencesContext';
import { backupClient, BackupListResponse } from '@/clients/backup-client';
import Link from 'next/link';
interface DatabaseStats {
taskCount: number;
tagCount: number;
completionRate: number;
}
interface AdvancedSettingsPageClientProps {
initialPreferences: UserPreferences;
initialDbStats: DatabaseStats;
initialBackupData: BackupListResponse;
}
export function AdvancedSettingsPageClient({ initialPreferences }: AdvancedSettingsPageClientProps) {
export function AdvancedSettingsPageClient({
initialPreferences,
initialDbStats,
initialBackupData
}: AdvancedSettingsPageClientProps) {
const [backupData, setBackupData] = useState<BackupListResponse>(initialBackupData);
const [dbStats] = useState<DatabaseStats>(initialDbStats);
const [isCreatingBackup, setIsCreatingBackup] = useState(false);
const [isVerifying, setIsVerifying] = useState(false);
const reloadBackupData = async () => {
try {
const data = await backupClient.listBackups();
setBackupData(data);
} catch (error) {
console.error('Failed to reload backup data:', error);
}
};
const handleCreateBackup = async () => {
setIsCreatingBackup(true);
try {
await backupClient.createBackup();
await reloadBackupData();
} catch (error) {
console.error('Failed to create backup:', error);
} finally {
setIsCreatingBackup(false);
}
};
const handleVerifyDatabase = async () => {
setIsVerifying(true);
try {
await backupClient.verifyDatabase();
alert('✅ Base de données vérifiée avec succès');
} catch (error) {
console.error('Database verification failed:', error);
alert('❌ Erreur lors de la vérification de la base');
} finally {
setIsVerifying(false);
}
};
const formatFileSize = (bytes: number): string => {
const units = ['B', 'KB', 'MB', 'GB'];
let size = bytes;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(1)} ${units[unitIndex]}`;
};
const formatTimeAgo = (date: Date): string => {
const now = new Date();
const diffMs = now.getTime() - new Date(date).getTime();
const diffMins = Math.floor(diffMs / (1000 * 60));
const diffHours = Math.floor(diffMins / 60);
const diffDays = Math.floor(diffHours / 24);
if (diffMins < 60) {
return `il y a ${diffMins}min`;
} else if (diffHours < 24) {
return `il y a ${diffHours}h ${diffMins % 60}min`;
} else {
return `il y a ${diffDays}j`;
}
};
const getNextBackupTime = (): string => {
if (!backupData.scheduler.nextBackup) return 'Non planifiée';
const nextBackup = new Date(backupData.scheduler.nextBackup);
const now = new Date();
const diffMs = nextBackup.getTime() - now.getTime();
const diffMins = Math.floor(diffMs / (1000 * 60));
const diffHours = Math.floor(diffMins / 60);
if (diffMins < 60) {
return `dans ${diffMins}min`;
} else {
return `dans ${diffHours}h ${diffMins % 60}min`;
}
};
return (
<UserPreferencesProvider initialPreferences={initialPreferences}>
<div className="min-h-screen bg-[var(--background)]">
@@ -52,33 +151,65 @@ export function AdvancedSettingsPageClient({ initialPreferences }: AdvancedSetti
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="p-4 bg-[var(--card)] rounded border border-dashed border-[var(--border)]">
<h3 className="font-medium mb-2">Sauvegarde automatique</h3>
<div className="flex items-center justify-between mb-2">
<h3 className="font-medium">Sauvegarde automatique</h3>
<span className={`h-2 w-2 rounded-full ${
backupData.scheduler.isRunning ? 'bg-green-500' : 'bg-red-500'
}`}></span>
</div>
<p className="text-sm text-[var(--muted-foreground)] mb-2">
Sauvegarde toutes les 6 heures (configurable)
{backupData.scheduler.isEnabled
? `Sauvegarde ${backupData.scheduler.interval === 'hourly' ? 'toutes les heures' :
backupData.scheduler.interval === 'daily' ? 'quotidienne' : 'hebdomadaire'}`
: 'Sauvegarde automatique désactivée'
}
</p>
<p className="text-xs text-[var(--muted-foreground)]">
Prochaine sauvegarde: dans 3h 42min
{backupData.scheduler.isRunning
? `Prochaine sauvegarde: ${getNextBackupTime()}`
: 'Planificateur arrêté'
}
</p>
</div>
<div className="p-4 bg-[var(--card)] rounded border border-dashed border-[var(--border)]">
<h3 className="font-medium mb-2">Sauvegardes disponibles</h3>
<p className="text-sm text-[var(--muted-foreground)] mb-2">
5 sauvegardes conservées
</p>
<p className="text-xs text-[var(--muted-foreground)]">
Dernière: il y a 2h 18min
{backupData.backups.length} sauvegarde{backupData.backups.length > 1 ? 's' : ''} conservée{backupData.backups.length > 1 ? 's' : ''}
</p>
{backupData.backups.length > 0 ? (
<p className="text-xs text-[var(--muted-foreground)]">
Dernière: {formatTimeAgo(backupData.backups[0].createdAt)}
({formatFileSize(backupData.backups[0].size)})
</p>
) : (
<p className="text-xs text-[var(--muted-foreground)]">
Aucune sauvegarde disponible
</p>
)}
</div>
</div>
<div className="flex gap-2 pt-2">
<button className="px-3 py-1.5 bg-[var(--primary)] text-[var(--primary-foreground)] rounded text-sm font-medium">
Créer une sauvegarde
</button>
<button className="px-3 py-1.5 bg-[var(--card)] text-[var(--foreground)] border border-[var(--border)] rounded text-sm font-medium">
Gérer les sauvegardes
</button>
<Button
onClick={handleCreateBackup}
disabled={isCreatingBackup}
className="px-3 py-1.5 bg-[var(--primary)] text-[var(--primary-foreground)] rounded text-sm font-medium"
>
{isCreatingBackup ? 'Création...' : 'Créer une sauvegarde'}
</Button>
<Link href="/settings/backup">
<Button className="px-3 py-1.5 bg-[var(--card)] text-[var(--foreground)] border border-[var(--border)] rounded text-sm font-medium">
Gérer les sauvegardes
</Button>
</Link>
<Button
onClick={handleVerifyDatabase}
disabled={isVerifying}
className="px-3 py-1.5 bg-[var(--card)] text-[var(--foreground)] border border-[var(--border)] rounded text-sm font-medium"
>
{isVerifying ? 'Vérification...' : 'Vérifier DB'}
</Button>
</div>
</CardContent>
</Card>
@@ -95,126 +226,31 @@ export function AdvancedSettingsPageClient({ initialPreferences }: AdvancedSetti
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="p-4 bg-[var(--card)] rounded border border-dashed border-[var(--border)]">
<h3 className="font-medium mb-1">Tâches</h3>
<p className="text-2xl font-bold text-[var(--primary)]">247</p>
<p className="text-2xl font-bold text-[var(--primary)]">
{dbStats.taskCount}
</p>
<p className="text-xs text-[var(--muted-foreground)]">entrées</p>
</div>
<div className="p-4 bg-[var(--card)] rounded border border-dashed border-[var(--border)]">
<h3 className="font-medium mb-1">Tags</h3>
<p className="text-2xl font-bold text-[var(--primary)]">18</p>
<p className="text-2xl font-bold text-[var(--primary)]">
{dbStats.tagCount}
</p>
<p className="text-xs text-[var(--muted-foreground)]">entrées</p>
</div>
<div className="p-4 bg-[var(--card)] rounded border border-dashed border-[var(--border)]">
<h3 className="font-medium mb-1">Taille DB</h3>
<p className="text-2xl font-bold text-[var(--primary)]">2.4</p>
<p className="text-xs text-[var(--muted-foreground)]">MB</p>
<h3 className="font-medium mb-1">Taux complétion</h3>
<p className="text-2xl font-bold text-[var(--primary)]">
{dbStats.completionRate}
</p>
<p className="text-xs text-[var(--muted-foreground)]">%</p>
</div>
</div>
</CardContent>
</Card>
{/* Export / Import */}
<Card>
<CardHeader>
<h2 className="text-lg font-semibold">📤 Export / Import</h2>
<p className="text-sm text-[var(--muted-foreground)]">
Sauvegarde et restauration des données
</p>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="p-4 bg-[var(--card)] rounded border border-dashed border-[var(--border)]">
<h3 className="font-medium mb-2">Export des données</h3>
<p className="text-sm text-[var(--muted-foreground)] mb-3">
Exporter toutes les données au format JSON
</p>
<button className="px-3 py-1.5 bg-[var(--primary)] text-[var(--primary-foreground)] rounded text-sm">
Télécharger export
</button>
</div>
<div className="p-4 bg-[var(--card)] rounded border border-dashed border-[var(--border)]">
<h3 className="font-medium mb-2">Import des données</h3>
<p className="text-sm text-[var(--muted-foreground)] mb-3">
Restaurer des données depuis un fichier JSON
</p>
<button className="px-3 py-1.5 bg-[var(--card)] text-[var(--foreground)] border border-[var(--border)] rounded text-sm">
Choisir fichier
</button>
</div>
</div>
</CardContent>
</Card>
{/* Logs et debug */}
<Card>
<CardHeader>
<h2 className="text-lg font-semibold">🐛 Debug et logs</h2>
<p className="text-sm text-[var(--muted-foreground)]">
Outils de diagnostic et de résolution de problèmes
</p>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="p-4 bg-[var(--card)] rounded border border-dashed border-[var(--border)]">
<h3 className="font-medium mb-2">Logs système</h3>
<p className="text-sm text-[var(--muted-foreground)] mb-3">
Consultation des logs d&apos;erreur et d&apos;activité
</p>
<button className="px-3 py-1.5 bg-[var(--card)] text-[var(--foreground)] border border-[var(--border)] rounded text-sm">
Voir les logs
</button>
</div>
<div className="p-4 bg-[var(--card)] rounded border border-dashed border-[var(--border)]">
<h3 className="font-medium mb-2">Mode debug</h3>
<p className="text-sm text-[var(--muted-foreground)] mb-3">
Activer les informations de debug détaillées
</p>
<button className="px-3 py-1.5 bg-[var(--card)] text-[var(--foreground)] border border-[var(--border)] rounded text-sm">
Activer debug
</button>
</div>
</div>
</CardContent>
</Card>
{/* Zone dangereuse */}
<Card>
<CardHeader>
<h2 className="text-lg font-semibold text-[var(--destructive)]"> Zone dangereuse</h2>
<p className="text-sm text-[var(--muted-foreground)]">
Actions irréversibles - à utiliser avec précaution
</p>
</CardHeader>
<CardContent className="space-y-4">
<div className="p-4 bg-[var(--destructive)]/5 border border-[var(--destructive)]/20 rounded">
<h3 className="font-medium mb-2 text-[var(--destructive)]">Réinitialisation complète</h3>
<p className="text-sm text-[var(--muted-foreground)] mb-3">
Supprime toutes les données (tâches, tags, préférences)
</p>
<button className="px-3 py-1.5 bg-[var(--destructive)] text-[var(--destructive-foreground)] rounded text-sm">
Réinitialiser
</button>
</div>
</CardContent>
</Card>
{/* Note développement futur */}
<Card>
<CardContent className="p-4">
<div className="p-4 bg-[var(--warning)]/10 border border-[var(--warning)]/20 rounded">
<p className="text-sm text-[var(--warning)] font-medium mb-2">
🚧 Fonctionnalités en développement
</p>
<p className="text-xs text-[var(--muted-foreground)]">
La plupart des fonctions avancées seront implémentées dans les prochaines versions.
Cette page sert de prévisualisation de l&apos;interface à venir.
</p>
</div>
</CardContent>
</Card>
</div>
</div>
</div>