$sz > 1048576
- ? number_format($sz / 1048576, 2) . ' MB'
- : number_format($sz / 1024, 1) . ' KB',
- 'mtime' => date('d/m/Y H:i:s', filemtime($logPath)),
- ];
- }
+ // ── Log tab ────────────────────────────────────────────────────────────
+ $data = $_controller->getLogData($tab, $n);
+ $logLines = $data['lines'];
+ $logError = $data['error'];
+ $logMeta = $data['meta'];
?>
-
+
@@ -186,7 +106,7 @@ if ($tab === 'nginx_config') {
- = htmlspecialchars($logPath) ?>
+ = htmlspecialchars($logMeta['path']) ?>
= $logMeta['size'] ?>
= $logMeta['mtime'] ?>
@@ -209,7 +129,7 @@ if ($tab === 'nginx_config') {
$line): ?>
- = htmlspecialchars($line, ENT_QUOTES | ENT_SUBSTITUTE) ?>
diff --git a/public/admin/system.php b/public/admin/system.php
index f37287a..26ca1ac 100644
--- a/public/admin/system.php
+++ b/public/admin/system.php
@@ -3,359 +3,69 @@ require_once __DIR__ . "/../../config/bootstrap.php";
require_once __DIR__ . '/../../src/AdminAuth.php';
require_once APP_ROOT . '/src/Database.php';
require_once APP_ROOT . '/src/SystemCache.php';
+require_once APP_ROOT . '/src/SystemController.php';
AdminAuth::requireLogin();
$pageTitle = "Système";
-// Bootstrap cache (uses the same SQLite DB as the app)
-$_db = new Database();
-$_cache = new SystemCache($_db->getPDO());
+$_db = new Database();
+$_cache = new SystemCache($_db->getPDO());
+$_controller = new SystemController($_db, $_cache);
// ?refresh=1 force-busts all cached sections
-$forceRefresh = isset($_GET['refresh']) && $_GET['refresh'] === '1';
-if ($forceRefresh) {
- $_cache->invalidate('system_status');
- $_cache->invalidate('disk_info');
- $_cache->invalidate('php_info');
+if (isset($_GET['refresh']) && $_GET['refresh'] === '1') {
+ $_controller->invalidateAll();
}
-// ═══════════════════════════════════════════════════════════════════════════════
-// SECTION 1 — STATUS DATA
-// ═══════════════════════════════════════════════════════════════════════════════
+// ── Status / PHP / Disk data ──────────────────────────────────────────────────
+$statusData = $_controller->getStatusData();
+$checks = $statusData['checks'];
+$statusCached = $statusData['cached'];
+$statusCacheAge = $statusData['cacheAge'];
-function safeExec(string $cmd): ?string {
- if (!function_exists('exec')) return null;
- $output = [];
- $rc = 0;
- exec($cmd . ' 2>/dev/null', $output, $rc);
- return $rc === 0 ? trim(implode("\n", $output)) : null;
-}
+$phpInfo = $_controller->getPhpInfo();
+$diskInfo = $_controller->getDiskInfo();
-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';
-}
+$diskTotal = $diskInfo['total'];
+$diskFree = $diskInfo['free'];
+$diskUsed = $diskInfo['used'];
+$diskPct = $diskInfo['pct'];
+$diskColor = SystemController::diskColor($diskPct);
-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',
- };
-}
-
-// ── system_status cache (2-minute TTL: systemctl + curl checks) ─────────────
-$statusCacheAge = $_cache->ageSeconds('system_status');
-$checksFromCache = $_cache->get('system_status', 120);
-
-if ($checksFromCache !== null) {
- $checks = $checksFromCache;
- $statusCached = true;
-} else {
- $statusCached = false;
- $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 (DB object already created above, reuse it)
- $dbPath = $_db->getDatabasePath();
- $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 {
- $dbRowCount = $_db->getThesisCount();
- } 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é',
- ];
-
- $_cache->set('system_status', $checks);
- $statusCacheAge = 0;
-}
-
-// ── php_info cache (1-hour TTL: PHP ini values don't change at runtime) ───────
-$phpInfoFromCache = $_cache->get('php_info', 3600);
-if ($phpInfoFromCache !== null) {
- $phpInfo = $phpInfoFromCache;
-} else {
- $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',
- ];
- $_cache->set('php_info', $phpInfo);
-}
-
-// ── disk_info cache (5-minute TTL) ────────────────────────────────────────────
-$diskFromCache = $_cache->get('disk_info', 300);
-if ($diskFromCache !== null) {
- $diskTotal = $diskFromCache['total'];
- $diskFree = $diskFromCache['free'];
- $diskUsed = $diskFromCache['used'];
- $diskPct = $diskFromCache['pct'];
-} else {
- $diskTotal = disk_total_space(APP_ROOT);
- $diskFree = disk_free_space(APP_ROOT);
- $diskUsed = $diskTotal - $diskFree;
- $diskPct = $diskTotal > 0 ? (int) round($diskUsed / $diskTotal * 100) : 0;
- $_cache->set('disk_info', [
- 'total' => $diskTotal,
- 'free' => $diskFree,
- 'used' => $diskUsed,
- 'pct' => $diskPct,
- ]);
-}
-
-// ═══════════════════════════════════════════════════════════════════════════════
-// 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];
-
-// Nginx config paths (live deployed, then local reference fallback)
-const NGINX_CONFIG_LIVE = '/etc/nginx/sites-available/posterg';
-const NGINX_CONFIG_LOCAL = APP_ROOT . '/nginx/posterg.conf';
-
-// Active tab: 'nginx_config', or a log key (status is now always shown above tabs)
+// ── Active tab + line count ───────────────────────────────────────────────────
$activeTab = $_GET['tab'] ?? 'nginx_access';
if ($activeTab === 'status') {
- // legacy URL — redirect to default log tab, status is always visible
- $activeTab = 'nginx_access';
-} elseif ($activeTab !== 'nginx_config' && !array_key_exists($activeTab, LOG_FILES)) {
+ $activeTab = 'nginx_access'; // legacy redirect
+} elseif ($activeTab !== 'nginx_config' && !array_key_exists($activeTab, SystemController::LOG_FILES)) {
$activeTab = 'nginx_access';
}
$selectedN = isset($_GET['n']) ? (int) $_GET['n'] : 100;
-if (!in_array($selectedN, ALLOWED_LINES, true)) {
+if (!in_array($selectedN, SystemController::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
-}
+// ── Tab content data ──────────────────────────────────────────────────────────
+$logLines = null;
+$logError = null;
+$logFileMeta = null;
-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 !== 'nginx_config') {
- $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)),
- ];
- }
-}
-
-// Pre-load nginx config tab
$nginxConfigLines = null;
$nginxConfigSource = null;
$nginxConfigError = null;
$nginxConfigMeta = null;
if ($activeTab === 'nginx_config') {
- // Try live deployed config first, fall back to local reference copy
- $livePath = NGINX_CONFIG_LIVE;
- $localPath = NGINX_CONFIG_LOCAL;
-
- if (file_exists($livePath) && is_readable($livePath)) {
- $raw = file($livePath, FILE_IGNORE_NEW_LINES);
- if ($raw !== false) {
- $nginxConfigLines = $raw;
- $nginxConfigSource = 'live';
- $sz = filesize($livePath);
- $nginxConfigMeta = [
- 'path' => $livePath,
- 'size' => $sz > 1048576
- ? number_format($sz / 1048576, 2) . ' MB'
- : number_format($sz / 1024, 1) . ' KB',
- 'mtime' => date('d/m/Y H:i:s', filemtime($livePath)),
- ];
- }
- }
-
- if ($nginxConfigLines === null && file_exists($localPath) && is_readable($localPath)) {
- $raw = file($localPath, FILE_IGNORE_NEW_LINES);
- if ($raw !== false) {
- $nginxConfigLines = $raw;
- $nginxConfigSource = 'local';
- $sz = filesize($localPath);
- $nginxConfigMeta = [
- 'path' => $localPath,
- 'size' => $sz > 1048576
- ? number_format($sz / 1048576, 2) . ' MB'
- : number_format($sz / 1024, 1) . ' KB',
- 'mtime' => date('d/m/Y H:i:s', filemtime($localPath)),
- ];
- }
- }
-
- if ($nginxConfigLines === null) {
- $nginxConfigError = file_exists($livePath)
- ? "Fichier non lisible (permissions insuffisantes) : " . htmlspecialchars($livePath)
- : "Config live introuvable (" . htmlspecialchars($livePath) . ") et config locale introuvable (" . htmlspecialchars($localPath) . ").";
- }
+ $nginxData = $_controller->getNginxConfigData();
+ $nginxConfigLines = $nginxData['lines'];
+ $nginxConfigSource = $nginxData['source'];
+ $nginxConfigMeta = $nginxData['meta'];
+ $nginxConfigError = $nginxData['error'];
+} else {
+ $logData = $_controller->getLogData($activeTab, $selectedN);
+ $logLines = $logData['lines'];
+ $logError = $logData['error'];
+ $logFileMeta = $logData['meta'];
}
$isAdmin = true; $bodyClass = 'admin-body';
@@ -404,7 +114,7 @@ require_once APP_ROOT . '/templates/head.php';
= htmlspecialchars($check['detail']) ?>
@@ -427,13 +137,12 @@ require_once APP_ROOT . '/templates/head.php';
Espace disque
- 85 ? '#e05555' : ($diskPct > 70 ? '#ffc107' : '#4caf50'); ?>
- = humanBytes($diskUsed) ?> utilisé (= $diskPct ?>%)
- = humanBytes($diskFree) ?> libre / = humanBytes($diskTotal) ?>
+ = SystemController::humanBytes($diskUsed) ?> utilisé (= $diskPct ?>%)
+ = SystemController::humanBytes($diskFree) ?> libre / = SystemController::humanBytes($diskTotal) ?>
@@ -442,7 +151,7 @@ require_once APP_ROOT . '/templates/head.php';