diff --git a/partials/landingpages/index.php b/partials/landingpages/index.php index 0f119ae..66b994a 100755 --- a/partials/landingpages/index.php +++ b/partials/landingpages/index.php @@ -8,41 +8,6 @@ $modules = array_values(array_filter( static fn (array $module): bool => !empty($module['enabled']) )); ?> -
- - Nexus Logo - -
- Nexus -

-

Kompakter Einstieg fuer die verfuegbaren Module.

-
-
- isEnabled()): ?> - - - - - - -
-
-
diff --git a/partials/landingpages/modules/setup.php b/partials/landingpages/modules/setup.php index d32212b..da689c6 100644 --- a/partials/landingpages/modules/setup.php +++ b/partials/landingpages/modules/setup.php @@ -60,6 +60,8 @@ $getNested = function (array $source, string $path): mixed { }; $dbGroups = []; +$fieldsByDbGroup = []; +$generalFields = []; foreach ($fields as $field) { $name = (string)($field['name'] ?? ''); if (!str_contains($name, '.')) { @@ -77,6 +79,68 @@ foreach ($fields as $field) { $dbGroups[$group] = $label; } +foreach ($fields as $field) { + $name = (string)($field['name'] ?? ''); + if (str_contains($name, '.')) { + [$group] = explode('.', $name, 2); + if (array_key_exists($group, $dbGroups)) { + $fieldsByDbGroup[$group][] = $field; + continue; + } + } + + $generalFields[] = $field; +} + +$driverOptions = [ + 'pgsql' => 'PostgreSQL', + 'mysql' => 'MySQL / MariaDB', + 'sqlite' => 'SQLite', +]; + +$renderField = function (array $field) use ($current, $getNested, $driverOptions): void { + $name = (string)($field['name'] ?? ''); + if ($name === '') { + return; + } + $label = (string)($field['label'] ?? $name); + $type = (string)($field['type'] ?? 'text'); + $required = !empty($field['required']); + $help = (string)($field['help'] ?? $field['description'] ?? ''); + $postKey = str_replace('.', '_', $name); + + $value = ''; + if ($name === 'kea_auto_init') { + $value = !empty($current[$name]) ? '1' : '0'; + } elseif (str_contains($name, '.')) { + $value = (string)($getNested($current, $name) ?? ''); + } else { + $value = (string)($current[$name] ?? ''); + } + ?> + + -
+
Setup
-

– Einrichtung

+

– Einrichtung

Trage die benötigten Informationen für das Modul ein.

@@ -152,60 +216,66 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
-
+
-
- - - - - - -
- Datenbankverbindungen testen - Der Test nutzt die aktuell eingetragenen Werte aus diesem Formular und speichert sie nicht. -
- $label): ?> - + + +
+
+
+ Allgemein +

Moduleinstellungen

+
+
+
+ +
-
+
-
+ +
+
+
+ Datenbanken +

Verbindungen

+

Jede Verbindung kann getrennt konfiguriert und getestet werden.

+
+
+
+ $label): ?> + + +
+
+ $label): ?> +
+
+
+ +

konfigurieren

+
+ +
+
+ + + +
+
+ +
+
+ + +
Zugriff verwalten Zurück diff --git a/partials/structure/layout_start.php b/partials/structure/layout_start.php index 60fb263..000fe7d 100755 --- a/partials/structure/layout_start.php +++ b/partials/structure/layout_start.php @@ -1,3 +1,16 @@ +request()->path(); +$currentModuleName = current_module_name(); +if ($currentModuleName === null && preg_match('~^/modules/(?:setup|access)/([a-zA-Z0-9_-]+)~', $requestPath, $moduleMatch)) { + $currentModuleName = $moduleMatch[1]; +} +$currentModule = $currentModuleName !== null ? modules()->get($currentModuleName) : null; +$headerEyebrow = $currentModule ? 'Modul' : 'Nexus'; +$headerTitle = $currentModule ? (string)($currentModule['title'] ?? $currentModuleName) : (defined('APP_DOMAIN_PRIMARY') ? (string)APP_DOMAIN_PRIMARY : 'Nexus'); +$headerText = $currentModule ? (string)($currentModule['description'] ?? '') : 'Kompakter Einstieg fuer die verfuegbaren Module.'; +$auth = app()->auth(); +$authUser = $auth->user(); +?> @@ -6,18 +19,62 @@ Nexus
+
+ + Nexus Logo + +
+ +

