diff --git a/modules/boersenchecker/design.json b/modules/boersenchecker/design.json new file mode 100644 index 0000000..a761f63 --- /dev/null +++ b/modules/boersenchecker/design.json @@ -0,0 +1,14 @@ +{ + "eyebrow": "Modul", + "title": "Boersenchecker", + "description": "Depotverwaltung fuer Aktien, Kaufdaten, Kursverlauf und Waehrungsumrechnung.", + "actions": [ + { "label": "Zur Startseite", "href": "/", "variant": "ghost" }, + { "label": "Setup", "href": "/modules/setup/boersenchecker", "variant": "secondary" } + ], + "tabs": [ + { "label": "Ueberblick", "href": "/module/boersenchecker" }, + { "label": "Depotverwaltung", "href": "/module/boersenchecker/depotverwaltung" }, + { "label": "Aktienverwaltung", "href": "/module/boersenchecker/aktienverwaltung" } + ] +} diff --git a/modules/boersenchecker/partials/dashboard.php b/modules/boersenchecker/partials/dashboard.php index 304cb75..eb84e68 100644 --- a/modules/boersenchecker/partials/dashboard.php +++ b/modules/boersenchecker/partials/dashboard.php @@ -1,26 +1,16 @@ + 'Depotverwaltung', + 'description' => 'Depots, Positionen und Kurs-Historien verwalten.', + 'tabs' => [ + ['label' => 'Ueberblick', 'href' => '/module/boersenchecker'], + ['label' => 'Depotverwaltung', 'href' => '/module/boersenchecker/depotverwaltung', 'active' => true], + ['label' => 'Aktienverwaltung', 'href' => '/module/boersenchecker/aktienverwaltung'], + ], +]) ?>
-
-
-
-
Boersenchecker Modul
-

Depotverwaltung

-

Depots, Positionen und Kurs-Historien verwalten. Die Waehrungsumrechnung nutzt weiterhin die bestehende FX-Logik des Mining-Checkers.

-
- -
- - -
@@ -479,3 +469,4 @@
+ diff --git a/modules/boersenchecker/partials/home.php b/modules/boersenchecker/partials/home.php index e2597d2..f236cb7 100644 --- a/modules/boersenchecker/partials/home.php +++ b/modules/boersenchecker/partials/home.php @@ -1,3 +1,12 @@ + 'Depot-Ueberblick', + 'description' => 'Depots, Aktien und Kursverlaeufe in einer Oberflaeche.', + 'tabs' => [ + ['label' => 'Ueberblick', 'href' => '/module/boersenchecker', 'active' => true], + ['label' => 'Depotverwaltung', 'href' => '/module/boersenchecker/depotverwaltung'], + ['label' => 'Aktienverwaltung', 'href' => '/module/boersenchecker/aktienverwaltung'], + ], +]) ?>
@@ -10,29 +19,6 @@ ]; }, $positions), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?> -
-
-
-
Boersenchecker Modul
-

Depot-Ueberblick

-

Depots, Aktien und Kursverlaeufe in einer Oberflaeche. Die Navigation folgt jetzt dem gleichen sichtbaren Prinzip wie beim Mining-Checker.

-
-
- Zur Startseite -
-
Aktives Depot
-
0 && $portfolios !== [] ? e((string) (($portfolios[array_search($selectedPortfolioId, array_column($portfolios, 'id'), true)]['name'] ?? 'Auswahl aktiv'))) : 'Kein Depot ausgewaehlt' ?>
-
-
-
- - -
-
@@ -207,3 +193,4 @@
+ diff --git a/modules/boersenchecker/partials/instruments.php b/modules/boersenchecker/partials/instruments.php index 38403fd..10070f9 100644 --- a/modules/boersenchecker/partials/instruments.php +++ b/modules/boersenchecker/partials/instruments.php @@ -1,25 +1,15 @@ + 'Aktienverwaltung', + 'description' => 'Stammdaten der Aktien pflegen, Symbole suchen und manuelle Kurse verwalten.', + 'tabs' => [ + ['label' => 'Ueberblick', 'href' => '/module/boersenchecker'], + ['label' => 'Depotverwaltung', 'href' => '/module/boersenchecker/depotverwaltung'], + ['label' => 'Aktienverwaltung', 'href' => '/module/boersenchecker/aktienverwaltung', 'active' => true], + ], +]) ?>
-
-
-
-
Boersenchecker Modul
-

