pi hole setup
This commit is contained in:
@@ -128,6 +128,12 @@
|
|||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pihole-error {
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #a83a28;
|
||||||
|
}
|
||||||
|
|
||||||
.pihole-blocked {
|
.pihole-blocked {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
@@ -170,6 +176,17 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pihole-test-result {
|
||||||
|
margin-top: 6px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
.pihole-test-result.is-ok { color: #0a6b63; }
|
||||||
|
.pihole-test-result.is-auth { color: #a83a28; }
|
||||||
|
.pihole-test-result.is-unreachable { color: #a83a28; }
|
||||||
|
.pihole-test-result.is-invalid { color: #8a5a00; }
|
||||||
|
.pihole-test-result.is-error { color: #a83a28; }
|
||||||
|
|
||||||
.form-grid {
|
.form-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||||
|
|||||||
@@ -131,6 +131,21 @@
|
|||||||
updateEl.textContent = 'Keine Updates erkannt';
|
updateEl.textContent = 'Keine Updates erkannt';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const errorEl = root.querySelector('[data-instance-errors]');
|
||||||
|
if (errorEl) {
|
||||||
|
if (Array.isArray(entry.errors) && entry.errors.length) {
|
||||||
|
const lines = entry.errors.map((err) => {
|
||||||
|
const code = err.http_code ? `HTTP ${err.http_code}` : 'HTTP ?';
|
||||||
|
const scope = err.scope || 'request';
|
||||||
|
const msg = err.error || 'error';
|
||||||
|
return `${scope}: ${msg} (${code})`;
|
||||||
|
});
|
||||||
|
errorEl.textContent = `API Fehler: ${lines.join(' | ')}`;
|
||||||
|
} else {
|
||||||
|
errorEl.textContent = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
holder.appendChild(node);
|
holder.appendChild(node);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -49,6 +49,44 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const apiCall = async (action, payload = {}) => {
|
||||||
|
const res = await fetch(`/module/pihole/api?action=${encodeURIComponent(action)}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
const data = await res.json().catch(() => ({}));
|
||||||
|
if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
document.querySelectorAll('[data-instance-test]').forEach((btn) => {
|
||||||
|
btn.addEventListener('click', async () => {
|
||||||
|
const card = btn.closest('[data-instance-id]');
|
||||||
|
if (!card) return;
|
||||||
|
const instanceId = card.dataset.instanceId || '';
|
||||||
|
const resultEl = card.querySelector('[data-instance-result]');
|
||||||
|
if (resultEl) {
|
||||||
|
resultEl.classList.remove('is-ok', 'is-auth', 'is-unreachable', 'is-invalid', 'is-error');
|
||||||
|
resultEl.textContent = 'Teste Verbindung...';
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await apiCall('test', { instance: instanceId });
|
||||||
|
if (resultEl) {
|
||||||
|
const statusClass = res.status ? `is-${res.status}` : 'is-ok';
|
||||||
|
resultEl.classList.add(statusClass);
|
||||||
|
resultEl.textContent = res.message || 'Verbindung OK.';
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (resultEl) {
|
||||||
|
const msg = err.message || 'Fehler';
|
||||||
|
resultEl.classList.add('is-error');
|
||||||
|
resultEl.textContent = `Test fehlgeschlagen: ${msg}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
if (newBtn) {
|
if (newBtn) {
|
||||||
newBtn.addEventListener('click', () => {
|
newBtn.addEventListener('click', () => {
|
||||||
resetForm();
|
resetForm();
|
||||||
|
|||||||
@@ -220,42 +220,51 @@ if ($action === 'dashboard') {
|
|||||||
$instancePayloads = [];
|
$instancePayloads = [];
|
||||||
$statuses = [];
|
$statuses = [];
|
||||||
|
|
||||||
|
$makeError = function (string $scope, array $result): array {
|
||||||
|
return [
|
||||||
|
'scope' => $scope,
|
||||||
|
'error' => $result['error'] ?? 'error',
|
||||||
|
'http_code' => $result['http_code'] ?? 0,
|
||||||
|
'url' => $result['url'] ?? '',
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
foreach ($instances as $id => $instance) {
|
foreach ($instances as $id => $instance) {
|
||||||
$errors = [];
|
$errors = [];
|
||||||
|
|
||||||
$summary = $apiRequest($instance, ['summaryRaw' => 1]);
|
$summary = $apiRequest($instance, ['summaryRaw' => 1]);
|
||||||
if (!$summary['ok']) {
|
if (!$summary['ok']) {
|
||||||
$errors[] = ['scope' => 'summary', 'error' => $summary['error'] ?? 'error'];
|
$errors[] = $makeError('summary', $summary);
|
||||||
}
|
}
|
||||||
|
|
||||||
$topItems = $apiRequest($instance, ['topItems' => 50]);
|
$topItems = $apiRequest($instance, ['topItems' => 50]);
|
||||||
if (!$topItems['ok']) {
|
if (!$topItems['ok']) {
|
||||||
$errors[] = ['scope' => 'topItems', 'error' => $topItems['error'] ?? 'error'];
|
$errors[] = $makeError('topItems', $topItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
$queryTypes = $apiRequest($instance, ['getQueryTypes' => 1]);
|
$queryTypes = $apiRequest($instance, ['getQueryTypes' => 1]);
|
||||||
if (!$queryTypes['ok']) {
|
if (!$queryTypes['ok']) {
|
||||||
$errors[] = ['scope' => 'queryTypes', 'error' => $queryTypes['error'] ?? 'error'];
|
$errors[] = $makeError('queryTypes', $queryTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
$querySources = $apiRequest($instance, ['getQuerySources' => 1]);
|
$querySources = $apiRequest($instance, ['getQuerySources' => 1]);
|
||||||
if (!$querySources['ok']) {
|
if (!$querySources['ok']) {
|
||||||
$errors[] = ['scope' => 'querySources', 'error' => $querySources['error'] ?? 'error'];
|
$errors[] = $makeError('querySources', $querySources);
|
||||||
}
|
}
|
||||||
|
|
||||||
$forwardDest = $apiRequest($instance, ['getForwardDestinations' => 1]);
|
$forwardDest = $apiRequest($instance, ['getForwardDestinations' => 1]);
|
||||||
if (!$forwardDest['ok']) {
|
if (!$forwardDest['ok']) {
|
||||||
$errors[] = ['scope' => 'forwardDestinations', 'error' => $forwardDest['error'] ?? 'error'];
|
$errors[] = $makeError('forwardDestinations', $forwardDest);
|
||||||
}
|
}
|
||||||
|
|
||||||
$recentBlocked = $apiRequest($instance, ['recentBlocked' => 30]);
|
$recentBlocked = $apiRequest($instance, ['recentBlocked' => 30]);
|
||||||
if (!$recentBlocked['ok']) {
|
if (!$recentBlocked['ok']) {
|
||||||
$errors[] = ['scope' => 'recentBlocked', 'error' => $recentBlocked['error'] ?? 'error'];
|
$errors[] = $makeError('recentBlocked', $recentBlocked);
|
||||||
}
|
}
|
||||||
|
|
||||||
$versions = $apiRequest($instance, ['versions' => 1]);
|
$versions = $apiRequest($instance, ['versions' => 1]);
|
||||||
if (!$versions['ok']) {
|
if (!$versions['ok']) {
|
||||||
$errors[] = ['scope' => 'versions', 'error' => $versions['error'] ?? 'error'];
|
$errors[] = $makeError('versions', $versions);
|
||||||
}
|
}
|
||||||
|
|
||||||
$summaryData = $summary['ok'] ? $summary['data'] : null;
|
$summaryData = $summary['ok'] ? $summary['data'] : null;
|
||||||
@@ -364,6 +373,55 @@ if ($action === 'dashboard') {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($action === 'test') {
|
||||||
|
require_admin();
|
||||||
|
$target = (string)($payload['instance'] ?? '');
|
||||||
|
if ($target === '') {
|
||||||
|
$respond(['ok' => false, 'error' => 'missing_instance'], 400);
|
||||||
|
}
|
||||||
|
if (!isset($instances[$target])) {
|
||||||
|
$respond(['ok' => false, 'error' => 'invalid_instance'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$instance = $instances[$target];
|
||||||
|
$result = $apiRequest($instance, ['summaryRaw' => 1]);
|
||||||
|
if ($result['ok']) {
|
||||||
|
$respond([
|
||||||
|
'ok' => true,
|
||||||
|
'status' => 'ok',
|
||||||
|
'message' => 'Verbindung OK. API antwortet.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$httpCode = (int)($result['http_code'] ?? 0);
|
||||||
|
$error = (string)($result['error'] ?? 'error');
|
||||||
|
$status = 'error';
|
||||||
|
$message = 'Unbekannter Fehler.';
|
||||||
|
|
||||||
|
if ($httpCode === 0) {
|
||||||
|
$status = 'unreachable';
|
||||||
|
$message = 'Host nicht erreichbar oder kein HTTP-Response.';
|
||||||
|
} elseif ($httpCode === 401 || $httpCode === 403) {
|
||||||
|
$status = 'auth';
|
||||||
|
$message = 'API Token/Passwort falsch oder nicht berechtigt.';
|
||||||
|
} elseif ($error === 'invalid_json') {
|
||||||
|
$status = 'invalid';
|
||||||
|
$message = 'API antwortet nicht mit JSON. URL oder API-Pfad pruefen.';
|
||||||
|
} else {
|
||||||
|
$status = 'error';
|
||||||
|
$message = 'API Fehler: ' . $error . ' (HTTP ' . $httpCode . ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
$respond([
|
||||||
|
'ok' => false,
|
||||||
|
'status' => $status,
|
||||||
|
'message' => $message,
|
||||||
|
'http_code' => $httpCode,
|
||||||
|
'error' => $error,
|
||||||
|
'url' => (string)($result['url'] ?? ''),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
if ($action === 'disable') {
|
if ($action === 'disable') {
|
||||||
require_admin();
|
require_admin();
|
||||||
$minutes = (int)($payload['minutes'] ?? 0);
|
$minutes = (int)($payload['minutes'] ?? 0);
|
||||||
|
|||||||
@@ -131,5 +131,6 @@ $hasConfig = !empty($instances);
|
|||||||
<button class="nav-link" data-action="update" data-instance="">Pi-hole Update</button>
|
<button class="nav-link" data-action="update" data-instance="">Pi-hole Update</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="pihole-update" data-instance-update></div>
|
<div class="pihole-update" data-instance-update></div>
|
||||||
|
<div class="pihole-error" data-instance-errors></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -174,11 +174,13 @@ if ($primaryId === '') {
|
|||||||
</div>
|
</div>
|
||||||
<div class="pihole-card-actions">
|
<div class="pihole-card-actions">
|
||||||
<button class="nav-link" type="button" data-instance-edit>Bearbeiten</button>
|
<button class="nav-link" type="button" data-instance-edit>Bearbeiten</button>
|
||||||
|
<button class="nav-link" type="button" data-instance-test>Test Verbindung</button>
|
||||||
<form method="post" onsubmit="return confirm('Instanz wirklich loeschen?')">
|
<form method="post" onsubmit="return confirm('Instanz wirklich loeschen?')">
|
||||||
<input type="hidden" name="delete_id" value="<?= e((string)($instance['id'] ?? '')) ?>">
|
<input type="hidden" name="delete_id" value="<?= e((string)($instance['id'] ?? '')) ?>">
|
||||||
<button class="nav-link" type="submit">Loeschen</button>
|
<button class="nav-link" type="submit">Loeschen</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="pihole-test-result" data-instance-result></div>
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|||||||
Reference in New Issue
Block a user