- 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.
261 lines
10 KiB
TypeScript
261 lines
10 KiB
TypeScript
'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,
|
||
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)]">
|
||
<Header
|
||
title="TowerControl"
|
||
subtitle="Paramètres avancés"
|
||
/>
|
||
|
||
<div className="container mx-auto px-4 py-4">
|
||
<div className="max-w-4xl mx-auto">
|
||
{/* Breadcrumb */}
|
||
<div className="mb-4 text-sm">
|
||
<Link href="/settings" className="text-[var(--muted-foreground)] hover:text-[var(--primary)]">
|
||
Paramètres
|
||
</Link>
|
||
<span className="mx-2 text-[var(--muted-foreground)]">/</span>
|
||
<span className="text-[var(--foreground)]">Avancé</span>
|
||
</div>
|
||
|
||
{/* Page Header */}
|
||
<div className="mb-6">
|
||
<h1 className="text-2xl font-mono font-bold text-[var(--foreground)] mb-2">
|
||
🛠️ Paramètres avancés
|
||
</h1>
|
||
<p className="text-[var(--muted-foreground)]">
|
||
Configuration système, sauvegarde et outils de développement
|
||
</p>
|
||
</div>
|
||
|
||
<div className="space-y-6">
|
||
{/* Sauvegarde et données */}
|
||
<Card>
|
||
<CardHeader>
|
||
<h2 className="text-lg font-semibold">💾 Sauvegarde et données</h2>
|
||
<p className="text-sm text-[var(--muted-foreground)]">
|
||
Gestion des sauvegardes automatiques et manuelles
|
||
</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)]">
|
||
<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">
|
||
{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)]">
|
||
{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">
|
||
{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
|
||
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>
|
||
|
||
{/* Base de données */}
|
||
<Card>
|
||
<CardHeader>
|
||
<h2 className="text-lg font-semibold">🗄️ Base de données</h2>
|
||
<p className="text-sm text-[var(--muted-foreground)]">
|
||
Informations et maintenance de la base de données
|
||
</p>
|
||
</CardHeader>
|
||
<CardContent className="space-y-4">
|
||
<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)]">
|
||
{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)]">
|
||
{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">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>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</UserPreferencesProvider>
|
||
);
|
||
}
|