Aktienverwaltung

-

Stammdaten der Aktien pflegen, Symbole suchen und manuelle Kurse verwalten.

-
- -
- - -
@@ -155,3 +145,4 @@
+ diff --git a/modules/kea/design.json b/modules/kea/design.json new file mode 100644 index 0000000..36b7962 --- /dev/null +++ b/modules/kea/design.json @@ -0,0 +1,9 @@ +{ + "eyebrow": "Modul", + "title": "KEA DHCP", + "description": "Verwaltung von KEA DHCP Hosts und Reservierungen.", + "actions": [ + { "label": "Gruppen verwalten", "href": "/module/kea/groups", "variant": "secondary" }, + { "label": "Setup", "href": "/modules/setup/kea", "variant": "secondary" } + ] +} diff --git a/modules/kea/partials/dashboard.php b/modules/kea/partials/dashboard.php index 6ced4ab..319ea3a 100644 --- a/modules/kea/partials/dashboard.php +++ b/modules/kea/partials/dashboard.php @@ -6,6 +6,10 @@ * @var array $stats Kennzahlen fuer die Uebersicht. */ ?> + 'KEA DHCP Hosts', + 'description' => 'Reservierungen und aktuelle Leases aus der KEA-Datenbank.', +]) ?>
@@ -15,10 +19,6 @@ Automatische Aktualisierung alle 5 Sekunden.

-
@@ -226,3 +226,4 @@ window.setInterval(refresh, 5000); })(); + diff --git a/modules/pihole/design.json b/modules/pihole/design.json new file mode 100644 index 0000000..d9e8d7d --- /dev/null +++ b/modules/pihole/design.json @@ -0,0 +1,15 @@ +{ + "eyebrow": "Modul", + "title": "Pi-hole", + "description": "Pi-hole Monitoring, Listen und Steuerung fuer zwei Instanzen.", + "actions": [ + { "label": "Zur Startseite", "href": "/", "variant": "ghost" }, + { "label": "Instanzen", "href": "/module/pihole/instances", "variant": "secondary" } + ], + "tabs": [ + { "label": "Dashboard", "href": "/module/pihole" }, + { "label": "Instanzen", "href": "/module/pihole/instances" }, + { "label": "Listen", "href": "/module/pihole/lists" }, + { "label": "Queries", "href": "/module/pihole/queries" } + ] +} diff --git a/modules/pihole/pages/index.php b/modules/pihole/pages/index.php index 4e9a5a3..0292e7d 100644 --- a/modules/pihole/pages/index.php +++ b/modules/pihole/pages/index.php @@ -6,10 +6,17 @@ $assets->addScript('/module/pihole/asset?file=pihole.js', 'footer', true); $instances = module_fn('pihole', 'instances'); $hasConfig = !empty($instances); ?> + 'Pi-hole Dashboard', + 'description' => 'Status, Blockings, Usage und Steuerung fuer beide Instanzen.', + 'tabs' => [ + ['label' => 'Dashboard', 'href' => '/module/pihole', 'active' => true], + ['label' => 'Instanzen', 'href' => '/module/pihole/instances'], + ['label' => 'Listen', 'href' => '/module/pihole/lists'], + ['label' => 'Queries', 'href' => '/module/pihole/queries'], + ], +]) ?>
-
Pi-hole
-

Pi-hole Dashboard

-

Status, Blockings, Usage und Steuerung fuer beide Instanzen.

