diff --git a/modules/pihole/pages/instances.php b/modules/pihole/pages/instances.php index f617068..f4599aa 100644 --- a/modules/pihole/pages/instances.php +++ b/modules/pihole/pages/instances.php @@ -9,6 +9,143 @@ require_admin(); $settings = modules()->settings($moduleName); $notice = null; $error = null; +$testResults = []; + +$httpRequest = static function (string $method, string $url, array $headers, ?string $body, bool $verify, int $timeout): array { + $raw = ''; + $httpCode = 0; + $requestError = ''; + + if (function_exists('curl_init')) { + $ch = curl_init($url); + curl_setopt_array($ch, [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_TIMEOUT => $timeout, + CURLOPT_CONNECTTIMEOUT => $timeout, + CURLOPT_SSL_VERIFYPEER => $verify, + CURLOPT_SSL_VERIFYHOST => $verify ? 2 : 0, + CURLOPT_CUSTOMREQUEST => $method, + CURLOPT_HTTPHEADER => $headers, + ]); + if ($body !== null) { + curl_setopt($ch, CURLOPT_POSTFIELDS, $body); + } + $raw = (string)curl_exec($ch); + if ($raw === '' && curl_errno($ch)) { + $requestError = curl_error($ch); + } + $httpCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + } else { + $ctx = stream_context_create([ + 'http' => [ + 'method' => $method, + 'timeout' => $timeout, + 'header' => implode("\r\n", $headers), + 'content' => $body ?? '', + ], + 'ssl' => [ + 'verify_peer' => $verify, + 'verify_peer_name' => $verify, + ], + ]); + $raw = (string)@file_get_contents($url, false, $ctx); + if ($raw === '') { + $requestError = 'HTTP request failed'; + } else { + $httpCode = 200; + } + } + + if ($requestError !== '') { + return ['ok' => false, 'http_code' => $httpCode, 'error' => $requestError, 'raw' => $raw, 'url' => $url]; + } + + $decoded = json_decode($raw, true); + if (!is_array($decoded)) { + return ['ok' => false, 'http_code' => $httpCode, 'error' => 'invalid_json', 'raw' => $raw, 'url' => $url]; + } + + return ['ok' => true, 'http_code' => $httpCode, 'data' => $decoded, 'url' => $url]; +}; + +$runConnectionTest = static function (array $instance, array $settings) use ($httpRequest): array { + $verifyTls = !isset($settings['verify_tls']) || $settings['verify_tls'] === '1' || $settings['verify_tls'] === 1 || $settings['verify_tls'] === true; + $timeout = (int)($settings['api_timeout_sec'] ?? 8); + if ($timeout <= 0) { + $timeout = 8; + } + + $url = rtrim((string)($instance['url'] ?? ''), '/'); + $password = trim((string)($instance['password'] ?? '')); + $v6AuthUrl = $url . '/api/auth'; + $v6Payload = json_encode(['password' => $password], JSON_UNESCAPED_UNICODE); + $v6Auth = $httpRequest('POST', $v6AuthUrl, ['Accept: application/json', 'Content-Type: application/json'], $v6Payload, $verifyTls, $timeout); + + if ($v6Auth['ok']) { + $session = (array)(($v6Auth['data']['session'] ?? []) ?: []); + $sid = trim((string)($session['sid'] ?? '')); + if ($sid !== '') { + $probe = $httpRequest('GET', $url . '/api/stats/summary', ['Accept: application/json', 'X-FTL-SID: ' . $sid], null, $verifyTls, $timeout); + if ($probe['ok']) { + return [ + 'ok' => true, + 'status' => 'ok', + 'message' => 'Verbindung OK. API v6 antwortet.', + 'version' => 6, + 'details' => ['auth' => $v6Auth, 'probe' => $probe], + ]; + } + + return [ + 'ok' => false, + 'status' => 'error', + 'message' => 'v6 Auth OK, aber Stats-Endpoint antwortet nicht sauber.', + 'version' => 6, + 'details' => ['auth' => $v6Auth, 'probe' => $probe], + ]; + } + } + + $legacyUrl = $url . '/admin/api.php?summaryRaw'; + if ($password !== '') { + $legacyUrl .= '&auth=' . rawurlencode($password); + } + $v5Probe = $httpRequest('GET', $legacyUrl, ['Accept: application/json'], null, $verifyTls, $timeout); + if ($v5Probe['ok']) { + return [ + 'ok' => true, + 'status' => 'ok', + 'message' => 'Verbindung OK. API v5 antwortet.', + 'version' => 5, + 'details' => ['auth' => $v6Auth, 'probe' => $v5Probe], + ]; + } + + $status = 'error'; + $message = 'Unbekannter Fehler.'; + $httpCode = (int)($v6Auth['http_code'] ?? $v5Probe['http_code'] ?? 0); + $requestError = (string)($v6Auth['error'] ?? $v5Probe['error'] ?? 'error'); + if ($httpCode === 0) { + $status = 'unreachable'; + $message = 'Host nicht erreichbar oder kein HTTP-Response.'; + } elseif ($httpCode === 401 || $httpCode === 403) { + $status = 'auth'; + $message = 'Passwort oder App-Passwort falsch oder nicht berechtigt.'; + } elseif ($requestError === 'invalid_json') { + $status = 'invalid'; + $message = 'API antwortet nicht mit JSON. URL pruefen.'; + } else { + $message = 'API Fehler: ' . $requestError . ' (HTTP ' . $httpCode . ')'; + } + + return [ + 'ok' => false, + 'status' => $status, + 'message' => $message, + 'details' => ['auth' => $v6Auth, 'probe' => $v5Probe], + ]; +}; $loadInstances = function (array $settings): array { $normalizeSecret = static function (array $row): string { @@ -79,6 +216,7 @@ $saveInstances = function (array $settings, array $instances): void { if ($_SERVER['REQUEST_METHOD'] === 'POST') { $deleteId = trim((string)($_POST['delete_id'] ?? '')); + $testId = trim((string)($_POST['test_id'] ?? '')); $currentId = trim((string)($_POST['current_id'] ?? '')); $instanceId = trim((string)($_POST['instance_id'] ?? '')); $name = trim((string)($_POST['name'] ?? '')); @@ -86,7 +224,30 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $password = trim((string)($_POST['password'] ?? '')); $isPrimary = isset($_POST['is_primary']); - if ($deleteId !== '') { + if ($testId !== '') { + if (isset($instances[$testId])) { + module_debug_push('pihole', [ + 'label' => 'connection.test.start', + 'instance_id' => $testId, + 'instance_name' => (string)($instances[$testId]['name'] ?? $testId), + 'url' => (string)($instances[$testId]['url'] ?? ''), + ]); + + $result = $runConnectionTest($instances[$testId], $settings); + $testResults[$testId] = $result; + + module_debug_push('pihole', [ + 'label' => 'connection.test.result', + 'instance_id' => $testId, + 'instance_name' => (string)($instances[$testId]['name'] ?? $testId), + 'result' => $result, + ]); + + $notice = $result['message'] ?? null; + } else { + $error = 'Test-Instanz nicht gefunden.'; + } + } elseif ($deleteId !== '') { if (isset($instances[$deleteId])) { unset($instances[$deleteId]); $notice = 'Instanz geloescht.'; @@ -189,13 +350,16 @@ if ($primaryId === '') {