Files
desktop/custom/apps/pihole/pages/index.php
Lars Gebhardt-Kusche ac2d95fc56
All checks were successful
Deploy / deploy-staging (push) Successful in 25s
Deploy / deploy-production (push) Has been skipped
asdasd
2026-06-25 00:17:31 +02:00

261 lines
11 KiB
PHP

<?php
declare(strict_types=1);
require_once dirname(__DIR__, 3) . '/src/App/bootstrap.php';
require_once dirname(__DIR__) . '/bootstrap.php';
use ModulesCore\ModuleHttp;
session_start();
$projectRoot = dirname(__DIR__, 3);
ModuleHttp::requireDesktopAccess($projectRoot);
$isPartial = !empty($_GET['partial']);
$view = trim((string) ($_GET['view'] ?? 'dashboard'));
$allowedViews = ['dashboard', 'lists', 'queries', 'instances', 'setup'];
if (!in_array($view, $allowedViews, true)) {
$view = 'dashboard';
}
$viewMeta = [
'dashboard' => [
'label' => 'Dashboard',
'title' => 'Pi-hole Dashboard',
'description' => 'Status, Blockings, Usage und direkte Steuerung fuer alle angebundenen Instanzen.',
'nav_description' => 'Live-Status, Sperren und aggregierte DNS-Zahlen.',
],
'lists' => [
'label' => 'Listen',
'title' => 'Listen & Domains',
'description' => 'Gravity-Updates, Top-Domains und neue Listen-/Domain-Eintraege.',
'nav_description' => 'Blocklisten, Whitelist und Adlist-Pflege.',
],
'queries' => [
'label' => 'Queries',
'title' => 'Zugriffe & Blockings',
'description' => 'Aktuelle Blockings und Top-Clients ueber alle Instanzen.',
'nav_description' => 'Abfragen, blockierte Domains und Client-Nutzung.',
],
'instances' => [
'label' => 'Instanzen',
'title' => 'Pi-hole Instanzen',
'description' => 'Hosts, Zugangsdaten und Primaer-Instanz administrieren.',
'nav_description' => 'Instanzen anlegen, testen, bearbeiten und loeschen.',
],
'setup' => [
'label' => 'Setup',
'title' => 'Setup',
'description' => 'Timeouts, API-Pfad und globale Pi-hole-Moduloptionen verwalten.',
'nav_description' => 'Standard-Timeouts, TLS und Refresh-Regeln.',
],
];
$assetVersion = static function (string $relativePath): string {
$fullPath = dirname(__DIR__) . '/' . ltrim($relativePath, '/');
$mtime = is_file($fullPath) ? filemtime($fullPath) : false;
return $mtime === false ? '0' : (string) $mtime;
};
$renderSetup = static function (): void {
$moduleName = 'pihole';
$manifestPath = dirname(__DIR__) . '/module.json';
$manifestRaw = is_file($manifestPath) ? file_get_contents($manifestPath) : false;
$manifest = is_string($manifestRaw) && trim($manifestRaw) !== '' ? json_decode($manifestRaw, true) : [];
$fields = is_array($manifest['setup']['fields'] ?? null) ? $manifest['setup']['fields'] : [];
$settings = modules()->settings($moduleName);
$notice = null;
$error = null;
$getValue = static function (array $source, string $path): mixed {
$value = $source;
foreach (explode('.', $path) as $segment) {
if (!is_array($value) || !array_key_exists($segment, $value)) {
return null;
}
$value = $value[$segment];
}
return $value;
};
$setValue = static function (array &$target, string $path, mixed $value): void {
$segments = explode('.', $path);
$cursor = &$target;
foreach ($segments as $index => $segment) {
if ($index === count($segments) - 1) {
$cursor[$segment] = $value;
return;
}
if (!isset($cursor[$segment]) || !is_array($cursor[$segment])) {
$cursor[$segment] = [];
}
$cursor = &$cursor[$segment];
}
};
if ($_SERVER['REQUEST_METHOD'] === 'POST' && (string) ($_POST['action'] ?? '') === 'save_setup') {
try {
$nextSettings = $settings;
foreach ($fields as $field) {
if (!is_array($field)) {
continue;
}
$name = trim((string) ($field['name'] ?? ''));
$type = trim((string) ($field['type'] ?? 'text'));
if ($name === '') {
continue;
}
$raw = $_POST['settings'][$name] ?? null;
$value = match ($type) {
'checkbox' => $raw !== null,
'number' => ($raw === null || $raw === '') ? null : (is_numeric((string) $raw) ? 0 + $raw : null),
default => is_string($raw) ? trim($raw) : '',
};
$setValue($nextSettings, $name, $value);
}
modules()->saveSettings($moduleName, $nextSettings);
$settings = modules()->settings($moduleName);
$notice = 'Setup gespeichert.';
} catch (\Throwable $e) {
$error = $e->getMessage();
}
}
?>
<div class="pihole-content">
<?php if ($error): ?>
<section class="window-app-card"><div class="pihole-message pihole-message--error"><?= e($error) ?></div></section>
<?php elseif ($notice): ?>
<section class="window-app-card"><div class="pihole-message pihole-message--success"><?= e($notice) ?></div></section>
<?php endif; ?>
<section class="window-app-card">
<div class="pihole-section-header">
<div>
<h3 class="pihole-section-title">Modul-Setup</h3>
<p class="window-app-copy">Bestehende Nexus-Einstellungen werden uebernommen. Aenderungen werden lokal als Override gespeichert.</p>
</div>
</div>
<form method="post" class="pihole-setup-form">
<input type="hidden" name="action" value="save_setup">
<div class="pihole-setup-grid">
<?php foreach ($fields as $field): ?>
<?php
if (!is_array($field)) {
continue;
}
$name = trim((string) ($field['name'] ?? ''));
$label = trim((string) ($field['label'] ?? $name));
$type = trim((string) ($field['type'] ?? 'text'));
$help = trim((string) ($field['help'] ?? ''));
$value = $getValue($settings, $name);
?>
<label class="pihole-field">
<span><?= e($label) ?></span>
<?php if ($type === 'checkbox'): ?>
<input type="checkbox" name="settings[<?= e($name) ?>]" value="1" <?= !empty($value) ? 'checked' : '' ?>>
<?php elseif ($type === 'number'): ?>
<input type="number" step="any" name="settings[<?= e($name) ?>]" value="<?= e(is_scalar($value) ? (string) $value : '') ?>">
<?php else: ?>
<input type="<?= $type === 'password' ? 'password' : 'text' ?>" name="settings[<?= e($name) ?>]" value="<?= e(is_scalar($value) ? (string) $value : '') ?>">
<?php endif; ?>
<?php if ($help !== ''): ?>
<small class="window-app-meta"><?= e($help) ?></small>
<?php endif; ?>
</label>
<?php endforeach; ?>
</div>
<div class="pihole-actions">
<button class="window-app-nav-button pihole-primary-action" type="submit">Setup speichern</button>
</div>
</form>
</section>
</div>
<?php
};
$renderContent = static function () use ($view, $renderSetup, $viewMeta): void {
$currentViewMeta = $viewMeta[$view] ?? $viewMeta['dashboard'];
$navItems = [
'dashboard' => '/apps/pihole/index.php?view=dashboard',
'lists' => '/apps/pihole/index.php?view=lists',
'queries' => '/apps/pihole/index.php?view=queries',
'instances' => '/apps/pihole/index.php?view=instances',
'setup' => '/apps/pihole/index.php?view=setup',
];
ob_start();
if ($view === 'setup') {
$renderSetup();
} else {
require dirname(__DIR__) . '/partials/' . $view . '.php';
}
$innerContent = (string) ob_get_clean();
?>
<div class="pihole-module-shell window-app-shell">
<div class="window-app-frame pihole-frame">
<aside class="window-app-sidebar pihole-sidebar">
<div class="window-app-brand pihole-brand">
<p class="window-app-kicker">Modul</p>
<h1>Pi-hole</h1>
<p class="window-app-copy">Pi-hole Monitoring, Listen und Steuerung fuer mehrere Instanzen im Desktop-Standardlayout.</p>
</div>
<div class="window-app-nav-list pihole-nav-list">
<?php foreach ($navItems as $navView => $href): ?>
<?php $meta = $viewMeta[$navView] ?? null; ?>
<?php if (!$meta): continue; endif; ?>
<a class="window-app-nav-button pihole-nav-button<?= $view === $navView ? ' is-active' : '' ?>" href="<?= e($href) ?>">
<strong><?= e((string) $meta['label']) ?></strong>
<span class="window-app-meta"><?= e((string) $meta['nav_description']) ?></span>
</a>
<?php endforeach; ?>
</div>
</aside>
<main class="window-app-main pihole-main">
<div class="window-app-panel pihole-panel-shell">
<section class="window-app-hero pihole-hero">
<div>
<p class="window-app-kicker">Pi-hole</p>
<h2 class="window-app-title"><?= e((string) $currentViewMeta['title']) ?></h2>
<p class="window-app-copy"><?= e((string) $currentViewMeta['description']) ?></p>
</div>
<div class="window-app-pill-row pihole-pill-row">
<span class="window-app-pill">Desktop-App</span>
<span class="window-app-pill">Netzwerk-Tools</span>
<span class="window-app-pill">View: <?= e((string) $currentViewMeta['label']) ?></span>
</div>
</section>
<?= $innerContent ?>
</div>
</main>
</div>
</div>
<?php
};
ob_start();
$renderContent();
$pageContent = (string) ob_get_clean();
if ($isPartial) {
echo $pageContent;
return;
}
?><!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Pi-hole</title>
<link rel="stylesheet" href="/assets/desktop/desktop.css">
<link rel="stylesheet" href="/module-assets/index.php?module=pihole&amp;path=assets/pihole.css&amp;v=<?= htmlspecialchars($assetVersion('assets/pihole.css'), ENT_QUOTES) ?>">
</head>
<body>
<?= $pageContent ?>
<script src="/module-assets/index.php?module=pihole&amp;path=assets/pihole.js&amp;v=<?= htmlspecialchars($assetVersion('assets/pihole.js'), ENT_QUOTES) ?>"></script>
<script src="/module-assets/index.php?module=pihole&amp;path=assets/pihole_instances.js&amp;v=<?= htmlspecialchars($assetVersion('assets/pihole_instances.js'), ENT_QUOTES) ?>"></script>
</body>
</html>