feat: add monitoring dashboard under /monitor/
Status page and JSON API read from private/data; root index.php stays a placeholder. Mark todo 06 done. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/monitor-lib.php';
|
||||
|
||||
function loadStatusSnapshot(): array
|
||||
{
|
||||
$checksData = readJsonFile(privateDataDir() . '/last-checks.json');
|
||||
$bokaData = readJsonFile(privateDataDir() . '/boka-status.json');
|
||||
|
||||
return [
|
||||
'checks_data' => $checksData,
|
||||
'boka_data' => $bokaData,
|
||||
];
|
||||
}
|
||||
|
||||
/** @param array{checks_data: ?array, boka_data: ?array} $snapshot */
|
||||
function formatPublicStatus(array $snapshot): array
|
||||
{
|
||||
$checksData = $snapshot['checks_data'];
|
||||
$bokaData = $snapshot['boka_data'];
|
||||
|
||||
if ($checksData === null && $bokaData === null) {
|
||||
return [
|
||||
'initialized' => false,
|
||||
'message' => 'Monitoring not initialized',
|
||||
'updated_at' => null,
|
||||
'duration_ms' => null,
|
||||
'checks' => [],
|
||||
'boka' => null,
|
||||
];
|
||||
}
|
||||
|
||||
$checks = [];
|
||||
foreach ($checksData['checks'] ?? [] as $check) {
|
||||
if (!is_array($check)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = (string) ($check['name'] ?? '');
|
||||
if ($name === '' || str_starts_with($name, '_')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$status = (string) ($check['status'] ?? 'down');
|
||||
if (!in_array($status, ['ok', 'warn', 'down'], true)) {
|
||||
$status = 'down';
|
||||
}
|
||||
|
||||
$message = $check['message'] ?? null;
|
||||
if (is_string($message) && $message !== '') {
|
||||
$message = sanitizeError($message);
|
||||
} else {
|
||||
$message = null;
|
||||
}
|
||||
|
||||
$checks[] = [
|
||||
'name' => $name,
|
||||
'status' => $status,
|
||||
'message' => $message,
|
||||
'response_ms' => isset($check['response_ms']) ? (int) $check['response_ms'] : null,
|
||||
'ssl_days_remaining' => isset($check['ssl_days_remaining']) ? (int) $check['ssl_days_remaining'] : null,
|
||||
'checked_at' => isset($check['checked_at']) && is_string($check['checked_at']) ? $check['checked_at'] : null,
|
||||
];
|
||||
}
|
||||
|
||||
$boka = null;
|
||||
if ($bokaData !== null) {
|
||||
$lastPush = $bokaData['last_push'] ?? null;
|
||||
$ageMinutes = null;
|
||||
if (is_string($lastPush) && $lastPush !== '') {
|
||||
$lastPushAt = strtotime($lastPush) ?: 0;
|
||||
if ($lastPushAt > 0) {
|
||||
$ageMinutes = (int) floor((time() - $lastPushAt) / 60);
|
||||
}
|
||||
}
|
||||
|
||||
$boka = [
|
||||
'host' => isset($bokaData['host']) && is_string($bokaData['host']) ? $bokaData['host'] : 'boka',
|
||||
'last_push' => is_string($lastPush) ? $lastPush : null,
|
||||
'age_minutes' => $ageMinutes,
|
||||
'disk_percent' => isset($bokaData['disk_percent']) ? (int) $bokaData['disk_percent'] : null,
|
||||
'mem_available_mb' => isset($bokaData['mem_available_mb']) ? (int) $bokaData['mem_available_mb'] : null,
|
||||
'containers_running' => isset($bokaData['containers_running']) ? (int) $bokaData['containers_running'] : null,
|
||||
'containers_unhealthy' => isset($bokaData['containers_unhealthy']) ? (int) $bokaData['containers_unhealthy'] : null,
|
||||
'backup_last_success' => isset($bokaData['backup_last_success']) && is_string($bokaData['backup_last_success'])
|
||||
? $bokaData['backup_last_success']
|
||||
: null,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'initialized' => $checksData !== null || $bokaData !== null,
|
||||
'message' => null,
|
||||
'updated_at' => isset($checksData['updated_at']) && is_string($checksData['updated_at'])
|
||||
? $checksData['updated_at']
|
||||
: null,
|
||||
'duration_ms' => isset($checksData['duration_ms']) ? (int) $checksData['duration_ms'] : null,
|
||||
'checks' => $checks,
|
||||
'boka' => $boka,
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user