assets(); $assets->addStyle('/module/pihole/asset?file=pihole.css'); $assets->addScript('/module/pihole/asset?file=pihole_instances.js', 'footer', true); 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 { $password = trim((string)($row['password'] ?? '')); if ($password !== '') { return $password; } return trim((string)($row['token'] ?? '')); }; $instances = []; $rawJson = trim((string)($settings['instances_json'] ?? '')); if ($rawJson !== '') { $decoded = json_decode($rawJson, true); if (is_array($decoded)) { foreach ($decoded as $row) { if (!is_array($row)) { continue; } $id = trim((string)($row['id'] ?? '')); $url = trim((string)($row['url'] ?? '')); if ($id === '' || $url === '') { continue; } $instances[$id] = [ 'id' => $id, 'name' => trim((string)($row['name'] ?? '')) ?: $id, 'url' => $url, 'password' => $normalizeSecret($row), 'is_primary' => !empty($row['is_primary']), ]; } } } if (!$instances) { foreach (['primary', 'secondary'] as $key) { $url = trim((string)($settings[$key . '_url'] ?? '')); if ($url === '') { continue; } $instances[$key] = [ 'id' => $key, 'name' => trim((string)($settings[$key . '_name'] ?? '')) ?: ($key === 'primary' ? 'Primaer' : 'Sekundaer'), 'url' => $url, 'password' => trim((string)($settings[$key . '_token'] ?? '')), 'is_primary' => $key === 'primary', ]; } } return $instances; }; $instances = $loadInstances($settings); $sanitizeId = function (string $id): string { $id = preg_replace('/[^a-zA-Z0-9_-]/', '', $id); return trim((string)$id); }; $saveInstances = function (array $settings, array $instances): void { $payload = $settings; $payload['instances_json'] = json_encode(array_values($instances), JSON_UNESCAPED_UNICODE); modules()->saveSettings('pihole', $payload); }; 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'] ?? '')); $url = trim((string)($_POST['url'] ?? '')); $password = trim((string)($_POST['password'] ?? '')); $isPrimary = isset($_POST['is_primary']); 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.'; } } else { $instanceId = $sanitizeId($instanceId); if ($instanceId === '' || $url === '') { $error = 'Bitte ID und URL angeben.'; } elseif ($currentId === '' && isset($instances[$instanceId])) { $error = 'Die Instanz-ID ist bereits vergeben. Bitte eine eindeutige ID verwenden.'; } elseif ($currentId !== '' && $currentId !== $instanceId && isset($instances[$instanceId])) { $error = 'Die neue Instanz-ID ist bereits vergeben. Bitte eine eindeutige ID verwenden.'; } else { $existingPassword = ''; if ($currentId !== '' && isset($instances[$currentId])) { $existingPassword = (string)($instances[$currentId]['password'] ?? ''); } $passwordToStore = $password !== '' ? $password : $existingPassword; if ($currentId !== '' && $currentId !== $instanceId) { unset($instances[$currentId]); } $instances[$instanceId] = [ 'id' => $instanceId, 'name' => $name !== '' ? $name : $instanceId, 'url' => $url, 'password' => $passwordToStore, 'is_primary' => $isPrimary, ]; if ($isPrimary) { foreach ($instances as $id => &$row) { $row['is_primary'] = ($id === $instanceId); } unset($row); $settings['primary_id'] = $instanceId; } $notice = $currentId !== '' ? 'Instanz aktualisiert.' : 'Instanz gespeichert.'; } } if (!$error) { $saveInstances($settings, $instances); $settings = modules()->settings($moduleName); $instances = $loadInstances($settings); } } $primaryId = trim((string)($settings['primary_id'] ?? '')); if ($primaryId === '') { foreach ($instances as $id => $row) { if (!empty($row['is_primary'])) { $primaryId = $id; break; } } } ?> = module_shell_header('pihole', [ 'title' => 'Pi-hole Instanzen', 'description' => 'Pi-hole Instanzen hinzufuegen, bearbeiten und loeschen.', ]) ?>
Pi-hole Instanzen hinzufuegen, bearbeiten und loeschen.