1282 lines
56 KiB
TypeScript
1282 lines
56 KiB
TypeScript
'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 { TabView, TabPanel } from 'primereact/tabview';
|
|
import { SpeedDial } from 'primereact/speeddial';
|
|
import { Accordion, AccordionTab } from 'primereact/accordion';
|
|
import { Avatar } from 'primereact/avatar';
|
|
import { AvatarGroup } from 'primereact/avatargroup';
|
|
import { Chip } from 'primereact/chip';
|
|
import { Message } from 'primereact/message';
|
|
import { Messages } from 'primereact/messages';
|
|
import { Skeleton } from 'primereact/skeleton';
|
|
import { ScrollPanel } from 'primereact/scrollpanel';
|
|
import { Splitter, SplitterPanel } from 'primereact/splitter';
|
|
import { DataView } from 'primereact/dataview';
|
|
import { Knob } from 'primereact/knob';
|
|
// import { MeterGroup } from 'primereact/metergroup'; // Non disponible dans cette version
|
|
import { Chart } from 'primereact/chart';
|
|
import { Sidebar } from 'primereact/sidebar';
|
|
import { OverlayPanel } from 'primereact/overlaypanel';
|
|
import { ContextMenu } from 'primereact/contextmenu';
|
|
import { Menu } from 'primereact/menu';
|
|
import { TieredMenu } from 'primereact/tieredmenu';
|
|
import { Fieldset } from 'primereact/fieldset';
|
|
import { Steps } from 'primereact/steps';
|
|
import { MenuItem } from 'primereact/menuitem';
|
|
import { InputText } from 'primereact/inputtext';
|
|
import { Rating } from 'primereact/rating';
|
|
import { Slider } from 'primereact/slider';
|
|
import { ToggleButton } from 'primereact/togglebutton';
|
|
import { SelectButton } from 'primereact/selectbutton';
|
|
import { ColorPicker } from 'primereact/colorpicker';
|
|
import { MultiSelect } from 'primereact/multiselect';
|
|
import { ListBox } from 'primereact/listbox';
|
|
import { OrderList } from 'primereact/orderlist';
|
|
import { PickList } from 'primereact/picklist';
|
|
import { DataScroller } from 'primereact/datascroller';
|
|
import { VirtualScroller } from 'primereact/virtualscroller';
|
|
import { Galleria } from 'primereact/galleria';
|
|
import { Image } from 'primereact/image';
|
|
import { Carousel } from 'primereact/carousel';
|
|
import { Terminal } from 'primereact/terminal';
|
|
import { DeferredContent } from 'primereact/deferredcontent';
|
|
import { ScrollTop } from 'primereact/scrolltop';
|
|
import { BlockUI } from 'primereact/blockui';
|
|
import { TreeTable } from 'primereact/treetable';
|
|
import { Tree } from 'primereact/tree';
|
|
import { Mention } from 'primereact/mention';
|
|
import { Editor } from 'primereact/editor';
|
|
import { AutoComplete } from 'primereact/autocomplete';
|
|
import { InputMask } from 'primereact/inputmask';
|
|
import { Password } from 'primereact/password';
|
|
import { CascadeSelect } from 'primereact/cascadeselect';
|
|
import { TreeSelect } from 'primereact/treeselect';
|
|
|
|
interface Backup {
|
|
id: string;
|
|
nom: string;
|
|
type: string;
|
|
statut: 'EN_COURS' | 'TERMINE' | 'ERREUR';
|
|
dateCreation: Date;
|
|
dateModification?: Date;
|
|
taille: number;
|
|
destination: string;
|
|
checksum?: string;
|
|
description?: string;
|
|
}
|
|
|
|
interface BackupSchedule {
|
|
id: string;
|
|
nom: string;
|
|
frequence: string;
|
|
heure: Date;
|
|
actif: boolean;
|
|
prochainExecution: Date;
|
|
derniereExecution?: Date;
|
|
nombreBackups: number;
|
|
description?: string;
|
|
inclureBase: boolean;
|
|
inclureFichiers: boolean;
|
|
inclureConfig: boolean;
|
|
compression: boolean;
|
|
joursSelection?: string[];
|
|
}
|
|
|
|
interface RestorePoint {
|
|
id: string;
|
|
nom: string;
|
|
dateCreation: Date;
|
|
taille: number;
|
|
description?: string;
|
|
}
|
|
|
|
const SauvegardePage = () => {
|
|
const [backups, setBackups] = useState<Backup[]>([]);
|
|
const [schedules, setSchedules] = useState<BackupSchedule[]>([]);
|
|
const [restorePoints, setRestorePoints] = useState<RestorePoint[]>([]);
|
|
const [loading, setLoading] = useState(false);
|
|
const [blocked, setBlocked] = useState(false);
|
|
const [backupInProgress, setBackupInProgress] = useState(false);
|
|
const [restoreInProgress, setRestoreInProgress] = useState(false);
|
|
const [progress, setProgress] = useState(0);
|
|
const [restoreProgress, setRestoreProgress] = useState(0);
|
|
const [globalFilter, setGlobalFilter] = useState('');
|
|
const [scheduleDialog, setScheduleDialog] = useState(false);
|
|
const [restoreDialog, setRestoreDialog] = useState(false);
|
|
const [monitoringDialog, setMonitoringDialog] = useState(false);
|
|
const [analyticsDialog, setAnalyticsDialog] = useState(false);
|
|
const [configDialog, setConfigDialog] = useState(false);
|
|
const [selectedBackup, setSelectedBackup] = useState<Backup | null>(null);
|
|
const [selectedSchedule, setSelectedSchedule] = useState<BackupSchedule | null>(null);
|
|
const [activeIndex, setActiveIndex] = useState(0);
|
|
const [sidebarVisible, setSidebarVisible] = useState(false);
|
|
const [autoRefresh, setAutoRefresh] = useState(true);
|
|
const [refreshInterval, setRefreshInterval] = useState(30);
|
|
const [viewMode, setViewMode] = useState<'table' | 'grid' | 'timeline'>('table');
|
|
const [storageQuota, setStorageQuota] = useState(85);
|
|
const [systemHealth, setSystemHealth] = useState(92);
|
|
const [backupSpeed, setBackupSpeed] = useState(45);
|
|
const [securityLevel, setSecurityLevel] = useState(88);
|
|
const [compressionRatio, setCompressionRatio] = useState(67);
|
|
const [networkBandwidth, setNetworkBandwidth] = useState(234);
|
|
const [activeConnections, setActiveConnections] = useState(12);
|
|
const [encryptionStrength, setEncryptionStrength] = useState(256);
|
|
const [retentionCompliance, setRetentionCompliance] = useState(95);
|
|
const [replicationStatus, setReplicationStatus] = useState('active');
|
|
const [cloudSync, setCloudSync] = useState(true);
|
|
const [incrementalBackup, setIncrementalBackup] = useState(true);
|
|
const [verificationEnabled, setVerificationEnabled] = useState(true);
|
|
const [compressionLevel, setCompressionLevel] = useState(6);
|
|
const [notificationLevel, setNotificationLevel] = useState('all');
|
|
const [searchTerm, setSearchTerm] = useState('');
|
|
const [selectedBackups, setSelectedBackups] = useState<Backup[]>([]);
|
|
const [kpiData, setKpiData] = useState({
|
|
totalBackups: 0,
|
|
successRate: 0,
|
|
storageUsed: 0,
|
|
avgDuration: 0,
|
|
lastBackup: null,
|
|
scheduledCount: 0
|
|
});
|
|
const [chartData, setChartData] = useState({});
|
|
const [performanceMetrics, setPerformanceMetrics] = useState([]);
|
|
const [alertsData, setAlertsData] = useState([]);
|
|
const [storageAnalytics, setStorageAnalytics] = useState([]);
|
|
const [complianceReport, setComplianceReport] = useState([]);
|
|
const [auditTrail, setAuditTrail] = useState([]);
|
|
const [backupVerification, setBackupVerification] = useState([]);
|
|
const [cloudProviders, setCloudProviders] = useState([]);
|
|
const [replicationTargets, setReplicationTargets] = useState([]);
|
|
const [comparisonMode, setComparisonMode] = useState(false);
|
|
const [selectedForComparison, setSelectedForComparison] = useState<Backup[]>([]);
|
|
const [exportProgress, setExportProgress] = useState(0);
|
|
const [importProgress, setImportProgress] = useState(0);
|
|
const [terminalVisible, setTerminalVisible] = useState(false);
|
|
const [wizardMode, setWizardMode] = useState(false);
|
|
const [wizardStep, setWizardStep] = useState(0);
|
|
const [smartRecommendations, setSmartRecommendations] = useState([]);
|
|
const [predictiveAnalysis, setPredictiveAnalysis] = useState({});
|
|
const [costOptimization, setCostOptimization] = useState([]);
|
|
|
|
const toast = useRef<Toast>(null);
|
|
const messages = useRef<Messages>(null);
|
|
const overlayPanel = useRef<OverlayPanel>(null);
|
|
const contextMenu = useRef<ContextMenu>(null);
|
|
const terminal = useRef<Terminal>(null);
|
|
const dt = useRef<DataTable<any>>(null);
|
|
|
|
// Configuration pour l'interface utilisateur ultra-avancée
|
|
const frequenceOptions = [
|
|
{ label: 'Toutes les heures', value: 'HORAIRE', icon: 'pi pi-clock' },
|
|
{ label: 'Quotidien', value: 'QUOTIDIEN', icon: 'pi pi-calendar' },
|
|
{ label: 'Hebdomadaire', value: 'HEBDOMADAIRE', icon: 'pi pi-calendar-times' },
|
|
{ label: 'Mensuel', value: 'MENSUEL', icon: 'pi pi-calendar-plus' },
|
|
{ label: 'Temps réel', value: 'REALTIME', icon: 'pi pi-bolt' },
|
|
{ label: 'Sur événement', value: 'EVENT', icon: 'pi pi-exclamation-triangle' }
|
|
];
|
|
|
|
const viewModeOptions = [
|
|
{ label: 'Tableau', value: 'table', icon: 'pi pi-table' },
|
|
{ label: 'Grille', value: 'grid', icon: 'pi pi-th-large' },
|
|
{ label: 'Timeline', value: 'timeline', icon: 'pi pi-calendar' }
|
|
];
|
|
|
|
const notificationOptions = [
|
|
{ label: 'Toutes', value: 'all' },
|
|
{ label: 'Erreurs uniquement', value: 'errors' },
|
|
{ label: 'Critiques', value: 'critical' },
|
|
{ label: 'Aucune', value: 'none' }
|
|
];
|
|
|
|
const speedDialActions = [
|
|
{ label: 'Sauvegarde rapide', icon: 'pi pi-save', command: () => startManualBackup() },
|
|
{ label: 'Restauration', icon: 'pi pi-replay', command: () => setRestoreDialog(true) },
|
|
{ label: 'Nouvelle planification', icon: 'pi pi-calendar-plus', command: () => setScheduleDialog(true) },
|
|
{ label: 'Nettoyage', icon: 'pi pi-trash', command: () => cleanupOldBackups() },
|
|
{ label: 'Synchronisation cloud', icon: 'pi pi-cloud-upload', command: () => syncToCloud() },
|
|
{ label: 'Vérification intégrité', icon: 'pi pi-shield', command: () => verifyBackups() }
|
|
];
|
|
|
|
const wizardSteps = [
|
|
{ label: 'Configuration', icon: 'pi pi-cog' },
|
|
{ label: 'Destination', icon: 'pi pi-folder' },
|
|
{ label: 'Planification', icon: 'pi pi-calendar' },
|
|
{ label: 'Sécurité', icon: 'pi pi-shield' },
|
|
{ label: 'Validation', icon: 'pi pi-check' }
|
|
];
|
|
|
|
const cloudProvidersData = [
|
|
{ name: 'AWS S3', status: 'connected', quota: 1000, used: 345, icon: 'pi pi-amazon' },
|
|
{ name: 'Google Cloud', status: 'connected', quota: 500, used: 123, icon: 'pi pi-google' },
|
|
{ name: 'Azure Blob', status: 'disconnected', quota: 750, used: 0, icon: 'pi pi-microsoft' },
|
|
{ name: 'Dropbox', status: 'error', quota: 100, used: 89, icon: 'pi pi-cloud' }
|
|
];
|
|
|
|
const alertsExample = [
|
|
{ type: 'warning', message: 'Espace de stockage à 85%', time: new Date(), severity: 'warn' },
|
|
{ type: 'error', message: 'Échec sauvegarde AWS S3', time: new Date(), severity: 'error' },
|
|
{ type: 'info', message: 'Sauvegarde programmée terminée', time: new Date(), severity: 'success' },
|
|
{ type: 'critical', message: 'Corruption détectée dans backup_2024_01_15', time: new Date(), severity: 'error' }
|
|
];
|
|
|
|
const performanceData = [
|
|
{ label: 'Vitesse sauvegarde', value: backupSpeed, color: '#3B82F6', unit: 'MB/s' },
|
|
{ label: 'Compression', value: compressionRatio, color: '#10B981', unit: '%' },
|
|
{ label: 'Bande passante', value: networkBandwidth, color: '#F59E0B', unit: 'Mbps' },
|
|
{ label: 'Vérifications', value: retentionCompliance, color: '#8B5CF6', unit: '%' }
|
|
];
|
|
|
|
// Fonctions utilitaires
|
|
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 formatDateTime = (date: Date) => {
|
|
return new Date(date).toLocaleString('fr-FR');
|
|
};
|
|
|
|
const formatDate = (date: Date) => {
|
|
return new Date(date).toLocaleDateString('fr-FR');
|
|
};
|
|
|
|
// Effets et chargement des données
|
|
useEffect(() => {
|
|
loadBackupData();
|
|
loadKPIData();
|
|
loadAnalytics();
|
|
loadAlertsData();
|
|
setCloudProviders(cloudProvidersData);
|
|
setAlertsData(alertsExample);
|
|
setPerformanceMetrics(performanceData);
|
|
|
|
if (autoRefresh) {
|
|
const interval = setInterval(() => {
|
|
loadBackupData();
|
|
loadKPIData();
|
|
updateRealtimeMetrics();
|
|
}, refreshInterval * 1000);
|
|
return () => clearInterval(interval);
|
|
}
|
|
}, [autoRefresh, refreshInterval]);
|
|
|
|
const loadBackupData = async () => {
|
|
setLoading(true);
|
|
// TODO: Remplacer par un appel API réel
|
|
const mockBackups: Backup[] = [];
|
|
setBackups(mockBackups);
|
|
|
|
const mockSchedules: BackupSchedule[] = [];
|
|
setSchedules(mockSchedules);
|
|
|
|
const mockRestorePoints: RestorePoint[] = [];
|
|
setRestorePoints(mockRestorePoints);
|
|
|
|
setLoading(false);
|
|
};
|
|
|
|
const loadKPIData = async () => {
|
|
// TODO: Remplacer par un appel API réel
|
|
const mockKPI = {
|
|
totalBackups: 156,
|
|
successRate: 94.2,
|
|
storageUsed: 2.4,
|
|
avgDuration: 4.2,
|
|
lastBackup: new Date(),
|
|
scheduledCount: 8
|
|
};
|
|
setKpiData(mockKPI);
|
|
};
|
|
|
|
const loadAnalytics = async () => {
|
|
// TODO: Remplacer par un appel API réel pour les analytics
|
|
const mockChartData = {
|
|
labels: ['Jan', 'Fév', 'Mar', 'Avr', 'Mai', 'Jun'],
|
|
datasets: [
|
|
{
|
|
label: 'Sauvegardes réussies',
|
|
data: [25, 32, 28, 45, 38, 42],
|
|
backgroundColor: '#10B981',
|
|
borderColor: '#047857'
|
|
},
|
|
{
|
|
label: 'Sauvegardes échouées',
|
|
data: [2, 1, 3, 2, 1, 2],
|
|
backgroundColor: '#EF4444',
|
|
borderColor: '#DC2626'
|
|
}
|
|
]
|
|
};
|
|
setChartData(mockChartData);
|
|
};
|
|
|
|
const loadAlertsData = async () => {
|
|
// TODO: Remplacer par un appel API réel
|
|
setAlertsData(alertsExample);
|
|
};
|
|
|
|
const updateRealtimeMetrics = () => {
|
|
setStorageQuota(Math.floor(Math.random() * 15) + 80);
|
|
setSystemHealth(Math.floor(Math.random() * 10) + 85);
|
|
setBackupSpeed(Math.floor(Math.random() * 50) + 30);
|
|
setNetworkBandwidth(Math.floor(Math.random() * 100) + 200);
|
|
setActiveConnections(Math.floor(Math.random() * 20) + 5);
|
|
};
|
|
|
|
// Actions principales
|
|
const startManualBackup = () => {
|
|
setBackupInProgress(true);
|
|
setProgress(0);
|
|
|
|
const interval = setInterval(() => {
|
|
setProgress(prev => {
|
|
if (prev >= 100) {
|
|
clearInterval(interval);
|
|
setBackupInProgress(false);
|
|
toast.current?.show({
|
|
severity: 'success',
|
|
summary: 'Sauvegarde terminée',
|
|
detail: 'La sauvegarde manuelle a été créée avec succès',
|
|
life: 3000
|
|
});
|
|
loadBackupData();
|
|
return 100;
|
|
}
|
|
return prev + 5;
|
|
});
|
|
}, 200);
|
|
};
|
|
|
|
const cleanupOldBackups = () => {
|
|
confirmDialog({
|
|
message: 'Supprimer toutes les sauvegardes de plus de 90 jours ?',
|
|
header: 'Nettoyage automatique',
|
|
icon: 'pi pi-exclamation-triangle',
|
|
acceptLabel: 'Nettoyer',
|
|
rejectLabel: 'Annuler',
|
|
acceptClassName: 'p-button-warning',
|
|
accept: () => {
|
|
setBlocked(true);
|
|
setTimeout(() => {
|
|
setBlocked(false);
|
|
toast.current?.show({
|
|
severity: 'success',
|
|
summary: 'Nettoyage terminé',
|
|
detail: '12 anciennes sauvegardes supprimées - 4.2 GB libérés',
|
|
life: 5000
|
|
});
|
|
}, 3000);
|
|
}
|
|
});
|
|
};
|
|
|
|
const syncToCloud = () => {
|
|
setBlocked(true);
|
|
let progress = 0;
|
|
const interval = setInterval(() => {
|
|
progress += 10;
|
|
if (progress >= 100) {
|
|
clearInterval(interval);
|
|
setBlocked(false);
|
|
toast.current?.show({
|
|
severity: 'success',
|
|
summary: 'Synchronisation terminée',
|
|
detail: 'Toutes les sauvegardes ont été synchronisées avec le cloud',
|
|
life: 3000
|
|
});
|
|
}
|
|
}, 300);
|
|
};
|
|
|
|
const verifyBackups = () => {
|
|
setBlocked(true);
|
|
setTimeout(() => {
|
|
setBlocked(false);
|
|
toast.current?.show({
|
|
severity: 'success',
|
|
summary: 'Vérification terminée',
|
|
detail: 'Toutes les sauvegardes ont été vérifiées avec succès',
|
|
life: 3000
|
|
});
|
|
}, 5000);
|
|
};
|
|
|
|
// Composants de rendu
|
|
const renderKPIDashboard = () => (
|
|
<div className="grid mb-4">
|
|
<div className="col-12 md:col-2">
|
|
<Card className="bg-gradient-to-r from-blue-500 to-blue-600 text-white text-center">
|
|
<div className="flex flex-column align-items-center">
|
|
<Knob
|
|
value={kpiData.successRate}
|
|
readOnly
|
|
size={60}
|
|
strokeWidth={8}
|
|
valueColor="white"
|
|
rangeColor="rgba(255,255,255,0.3)"
|
|
/>
|
|
<div className="text-sm mt-2 opacity-90">Taux de succès</div>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
|
|
<div className="col-12 md:col-2">
|
|
<Card className="bg-gradient-to-r from-green-500 to-green-600 text-white text-center">
|
|
<div className="flex flex-column align-items-center">
|
|
<div className="text-2xl font-bold">{kpiData.totalBackups}</div>
|
|
<div className="text-sm opacity-90">Sauvegardes</div>
|
|
<Avatar icon="pi pi-database" size="large" className="mt-2 bg-white-alpha-20" />
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
|
|
<div className="col-12 md:col-2">
|
|
<Card className="bg-gradient-to-r from-purple-500 to-purple-600 text-white text-center">
|
|
<div className="flex flex-column align-items-center">
|
|
<div className="text-2xl font-bold">{kpiData.storageUsed}TB</div>
|
|
<div className="text-sm opacity-90">Stockage utilisé</div>
|
|
<ProgressBar
|
|
value={storageQuota}
|
|
className="mt-2 w-full"
|
|
style={{ height: '4px' }}
|
|
/>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
|
|
<div className="col-12 md:col-2">
|
|
<Card className="bg-gradient-to-r from-orange-500 to-orange-600 text-white text-center">
|
|
<div className="flex flex-column align-items-center">
|
|
<div className="text-2xl font-bold">{kpiData.avgDuration}min</div>
|
|
<div className="text-sm opacity-90">Durée moyenne</div>
|
|
<Avatar icon="pi pi-clock" size="large" className="mt-2 bg-white-alpha-20" />
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
|
|
<div className="col-12 md:col-2">
|
|
<Card className="bg-gradient-to-r from-teal-500 to-teal-600 text-white text-center">
|
|
<div className="flex flex-column align-items-center">
|
|
<div className="text-2xl font-bold">{kpiData.scheduledCount}</div>
|
|
<div className="text-sm opacity-90">Planifiées actives</div>
|
|
<Avatar icon="pi pi-calendar" size="large" className="mt-2 bg-white-alpha-20" />
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
|
|
<div className="col-12 md:col-2">
|
|
<Card className="bg-gradient-to-r from-pink-500 to-pink-600 text-white text-center">
|
|
<div className="flex flex-column align-items-center">
|
|
<Knob
|
|
value={systemHealth}
|
|
readOnly
|
|
size={60}
|
|
strokeWidth={8}
|
|
valueColor="white"
|
|
rangeColor="rgba(255,255,255,0.3)"
|
|
/>
|
|
<div className="text-sm mt-2 opacity-90">Santé système</div>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
const renderRealtimeMonitoring = () => (
|
|
<Card title="Monitoring temps réel" className="mb-4">
|
|
<div className="grid">
|
|
<div className="col-12 md:col-8">
|
|
<div className="mb-4">
|
|
{performanceMetrics.map((metric, index) => (
|
|
<div key={index} className="mb-3">
|
|
<div className="flex justify-content-between mb-1">
|
|
<span className="text-sm font-medium">{metric.label}</span>
|
|
<span className="text-sm font-bold">{metric.value}{metric.unit}</span>
|
|
</div>
|
|
<ProgressBar
|
|
value={metric.value}
|
|
className="h-1rem"
|
|
style={{ '--p-progressbar-value-bg': metric.color } as any}
|
|
/>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
<Chart
|
|
type="line"
|
|
data={{
|
|
labels: ['00:00', '04:00', '08:00', '12:00', '16:00', '20:00'],
|
|
datasets: [{
|
|
label: 'Vitesse de sauvegarde (MB/s)',
|
|
data: [20, 35, 60, 45, 70, 30],
|
|
borderColor: '#3B82F6',
|
|
backgroundColor: 'rgba(59, 130, 245, 0.1)',
|
|
tension: 0.4
|
|
}]
|
|
}}
|
|
options={{
|
|
responsive: true,
|
|
plugins: {
|
|
legend: { position: 'top' }
|
|
},
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true
|
|
}
|
|
}
|
|
}}
|
|
/>
|
|
</div>
|
|
|
|
<div className="col-12 md:col-4">
|
|
<div className="grid">
|
|
<div className="col-12">
|
|
<div className="bg-blue-50 p-3 border-round text-center">
|
|
<i className="pi pi-server text-blue-500 text-2xl mb-2" />
|
|
<div className="text-lg font-bold text-blue-700">{activeConnections}</div>
|
|
<div className="text-sm text-600">Connexions actives</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="col-12">
|
|
<div className="bg-green-50 p-3 border-round text-center">
|
|
<i className="pi pi-shield text-green-500 text-2xl mb-2" />
|
|
<div className="text-lg font-bold text-green-700">AES-{encryptionStrength}</div>
|
|
<div className="text-sm text-600">Chiffrement</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="col-12">
|
|
<div className="bg-purple-50 p-3 border-round text-center">
|
|
<i className="pi pi-cloud text-purple-500 text-2xl mb-2" />
|
|
<div className="text-lg font-bold text-purple-700">
|
|
{replicationStatus === 'active' ? 'Actif' : 'Inactif'}
|
|
</div>
|
|
<div className="text-sm text-600">Réplication</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
);
|
|
|
|
const renderCloudProviders = () => (
|
|
<Card title="Fournisseurs Cloud" className="mb-4">
|
|
<DataView
|
|
value={cloudProviders}
|
|
itemTemplate={(provider) => (
|
|
<div className="col-12 md:col-6 lg:col-3 p-2">
|
|
<Card className="h-full">
|
|
<div className="flex align-items-center justify-content-between mb-3">
|
|
<div className="flex align-items-center">
|
|
<Avatar
|
|
icon={provider.icon}
|
|
className="mr-2"
|
|
style={{ backgroundColor: '#DEE2E6', color: '#495057' }}
|
|
/>
|
|
<span className="font-bold">{provider.name}</span>
|
|
</div>
|
|
<Tag
|
|
value={provider.status}
|
|
severity={
|
|
provider.status === 'connected' ? 'success' :
|
|
provider.status === 'error' ? 'danger' : 'info'
|
|
}
|
|
/>
|
|
</div>
|
|
|
|
<div className="mb-3">
|
|
<div className="flex justify-content-between text-sm mb-1">
|
|
<span>Utilisation</span>
|
|
<span>{provider.used}GB / {provider.quota}GB</span>
|
|
</div>
|
|
<ProgressBar
|
|
value={(provider.used / provider.quota) * 100}
|
|
className="h-1rem"
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex gap-2">
|
|
<Button
|
|
label="Configurer"
|
|
size="small"
|
|
className="flex-1"
|
|
/>
|
|
<Button
|
|
icon="pi pi-cog"
|
|
size="small"
|
|
outlined
|
|
/>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
)}
|
|
layout="grid"
|
|
/>
|
|
</Card>
|
|
);
|
|
|
|
const renderAlertsPanel = () => (
|
|
<Card title="Alertes et notifications" className="mb-4">
|
|
<div className="flex align-items-center justify-content-between mb-3">
|
|
<div className="flex align-items-center gap-2">
|
|
<Badge value={alertsData.length} severity="warning" />
|
|
<span>alertes actives</span>
|
|
</div>
|
|
<Button
|
|
label="Tout marquer comme lu"
|
|
size="small"
|
|
text
|
|
/>
|
|
</div>
|
|
|
|
<Timeline
|
|
value={alertsData}
|
|
content={(item) => (
|
|
<div className="flex align-items-center">
|
|
<Badge
|
|
value={item.type}
|
|
severity={item.severity}
|
|
className="mr-2"
|
|
/>
|
|
<div className="flex-1">
|
|
<div className="font-medium">{item.message}</div>
|
|
<div className="text-sm text-600">{item.time.toLocaleTimeString('fr-FR')}</div>
|
|
</div>
|
|
<Button
|
|
icon="pi pi-times"
|
|
size="small"
|
|
text
|
|
rounded
|
|
/>
|
|
</div>
|
|
)}
|
|
className="w-full"
|
|
/>
|
|
</Card>
|
|
);
|
|
|
|
const renderAdvancedToolbar = () => (
|
|
<div className="flex flex-wrap align-items-center justify-content-between gap-3 p-3 bg-gradient-to-r from-indigo-500 to-purple-600 text-white border-round mb-4">
|
|
<div className="flex align-items-center gap-3">
|
|
<Avatar icon="pi pi-database" className="bg-white-alpha-20" style={{ color: 'white' }} />
|
|
<div>
|
|
<div className="text-xl font-bold">Centre de Sauvegarde Avancé</div>
|
|
<div className="text-sm opacity-90">
|
|
{kpiData.totalBackups} sauvegardes | {kpiData.successRate}% de réussite | {kpiData.storageUsed}TB utilisés
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex align-items-center gap-2">
|
|
<SelectButton
|
|
value={viewMode}
|
|
options={viewModeOptions}
|
|
onChange={(e) => setViewMode(e.value)}
|
|
optionLabel="label"
|
|
optionValue="value"
|
|
className="view-mode-selector"
|
|
/>
|
|
|
|
<div className="flex align-items-center gap-2 bg-white-alpha-20 px-3 py-1 border-round">
|
|
<i className="pi pi-wifi" />
|
|
<span className="text-sm">Auto-refresh</span>
|
|
<ToggleButton
|
|
checked={autoRefresh}
|
|
onChange={(e) => setAutoRefresh(e.value)}
|
|
onIcon="pi pi-pause"
|
|
offIcon="pi pi-play"
|
|
className="ml-2"
|
|
/>
|
|
</div>
|
|
|
|
<Button
|
|
icon="pi pi-chart-line"
|
|
rounded
|
|
text
|
|
onClick={() => setAnalyticsDialog(true)}
|
|
tooltip="Analytics avancées"
|
|
className="text-white"
|
|
/>
|
|
|
|
<Button
|
|
icon="pi pi-desktop"
|
|
rounded
|
|
text
|
|
onClick={() => setTerminalVisible(true)}
|
|
tooltip="Terminal"
|
|
className="text-white"
|
|
/>
|
|
|
|
<Button
|
|
icon="pi pi-cog"
|
|
rounded
|
|
text
|
|
onClick={() => setSidebarVisible(true)}
|
|
tooltip="Configuration"
|
|
className="text-white"
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
const renderBackupList = () => (
|
|
<Card title="Liste des sauvegardes">
|
|
<DataTable
|
|
ref={dt}
|
|
value={backups}
|
|
paginator
|
|
rows={10}
|
|
rowsPerPageOptions={[10, 25, 50]}
|
|
loading={loading}
|
|
globalFilter={globalFilter}
|
|
emptyMessage="Aucune sauvegarde trouvée"
|
|
responsiveLayout="stack"
|
|
breakpoint="768px"
|
|
>
|
|
<Column field="nom" header="Nom" sortable />
|
|
<Column field="type" header="Type" sortable />
|
|
<Column field="statut" header="Statut" sortable />
|
|
<Column field="dateCreation" header="Date" sortable />
|
|
<Column field="taille" header="Taille" sortable />
|
|
<Column header="Actions" />
|
|
</DataTable>
|
|
</Card>
|
|
);
|
|
|
|
const renderSchedules = () => (
|
|
<Card title="Planifications">
|
|
<DataTable
|
|
value={schedules}
|
|
emptyMessage="Aucune planification configurée"
|
|
>
|
|
<Column field="nom" header="Nom" />
|
|
<Column field="frequence" header="Fréquence" />
|
|
<Column field="actif" header="Actif" />
|
|
<Column header="Actions" />
|
|
</DataTable>
|
|
</Card>
|
|
);
|
|
|
|
return (
|
|
<div className="backup-management-system">
|
|
<BlockUI blocked={blocked}>
|
|
<Toast ref={toast} />
|
|
<Messages ref={messages} />
|
|
<ConfirmDialog />
|
|
|
|
{renderAdvancedToolbar()}
|
|
|
|
{(backupInProgress || restoreInProgress || exportProgress > 0) && (
|
|
<Card className="mb-4">
|
|
<div className="grid">
|
|
{backupInProgress && (
|
|
<div className="col-12 md:col-4">
|
|
<div className="flex align-items-center gap-3">
|
|
<i className="pi pi-spin pi-save text-blue-500" />
|
|
<div className="flex-1">
|
|
<p className="m-0 font-bold">Sauvegarde en cours...</p>
|
|
<ProgressBar value={progress} className="mt-2" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{restoreInProgress && (
|
|
<div className="col-12 md:col-4">
|
|
<div className="flex align-items-center gap-3">
|
|
<i className="pi pi-spin pi-replay text-green-500" />
|
|
<div className="flex-1">
|
|
<p className="m-0 font-bold">Restauration en cours...</p>
|
|
<ProgressBar value={restoreProgress} className="mt-2" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{exportProgress > 0 && exportProgress < 100 && (
|
|
<div className="col-12 md:col-4">
|
|
<div className="flex align-items-center gap-3">
|
|
<i className="pi pi-spin pi-download text-purple-500" />
|
|
<div className="flex-1">
|
|
<p className="m-0 font-bold">Export en cours...</p>
|
|
<ProgressBar value={exportProgress} className="mt-2" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</Card>
|
|
)}
|
|
|
|
<TabView activeIndex={activeIndex} onTabChange={(e) => setActiveIndex(e.index)}>
|
|
<TabPanel header="Vue d'ensemble" leftIcon="pi pi-home mr-2">
|
|
{renderKPIDashboard()}
|
|
{renderRealtimeMonitoring()}
|
|
{renderAlertsPanel()}
|
|
</TabPanel>
|
|
|
|
<TabPanel header="Sauvegardes" leftIcon="pi pi-database mr-2">
|
|
{renderBackupList()}
|
|
</TabPanel>
|
|
|
|
<TabPanel header="Planifications" leftIcon="pi pi-calendar mr-2">
|
|
{renderSchedules()}
|
|
</TabPanel>
|
|
|
|
<TabPanel header="Cloud" leftIcon="pi pi-cloud mr-2">
|
|
{renderCloudProviders()}
|
|
</TabPanel>
|
|
|
|
<TabPanel header="Analytics" leftIcon="pi pi-chart-bar mr-2">
|
|
<div className="grid">
|
|
<div className="col-12 md:col-8">
|
|
<Card title="Tendances des sauvegardes">
|
|
<Chart type="bar" data={chartData} className="w-full" />
|
|
</Card>
|
|
</div>
|
|
|
|
<div className="col-12 md:col-4">
|
|
<Card title="Répartition par destination">
|
|
<Chart
|
|
type="doughnut"
|
|
data={{
|
|
labels: ['Local', 'AWS S3', 'Google Cloud', 'Azure'],
|
|
datasets: [{
|
|
data: [40, 30, 20, 10],
|
|
backgroundColor: ['#3B82F6', '#10B981', '#F59E0B', '#8B5CF6']
|
|
}]
|
|
}}
|
|
options={{
|
|
plugins: {
|
|
legend: { position: 'bottom' }
|
|
}
|
|
}}
|
|
/>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
</TabPanel>
|
|
|
|
<TabPanel header="Sécurité" leftIcon="pi pi-shield mr-2">
|
|
<div className="grid">
|
|
<div className="col-12 md:col-6">
|
|
<Card title="Chiffrement et sécurité">
|
|
<Fieldset legend="Configuration du chiffrement">
|
|
<div className="field">
|
|
<label>Algorithme de chiffrement</label>
|
|
<Dropdown
|
|
value="AES-256"
|
|
options={[
|
|
{ label: 'AES-128', value: 'AES-128' },
|
|
{ label: 'AES-256', value: 'AES-256' },
|
|
{ label: 'RSA-2048', value: 'RSA-2048' }
|
|
]}
|
|
className="w-full"
|
|
/>
|
|
</div>
|
|
|
|
<div className="field">
|
|
<div className="flex align-items-center gap-2">
|
|
<InputSwitch checked={verificationEnabled} onChange={(e) => setVerificationEnabled(e.value)} />
|
|
<label>Vérification d'intégrité</label>
|
|
</div>
|
|
</div>
|
|
</Fieldset>
|
|
</Card>
|
|
</div>
|
|
|
|
<div className="col-12 md:col-6">
|
|
<Card title="Audit Trail">
|
|
<Timeline
|
|
value={[
|
|
{ event: 'Sauvegarde créée', time: new Date(), user: 'Admin' },
|
|
{ event: 'Accès à la sauvegarde', time: new Date(), user: 'User1' },
|
|
{ event: 'Restauration initialisée', time: new Date(), user: 'Admin' }
|
|
]}
|
|
content={(item) => (
|
|
<div>
|
|
<div className="font-bold">{item.event}</div>
|
|
<div className="text-sm text-600">{item.user} - {item.time.toLocaleTimeString()}</div>
|
|
</div>
|
|
)}
|
|
/>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
</TabPanel>
|
|
|
|
<TabPanel header="Maintenance" leftIcon="pi pi-wrench mr-2">
|
|
<Splitter style={{ height: '500px' }}>
|
|
<SplitterPanel size={30}>
|
|
<ScrollPanel style={{ width: '100%', height: '100%' }}>
|
|
<div className="p-3">
|
|
<h6>Actions de maintenance</h6>
|
|
<Menu
|
|
model={[
|
|
{ label: 'Nettoyage automatique', icon: 'pi pi-trash', command: cleanupOldBackups },
|
|
{ label: 'Vérification intégrité', icon: 'pi pi-shield', command: verifyBackups },
|
|
{ label: 'Optimisation stockage', icon: 'pi pi-cog' },
|
|
{ label: 'Synchronisation cloud', icon: 'pi pi-cloud-upload', command: syncToCloud },
|
|
{ label: 'Rapport de santé', icon: 'pi pi-chart-line' },
|
|
{ label: 'Réindexation', icon: 'pi pi-refresh' }
|
|
]}
|
|
/>
|
|
</div>
|
|
</ScrollPanel>
|
|
</SplitterPanel>
|
|
|
|
<SplitterPanel size={70}>
|
|
<ScrollPanel style={{ width: '100%', height: '100%' }}>
|
|
<div className="p-3">
|
|
<Card title="Console de maintenance">
|
|
<Terminal
|
|
ref={terminal}
|
|
welcomeMessage="Console de maintenance BTpXpress - Tapez 'help' pour les commandes disponibles"
|
|
prompt="backup $"
|
|
className="bg-gray-900 text-white"
|
|
/>
|
|
</Card>
|
|
</div>
|
|
</ScrollPanel>
|
|
</SplitterPanel>
|
|
</Splitter>
|
|
</TabPanel>
|
|
</TabView>
|
|
|
|
<SpeedDial
|
|
model={speedDialActions}
|
|
radius={80}
|
|
type="semi-circle"
|
|
direction="up-left"
|
|
style={{ left: 'calc(50% - 2rem)', bottom: 0 }}
|
|
buttonClassName="p-button-help"
|
|
maskClassName="bg-black-alpha-30"
|
|
/>
|
|
|
|
<Sidebar
|
|
visible={sidebarVisible}
|
|
onHide={() => setSidebarVisible(false)}
|
|
position="right"
|
|
className="w-25rem"
|
|
>
|
|
<h3>Configuration avancée</h3>
|
|
|
|
<Accordion multiple>
|
|
<AccordionTab header="Paramètres généraux">
|
|
<div className="field">
|
|
<label>Mode d'affichage</label>
|
|
<SelectButton
|
|
value={viewMode}
|
|
options={viewModeOptions}
|
|
onChange={(e) => setViewMode(e.value)}
|
|
optionLabel="label"
|
|
optionValue="value"
|
|
className="w-full mt-2"
|
|
/>
|
|
</div>
|
|
|
|
<div className="field">
|
|
<div className="flex align-items-center gap-2">
|
|
<InputSwitch
|
|
checked={autoRefresh}
|
|
onChange={(e) => setAutoRefresh(e.value)}
|
|
/>
|
|
<label>Actualisation automatique</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="field">
|
|
<label>Intervalle (secondes)</label>
|
|
<Slider
|
|
value={refreshInterval}
|
|
onChange={(e) => setRefreshInterval(e.value as number)}
|
|
min={10}
|
|
max={300}
|
|
step={10}
|
|
className="w-full mt-2"
|
|
/>
|
|
</div>
|
|
</AccordionTab>
|
|
|
|
<AccordionTab header="Compression et optimisation">
|
|
<div className="field">
|
|
<label>Niveau de compression</label>
|
|
<Knob
|
|
value={compressionLevel}
|
|
onChange={(e) => setCompressionLevel(e.value)}
|
|
min={1}
|
|
max={9}
|
|
size={80}
|
|
className="mt-2"
|
|
/>
|
|
</div>
|
|
|
|
<div className="field">
|
|
<div className="flex align-items-center gap-2">
|
|
<InputSwitch
|
|
checked={incrementalBackup}
|
|
onChange={(e) => setIncrementalBackup(e.value)}
|
|
/>
|
|
<label>Sauvegarde incrémentale</label>
|
|
</div>
|
|
</div>
|
|
</AccordionTab>
|
|
|
|
<AccordionTab header="Notifications">
|
|
<div className="field">
|
|
<label>Niveau de notification</label>
|
|
<Dropdown
|
|
value={notificationLevel}
|
|
options={notificationOptions}
|
|
onChange={(e) => setNotificationLevel(e.value)}
|
|
className="w-full"
|
|
/>
|
|
</div>
|
|
|
|
<div className="field">
|
|
<div className="flex align-items-center gap-2">
|
|
<InputSwitch
|
|
checked={cloudSync}
|
|
onChange={(e) => setCloudSync(e.value)}
|
|
/>
|
|
<label>Synchronisation cloud</label>
|
|
</div>
|
|
</div>
|
|
</AccordionTab>
|
|
</Accordion>
|
|
</Sidebar>
|
|
|
|
<Dialog
|
|
header="Assistant de configuration"
|
|
visible={wizardMode}
|
|
onHide={() => setWizardMode(false)}
|
|
style={{ width: '70vw' }}
|
|
modal
|
|
>
|
|
<Steps
|
|
model={wizardSteps}
|
|
activeIndex={wizardStep}
|
|
onSelect={(e) => setWizardStep(e.index)}
|
|
readOnly={false}
|
|
className="mb-4"
|
|
/>
|
|
|
|
<div className="wizard-content">
|
|
{wizardStep === 0 && (
|
|
<div>
|
|
<h4>Configuration de base</h4>
|
|
<p>Définissons les paramètres fondamentaux de votre système de sauvegarde.</p>
|
|
<div className="field">
|
|
<label>Fréquence par défaut</label>
|
|
<Dropdown options={frequenceOptions} className="w-full" />
|
|
</div>
|
|
</div>
|
|
)}
|
|
{wizardStep === 4 && (
|
|
<div className="text-center">
|
|
<i className="pi pi-check-circle text-green-500 text-6xl mb-3" />
|
|
<h4>Configuration terminée !</h4>
|
|
<p>Votre système de sauvegarde est maintenant configuré et opérationnel.</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex justify-content-between mt-4">
|
|
<Button
|
|
label="Précédent"
|
|
icon="pi pi-chevron-left"
|
|
outlined
|
|
disabled={wizardStep === 0}
|
|
onClick={() => setWizardStep(wizardStep - 1)}
|
|
/>
|
|
<Button
|
|
label={wizardStep === wizardSteps.length - 1 ? 'Terminer' : 'Suivant'}
|
|
icon={wizardStep === wizardSteps.length - 1 ? 'pi pi-check' : 'pi pi-chevron-right'}
|
|
iconPos="right"
|
|
onClick={() => {
|
|
if (wizardStep === wizardSteps.length - 1) {
|
|
setWizardMode(false);
|
|
toast.current?.show({
|
|
severity: 'success',
|
|
summary: 'Configuration terminée',
|
|
detail: 'Votre système de sauvegarde a été configuré avec succès',
|
|
life: 5000
|
|
});
|
|
} else {
|
|
setWizardStep(wizardStep + 1);
|
|
}
|
|
}}
|
|
/>
|
|
</div>
|
|
</Dialog>
|
|
|
|
<ScrollTop threshold={600} className="w-3rem h-3rem border-round bg-primary" />
|
|
</BlockUI>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default SauvegardePage;
|
|
|
|
// Styles CSS pour l'interface ultra-professionnelle de sauvegarde
|
|
const customStyles = `
|
|
.backup-management-system .p-tabview-nav {
|
|
background: linear-gradient(90deg, #4f46e5 0%, #7c3aed 100%);
|
|
border-radius: 10px 10px 0 0;
|
|
}
|
|
|
|
.backup-management-system .p-tabview-nav li .p-tabview-nav-link {
|
|
color: white;
|
|
border: none;
|
|
}
|
|
|
|
.backup-management-system .p-tabview-nav li.p-highlight .p-tabview-nav-link {
|
|
background: rgba(255, 255, 255, 0.2);
|
|
border-radius: 8px;
|
|
margin: 4px;
|
|
}
|
|
|
|
.view-mode-selector .p-button {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
color: white;
|
|
}
|
|
|
|
.view-mode-selector .p-button.p-highlight {
|
|
background: rgba(255, 255, 255, 0.3);
|
|
}
|
|
|
|
.hover\\:shadow-lg:hover {
|
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
|
transition: box-shadow 0.3s ease;
|
|
}
|
|
|
|
.transition-shadow {
|
|
transition: box-shadow 0.3s ease;
|
|
}
|
|
|
|
.bg-gradient-to-r {
|
|
background: linear-gradient(90deg, var(--tw-gradient-stops));
|
|
}
|
|
|
|
.from-blue-500 {
|
|
--tw-gradient-from: #3b82f6;
|
|
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to, rgba(59, 130, 246, 0));
|
|
}
|
|
|
|
.to-blue-600 {
|
|
--tw-gradient-to: #2563eb;
|
|
}
|
|
|
|
.from-green-500 {
|
|
--tw-gradient-from: #22c55e;
|
|
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to, rgba(34, 197, 94, 0));
|
|
}
|
|
|
|
.to-green-600 {
|
|
--tw-gradient-to: #16a34a;
|
|
}
|
|
|
|
.from-purple-500 {
|
|
--tw-gradient-from: #a855f7;
|
|
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to, rgba(168, 85, 247, 0));
|
|
}
|
|
|
|
.to-purple-600 {
|
|
--tw-gradient-to: #9333ea;
|
|
}
|
|
|
|
.from-orange-500 {
|
|
--tw-gradient-from: #f97316;
|
|
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to, rgba(249, 115, 22, 0));
|
|
}
|
|
|
|
.to-orange-600 {
|
|
--tw-gradient-to: #ea580c;
|
|
}
|
|
|
|
.from-teal-500 {
|
|
--tw-gradient-from: #14b8a6;
|
|
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to, rgba(20, 184, 166, 0));
|
|
}
|
|
|
|
.to-teal-600 {
|
|
--tw-gradient-to: #0d9488;
|
|
}
|
|
|
|
.from-pink-500 {
|
|
--tw-gradient-from: #ec4899;
|
|
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to, rgba(236, 72, 153, 0));
|
|
}
|
|
|
|
.to-pink-600 {
|
|
--tw-gradient-to: #db2777;
|
|
}
|
|
|
|
.from-indigo-500 {
|
|
--tw-gradient-from: #6366f1;
|
|
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to, rgba(99, 102, 241, 0));
|
|
}
|
|
|
|
.to-purple-600 {
|
|
--tw-gradient-to: #9333ea;
|
|
}
|
|
|
|
.bg-white-alpha-20 {
|
|
background: rgba(255, 255, 255, 0.2);
|
|
}
|
|
|
|
.wizard-content {
|
|
min-height: 300px;
|
|
}
|
|
`;
|
|
|
|
// Injection du style dans le document
|
|
if (typeof document !== 'undefined') {
|
|
const style = document.createElement('style');
|
|
style.textContent = customStyles;
|
|
document.head.appendChild(style);
|
|
}
|
|
|
|
|