yxcxyc
All checks were successful
Deploy / deploy-production (push) Has been skipped
Deploy / deploy-staging (push) Successful in 6s

This commit is contained in:
2026-05-06 00:21:53 +02:00
parent ecf0e83c4e
commit 4d9d9f3480
2 changed files with 365 additions and 41 deletions

View File

@@ -74,6 +74,13 @@ $dbDefaultsByGroup = [
'db' => is_array($defaults) ? $defaults : [],
'metadata_db' => is_array($metadataDefaults) ? $metadataDefaults : [],
];
$authConfig = is_array($module['auth'] ?? null) ? $module['auth'] : ['required' => false, 'users' => [], 'groups' => []];
$allowedUsers = [];
$allowedGroups = [];
$knownUsers = [];
$knownGroups = [];
$manualUsers = [];
$manualGroups = [];
$setNested = function (array &$target, string $path, mixed $value): void {
$parts = explode('.', $path);
@@ -134,6 +141,22 @@ foreach ($fields as $field) {
$generalFields[] = $field;
}
$generalSetupFields = [];
$cronSetupFields = [];
$customSetupFields = [];
foreach ($generalFields as $field) {
$fieldName = (string)($field['name'] ?? '');
if ($fieldName === 'debug_enabled') {
$generalSetupFields[] = $field;
continue;
}
if ($fieldName === 'schedule_timezone') {
$cronSetupFields[] = $field;
continue;
}
$customSetupFields[] = $field;
}
$driverOptions = [
'pgsql' => 'PostgreSQL',
'mysql' => 'MySQL / MariaDB',
@@ -576,6 +599,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
} else {
modules()->saveSettings($moduleName, $current);
$selectedUsers = is_array($_POST['auth_user_values'] ?? null) ? $_POST['auth_user_values'] : [];
$selectedGroups = is_array($_POST['auth_group_values'] ?? null) ? $_POST['auth_group_values'] : [];
$manualUserValues = preg_split('/[,\\n]+/', (string) ($_POST['auth_users'] ?? '')) ?: [];
$manualGroupValues = preg_split('/[,\\n]+/', (string) ($_POST['auth_groups'] ?? '')) ?: [];
modules()->saveAuth($moduleName, [
'required' => isset($_POST['auth_required']),
'users' => array_merge($selectedUsers, $manualUserValues),
'groups' => array_merge($selectedGroups, $manualGroupValues),
]);
if ($isFxRatesSetup && modules()->hasFunction($moduleName, 'save_runtime_settings')) {
module_fn($moduleName, 'save_runtime_settings', $payload);
$current = modules()->settings($moduleName);
@@ -586,14 +618,54 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
$notice = 'Setup gespeichert.';
$module = modules()->get($moduleName) ?: $module;
$authConfig = is_array($module['auth'] ?? null) ? $module['auth'] : $authConfig;
}
}
$moduleStatusPanel = null;
$activeSetupSection = trim((string) ($_POST['active_setup_section'] ?? 'setup-section-general'));
if (!in_array($activeSetupSection, ['setup-section-general', 'setup-section-access', 'setup-section-cron', 'setup-section-custom'], true)) {
$activeSetupSection = 'setup-section-general';
}
$activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups)
? $testGroup
: (array_key_first($dbGroups) ?? '');
$authConfig = is_array($module['auth'] ?? null) ? $module['auth'] : ['required' => false, 'users' => [], 'groups' => []];
$allowedUsers = is_array($authConfig['users'] ?? null) ? array_values(array_filter(array_map('strval', $authConfig['users']))) : [];
$allowedGroups = is_array($authConfig['groups'] ?? null) ? array_values(array_filter(array_map('strval', $authConfig['groups']))) : [];
$knownUsers = modules()->knownAuthUsers();
$knownGroups = modules()->knownAuthGroups();
$currentUser = auth_user();
if (is_array($currentUser) && trim((string)($currentUser['sub'] ?? '')) !== '') {
$currentSub = (string) $currentUser['sub'];
$hasCurrentUser = false;
foreach ($knownUsers as $knownUser) {
if ((string) ($knownUser['sub'] ?? '') === $currentSub) {
$hasCurrentUser = true;
break;
}
}
if (!$hasCurrentUser) {
$knownUsers[] = [
'sub' => $currentSub,
'username' => (string) ($currentUser['username'] ?? ''),
'email' => (string) ($currentUser['email'] ?? ''),
'name' => (string) ($currentUser['name'] ?? ''),
'groups' => is_array($currentUser['groups'] ?? null) ? $currentUser['groups'] : [],
];
}
}
$knownGroups = array_values(array_unique(array_merge($knownGroups, auth_groups())));
sort($knownGroups, SORT_NATURAL | SORT_FLAG_CASE);
$knownUserValues = array_column($knownUsers, 'sub');
$manualUsers = array_values(array_filter($allowedUsers, fn (string $value): bool => !in_array($value, $knownUserValues, true)));
$manualGroups = array_values(array_filter($allowedGroups, fn (string $value): bool => !in_array($value, $knownGroups, true)));
$hasCronSection = $cronSetupFields !== [] || $intervalTaskStatuses !== [] || $cronTaskDefinitions !== [];
$hasCustomSection = $customSetupFields !== [] || $setupActions !== [] || $isFxRatesSetup;
if ($activeSetupSection === 'setup-section-custom' && !$hasCustomSection) {
$activeSetupSection = 'setup-section-general';
}
?>
<div class="setup-shell">
<div class="pill">Setup</div>
@@ -610,12 +682,27 @@ $activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups)
</div>
<?php endif; ?>
<form method="post" class="setup-form">
<datalist id="timezone-options">
<?php foreach ($timezoneOptions as $timezoneOption): ?>
<option value="<?= e((string) $timezoneOption['value']) ?>"><?= e((string) $timezoneOption['label']) ?></option>
<?php endforeach; ?>
</datalist>
<div class="setup-shell__layout">
<aside class="setup-shell__sidebar setup-shell__sidebar--left" aria-label="Setup-Bereiche">
<div class="setup-nav">
<span class="pill">Bereiche</span>
<a class="nav-link setup-nav__link" href="#setup-general">Allgemein</a>
<a class="nav-link setup-nav__link" href="#setup-access">Zugriffsrechte</a>
<a class="nav-link setup-nav__link" href="#setup-cron">Cron Einstellungen</a>
<?php if ($hasCustomSection): ?>
<a class="nav-link setup-nav__link" href="#setup-custom">Custom Settings</a>
<?php endif; ?>
</div>
</aside>
<div class="setup-shell__content">
<form method="post" class="setup-form">
<input type="hidden" name="active_setup_section" value="<?= e($activeSetupSection) ?>">
<datalist id="timezone-options">
<?php foreach ($timezoneOptions as $timezoneOption): ?>
<option value="<?= e((string) $timezoneOption['value']) ?>"><?= e((string) $timezoneOption['label']) ?></option>
<?php endforeach; ?>
</datalist>
<?php if ($isFxRatesSetup): ?>
<?php
$fxCatalog = is_array($current['currency_catalog'] ?? null) ? $current['currency_catalog'] : [];
@@ -641,10 +728,10 @@ $activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups)
array_keys($fxCatalogOptions)
), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
?>
<section class="setup-panel">
<section class="setup-panel" id="setup-custom">
<div class="setup-panel__head">
<div>
<span class="pill">API</span>
<span class="pill">Custom Settings</span>
<h2>Provider und Abruf</h2>
</div>
</div>
@@ -687,30 +774,17 @@ $activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups)
</div>
</section>
<section class="setup-panel">
<section class="setup-panel" id="setup-general">
<div class="setup-panel__head">
<div>
<span class="pill">Scheduler</span>
<h2>Cron-Zeitzone</h2>
</div>
</div>
<div class="setup-grid">
<label class="setup-field muted">
<span>Scheduler-Zeitzone</span>
<input type="text" name="schedule_timezone" value="<?= e((string) ($current['schedule_timezone'] ?? 'Europe/Berlin')) ?>" list="timezone-options" autocomplete="off">
<small class="muted">Diese Zeitzone wird fuer Cron-Jobs und die lokale Zeitberechnung verwendet.</small>
</label>
</div>
</section>
<section class="setup-panel">
<div class="setup-panel__head">
<div>
<span class="pill">DB</span>
<h2>Datenbank</h2>
<span class="pill">Allgemein</span>
<h2>Datenbank und Debug</h2>
</div>
</div>
<div class="setup-grid">
<?php foreach ($generalSetupFields as $field): ?>
<?php $renderField($field); ?>
<?php endforeach; ?>
<label class="setup-field muted">
<span>Eigene Modul-DB nutzen</span>
<input type="checkbox" name="use_separate_db" value="1" <?= $fxUseSeparateDb ? 'checked' : '' ?> data-fx-db-toggle>
@@ -779,6 +853,84 @@ $activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups)
</div>
</section>
<section class="setup-panel" id="setup-access">
<div class="setup-panel__head">
<div>
<span class="pill">Zugriffsrechte</span>
<h2>Zugriff verwalten</h2>
<p class="muted">Steuert, ob Login erforderlich ist und welche Benutzer oder Gruppen das Modul oeffnen duerfen.</p>
</div>
</div>
<div class="setup-grid">
<div class="setup-field muted">
<span>Login erforderlich</span>
<label style="display:flex; align-items:center; gap:10px;">
<input type="checkbox" name="auth_required" value="1" <?= !empty($authConfig['required']) ? 'checked' : '' ?>>
<span>Nur eingeloggte Nutzer duerfen dieses Modul oeffnen.</span>
</label>
</div>
<div class="setup-field muted">
<span>Erlaubte Benutzer</span>
<?php if ($knownUsers === []): ?>
<small class="muted">Noch keine bekannten Benutzer vorhanden. Nutzer erscheinen hier, sobald sie sich einmal angemeldet haben.</small>
<?php else: ?>
<div class="setup-auth-list">
<?php foreach ($knownUsers as $knownUser): ?>
<?php
$sub = (string) ($knownUser['sub'] ?? '');
$label = trim((string) ($knownUser['name'] ?? ''));
if ($label === '') {
$label = trim((string) ($knownUser['username'] ?? ''));
}
$email = trim((string) ($knownUser['email'] ?? ''));
$suffix = $email !== '' && $email !== $label ? ' (' . $email . ')' : '';
?>
<label style="display:flex; align-items:center; gap:10px;">
<input type="checkbox" name="auth_user_values[]" value="<?= e($sub) ?>" <?= in_array($sub, $allowedUsers, true) ? 'checked' : '' ?>>
<span><?= e(($label !== '' ? $label : $sub) . $suffix) ?></span>
</label>
<?php endforeach; ?>
</div>
<?php endif; ?>
<textarea name="auth_users" rows="3" placeholder="Weitere Keycloak-Sub, Benutzername oder E-Mail, je Zeile oder Komma"><?= e(implode("\n", $manualUsers)) ?></textarea>
</div>
<div class="setup-field muted">
<span>Erlaubte Gruppen</span>
<?php if ($knownGroups === []): ?>
<small class="muted">Noch keine bekannten Gruppen vorhanden.</small>
<?php else: ?>
<div class="setup-auth-list">
<?php foreach ($knownGroups as $knownGroup): ?>
<label style="display:flex; align-items:center; gap:10px;">
<input type="checkbox" name="auth_group_values[]" value="<?= e($knownGroup) ?>" <?= in_array($knownGroup, $allowedGroups, true) ? 'checked' : '' ?>>
<span><?= e($knownGroup) ?></span>
</label>
<?php endforeach; ?>
</div>
<?php endif; ?>
<textarea name="auth_groups" rows="3" placeholder="Weitere Gruppen, je Zeile oder Komma"><?= e(implode("\n", $manualGroups)) ?></textarea>
<small class="muted">Wenn Login aktiv ist und Benutzer sowie Gruppen leer bleiben, darf jeder eingeloggte Benutzer das Modul oeffnen.</small>
</div>
</div>
</section>
<section class="setup-panel" id="setup-cron">
<div class="setup-panel__head">
<div>
<span class="pill">Cron Einstellungen</span>
<h2>Scheduler und Zeitsteuerung</h2>
<p class="muted">Hier liegen die zeitbezogenen Modul-Einstellungen, Intervall-Tasks und Cron-Jobs.</p>
</div>
</div>
<div class="setup-grid">
<label class="setup-field muted">
<span>Scheduler-Zeitzone</span>
<input type="text" name="schedule_timezone" value="<?= e((string) ($current['schedule_timezone'] ?? 'Europe/Berlin')) ?>" list="timezone-options" autocomplete="off">
<small class="muted">Diese Zeitzone wird fuer Cron-Jobs und lokale Zeitangaben genutzt.</small>
</label>
</div>
</section>
<?php if ($intervalTaskStatuses !== []): ?>
<section class="setup-panel">
<div class="setup-panel__head">
@@ -883,10 +1035,8 @@ $activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups)
</section>
<?php endif; ?>
<div class="setup-actions">
<button class="cta-button" type="submit">Speichern</button>
<a class="nav-link" href="/modules/access/<?= e($moduleName) ?>">Zugriff verwalten</a>
<a class="nav-link" href="/modules">Zurück</a>
<div class="setup-actions setup-actions--footer">
<button class="cta-button" type="submit">Setup speichern</button>
</div>
<script>
@@ -1136,22 +1286,102 @@ $activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups)
}
</style>
<?php else: ?>
<?php if ($generalFields !== []): ?>
<section class="setup-panel">
<?php if ($generalSetupFields !== []): ?>
<section class="setup-panel" id="setup-general">
<div class="setup-panel__head">
<div>
<span class="pill">Allgemein</span>
<h2>Moduleinstellungen</h2>
<h2>Datenbank und Debug</h2>
</div>
</div>
<div class="setup-grid">
<?php foreach ($generalFields as $field): ?>
<?php foreach ($generalSetupFields as $field): ?>
<?php $renderField($field); ?>
<?php endforeach; ?>
</div>
</section>
<?php endif; ?>
<section class="setup-panel" id="setup-access">
<div class="setup-panel__head">
<div>
<span class="pill">Zugriffsrechte</span>
<h2>Zugriff verwalten</h2>
<p class="muted">Steuert, ob Login erforderlich ist und welche Benutzer oder Gruppen das Modul oeffnen duerfen.</p>
</div>
</div>
<div class="setup-grid">
<div class="setup-field muted">
<span>Login erforderlich</span>
<label style="display:flex; align-items:center; gap:10px;">
<input type="checkbox" name="auth_required" value="1" <?= !empty($authConfig['required']) ? 'checked' : '' ?>>
<span>Nur eingeloggte Nutzer duerfen dieses Modul oeffnen.</span>
</label>
</div>
<div class="setup-field muted">
<span>Erlaubte Benutzer</span>
<?php if ($knownUsers === []): ?>
<small class="muted">Noch keine bekannten Benutzer vorhanden. Nutzer erscheinen hier, sobald sie sich einmal angemeldet haben.</small>
<?php else: ?>
<div class="setup-auth-list">
<?php foreach ($knownUsers as $knownUser): ?>
<?php
$sub = (string) ($knownUser['sub'] ?? '');
$label = trim((string) ($knownUser['name'] ?? ''));
if ($label === '') {
$label = trim((string) ($knownUser['username'] ?? ''));
}
$email = trim((string) ($knownUser['email'] ?? ''));
$suffix = $email !== '' && $email !== $label ? ' (' . $email . ')' : '';
?>
<label style="display:flex; align-items:center; gap:10px;">
<input type="checkbox" name="auth_user_values[]" value="<?= e($sub) ?>" <?= in_array($sub, $allowedUsers, true) ? 'checked' : '' ?>>
<span><?= e(($label !== '' ? $label : $sub) . $suffix) ?></span>
</label>
<?php endforeach; ?>
</div>
<?php endif; ?>
<textarea name="auth_users" rows="3" placeholder="Weitere Keycloak-Sub, Benutzername oder E-Mail, je Zeile oder Komma"><?= e(implode("\n", $manualUsers)) ?></textarea>
</div>
<div class="setup-field muted">
<span>Erlaubte Gruppen</span>
<?php if ($knownGroups === []): ?>
<small class="muted">Noch keine bekannten Gruppen vorhanden.</small>
<?php else: ?>
<div class="setup-auth-list">
<?php foreach ($knownGroups as $knownGroup): ?>
<label style="display:flex; align-items:center; gap:10px;">
<input type="checkbox" name="auth_group_values[]" value="<?= e($knownGroup) ?>" <?= in_array($knownGroup, $allowedGroups, true) ? 'checked' : '' ?>>
<span><?= e($knownGroup) ?></span>
</label>
<?php endforeach; ?>
</div>
<?php endif; ?>
<textarea name="auth_groups" rows="3" placeholder="Weitere Gruppen, je Zeile oder Komma"><?= e(implode("\n", $manualGroups)) ?></textarea>
<small class="muted">Wenn Login aktiv ist und Benutzer sowie Gruppen leer bleiben, darf jeder eingeloggte Benutzer das Modul oeffnen.</small>
</div>
</div>
</section>
<section class="setup-panel" id="setup-cron">
<div class="setup-panel__head">
<div>
<span class="pill">Cron Einstellungen</span>
<h2>Scheduler und Zeitsteuerung</h2>
<p class="muted">Hier liegen die zeitbezogenen Modul-Einstellungen, Intervall-Tasks und Cron-Jobs.</p>
</div>
</div>
<?php if ($cronSetupFields !== []): ?>
<div class="setup-grid">
<?php foreach ($cronSetupFields as $field): ?>
<?php $renderField($field); ?>
<?php endforeach; ?>
</div>
<?php else: ?>
<p class="muted">Dieses Modul hat keine eigenen Zeitzonenfelder. Intervall-Tasks und Cron-Jobs koennen trotzdem weiter unten verwaltet werden.</p>
<?php endif; ?>
</section>
<?php if ($intervalTaskStatuses !== []): ?>
<section class="setup-panel">
<div class="setup-panel__head">
@@ -1261,7 +1491,7 @@ $activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups)
<?php endif; ?>
<?php if ($setupActions !== []): ?>
<section class="setup-panel">
<section class="setup-panel"<?= $customSetupFields === [] ? ' id="setup-custom"' : '' ?>>
<div class="setup-panel__head">
<div>
<span class="pill">Aktionen</span>
@@ -1321,7 +1551,7 @@ $activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups)
<?php endif; ?>
<?php if ($dbGroups !== []): ?>
<section class="setup-panel">
<section class="setup-panel"<?= $generalSetupFields === [] ? ' id="setup-general"' : '' ?>>
<div class="setup-panel__head">
<div>
<span class="pill">Datenbanken</span>
@@ -1392,12 +1622,37 @@ $activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups)
</section>
<?php endif; ?>
<div class="setup-actions">
<button class="cta-button" type="submit">Speichern</button>
<a class="nav-link" href="/modules/access/<?= e($moduleName) ?>">Zugriff verwalten</a>
<a class="nav-link" href="/modules">Zurück</a>
<?php if ($customSetupFields !== []): ?>
<section class="setup-panel" id="setup-custom">
<div class="setup-panel__head">
<div>
<span class="pill">Custom Settings</span>
<h2>Modulspezifische Einstellungen</h2>
</div>
</div>
<div class="setup-grid">
<?php foreach ($customSetupFields as $field): ?>
<?php $renderField($field); ?>
<?php endforeach; ?>
</div>
</section>
<?php endif; ?>
<div class="setup-actions setup-actions--footer">
<button class="cta-button" type="submit">Setup speichern</button>
</div>
<?php endif; ?>
</form>
</div>
<aside class="setup-shell__sidebar setup-shell__sidebar--right" aria-label="Setup-Aktionen">
<div class="setup-nav">
<span class="pill">Aktionen</span>
<a class="nav-link setup-nav__link" href="/modules">Nexus Übersicht</a>
<a class="nav-link setup-nav__link" href="/module/<?= e($moduleName) ?>">Zurück zum Modul</a>
</div>
</aside>
</div>
<div class="scheduler-modal" data-scheduler-modal hidden>
<div class="scheduler-modal__backdrop" data-scheduler-close></div>
<div class="scheduler-modal__dialog" role="dialog" aria-modal="true" aria-labelledby="scheduler-modal-title">

