From 37f3a07c6e51ade3c35af156ec131cc80b5bc38d Mon Sep 17 00:00:00 2001 From: Pontoporeia Date: Tue, 24 Mar 2026 15:55:48 +0100 Subject: [PATCH] admin: merge status + logs into unified system.php with instant tabs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the separate /admin/status.php and /admin/logs.php pages with a single /admin/system.php page organised around a tab bar. - system.php — top-level tab bar: 'Statut' + one tab per log file (nginx accès, nginx erreurs, PHP-FPM). Switching tabs is a plain href (?tab=…) so no JS required for navigation; the lines-selector SELECT triggers a location change on 'change' for instant reload without a submit button. - Status tab preserves all existing service cards, PHP runtime grid, and disk-usage bar from the old status.php. - Log tabs preserve line-count selector, file metadata bar, and per-line colour coding from the old logs.php. - New: copy-to-clipboard button on each log output block (Clipboard API with textarea execCommand fallback). - status.php / logs.php replaced with 301 redirect stubs so existing bookmarks and links keep working. - templates/admin/head.php: 'Statut' + 'Journaux' nav items replaced with a single 'Système' item; active state covers all three page names for redirect compatibility. --- TODO.md | 4 +- public/admin/logs.php | 281 +--------------- public/admin/status.php | 352 +------------------- public/admin/system.php | 698 +++++++++++++++++++++++++++++++++++++++ templates/admin/head.php | 3 +- 5 files changed, 707 insertions(+), 631 deletions(-) create mode 100644 public/admin/system.php diff --git a/TODO.md b/TODO.md index 30b883d..a2354b6 100644 --- a/TODO.md +++ b/TODO.md @@ -334,5 +334,5 @@ Goal: rename the tables and column to the canonical M2M pattern (`tags`, `thesis - [x] Add server log viewer in admin panel (tail nginx error/access logs via SSH or log endpoint) - [ ] Add nginx config deploy flow to admin panel (upload `scripts/deploy-server.sh`, run remotely) - [x] Add admin user management UI — password change/set for PHP auth layer (`public/admin/account.php` + `actions/account.php`; "Compte" nav link; account CSS) -- [ ] Merge `status.php` and `logs.php` into a single `system.php` page; remove "Statut" and "Journaux" nav links, add single "Système" link; preserve all existing content in their respective sections -- [ ] Rework logs UI: replace the select-then-click-Afficher flow with instant tabs (nginx access, nginx error, php-fpm); switching tabs loads the selected log immediately without a form submit; add a copy-to-clipboard button per log view +- [x] Merge `status.php` and `logs.php` into a single `system.php` page; remove "Statut" and "Journaux" nav links, add single "Système" link; preserve all existing content in their respective sections +- [x] Rework logs UI: replace the select-then-click-Afficher flow with instant tabs (nginx access, nginx error, php-fpm); switching tabs loads the selected log immediately without a form submit; add a copy-to-clipboard button per log view diff --git a/public/admin/logs.php b/public/admin/logs.php index 3ef0859..3bac2a6 100644 --- a/public/admin/logs.php +++ b/public/admin/logs.php @@ -1,279 +1,4 @@ ['label' => 'nginx — erreurs', 'path' => '/var/log/nginx/posterg_error.log'], - 'nginx_access' => ['label' => 'nginx — accès', 'path' => '/var/log/nginx/posterg_access.log'], - 'php_error' => ['label' => 'PHP-FPM — erreurs', 'path' => '/var/log/php8.4-fpm.log'], -]; - -const ALLOWED_LINES = [50, 100, 200, 500]; - -// ── Input validation ────────────────────────────────────────────────────────── -$selectedKey = $_GET['log'] ?? 'nginx_error'; -$selectedN = isset($_GET['n']) ? (int) $_GET['n'] : 100; - -if (!array_key_exists($selectedKey, LOG_FILES)) { - $selectedKey = 'nginx_error'; -} -if (!in_array($selectedN, ALLOWED_LINES, true)) { - $selectedN = 100; -} - -$logDef = LOG_FILES[$selectedKey]; -$logPath = $logDef['path']; - -// ── Read log ────────────────────────────────────────────────────────────────── -$lines = null; // null = unavailable, [] = empty, [...] = lines -$readError = null; - -if (!function_exists('exec')) { - $readError = "exec() est désactivé sur ce serveur."; -} elseif (!file_exists($logPath)) { - $readError = "Fichier introuvable : " . htmlspecialchars($logPath); -} elseif (!is_readable($logPath)) { - $readError = "Fichier non lisible (permissions insuffisantes) : " . htmlspecialchars($logPath); -} else { - $output = []; - $rc = 0; - // tail -n N ensures we never load the whole file into memory - exec('tail -n ' . intval($selectedN) . ' ' . escapeshellarg($logPath) . ' 2>/dev/null', $output, $rc); - if ($rc !== 0) { - $readError = "Erreur lors de la lecture du fichier journal."; - } else { - // Reverse so newest lines appear first - $lines = array_reverse($output); - } -} - -// ── File metadata ───────────────────────────────────────────────────────────── -$fileMeta = null; -if (file_exists($logPath)) { - $size = filesize($logPath); - $mtime = filemtime($logPath); - $fileMeta = [ - 'size' => $size > 1048576 - ? number_format($size / 1048576, 2) . ' MB' - : number_format($size / 1024, 1) . ' KB', - 'mtime' => date('d/m/Y H:i:s', $mtime), - ]; -} - -// ── Log-level classifier (for nginx error log coloring) ─────────────────────── -/** - * Returns a CSS class based on the log level token in a log line. - * nginx error lines look like: 2024/01/15 12:34:56 [error] 1234#1234: … - * nginx access lines: 1.2.3.4 - - [15/Jan/2024:12:34:56 +0000] "GET / HTTP/1.1" 200 … - */ -function logLineClass(string $line): string { - // nginx error log: [crit], [error], [warn], [notice], [info], [debug] - if (preg_match('/\[(crit|emerg|alert)\]/', $line)) return 'log-crit'; - if (preg_match('/\[error\]/', $line)) return 'log-error'; - if (preg_match('/\[warn\]/', $line)) return 'log-warn'; - if (preg_match('/\[notice\]/', $line)) return 'log-notice'; - // access log: highlight HTTP 4xx / 5xx - if (preg_match('/" [45]\d\d /', $line)) return 'log-error'; - if (preg_match('/" 3\d\d /', $line)) return 'log-notice'; - return ''; -} - -require_once APP_ROOT . '/templates/admin/head.php'; -?> - - - -
-

Journaux serveur

- -

- Affiché le — - Rafraîchir -

- - -
- - - - - - - 0): ?> - ligne(s) - -
- - - -
- - - -
- - - - -
- Journaux non disponibles -
- -
- En environnement de développement, les logs nginx ne sont pas disponibles. - Cette page est pleinement fonctionnelle sur le serveur de production. -
- -
- - -
Le fichier journal est vide.
- - -
- $line): ?> - - -
- - -
- - +// Redirects legacy /admin/logs.php → /admin/system.php?tab=nginx_access +header('Location: /admin/system.php?tab=nginx_access', true, 301); +exit; diff --git a/public/admin/status.php b/public/admin/status.php index b066a4b..a75231e 100644 --- a/public/admin/status.php +++ b/public/admin/status.php @@ -1,350 +1,4 @@ /dev/null', $output, $rc); - return $rc === 0 ? trim(implode("\n", $output)) : null; -} - -/** - * Check whether a systemd unit is active. - * Returns 'active', 'inactive', 'failed', or null when unavailable. - */ -function systemdStatus(string $unit): ?string { - $raw = safeExec("systemctl is-active " . escapeshellarg($unit)); - if ($raw === null) return null; - return in_array($raw, ['active', 'inactive', 'failed', 'activating', 'deactivating'], true) - ? $raw : 'unknown'; -} - -/** - * Perform a local HTTP HEAD/GET and return [http_code, latency_ms] or null. - */ -function localHttpCheck(string $url): ?array { - if (!function_exists('curl_init')) return null; - $ch = curl_init($url); - curl_setopt_array($ch, [ - CURLOPT_RETURNTRANSFER => true, - CURLOPT_NOBODY => true, - CURLOPT_TIMEOUT => 5, - CURLOPT_CONNECTTIMEOUT => 3, - CURLOPT_SSL_VERIFYPEER => false, - CURLOPT_SSL_VERIFYHOST => 0, - CURLOPT_FOLLOWLOCATION => true, - CURLOPT_MAXREDIRS => 3, - ]); - $start = microtime(true); - curl_exec($ch); - $ms = (int) round((microtime(true) - $start) * 1000); - $code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); - // curl_close() is a no-op since PHP 8.0 and deprecated in PHP 8.5; omitted. - unset($ch); - return $code > 0 ? [$code, $ms] : null; -} - -// ── data collection ─────────────────────────────────────────────────────────── - -$checks = []; - -// 1. nginx -$nginxStatus = systemdStatus('nginx'); -$nginxVersion = safeExec('nginx -v 2>&1 | head -1'); -$checks['nginx'] = [ - 'label' => 'nginx', - 'status' => $nginxStatus, - 'detail' => $nginxVersion, -]; - -// 2. php-fpm (try versioned names: php8.2-fpm, php8.3-fpm, php-fpm) -$phpFpmStatus = null; -$phpFpmUnit = null; -foreach (['php8.3-fpm', 'php8.2-fpm', 'php8.1-fpm', 'php-fpm'] as $unit) { - $s = systemdStatus($unit); - if ($s !== null && $s !== 'unknown') { - $phpFpmStatus = $s; - $phpFpmUnit = $unit; - break; - } -} -$checks['php_fpm'] = [ - 'label' => 'php-fpm' . ($phpFpmUnit ? " ($phpFpmUnit)" : ''), - 'status' => $phpFpmStatus, - 'detail' => null, -]; - -// 3. Site HTTP ping -$siteUrl = (isset($_SERVER['HTTPS']) ? 'https' : 'http') . '://' . ($_SERVER['HTTP_HOST'] ?? 'localhost') . '/'; -$httpResult = localHttpCheck($siteUrl); -$checks['site_http'] = [ - 'label' => 'Site HTTP', - 'status' => $httpResult !== null ? ($httpResult[0] < 500 ? 'active' : 'failed') : null, - 'detail' => $httpResult !== null ? "HTTP {$httpResult[0]} — {$httpResult[1]} ms" : 'curl indisponible', -]; - -// 4. Database file -require_once APP_ROOT . '/src/Database.php'; -$dbPath = APP_ROOT . '/storage/test.db'; -$dbExists = file_exists($dbPath); -$dbWritable = $dbExists && is_writable($dbPath); -$dbSizeBytes = $dbExists ? filesize($dbPath) : null; -$dbSizeHuman = $dbSizeBytes !== null - ? ($dbSizeBytes > 1048576 - ? number_format($dbSizeBytes / 1048576, 1) . ' MB' - : number_format($dbSizeBytes / 1024, 1) . ' KB') - : 'N/A'; - -// Quick DB sanity: count rows -$dbRowCount = null; -if ($dbExists) { - try { - $db = new Database(); - $stmt = $db->getConnection()->query("SELECT COUNT(*) FROM theses"); - $dbRowCount = (int) $stmt->fetchColumn(); - } catch (Throwable $e) { - $dbRowCount = null; - } -} - -$checks['database'] = [ - 'label' => 'Base de données SQLite', - 'status' => $dbExists ? ($dbWritable ? 'active' : 'inactive') : 'failed', - 'detail' => $dbExists - ? ($dbRowCount !== null ? "$dbRowCount thèses — $dbSizeHuman" : "Lecture impossible — $dbSizeHuman") - : 'Fichier introuvable', -]; - -// 5. Storage directory -$storageDir = APP_ROOT . '/storage'; -$storageWritable = is_dir($storageDir) && is_writable($storageDir); -$bannersDir = $storageDir . '/banners'; -$coversDir = $storageDir . '/covers'; -$checks['storage'] = [ - 'label' => 'Répertoire storage', - 'status' => $storageWritable ? 'active' : ($storageDir ? 'inactive' : 'failed'), - 'detail' => $storageWritable - ? implode(' · ', array_filter([ - is_dir($bannersDir) ? ('banners/ ' . count(array_diff(scandir($bannersDir), ['.','..'])) . ' fichiers') : null, - is_dir($coversDir) ? ('covers/ ' . count(array_diff(scandir($coversDir), ['.','..'])) . ' fichiers') : null, - ])) - : 'Non accessible en écriture', -]; - -// 6. Maintenance mode -$maintenanceOn = file_exists(APP_ROOT . '/storage/maintenance.flag'); -$checks['maintenance'] = [ - 'label' => 'Mode maintenance', - 'status' => $maintenanceOn ? 'warn' : 'active', - 'detail' => $maintenanceOn ? 'Activé — site public inaccessible' : 'Désactivé', -]; - -// 7. PHP info -$phpInfo = [ - 'version' => PHP_VERSION, - 'sapi' => PHP_SAPI, - 'memory_limit' => ini_get('memory_limit'), - 'upload_max' => ini_get('upload_max_filesize'), - 'post_max' => ini_get('post_max_size'), - 'max_exec' => ini_get('max_execution_time') . 's', -]; - -// 8. Disk usage (partition containing APP_ROOT) -$diskTotal = disk_total_space(APP_ROOT); -$diskFree = disk_free_space(APP_ROOT); -$diskUsed = $diskTotal - $diskFree; -function humanBytes(int $bytes): string { - if ($bytes > 1073741824) return number_format($bytes / 1073741824, 1) . ' GB'; - if ($bytes > 1048576) return number_format($bytes / 1048576, 1) . ' MB'; - return number_format($bytes / 1024, 1) . ' KB'; -} -$diskPct = $diskTotal > 0 ? (int) round($diskUsed / $diskTotal * 100) : 0; - -// ── label helpers ───────────────────────────────────────────────────────────── - -function statusLabel(string $status): string { - return match($status) { - 'active' => '● En ligne', - 'inactive' => '○ Inactif', - 'failed' => '✕ Erreur', - 'warn' => '⚠ Attention', - default => '? Inconnu', - }; -} -function statusClass(string $status): string { - return match($status) { - 'active' => 'status-ok', - 'inactive' => 'status-warn', - 'warn' => 'status-warn', - 'failed' => 'status-err', - default => 'status-unknown', - }; -} - -require_once APP_ROOT . '/templates/admin/head.php'; -?> - - - -
-

Statut serveur

- -

- Vérification effectuée le — - Rafraîchir -

- - -

Services

-
- - -
-
- - -
- -
- -
- -
- - -

Environnement PHP

-
- $val): ?> -
-
-
-
- -
- - -

Espace disque

-
-
-
- utilisé (%) - libre / -
-
- -
- - +// Redirects legacy /admin/status.php → /admin/system.php?tab=status +header('Location: /admin/system.php?tab=status', true, 301); +exit; diff --git a/public/admin/system.php b/public/admin/system.php new file mode 100644 index 0000000..0723050 --- /dev/null +++ b/public/admin/system.php @@ -0,0 +1,698 @@ +/dev/null', $output, $rc); + return $rc === 0 ? trim(implode("\n", $output)) : null; +} + +function systemdStatus(string $unit): ?string { + $raw = safeExec("systemctl is-active " . escapeshellarg($unit)); + if ($raw === null) return null; + return in_array($raw, ['active', 'inactive', 'failed', 'activating', 'deactivating'], true) + ? $raw : 'unknown'; +} + +function localHttpCheck(string $url): ?array { + if (!function_exists('curl_init')) return null; + $ch = curl_init($url); + curl_setopt_array($ch, [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_NOBODY => true, + CURLOPT_TIMEOUT => 5, + CURLOPT_CONNECTTIMEOUT => 3, + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_SSL_VERIFYHOST => 0, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_MAXREDIRS => 3, + ]); + $start = microtime(true); + curl_exec($ch); + $ms = (int) round((microtime(true) - $start) * 1000); + $code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); + unset($ch); + return $code > 0 ? [$code, $ms] : null; +} + +function humanBytes(int $bytes): string { + if ($bytes > 1073741824) return number_format($bytes / 1073741824, 1) . ' GB'; + if ($bytes > 1048576) return number_format($bytes / 1048576, 1) . ' MB'; + return number_format($bytes / 1024, 1) . ' KB'; +} + +function statusLabel(string $status): string { + return match($status) { + 'active' => '● En ligne', + 'inactive' => '○ Inactif', + 'failed' => '✕ Erreur', + 'warn' => '⚠ Attention', + default => '? Inconnu', + }; +} + +function statusClass(string $status): string { + return match($status) { + 'active' => 'status-ok', + 'inactive' => 'status-warn', + 'warn' => 'status-warn', + 'failed' => 'status-err', + default => 'status-unknown', + }; +} + +$checks = []; + +// nginx +$nginxStatus = systemdStatus('nginx'); +$nginxVersion = safeExec('nginx -v 2>&1 | head -1'); +$checks['nginx'] = [ + 'label' => 'nginx', + 'status' => $nginxStatus, + 'detail' => $nginxVersion, +]; + +// php-fpm +$phpFpmStatus = null; +$phpFpmUnit = null; +foreach (['php8.3-fpm', 'php8.2-fpm', 'php8.1-fpm', 'php-fpm'] as $unit) { + $s = systemdStatus($unit); + if ($s !== null && $s !== 'unknown') { + $phpFpmStatus = $s; + $phpFpmUnit = $unit; + break; + } +} +$checks['php_fpm'] = [ + 'label' => 'php-fpm' . ($phpFpmUnit ? " ($phpFpmUnit)" : ''), + 'status' => $phpFpmStatus, + 'detail' => null, +]; + +// Site HTTP ping +$siteUrl = (isset($_SERVER['HTTPS']) ? 'https' : 'http') . '://' . ($_SERVER['HTTP_HOST'] ?? 'localhost') . '/'; +$httpResult = localHttpCheck($siteUrl); +$checks['site_http'] = [ + 'label' => 'Site HTTP', + 'status' => $httpResult !== null ? ($httpResult[0] < 500 ? 'active' : 'failed') : null, + 'detail' => $httpResult !== null ? "HTTP {$httpResult[0]} — {$httpResult[1]} ms" : 'curl indisponible', +]; + +// Database +require_once APP_ROOT . '/src/Database.php'; +$dbPath = APP_ROOT . '/storage/test.db'; +$dbExists = file_exists($dbPath); +$dbWritable = $dbExists && is_writable($dbPath); +$dbSizeBytes = $dbExists ? filesize($dbPath) : null; +$dbSizeHuman = $dbSizeBytes !== null + ? ($dbSizeBytes > 1048576 + ? number_format($dbSizeBytes / 1048576, 1) . ' MB' + : number_format($dbSizeBytes / 1024, 1) . ' KB') + : 'N/A'; + +$dbRowCount = null; +if ($dbExists) { + try { + $db = new Database(); + $stmt = $db->getConnection()->query("SELECT COUNT(*) FROM theses"); + $dbRowCount = (int) $stmt->fetchColumn(); + } catch (Throwable $e) { + $dbRowCount = null; + } +} +$checks['database'] = [ + 'label' => 'Base de données SQLite', + 'status' => $dbExists ? ($dbWritable ? 'active' : 'inactive') : 'failed', + 'detail' => $dbExists + ? ($dbRowCount !== null ? "$dbRowCount thèses — $dbSizeHuman" : "Lecture impossible — $dbSizeHuman") + : 'Fichier introuvable', +]; + +// Storage directory +$storageDir = APP_ROOT . '/storage'; +$storageWritable = is_dir($storageDir) && is_writable($storageDir); +$bannersDir = $storageDir . '/banners'; +$coversDir = $storageDir . '/covers'; +$checks['storage'] = [ + 'label' => 'Répertoire storage', + 'status' => $storageWritable ? 'active' : ($storageDir ? 'inactive' : 'failed'), + 'detail' => $storageWritable + ? implode(' · ', array_filter([ + is_dir($bannersDir) ? ('banners/ ' . count(array_diff(scandir($bannersDir), ['.','..'])) . ' fichiers') : null, + is_dir($coversDir) ? ('covers/ ' . count(array_diff(scandir($coversDir), ['.','..'])) . ' fichiers') : null, + ])) + : 'Non accessible en écriture', +]; + +// Maintenance mode +$maintenanceOn = file_exists(APP_ROOT . '/storage/maintenance.flag'); +$checks['maintenance'] = [ + 'label' => 'Mode maintenance', + 'status' => $maintenanceOn ? 'warn' : 'active', + 'detail' => $maintenanceOn ? 'Activé — site public inaccessible' : 'Désactivé', +]; + +// PHP info +$phpInfo = [ + 'version' => PHP_VERSION, + 'sapi' => PHP_SAPI, + 'memory_limit' => ini_get('memory_limit'), + 'upload_max' => ini_get('upload_max_filesize'), + 'post_max' => ini_get('post_max_size'), + 'max_exec' => ini_get('max_execution_time') . 's', +]; + +// Disk +$diskTotal = disk_total_space(APP_ROOT); +$diskFree = disk_free_space(APP_ROOT); +$diskUsed = $diskTotal - $diskFree; +$diskPct = $diskTotal > 0 ? (int) round($diskUsed / $diskTotal * 100) : 0; + +// ═══════════════════════════════════════════════════════════════════════════════ +// SECTION 2 — LOGS DATA +// ═══════════════════════════════════════════════════════════════════════════════ + +const LOG_FILES = [ + 'nginx_access' => ['label' => 'nginx — accès', 'path' => '/var/log/nginx/posterg_access.log'], + 'nginx_error' => ['label' => 'nginx — erreurs', 'path' => '/var/log/nginx/posterg_error.log'], + 'php_error' => ['label' => 'PHP-FPM — erreurs', 'path' => '/var/log/php8.4-fpm.log'], +]; + +const ALLOWED_LINES = [50, 100, 200, 500]; + +// Active tab: 'status' or a log key +$activeTab = $_GET['tab'] ?? 'nginx_access'; +if ($activeTab === 'status') { + $activeTab = 'status'; +} elseif (!array_key_exists($activeTab, LOG_FILES)) { + $activeTab = 'nginx_access'; +} + +$selectedN = isset($_GET['n']) ? (int) $_GET['n'] : 100; +if (!in_array($selectedN, ALLOWED_LINES, true)) { + $selectedN = 100; +} + +/** + * Read the tail of a log file. Returns null on error, [] if empty, or string[]. + * $errorMsg is set on failure. + */ +function readLogTail(string $logPath, int $n, ?string &$errorMsg): ?array { + $errorMsg = null; + if (!function_exists('exec')) { + $errorMsg = "exec() est désactivé sur ce serveur."; + return null; + } + if (!file_exists($logPath)) { + $errorMsg = "Fichier introuvable : " . htmlspecialchars($logPath); + return null; + } + if (!is_readable($logPath)) { + $errorMsg = "Fichier non lisible (permissions insuffisantes) : " . htmlspecialchars($logPath); + return null; + } + $output = []; + $rc = 0; + exec('tail -n ' . intval($n) . ' ' . escapeshellarg($logPath) . ' 2>/dev/null', $output, $rc); + if ($rc !== 0) { + $errorMsg = "Erreur lors de la lecture du fichier journal."; + return null; + } + return array_reverse($output); // newest first +} + +function logLineClass(string $line): string { + if (preg_match('/\[(crit|emerg|alert)\]/', $line)) return 'log-crit'; + if (preg_match('/\[error\]/', $line)) return 'log-error'; + if (preg_match('/\[warn\]/', $line)) return 'log-warn'; + if (preg_match('/\[notice\]/', $line)) return 'log-notice'; + if (preg_match('/" [45]\d\d /', $line)) return 'log-error'; + if (preg_match('/" 3\d\d /', $line)) return 'log-notice'; + return ''; +} + +// Pre-load the active log tab if it's a log key +$logLines = null; +$logError = null; +$logFileMeta = null; + +if ($activeTab !== 'status') { + $logPath = LOG_FILES[$activeTab]['path']; + $logLines = readLogTail($logPath, $selectedN, $logError); + if (file_exists($logPath)) { + $sz = filesize($logPath); + $logFileMeta = [ + 'size' => $sz > 1048576 + ? number_format($sz / 1048576, 2) . ' MB' + : number_format($sz / 1024, 1) . ' KB', + 'mtime' => date('d/m/Y H:i:s', filemtime($logPath)), + ]; + } +} + +require_once APP_ROOT . '/templates/admin/head.php'; +?> + + + +
+

Système

+ +

+ Affiché le — + Rafraîchir +

+ + + + + + + +

Services

+
+ + +
+
+ + +
+ +
+ +
+ +
+ +

Environnement PHP

+
+ $val): ?> +
+
+
+
+ +
+ +

Espace disque

+
+
+
+ utilisé (%) + libre / +
+
+ + + + + +
+ + + 0): ?> + ligne(s) + +
+ + + +
+ + + +
+ + + + +
+ Journaux non disponibles +
+ +
+ En environnement de développement, les logs nginx ne sont pas disponibles. + Cette page est pleinement fonctionnelle sur le serveur de production. +
+ +
+ + +
Le fichier journal est vide.
+ + +
+ + $line): ?> + + +
+ + + + +
+ + + + diff --git a/templates/admin/head.php b/templates/admin/head.php index 309662c..74e287e 100644 --- a/templates/admin/head.php +++ b/templates/admin/head.php @@ -29,8 +29,7 @@ Importer une liste de TFE Pages statiques Mots-clés - Statut - Journaux + Système Compte Modifier