/** * ========================================================================= * PLATEFORME ECO-PANNEAU.FR - VERSION 1.0.0 * Interface Administrateur - Onglet Tableau de bord (Supervision) * ========================================================================= */ const decodeHTML = (html) => { const txt = document.createElement("textarea"); txt.innerHTML = html; return txt.value; }; window.AdminDashboardTab = ({ data, refreshData, showToast, setActiveTab, openLocalModal, openLocalDialog, closeCurrentLayer, activeModal, activeDialog, targetId }) => { const { useState, useEffect, useMemo } = React; // FACTORISATION : Utilisation du Hook global de gestion des modales par URL const { activeModal: urlModal, activeDialog: urlDialog, openModal, openDialog, closeCurrentLayer: urlCloseLayer } = window.useUrlModal(); const routerActiveModal = activeModal || urlModal; const routerActiveDialog = activeDialog || urlDialog; const routerOpenModal = openLocalModal || openModal; const routerOpenDialog = openLocalDialog || openDialog; const routerCloseLayer = closeCurrentLayer || urlCloseLayer; const [isSaving, setIsSaving] = useState(false); const [diagnostics, setDiagnostics] = useState(null); const [logs, setLogs] = useState(''); const [backups, setBackups] = useState([]); const [codeStats, setCodeStats] = useState(null); const [sortConfig, setSortConfig] = useState({ key: 'lines', direction: 'desc' }); const [isLoadingBackups, setIsLoadingBackups] = useState(false); const [showAllBackups, setShowAllBackups] = useState(false); const [sysSummary, setSysSummary] = useState({ loaded: false, diagStatus: 'emerald', backupStatus: 'emerald', logStatus: 'emerald', php: '', ram: '', db: '', disk: '', backupsCount: 0, logDesc: '', backupDesc: '' }); const [pwdRequestData, setPwdRequestData] = useState(null); const [confirmConfig, setConfirmConfig] = useState(null); const [backupLabel, setBackupLabel] = useState(''); // FACTORISATION : Récupération sécurisée et propre des icônes const { ActivityIcon, UsersIcon, BuildingIcon, EyeIcon, MessageSquareIcon, TerminalIcon, ArchiveIcon, SaveIcon, DownloadIcon, RefreshCwIcon, Trash2Icon, LoaderIcon, FileDigitIcon, EditIcon } = window.getIcons(); // Composants globaux const StatCard = window.StatCard || (() => null); const Modal = window.Modal || (() => null); const PasswordPromptModal = window.PasswordPromptModal || (() => null); const ConfirmModal = window.ConfirmModal || (() => null); const Button = window.Button; const fetchTelemetry = async (silent = false) => { try { const cacheBuster = '&_t=' + Date.now(); const fetchOpts = silent ? { cache: 'no-store', headers: { 'x-silent': '1' } } : { cache: 'no-store' }; const [diagRes, backRes, logRes] = await Promise.all([ fetch(window.ECO_CONFIG.apiBaseUrl + 'system/diagnostics' + cacheBuster + (silent ? '&silent=1' : ''), fetchOpts).then(r => r.json()), fetch(window.ECO_CONFIG.apiBaseUrl + 'system/backups' + cacheBuster + (silent ? '&silent=1' : ''), fetchOpts).then(r => r.json()), fetch(window.ECO_CONFIG.apiBaseUrl + 'system/logs' + cacheBuster + (silent ? '&silent=1' : ''), fetchOpts).then(r => r.json()) ]); if (diagRes.status === 'success') { const dbDetail = diagRes.data.find(d => d.id === 'db')?.detail || '0 ms'; const diskDetail = diagRes.data.find(d => d.id === 'disk')?.detail || '0 GB'; const dbVal = parseFloat(dbDetail); const diskVal = parseFloat(diskDetail); let diagStatus = 'emerald'; if (dbVal > 500 || diskVal < 1) diagStatus = 'red'; else if (dbVal > 200 || diskVal < 5) diagStatus = 'amber'; const backupsData = backRes.data || []; const backupsCount = backupsData.length; let backupStatus = 'emerald'; let backupDesc = `${backupsCount} archive(s)`; if (backupsCount === 0) { backupStatus = 'red'; backupDesc = "Alerte : Aucune sauvegarde"; } else { const latestBackup = backupsData.reduce((max, b) => b.date > max.date ? b : max, backupsData[0]); const hoursSinceLast = (Date.now() - latestBackup.date) / (1000 * 60 * 60); if (hoursSinceLast > 48) { backupStatus = 'red'; backupDesc = `Critique : Retard de ${Math.floor(hoursSinceLast/24)} jours`; } else if (hoursSinceLast > 24) { backupStatus = 'amber'; backupDesc = `Attention : Retard de ${Math.floor(hoursSinceLast)}h`; } else { backupStatus = 'emerald'; backupDesc = `À jour (${backupsCount} archives)`; } } const logsContent = logRes.data?.logs || ''; let logStatus = 'emerald'; let logDesc = 'Journal vierge (sain)'; const lowerLogs = logsContent.toLowerCase(); if (lowerLogs.includes('fatal error') || lowerLogs.includes('exception')) { logStatus = 'red'; logDesc = 'Erreurs critiques détectées !'; } else if (logsContent.length > 5) { logStatus = 'amber'; logDesc = 'Avertissements enregistrés'; } setSysSummary({ loaded: true, diagStatus, backupStatus, logStatus, php: diagRes.data.find(d => d.id === 'php')?.detail || 'OK', ram: diagRes.data.find(d => d.id === 'ram')?.detail || 'OK', db: dbDetail, disk: diskDetail, backupsCount, logDesc, backupDesc }); } } catch(e) {} }; useEffect(() => { fetchTelemetry(); let telemetryInterval; let currentDelay = 10000; let lastActivityTime = Date.now(); let isTabVisible = !document.hidden; const performTelemetrySync = async () => { if (!isTabVisible) return; await fetchTelemetry(true); const idleTime = Date.now() - lastActivityTime; if (idleTime > 120000) currentDelay = 60000; else if (idleTime > 30000) currentDelay = 30000; else currentDelay = 10000; scheduleNextPoll(currentDelay); }; const scheduleNextPoll = (delay) => { clearTimeout(telemetryInterval); if (isTabVisible) telemetryInterval = setTimeout(performTelemetrySync, delay); }; const handleVisibilityChange = () => { isTabVisible = !document.hidden; if (isTabVisible) { lastActivityTime = Date.now(); currentDelay = 10000; performTelemetrySync(); } else { clearTimeout(telemetryInterval); } }; const handleUserInteraction = () => { if (currentDelay > 10000) { lastActivityTime = Date.now(); currentDelay = 10000; scheduleNextPoll(10000); } else { lastActivityTime = Date.now(); } }; document.addEventListener('visibilitychange', handleVisibilityChange); document.addEventListener('click', handleUserInteraction); document.addEventListener('keydown', handleUserInteraction); scheduleNextPoll(currentDelay); return () => { clearTimeout(telemetryInterval); document.removeEventListener('visibilitychange', handleVisibilityChange); document.removeEventListener('click', handleUserInteraction); document.removeEventListener('keydown', handleUserInteraction); }; }, []); useEffect(() => { let interval; if (routerActiveModal === 'diagnostics') { interval = setInterval(() => { fetch(window.ECO_CONFIG.apiBaseUrl + 'system/diagnostics&silent=1&_t=' + Date.now(), { cache: 'no-store', headers: { 'x-silent': '1' } }) .then(r => r.json()) .then(d => { if (d.status === 'success') setDiagnostics(d.data); }).catch(()=>{}); }, 1000); } return () => clearInterval(interval); }, [routerActiveModal]); const fetchDiagnostics = async () => { routerOpenModal('diagnostics'); setDiagnostics(null); const d = await window.apiFetch('system/diagnostics&_t=' + Date.now(), { method: 'GET' }); if (d) setDiagnostics(d.data); }; const fetchLogs = async () => { routerOpenModal('logs'); setLogs('Chargement...'); const d = await window.apiFetch('system/logs&_t=' + Date.now(), { method: 'GET' }); setLogs(d?.data?.logs || 'Aucun journal trouvé.'); }; const fetchCodeStats = async () => { routerOpenModal('code_stats'); setCodeStats(null); const d = await window.apiFetch('system/code_stats&_t=' + Date.now(), { method: 'GET' }); if (d) setCodeStats(d.data); }; const triggerCron = () => { setConfirmConfig({ title: "Exécution manuelle du CRON", message: "Voulez-vous vraiment lancer les tâches de fond (Nettoyage BDD, relances clients, purges des brouillons inactifs, rotation des sauvegardes) ? Cette opération peut prendre quelques instants.", confirmText: "Oui, exécuter", cancelText: "Annuler", onConfirm: async () => { routerCloseLayer(); const d = await window.apiFetch('system/cron_cleanup', { method: 'GET', setLoading: setIsSaving, successMessage: "Tâches planifiées exécutées avec succès." }); if (d) fetchTelemetry(); } }); routerOpenDialog('confirm'); }; const clearLogs = async () => { const d = await window.apiFetch('system/logs/clear'); if (d) { fetchLogs(); fetchTelemetry(); } }; const loadBackups = async () => { routerOpenModal('backups'); setShowAllBackups(false); const d = await window.apiFetch('system/backups&_t=' + Date.now(), { method: 'GET', setLoading: setIsLoadingBackups }); if (d) setBackups(d.data); }; const createBackup = () => { setBackupLabel(''); routerOpenDialog('backup_prompt'); }; const restoreBackup = (filename) => { setPwdRequestData({ title: "Restauration système", desc: "ATTENTION CRITIQUE : La restauration écrasera la base de données actuelle. Saisissez le mot de passe admin :", onConfirm: async (pwd) => { const d = await window.apiFetch('system/backups/restore', { body: { filename, pwd }, setLoading: setIsSaving, successMessage: "Restauration réussie. L'application va recharger." }); if (d) { setTimeout(() => window.location.reload(), 2000); } else { routerCloseLayer(); } } }); routerOpenDialog('pwd_request'); }; const deleteBackup = (filename) => { setPwdRequestData({ title: "Suppression d'archive", desc: "Saisissez le mot de passe admin pour supprimer définitivement cette sauvegarde :", onConfirm: async (pwd) => { const d = await window.apiFetch('system/backups/delete', { body: { filename, pwd }, setLoading: setIsSaving }); if (d) { loadBackups(); fetchTelemetry(); routerCloseLayer(); } } }); routerOpenDialog('pwd_request'); }; const requestSort = (key) => { let direction = 'asc'; if (sortConfig && sortConfig.key === key && sortConfig.direction === 'desc') { direction = 'asc'; } else { direction = 'desc'; } setSortConfig({ key, direction }); }; const sortedCodeStatsDetails = useMemo(() => { if (!codeStats || !codeStats.details) return []; let sortableItems = [...codeStats.details]; if (sortConfig !== null) { sortableItems.sort((a, b) => { if (a[sortConfig.key] < b[sortConfig.key]) { return sortConfig.direction === 'asc' ? -1 : 1; } if (a[sortConfig.key] > b[sortConfig.key]) { return sortConfig.direction === 'asc' ? 1 : -1; } return 0; }); } return sortableItems; }, [codeStats, sortConfig]); const getColorClass = (status) => { if (!sysSummary.loaded) return 'text-slate-500 border-slate-200 bg-slate-50'; if (status === 'red') return 'text-red-700 border-red-200 bg-red-50 hover:border-red-400'; if (status === 'amber') return 'text-amber-700 border-amber-200 bg-amber-50 hover:border-amber-400'; return 'text-emerald-700 border-emerald-200 bg-emerald-50 hover:border-emerald-400'; }; const clients = data.clients || []; const panneaux = data.panneaux || []; // CALCULS DES STATISTIQUES POUR L'ADMIN const activePanneaux = panneaux.filter(p => p.status === 'Actif' && p.id !== 'demo-panneau'); const draftPanneaux = panneaux.filter(p => p.status === 'Brouillon' && p.id !== 'demo-panneau'); const mrr = activePanneaux.filter(p => p.offerType === 'rental').reduce((sum, p) => sum + (p.currentRate || 0), 0); const interactions = data.interactions || []; const recentMessages = [...interactions].filter(m => m.target === 'Admin').reverse().slice(0, 5); return ( <>
Vue d'ensemble de la plateforme eco-panneau.fr.
{displayAuthor}
{m.detail.replace(/\[ATTACHMENT:[^\]]+\]/g, '[Pièce jointe]').replace(/<[^>]*>?/gm, '')}
{m.panneauId === 'CONTACT_PUBLIC' ? 'ORIGINE : Nous contacter' : 'ORIGINE : Espace client (Support)'}
Aucune interaction récente.
}Sauvegardes système
{sysSummary.loaded ? sysSummary.backupDesc : 'Vérification...'}
Diagnostics système
{sysSummary.loaded ? `RAM: ${sysSummary.ram} • DB: ${sysSummary.db}` : 'Analyse en cours...'}
Journaux d'erreurs (PHP)
{sysSummary.loaded ? sysSummary.logDesc : 'Chargement...'}
Statistiques du code source
Analyse des fichiers du projet
Exécuter le CRON manuellement
Purges, relances et sauvegardes automatiques
Analyse en cours...
Fichiers inclus
{codeStats.total_files.toLocaleString('fr-FR')}
Total Lignes
{codeStats.total_lines.toLocaleString('fr-FR')}
Total Caractères
{codeStats.total_chars.toLocaleString('fr-FR')}
Analyse complète en cours...
{d.label}
{d.status}
{logs}
Chargement en cours...
{decodeHTML(b.label)}
{new Date(b.date).toLocaleString('fr-FR')} • {b.size}
Aucune sauvegarde trouvée.
} > )}