From c86781b9be5b34576d6969b1a225481759d499a4 Mon Sep 17 00:00:00 2001
From: Pontoporeia
Date: Thu, 2 Apr 2026 18:31:38 +0200
Subject: [PATCH] admin/system: move status panel above tabs, add collapse
toggle
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Status (services, PHP env, disk) is now always visible above the log/config
tab bar rather than being one of the tab targets:
- Status section rendered unconditionally above .
- Services grid, PHP info grid and disk bar grouped inside a collapsible
with a header row containing the cache-freshness badge and a
toggle button (▲ Réduire / ▼ Développer).
- Collapse state persisted in localStorage so the preference survives
page reloads (e.g. when switching log tabs).
- Tab bar now only contains the three log tabs + nginx config; the 'Statut'
tab is removed. Legacy ?tab=status URLs fall through to nginx_access.
- PHP/disk sub-sections laid out in a 2-col grid inside the status panel;
responsive single-col below 700px.
- system.css: new .sys-status-section / .sys-status-header /
.sys-status-toggle / .sys-status-meta rules added.
- aria-current="page" added to active tab links.
- todo/03-system-cache.md: all items marked done; notes added explaining
why log caching was deliberately omitted.
---
public/admin/system.php | 165 +++++++++++++++++++++--------------
public/assets/css/system.css | 42 +++++++++
todo/03-system-cache.md | 24 +++--
3 files changed, 155 insertions(+), 76 deletions(-)
diff --git a/public/admin/system.php b/public/admin/system.php
index 6d0458f..0fe23f6 100644
--- a/public/admin/system.php
+++ b/public/admin/system.php
@@ -237,11 +237,12 @@ const ALLOWED_LINES = [50, 100, 200, 500];
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 === 'nginx_config') {
- // valid as-is
-} elseif (!array_key_exists($activeTab, LOG_FILES)) {
+// Active tab: 'nginx_config', or a log key (status is now always shown above tabs)
+$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';
}
@@ -293,7 +294,7 @@ $logLines = null;
$logError = null;
$logFileMeta = null;
-if ($activeTab !== 'status' && $activeTab !== 'nginx_config') {
+if ($activeTab !== 'nginx_config') {
$logPath = LOG_FILES[$activeTab]['path'];
$logLines = readLogTail($logPath, $selectedN, $logError);
if (file_exists($logPath)) {
@@ -361,6 +362,27 @@ $isAdmin = true; $bodyClass = 'admin-body';
$extraCss = ['/assets/css/system.css'];
$extraJsInline = <<<'JS'
(function () {
+ // ── Status section toggle ──────────────────────────────────────────
+ var toggleBtn = document.getElementById('sys-status-toggle');
+ var statusBody = document.getElementById('sys-status-body');
+ if (toggleBtn && statusBody) {
+ toggleBtn.addEventListener('click', function () {
+ var collapsed = statusBody.hidden;
+ statusBody.hidden = !collapsed;
+ toggleBtn.setAttribute('aria-expanded', collapsed ? 'true' : 'false');
+ toggleBtn.textContent = collapsed ? '▲ Réduire' : '▼ Développer';
+ try { localStorage.setItem('sys_status_collapsed', collapsed ? '0' : '1'); } catch(e) {}
+ });
+ // Restore collapsed state
+ try {
+ if (localStorage.getItem('sys_status_collapsed') === '1') {
+ statusBody.hidden = true;
+ toggleBtn.setAttribute('aria-expanded', 'false');
+ toggleBtn.textContent = '▼ Développer';
+ }
+ } catch(e) {}
+ }
+
// ── Instant tab switch on lines-select change ──────────────────────
var sel = document.getElementById('lines-select');
if (sel) {
@@ -425,76 +447,85 @@ require_once APP_ROOT . '/templates/head.php';
Forcer actualisation
+
+
+
+
+
+
+
+
+
+
+
+
= htmlspecialchars($check['detail']) ?>
+
+
+
+
+
+
+
+
+
-
- Statut
+
$def): ?>
+ class="sys-tab = $activeTab === $key ? 'active' : '' ?>"
+ = $activeTab === $key ? 'aria-current="page"' : '' ?>>
= htmlspecialchars($def['label']) ?>
nginx — config
+ class="sys-tab = $activeTab === 'nginx_config' ? 'active' : '' ?>"
+ = $activeTab === 'nginx_config' ? 'aria-current="page"' : '' ?>>nginx — config
-
-
-
- Services
-
-
- ⚡ Cache — il y a = $statusCacheAge ?>s
-
-
-
- ⟳ Actualisé
-
-
-
-
-
-
-
-
-
-
= htmlspecialchars($check['detail']) ?>
-
-
-
-
-
- Environnement PHP
-
- $val): ?>
-
-
= htmlspecialchars($key) ?>
-
= htmlspecialchars($val) ?>
-
-
-
-
- Espace disque
-
- 85 ? '#e05555' : ($diskPct > 70 ? '#ffc107' : '#4caf50');
- ?>
-
-
- = humanBytes($diskUsed) ?> utilisé (= $diskPct ?>%)
- = humanBytes($diskFree) ?> libre / = humanBytes($diskTotal) ?>
-
-
-
-
+
diff --git a/public/assets/css/system.css b/public/assets/css/system.css
index ddb8f8d..2e2a363 100644
--- a/public/assets/css/system.css
+++ b/public/assets/css/system.css
@@ -30,6 +30,48 @@
border-bottom-color: var(--accent-primary);
}
+/* ── Status section (always-visible panel above tabs) ─────────────────── */
+.sys-status-section {
+ background: #1a1a1a;
+ border: 1px solid #555;
+ border-radius: 6px;
+ padding: 1rem 1.25rem 1.25rem;
+ margin-bottom: 1.75rem;
+}
+.sys-status-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 1rem;
+}
+.sys-status-toggle {
+ background: none;
+ border: 1px solid #555;
+ color: #969696;
+ border-radius: 3px;
+ font-size: .72rem;
+ font-family: inherit;
+ padding: .2rem .55rem;
+ cursor: pointer;
+ white-space: nowrap;
+ transition: color .15s, border-color .15s;
+}
+.sys-status-toggle:hover {
+ color: #e8e8e8;
+ border-color: #888;
+}
+.sys-status-meta {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 1.5rem 2rem;
+ margin-top: 1.5rem;
+ padding-top: 1.25rem;
+ border-top: 1px solid #333;
+}
+@media (max-width: 700px) {
+ .sys-status-meta { grid-template-columns: 1fr; }
+}
+
/* ── Status cards ──────────────────────────────────────────────────────── */
.srv-grid {
display: grid;
diff --git a/todo/03-system-cache.md b/todo/03-system-cache.md
index 6c54b45..9f66b5b 100644
--- a/todo/03-system-cache.md
+++ b/todo/03-system-cache.md
@@ -1,4 +1,4 @@
-# System Page Caching — Database-Backed Status Cache
+# System Page Caching - Database-Backed Status Cache
## Problem
@@ -11,17 +11,23 @@ The admin system page (`/admin/system.php`) runs expensive operations on every l
## Solution: `system_cache` table + background refresh
-- [ ] **Add `system_cache` table** to schema: `CREATE TABLE system_cache (key TEXT PRIMARY KEY, value TEXT NOT NULL, updated_at INTEGER NOT NULL)`
-- [ ] **Add migration** `storage/migrations/007_system_cache.sql`
-- [ ] **Add `SystemCache` class** (`src/SystemCache.php`) with methods:
+- [x] **Add `system_cache` table** to schema: `CREATE TABLE system_cache (key TEXT PRIMARY KEY, value TEXT NOT NULL, updated_at INTEGER NOT NULL)`
+- [x] **Add migration** `storage/migrations/007_system_cache.sql`
+- [x] **Add `SystemCache` class** (`src/SystemCache.php`) with methods:
- `get(string $key, int $maxAgeSec = 60): ?array`
- `set(string $key, array $data): void`
- `isStale(string $key, int $maxAgeSec = 60): bool`
-- [ ] **Refactor `system.php` status section**:
+- [x] **Refactor `system.php` status section**:
1. Check `SystemCache::get('system_status', 120)` — 2-minute TTL
- 2. If cache hit → render from cache, show "mis en cache il y a X sec" label
+ 2. If cache hit → render from cache, show “mis en cache il y a X sec” label
3. If cache miss → run checks, store in cache, render
4. Add `?refresh=1` GET param to force-bypass cache
-- [ ] **Refactor `system.php` log sections** — avoid re-reading on every tab switch; only read the active tab's log
-- [ ] **Cache disk info** separately with 5-minute TTL: `SystemCache::get('disk_info', 300)`
-- [ ] **Cache PHP info** separately with 1-hour TTL: `SystemCache::get('php_info', 3600)`
+- [x] **Refactor `system.php` log sections** — avoid re-reading on every tab switch; only read the active tab’s log
+- [x] **Cache disk info** separately with 5-minute TTL: `SystemCache::get('disk_info', 300)`
+- [x] **Cache PHP info** separately with 1-hour TTL: `SystemCache::get('php_info', 3600)`
+
+## Notes
+
+- Log caching deliberately omitted: `tail` output is inherently real-time and caching even 30s would show stale data during the moments it matters most (deploys, errors). The existing tab guard already ensures only the active log file is read.
+- nginx config could be cached but `file()` on a small static config file is negligible; not worth the added complexity.
+- A future improvement could stream log tabs via `fetch()` to avoid full-page reloads on tab switch.