View File

@@ -825,6 +825,10 @@ body.has-modal-open {
scroll-margin-top: 18px;
}
.setup-panel {
scroll-margin-top: 18px;
}
.setup-panel__head {
display: flex;
align-items: flex-start;
@@ -842,6 +846,37 @@ body.has-modal-open {
margin: 6px 0 0;
}
.setup-shell__layout {
display: grid;
grid-template-columns: minmax(180px, 220px) minmax(0, 1fr) minmax(180px, 220px);
gap: 18px;
align-items: start;
}
.setup-shell__sidebar {
position: sticky;
top: 18px;
}
.setup-shell__content {
min-width: 0;
}
.setup-nav {
display: grid;
gap: 10px;
padding: 16px;
border: 1px solid var(--line);
border-radius: 14px;
background: var(--surface-strong);
box-shadow: 0 10px 24px rgba(1, 22, 32, 0.06);
}
.setup-nav__link {
justify-content: flex-start;
width: 100%;
}
.setup-tabs {
display: flex;
gap: 10px;
@@ -937,6 +972,40 @@ body.has-modal-open {
flex-wrap: wrap;
}
.setup-actions--footer {
margin-top: 18px;
}
.setup-auth-list {
display: grid;
gap: 6px;
max-height: 220px;
overflow: auto;
padding: 10px 12px;
border: 1px solid var(--line);
border-radius: 10px;
background: var(--surface);
}
@media (max-width: 1100px) {
.setup-shell__layout {
grid-template-columns: 1fr;
}
.setup-shell__sidebar {
position: static;
}
.setup-shell__sidebar--right {
order: -1;
}
.setup-nav {
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
align-items: start;
}
}
.kea-page {
display: grid;
gap: 16px;