admin/system: add nginx config viewer tab

Add a 'nginx — config' tab to the Système admin page (system.php).

- Reads /etc/nginx/sites-available/posterg (live deployed config) first;
  falls back to nginx/posterg.conf (local reference copy) when the live
  path is inaccessible (e.g. in dev, or wrong permissions).
- Displays a colour-coded badge: green '● Config déployée' for live,
  amber '⚠ Référence locale' for the fallback.
- Renders the full config in the shared .log-output code block with
  line numbers (data-n gutter via CSS ::before) and lightweight nginx
  syntax colouring (comments grey, block keywords purple, directives blue).
- Reuses the existing copy-to-clipboard button.
- Tab routing: activeTab validation extended to accept 'nginx_config';
  log pre-loading guards skip when activeTab is 'nginx_config'.
- No remote execution: read-only, zero new attack surface.
This commit is contained in:
Pontoporeia
2026-03-26 11:23:18 +01:00
parent 45acadaa0a
commit e4be230a04
2 changed files with 142 additions and 5 deletions

View File

@@ -332,7 +332,7 @@ Goal: rename the tables and column to the canonical M2M pattern (`tags`, `thesis
- [x] Update `docs/SERVER_SETUP.md` with correct permissions rationale and troubleshooting
- [x] Add server status view in admin panel (nginx + php-fpm health, site HTTP check)
- [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 nginx config viewer to admin panel: "nginx — config" tab in `system.php` reads `/etc/nginx/sites-available/posterg` (live, with badge) or falls back to local `nginx/posterg.conf`; line-numbered, syntax-coloured, copy-to-clipboard
- [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)
- [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

View File

@@ -190,10 +190,14 @@ const LOG_FILES = [
const ALLOWED_LINES = [50, 100, 200, 500];
// Active tab: 'status' or a log key
// 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: 'status', 'nginx_config', or a log key
$activeTab = $_GET['tab'] ?? 'nginx_access';
if ($activeTab === 'status') {
$activeTab = 'status';
if ($activeTab === 'status' || $activeTab === 'nginx_config') {
// valid as-is
} elseif (!array_key_exists($activeTab, LOG_FILES)) {
$activeTab = 'nginx_access';
}
@@ -246,7 +250,7 @@ $logLines = null;
$logError = null;
$logFileMeta = null;
if ($activeTab !== 'status') {
if ($activeTab !== 'status' && $activeTab !== 'nginx_config') {
$logPath = LOG_FILES[$activeTab]['path'];
$logLines = readLogTail($logPath, $selectedN, $logError);
if (file_exists($logPath)) {
@@ -260,6 +264,56 @@ if ($activeTab !== 'status') {
}
}
// 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) . ").";
}
}
require_once APP_ROOT . '/templates/admin/head.php';
?>
@@ -518,6 +572,33 @@ require_once APP_ROOT . '/templates/admin/head.php';
text-decoration: none;
}
.sys-refresh-note a:hover { text-decoration: underline; }
/* ── Nginx config viewer ───────────────────────────────────────────────── */
.nginx-source-badge {
display: inline-block;
font-size: .72rem;
font-family: ui-monospace, monospace;
padding: .15rem .55rem;
border-radius: 3px;
margin-left: .6rem;
vertical-align: middle;
}
.nginx-source-badge--live {
background: rgba(76,175,80,.15);
color: #4caf50;
border: 1px solid rgba(76,175,80,.35);
}
.nginx-source-badge--local {
background: rgba(255,193,7,.12);
color: #ffc107;
border: 1px solid rgba(255,193,7,.35);
}
/* Nginx syntax highlight layers inside .log-output */
.nginx-comment { color: #666; font-style: italic; }
.nginx-directive { color: #7eb8f7; }
.nginx-block { color: #d4a0ff; font-weight: 600; }
.nginx-value { color: #e2c08d; }
.nginx-location { color: #79dac8; }
</style>
<main class="admin-main">
@@ -538,6 +619,8 @@ require_once APP_ROOT . '/templates/admin/head.php';
<?= htmlspecialchars($def['label']) ?>
</a>
<?php endforeach; ?>
<a href="?tab=nginx_config"
class="sys-tab <?= $activeTab === 'nginx_config' ? 'active' : '' ?>">nginx — config</a>
</nav>
<?php if ($activeTab === 'status'): ?>
@@ -580,6 +663,60 @@ require_once APP_ROOT . '/templates/admin/head.php';
</div>
</div>
<?php elseif ($activeTab === 'nginx_config'): ?>
<!-- ════════════════════════════════════════════════════════════════════
NGINX CONFIG PANEL
════════════════════════════════════════════════════════════════════ -->
<?php if ($nginxConfigMeta): ?>
<div class="log-meta">
<span data-label="Fichier"><?= htmlspecialchars($nginxConfigMeta['path']) ?></span>
<span data-label="Taille"><?= $nginxConfigMeta['size'] ?></span>
<span data-label="Modifié"><?= $nginxConfigMeta['mtime'] ?></span>
<?php if ($nginxConfigSource === 'live'): ?>
<span class="nginx-source-badge nginx-source-badge--live">● Config déployée</span>
<?php else: ?>
<span class="nginx-source-badge nginx-source-badge--local">⚠ Référence locale (config live inaccessible)</span>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if ($nginxConfigError !== null): ?>
<div class="log-unavailable">
<strong>Configuration nginx non disponible</strong>
<div class="log-unavail-path"><?= htmlspecialchars($nginxConfigError) ?></div>
<?php if (php_sapi_name() === 'cli-server'): ?>
<div class="log-unavail-dev">
En développement, <code>/etc/nginx/sites-available/posterg</code> n'existe pas.
La config de référence se trouve dans <code>nginx/posterg.conf</code>.
</div>
<?php endif; ?>
</div>
<?php elseif (empty($nginxConfigLines)): ?>
<div class="log-empty">Le fichier de configuration est vide.</div>
<?php else: ?>
<?php
// Minimal nginx syntax coloring — applied per line
function nginxLineClass(string $line): string {
$trimmed = ltrim($line);
if ($trimmed === '' || str_starts_with($trimmed, '#')) return 'nginx-comment';
if (preg_match('/^\s*(location|server|upstream|events|http|geo|map|types)\b/', $line)) return 'nginx-block';
return 'nginx-directive';
}
?>
<div class="log-output" id="log-output" role="region" aria-label="Configuration nginx">
<button class="log-copy-btn" id="log-copy-btn" type="button" title="Copier la configuration">
Copier
</button>
<?php foreach ($nginxConfigLines as $i => $line): ?>
<span class="log-line <?= nginxLineClass($line) ?>"
data-n="<?= $i + 1 ?>"><?= htmlspecialchars($line, ENT_QUOTES | ENT_SUBSTITUTE) ?></span>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php else: ?>
<!-- ════════════════════════════════════════════════════════════════════
LOG PANEL