+ +

+ +
+
+ isEnabled()): ?> + + + + + + +
+
diff --git a/public/assets/css/app.css b/public/assets/css/app.css index 5e2cfd2..85c632e 100644 --- a/public/assets/css/app.css +++ b/public/assets/css/app.css @@ -194,6 +194,10 @@ a { color: var(--muted); } +.app-header { + margin-bottom: 18px; +} + .stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); @@ -591,3 +595,98 @@ a { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 14px; } + +.setup-shell { + display: grid; + gap: 10px; +} + +.setup-title { + margin: 0.75rem 0 0; +} + +.setup-notice { + margin-top: 1rem; + border: 1px solid var(--accent-2); + border-radius: 8px; + background: color-mix(in srgb, var(--surface) 94%, transparent); + padding: 14px; +} + +.setup-form { + margin-top: 1rem; + display: grid; + gap: 16px; +} + +.setup-panel, +.setup-db-panel { + border: 1px solid var(--line); + border-radius: 8px; + background: color-mix(in srgb, var(--surface) 92%, transparent); + padding: 16px; + box-shadow: 0 10px 24px rgba(1, 22, 32, 0.06); +} + +.setup-panel--flat { + border: 0; + border-radius: 0; + background: transparent; + padding: 0; + box-shadow: none; +} + +.setup-db-panel { + scroll-margin-top: 18px; +} + +.setup-panel__head { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 14px; + margin-bottom: 14px; +} + +.setup-panel__head h2, +.setup-panel__head h3 { + margin: 8px 0 0; +} + +.setup-panel__head p { + margin: 6px 0 0; +} + +.setup-tabs { + display: flex; + gap: 10px; + flex-wrap: wrap; + margin-bottom: 14px; +} + +.setup-db-panels { + display: grid; + gap: 14px; +} + +.setup-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); + gap: 14px; +} + +.setup-field { + display: grid; + gap: 6px; +} + +.setup-field > span { + font-weight: 800; + color: var(--text); +} + +.setup-actions { + display: flex; + gap: 10px; + flex-wrap: wrap; +} diff --git a/public/assets/js/app.js b/public/assets/js/app.js index b86a46c..728c96f 100755 --- a/public/assets/js/app.js +++ b/public/assets/js/app.js @@ -8,38 +8,75 @@ function readThemePreference(key, fallback) { } } -const themeMode = readThemePreference('nexus.theme', document.documentElement.dataset.theme || 'day'); -const themeAccent = readThemePreference('nexus.accent', document.documentElement.dataset.accent || 'logo'); +const moduleName = document.documentElement.dataset.module || ''; +const mainThemeMode = readThemePreference('nexus.theme', 'day'); +const mainThemeAccent = readThemePreference('nexus.accent', 'logo'); +const moduleThemeMode = moduleName ? readThemePreference(`nexus.module.${moduleName}.theme`, 'inherit') : mainThemeMode; +const moduleThemeAccent = moduleName ? readThemePreference(`nexus.module.${moduleName}.accent`, 'inherit') : mainThemeAccent; -function applyTheme(mode, accent) { - const normalizedMode = ['day', 'night'].includes(mode) ? mode : 'day'; - const normalizedAccent = ['logo', 'pink', 'cyan', 'orange', 'green'].includes(accent) ? accent : 'logo'; - document.documentElement.dataset.theme = normalizedMode; - document.documentElement.dataset.accent = normalizedAccent; +function normalizeThemeValue(value, allowed, fallback) { + return allowed.includes(value) ? value : fallback; +} + +function storedTheme() { + const rawMode = moduleName ? readThemePreference(`nexus.module.${moduleName}.theme`, 'inherit') : readThemePreference('nexus.theme', 'day'); + const rawAccent = moduleName ? readThemePreference(`nexus.module.${moduleName}.accent`, 'inherit') : readThemePreference('nexus.accent', 'logo'); + const mode = moduleName ? normalizeThemeValue(rawMode, ['inherit', 'day', 'night'], 'inherit') : normalizeThemeValue(rawMode, ['day', 'night'], 'day'); + const accent = moduleName ? normalizeThemeValue(rawAccent, ['inherit', 'logo', 'pink', 'cyan', 'orange', 'green'], 'inherit') : normalizeThemeValue(rawAccent, ['logo', 'pink', 'cyan', 'orange', 'green'], 'logo'); + return { mode, accent }; +} + +function applyTheme(mode, accent, persist = true) { + const allowedModes = moduleName ? ['inherit', 'day', 'night'] : ['day', 'night']; + const allowedAccents = moduleName ? ['inherit', 'logo', 'pink', 'cyan', 'orange', 'green'] : ['logo', 'pink', 'cyan', 'orange', 'green']; + const normalizedMode = normalizeThemeValue(mode, allowedModes, moduleName ? 'inherit' : 'day'); + const normalizedAccent = normalizeThemeValue(accent, allowedAccents, moduleName ? 'inherit' : 'logo'); + const effectiveMode = normalizedMode === 'inherit' ? mainThemeMode : normalizedMode; + const effectiveAccent = normalizedAccent === 'inherit' ? mainThemeAccent : normalizedAccent; + document.documentElement.dataset.theme = effectiveMode; + document.documentElement.dataset.accent = effectiveAccent; try { - localStorage.setItem('nexus.theme', normalizedMode); - localStorage.setItem('nexus.accent', normalizedAccent); + if (persist) { + if (moduleName) { + if (normalizedMode === 'inherit') { + localStorage.removeItem(`nexus.module.${moduleName}.theme`); + } else { + localStorage.setItem(`nexus.module.${moduleName}.theme`, normalizedMode); + } + if (normalizedAccent === 'inherit') { + localStorage.removeItem(`nexus.module.${moduleName}.accent`); + } else { + localStorage.setItem(`nexus.module.${moduleName}.accent`, normalizedAccent); + } + } else { + localStorage.setItem('nexus.theme', normalizedMode); + localStorage.setItem('nexus.accent', normalizedAccent); + } + } } catch (error) { // Ignore blocked storage; the current page still receives the theme. } + return { mode: normalizedMode, accent: normalizedAccent }; } -applyTheme(themeMode, themeAccent); +const initialTheme = applyTheme(moduleThemeMode, moduleThemeAccent, false); const themeModeSelect = document.querySelector('[data-theme-mode]'); const themeAccentSelect = document.querySelector('[data-theme-accent]'); if (themeModeSelect) { - themeModeSelect.value = document.documentElement.dataset.theme; + themeModeSelect.value = initialTheme.mode; themeModeSelect.addEventListener('change', () => { - applyTheme(themeModeSelect.value, document.documentElement.dataset.accent); + const current = storedTheme(); + applyTheme(themeModeSelect.value, current.accent); }); } if (themeAccentSelect) { - themeAccentSelect.value = document.documentElement.dataset.accent; + themeAccentSelect.value = initialTheme.accent; themeAccentSelect.addEventListener('change', () => { - applyTheme(document.documentElement.dataset.theme, themeAccentSelect.value); + const current = storedTheme(); + applyTheme(current.mode, themeAccentSelect.value); }); }