mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-07 03:29:19 +02:00
feat: system page caching via SystemCache + system_cache SQLite table
Add a TTL-based cache for the expensive checks on the admin system page,
eliminating repeated systemctl subprocess calls (~4×~100ms), curl self-pings
(~200-500ms), disk_*_space() and PHP ini reads on every page load.
Changes:
- storage/migrations/007_system_cache.sql: new migration creating the
system_cache table (key TEXT PK, value TEXT, updated_at INTEGER)
- storage/schema.sql: system_cache table added before pages table
- Applied migration to live storage/posterg.db
- src/SystemCache.php: new class with get/set/isStale/ageSeconds/invalidate;
uses SQLite INSERT … ON CONFLICT upsert; no external dependencies
- src/Database.php: added getDatabasePath(): string accessor
- public/admin/system.php:
- Bootstrap SystemCache at request start using the existing DB PDO handle
- system_status: cached with 2-min TTL (systemctl + curl checks)
- php_info: cached with 1-hour TTL (PHP ini values are runtime-constant)
- disk_info: cached with 5-min TTL (total/free/used/pct tuple)
- Logs section: unchanged — always reads live log tail per active tab
- ?refresh=1 GET param invalidates all three cache keys before rendering
- Status panel heading shows cache badge: '⚡ Cache — il y a Xs' (hit)
or '⟳ Actualisé' (miss/fresh), styled via new .sys-cache-badge rules
- public/assets/css/system.css: .sys-cache-badge / --hit / --miss styles
This commit is contained in:
111
src/SystemCache.php
Normal file
111
src/SystemCache.php
Normal file
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SystemCache — thin TTL cache for admin system page checks.
|
||||
*
|
||||
* Stores JSON-encoded data blobs in the `system_cache` SQLite table.
|
||||
* The table has a single schema:
|
||||
* key TEXT PRIMARY KEY, value TEXT NOT NULL, updated_at INTEGER NOT NULL
|
||||
*
|
||||
* Usage:
|
||||
* $cache = new SystemCache($pdo);
|
||||
*
|
||||
* // Read (returns array or null if stale/missing)
|
||||
* $data = $cache->get('system_status', 120);
|
||||
*
|
||||
* // Write
|
||||
* $cache->set('system_status', $myArray);
|
||||
*
|
||||
* // Check freshness without reading value
|
||||
* if ($cache->isStale('disk_info', 300)) { ... }
|
||||
*
|
||||
* // Force-invalidate a key (e.g. on ?refresh=1)
|
||||
* $cache->invalidate('system_status');
|
||||
*/
|
||||
class SystemCache
|
||||
{
|
||||
private PDO $pdo;
|
||||
|
||||
public function __construct(PDO $pdo)
|
||||
{
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return cached data for $key if it is no older than $maxAgeSec seconds.
|
||||
* Returns null when the entry is missing or stale.
|
||||
*/
|
||||
public function get(string $key, int $maxAgeSec = 60): ?array
|
||||
{
|
||||
$stmt = $this->pdo->prepare(
|
||||
'SELECT value, updated_at FROM system_cache WHERE key = ?'
|
||||
);
|
||||
$stmt->execute([$key]);
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$row) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((time() - (int)$row['updated_at']) > $maxAgeSec) {
|
||||
return null; // stale
|
||||
}
|
||||
|
||||
$decoded = json_decode((string)$row['value'], true);
|
||||
return is_array($decoded) ? $decoded : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upsert $data (JSON-encoded) for $key with current timestamp.
|
||||
*/
|
||||
public function set(string $key, array $data): void
|
||||
{
|
||||
$stmt = $this->pdo->prepare(
|
||||
'INSERT INTO system_cache (key, value, updated_at)
|
||||
VALUES (?, ?, ?)
|
||||
ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at'
|
||||
);
|
||||
$stmt->execute([$key, json_encode($data), time()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true when the entry is missing or older than $maxAgeSec.
|
||||
*/
|
||||
public function isStale(string $key, int $maxAgeSec = 60): bool
|
||||
{
|
||||
$stmt = $this->pdo->prepare(
|
||||
'SELECT updated_at FROM system_cache WHERE key = ?'
|
||||
);
|
||||
$stmt->execute([$key]);
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$row) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (time() - (int)$row['updated_at']) > $maxAgeSec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the age of the cached entry in seconds, or null if missing.
|
||||
*/
|
||||
public function ageSeconds(string $key): ?int
|
||||
{
|
||||
$stmt = $this->pdo->prepare(
|
||||
'SELECT updated_at FROM system_cache WHERE key = ?'
|
||||
);
|
||||
$stmt->execute([$key]);
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
return $row ? (time() - (int)$row['updated_at']) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a cached entry, forcing the next get() to re-compute.
|
||||
*/
|
||||
public function invalidate(string $key): void
|
||||
{
|
||||
$stmt = $this->pdo->prepare('DELETE FROM system_cache WHERE key = ?');
|
||||
$stmt->execute([$key]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user