/** * ========================================================================= * 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 ( <>

Supervision

Vue d'ensemble de la plateforme eco-panneau.fr.

} variant="success" /> } variant="info" onClick={() => setActiveTab('clients')} /> } variant="indigo" onClick={() => setActiveTab('panneaux')} /> } variant="secondary" /> } variant="warning" />

Derniers messages

setActiveTab('messages')} className="text-xs font-bold text-emerald-600 hover:underline cursor-pointer">Voir tout
{recentMessages.map((m, i) => { let displayAuthor = m.author; if (m.panneauId === 'CONTACT_PUBLIC') { if (m.author === 'Admin') displayAuthor = 'Service client'; else if (m.author === 'Système' || m.author === 'Systeme') displayAuthor = 'Système'; else displayAuthor = `Visiteur ${m.author}`; } else { if (m.author === 'Admin') displayAuthor = 'Support'; else if (m.author === 'Client') { if (m.panneauId.startsWith('SUPPORT_')) { const cUid = m.panneauId.split('_')[1]; const c = clients.find(cl => cl.id === cUid); if (c && c.name) displayAuthor = c.name; } else displayAuthor = 'Client'; } else if (m.author === 'Système' || m.author === 'Systeme') displayAuthor = 'Système'; } return (
{ let tId = m.panneauId; if (m.panneauId === 'CONTACT_PUBLIC') { tId = `CONTACT_PUBLIC_${m.author}`; } else if (m.panneauId.startsWith('SUPPORT_')) { tId = m.panneauId; } else if (m.author !== 'Client' && m.author !== 'Admin') { tId = `${m.panneauId}_${m.author}`; } else if (m.target !== 'Client' && m.target !== 'Admin' && m.target) { tId = `${m.panneauId}_${m.target}`; } const u = new URL(window.location); if (tId) u.searchParams.set('chat_id', tId); window.history.replaceState({}, '', u); setActiveTab('messages'); }}>

{displayAuthor}

{new Date(String(m.created_at || '').replace(' ', 'T')).toLocaleDateString('fr-FR')}

{m.detail.replace(/\[ATTACHMENT:[^\]]+\]/g, '[Pièce jointe]').replace(/<[^>]*>?/gm, '')}

{m.panneauId === 'CONTACT_PUBLIC' ? 'ORIGINE : Nous contacter' : 'ORIGINE : Espace client (Support)'}

); })} {recentMessages.length === 0 &&

Aucune interaction récente.

}

Serveur et maintenance

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

{/* MODALES SYSTÈME (EN DEHORS DE LA DIV ANIMÉE) */} {routerActiveModal === 'code_stats' && Modal && ( {!codeStats ? (

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')}

requestSort('path')}> Fichier {sortConfig?.key === 'path' ? (sortConfig.direction === 'asc' ? '↑' : '↓') : ''}
requestSort('lines')}> Lignes {sortConfig?.key === 'lines' ? (sortConfig.direction === 'asc' ? '↑' : '↓') : ''}
requestSort('chars')}> Caractères {sortConfig?.key === 'chars' ? (sortConfig.direction === 'asc' ? '↑' : '↓') : ''}
{sortedCodeStatsDetails.map((stat, i) => (
{stat.path}
{stat.lines.toLocaleString('fr-FR')}
{stat.chars.toLocaleString('fr-FR')}
))}
)}
)} {routerActiveModal === 'diagnostics' && Modal && ( {!diagnostics ?

Analyse complète en cours...

: (
{diagnostics.map(d => (

{d.label}

{d.status}

{d.detail}
))}
)}
)} {routerActiveModal === 'logs' && Modal && (
{logs}
)} {routerActiveModal === 'backups' && Modal && ( {if (!isSaving && !isLoadingBackups) routerCloseLayer();}} preventClose={isSaving || isLoadingBackups} zIndex="z-[250]">
{isLoadingBackups ? (

Chargement en cours...

) : ( <> {backups.slice(0, showAllBackups ? backups.length : 5).map(b => (
{b.label === 'Automatique' ? 'CRON' : 'MANUEL'}

{decodeHTML(b.label)}

{new Date(b.date).toLocaleString('fr-FR')} • {b.size}

))} {backups.length > 5 && !showAllBackups && ( )} {backups.length === 0 &&

Aucune sauvegarde trouvée.

} )}
)} {/* MODALE DÉDIÉE À LA SAUVEGARDE MANUELLE */} {routerActiveDialog === 'backup_prompt' && Modal && (
{ e.preventDefault(); const d = await window.apiFetch('system/backups/create', { body: { label: backupLabel }, setLoading: setIsSaving, successMessage: "Sauvegarde créée avec succès." }); if (d) { await loadBackups(); fetchTelemetry(); routerCloseLayer(); setBackupLabel(''); } }} className="space-y-6">

Saisissez un marqueur personnalisé pour identifier cette sauvegarde manuelle (Optionnel) :

setBackupLabel(e.target.value)} placeholder="Ex. : Avant la mise à jour..." className="w-full border-2 border-slate-200 rounded-xl p-3 focus:border-emerald-500 outline-none transition font-bold" autoFocus />
)} {routerActiveDialog === 'pwd_request' && pwdRequestData && PasswordPromptModal && ( )} {routerActiveDialog === 'confirm' && confirmConfig && ConfirmModal && } ); }; /* EOF ========== [_react/_admin_dashboard.jsx] */