diff --git a/README.md b/README.md index 968972d..c01edc9 100644 --- a/README.md +++ b/README.md @@ -307,6 +307,22 @@ http://guide.your-domain.com/health.php If you enable demo mode with a `.demo` file, visitors can open the page without the key. +For monitoring tools such as Uptime Kuma, GridTV also exposes a lightweight probe endpoint: + +```text +http://guide.your-domain.com/health.php?format=probe +``` + +- returns HTTP `200` when the instance looks healthy +- returns HTTP `503` when config, PHP extensions, or the first XMLTV source fail +- returns compact JSON suitable for external monitoring + +If you prefer a plain text response: + +```text +http://guide.your-domain.com/health.php?format=probe&plain=1 +``` + --- diff --git a/health.php b/health.php index 66d68b7..7e95ee8 100644 --- a/health.php +++ b/health.php @@ -1,10 +1,6 @@ format('d/m/Y H:i'); } +function health_probe_payload(): array { + $config_path = gridtv_config_path(); + $config_exists = file_exists($config_path); + $config_valid = false; + $config = []; + + if ($config_exists) { + $decoded = json_decode(file_get_contents($config_path), true); + if (is_array($decoded)) { + $config = $decoded; + $config_valid = true; + } + } + + $sources = $config_valid ? array_values($config['epg_sources'] ?? []) : []; + $first_source = $sources[0] ?? null; + + $checks = [ + 'config_exists' => $config_exists, + 'config_valid' => $config_valid, + 'php_curl' => extension_loaded('curl'), + 'php_xml' => class_exists('DOMDocument'), + 'source_configured' => !empty($sources), + ]; + + $details = [ + 'group_name' => $config['group_name'] ?? 'GridTV', + 'source_count' => count($sources), + ]; + + if ($first_source && !empty($first_source['epg_url'])) { + $fetch = gridtv_fetch_url((string) $first_source['epg_url'], 12); + $checks['xmltv_reachable'] = $fetch['ok']; + $details['xmltv_status'] = $fetch['status']; + $details['xmltv_time_total'] = round((float) $fetch['time_total'], 3); + if ($fetch['ok']) { + $stats = health_parse_xmltv($fetch['body']); + $checks['xmltv_valid'] = !empty($stats['ok']); + if (!empty($stats['ok'])) { + $details['xmltv_channels'] = $stats['channels']; + $details['xmltv_programmes'] = $stats['programmes']; + } else { + $details['xmltv_error'] = $stats['error'] ?? 'Invalid XMLTV'; + } + } else { + $details['xmltv_error'] = $fetch['error'] ?: ('HTTP ' . $fetch['status']); + } + } else { + $checks['xmltv_reachable'] = false; + $details['xmltv_error'] = 'No XMLTV source configured'; + } + + $ok = !in_array(false, $checks, true); + + return [ + 'ok' => $ok, + 'status' => $ok ? 'ok' : 'degraded', + 'timestamp' => gmdate('c'), + 'checks' => $checks, + 'details' => $details, + ]; +} + function health_build_payload(array $config, string $locale): array { $config_path = gridtv_config_path(); $config_exists = file_exists($config_path); @@ -164,6 +223,23 @@ function health_build_payload(array $config, string $locale): array { ]; } +if (isset($_GET['format']) && $_GET['format'] === 'probe') { + $payload = health_probe_payload(); + http_response_code($payload['ok'] ? 200 : 503); + if (isset($_GET['plain']) && $_GET['plain'] === '1') { + header('Content-Type: text/plain; charset=UTF-8'); + echo $payload['ok'] ? 'OK' : 'KO'; + } else { + header('Content-Type: application/json; charset=UTF-8'); + echo json_encode($payload, JSON_UNESCAPED_SLASHES); + } + exit; +} + +$config = gridtv_load_config(); +gridtv_require_admin($config, 'Health'); +[$locale, $L] = gridtv_load_locale($config); + if (isset($_GET['format']) && $_GET['format'] === 'json') { header('Content-Type: application/json; charset=UTF-8'); echo json_encode(health_build_payload($config, $locale), JSON_UNESCAPED_SLASHES); @@ -184,6 +260,12 @@ if (isset($_GET['format']) && $_GET['format'] === 'json') { .logo{font-family:'Share Tech Mono',monospace;font-size:26px;letter-spacing:.12em;color:var(--accent);text-transform:uppercase} .subtitle{color:var(--muted);font-size:14px;max-width:700px;line-height:1.5} .actions{display:flex;gap:10px;flex-wrap:wrap}.btn{display:inline-flex;align-items:center;gap:8px;padding:11px 14px;border:1px solid var(--border);background:var(--surface);color:var(--text);text-decoration:none;font-size:13px;letter-spacing:.08em;text-transform:uppercase}.btn-primary{background:var(--accent);color:#000;border-color:var(--accent)} +.probe-box{margin-bottom:20px;padding:18px 20px;background:rgba(19,22,27,.95);border:1px solid var(--border)} +.probe-box h2{margin:0 0 10px;font-size:18px;letter-spacing:.06em;text-transform:uppercase} +.probe-box p{margin:0 0 12px;color:var(--muted);font-size:13px;line-height:1.5} +.probe-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:12px} +.probe-item{padding:12px;background:var(--surface2);border:1px solid var(--border)} +.probe-item .eyebrow{margin-bottom:6px} .grid,.checks,.source-list{display:grid;gap:14px}.grid{grid-template-columns:repeat(auto-fit,minmax(220px,1fr));margin-bottom:22px}.checks{grid-template-columns:repeat(auto-fit,minmax(220px,1fr));margin-bottom:24px}.source-list{gap:18px} .card,.source,.check{background:rgba(19,22,27,.95);border:1px solid var(--border)}.card,.check,.source{padding:16px}.source{padding:20px} .eyebrow{font-family:'Share Tech Mono',monospace;font-size:11px;color:var(--accent2);letter-spacing:.12em;text-transform:uppercase;margin-bottom:10px}.value{font-size:28px;font-weight:700}.small{font-size:13px;color:var(--muted)} @@ -218,6 +300,21 @@ if (isset($_GET['format']) && $_GET['format'] === 'json') { +
+

Monitoring probe

+

Use these public endpoints with Uptime Kuma or any HTTP monitoring tool. They return 200 when the instance is healthy and 503 when a critical check fails.

+
+
+
JSON probe
+
?format=probe
+
+
+
Plain text probe
+
?format=probe&plain=1
+
+
+
+
Group
Sources