@@ -134,3 +141,4 @@ $hasConfig = !empty($instances);
+ diff --git a/modules/pihole/pages/instances.php b/modules/pihole/pages/instances.php index 2ea1807..1ed3290 100644 --- a/modules/pihole/pages/instances.php +++ b/modules/pihole/pages/instances.php @@ -132,15 +132,23 @@ if ($primaryId === '') { } } ?> + 'Pi-hole Instanzen', + 'description' => 'Pi-hole Instanzen hinzufuegen, bearbeiten und loeschen.', + 'tabs' => [ + ['label' => 'Dashboard', 'href' => '/module/pihole'], + ['label' => 'Instanzen', 'href' => '/module/pihole/instances', 'active' => true], + ['label' => 'Listen', 'href' => '/module/pihole/lists'], + ['label' => 'Queries', 'href' => '/module/pihole/queries'], + ], +]) ?>
-
Pi-hole
-
+

Instanzen

-

Pi-hole Instanzen hinzufuegen, bearbeiten und loeschen.

@@ -222,3 +230,4 @@ if ($primaryId === '') {
+ diff --git a/public/assets/css/app.css b/public/assets/css/app.css index fdcc639..b9cca60 100644 --- a/public/assets/css/app.css +++ b/public/assets/css/app.css @@ -897,3 +897,192 @@ a { .ip-dot.is-used { background: color-mix(in srgb, var(--muted) 55%, var(--surface)); } + +.module-shell { + color: var(--text); +} + +.module-page-bg { + position: relative; + padding: 8px 0 30px; +} + +.module-page-bg::before { + content: ""; + position: absolute; + inset: 0; + pointer-events: none; + background: + radial-gradient(circle at 12% 20%, color-mix(in srgb, var(--brand-accent-2) 12%, transparent), transparent 24%), + radial-gradient(circle at 90% 6%, color-mix(in srgb, var(--brand-accent-3) 12%, transparent), transparent 20%); +} + +.module-page-stack { + position: relative; + display: grid; + gap: 18px; +} + +.module-hero { + display: grid; + gap: 18px; + padding: 28px; + border: 1px solid var(--line); + border-radius: 28px; + background: + linear-gradient(135deg, rgba(255, 255, 255, 0.94), rgba(245, 252, 251, 0.88)), + linear-gradient(90deg, color-mix(in srgb, var(--brand-accent) 14%, transparent), color-mix(in srgb, var(--brand-accent-2) 14%, transparent)); + box-shadow: var(--shadow); +} + +:root[data-theme="night"] .module-hero { + background: + linear-gradient(135deg, rgba(8, 18, 28, 0.94), rgba(15, 29, 42, 0.86)), + linear-gradient(90deg, color-mix(in srgb, var(--brand-accent) 18%, transparent), color-mix(in srgb, var(--brand-accent-2) 16%, transparent)); +} + +.module-hero-top { + display: grid; + gap: 16px; + grid-template-columns: minmax(0, 1.6fr) minmax(220px, 0.8fr); + align-items: start; +} + +.module-hero-copy, +.module-hero-actions { + display: grid; + gap: 12px; +} + +.module-title { + margin: 0; + font-size: clamp(1.75rem, 4vw, 2.9rem); + line-height: 1; + font-weight: 700; + letter-spacing: -0.03em; +} + +.module-lead { + margin: 0; + color: var(--muted); + font-size: 1rem; + line-height: 1.5; +} + +.module-tabs { + display: flex; + flex-wrap: wrap; + gap: 10px; +} + +.module-button { + display: inline-flex; + align-items: center; + justify-content: center; + border: 1px solid transparent; + border-radius: 16px; + padding: 12px 16px; + cursor: pointer; + text-decoration: none; + transition: 160ms ease; + font: inherit; +} + +.module-button:hover { + transform: translateY(-1px); +} + +.module-button--tab-active, +.module-button--primary { + background: linear-gradient(135deg, var(--brand-accent), var(--brand-accent-3)); + color: #fff7fb; + font-weight: 700; + box-shadow: 0 14px 28px color-mix(in srgb, var(--brand-accent) 18%, transparent); +} + +.module-button--tab, +.module-button--secondary { + background: rgba(255, 255, 255, 0.92); + color: #09111f; + font-weight: 700; +} + +.module-button--ghost { + background: color-mix(in srgb, var(--brand-accent) 14%, transparent); + border-color: color-mix(in srgb, var(--brand-accent) 34%, transparent); + color: var(--brand-accent); + font-weight: 700; +} + +.module-box, +.module-box-soft, +.module-box-table, +.module-box-empty { + border: 1px solid var(--line); + border-radius: 22px; + background: var(--surface); + box-shadow: 0 12px 30px rgba(1, 22, 32, 0.08); + backdrop-filter: blur(8px); +} + +.module-box, +.module-box-soft, +.module-box-table { + padding: 18px 20px; +} + +.module-box-soft { + background: linear-gradient(180deg, rgba(255,255,255,0.96), rgba(248,252,252,0.92)); +} + +.module-box-empty { + padding: 20px; +} + +.module-box-grid { + display: grid; + gap: 16px; +} + +.module-box-grid--stats { + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); +} + +.module-box-grid--panels { + grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); +} + +.module-box-head { + display: flex; + align-items: end; + justify-content: space-between; + gap: 16px; + flex-wrap: wrap; +} + +.module-box-head p { + margin: 6px 0 0; + color: var(--muted); +} + +.module-box-title { + margin: 0; + font-size: 1.1rem; + font-weight: 700; +} + +.module-box-table { + overflow: auto; + padding: 0; +} + +.module-box-table > .module-box-head, +.module-box-table > .module-box-copy { + padding: 18px 20px 0; +} + +@media (max-width: 980px) { + .module-hero-top { + grid-template-columns: 1fr; + } +} diff --git a/src/App/functions.php b/src/App/functions.php index 5c74ed0..147c2c4 100644 --- a/src/App/functions.php +++ b/src/App/functions.php @@ -253,6 +253,96 @@ function module_tpl(string $module, string $name, array $data = []): void } } +function module_design(string $module): array +{ + if (preg_match('/[^a-zA-Z0-9_\-]/', $module)) { + return []; + } + + static $cache = []; + if (array_key_exists($module, $cache)) { + return $cache[$module]; + } + + $path = __DIR__ . '/../../modules/' . $module . '/design.json'; + if (!is_file($path)) { + $cache[$module] = []; + return $cache[$module]; + } + + $raw = file_get_contents($path); + $decoded = is_string($raw) && $raw !== '' ? json_decode($raw, true) : null; + $cache[$module] = is_array($decoded) ? $decoded : []; + return $cache[$module]; +} + +function module_shell_header(string $module, array $options = []): string +{ + $design = module_design($module); + $requestPath = app()->request()->path(); + $title = trim((string) ($options['title'] ?? $design['title'] ?? ucfirst($module))); + $description = trim((string) ($options['description'] ?? $design['description'] ?? '')); + $eyebrow = trim((string) ($options['eyebrow'] ?? $design['eyebrow'] ?? 'Modul')); + $actions = is_array($options['actions'] ?? null) ? $options['actions'] : (is_array($design['actions'] ?? null) ? $design['actions'] : []); + $tabs = is_array($options['tabs'] ?? null) ? $options['tabs'] : (is_array($design['tabs'] ?? null) ? $design['tabs'] : []); + + $html = '
'; + $html .= '
'; + $html .= '
'; + $html .= '
'; + $html .= '
' . e($eyebrow) . '
'; + $html .= '

' . e($title) . '

'; + if ($description !== '') { + $html .= '

' . e($description) . '

'; + } + $html .= '
'; + + if ($actions !== []) { + $html .= '
'; + foreach ($actions as $action) { + if (!is_array($action)) { + continue; + } + $label = trim((string) ($action['label'] ?? '')); + $href = trim((string) ($action['href'] ?? '')); + if ($label === '' || $href === '') { + continue; + } + $variant = trim((string) ($action['variant'] ?? 'secondary')); + $class = $variant === 'ghost' ? 'module-button module-button--ghost' : 'module-button module-button--secondary'; + $html .= '' . e($label) . ''; + } + $html .= '
'; + } + + $html .= '
'; + if ($tabs !== []) { + $html .= ''; + } + $html .= '
'; + + return $html; +} + +function module_shell_footer(): string +{ + return '
'; +} + /** * HTML Escaping Helper. */