Finalize init defaults and auth flow UX improvements

This commit is contained in:
2026-03-11 21:11:19 +01:00
parent f27de98bed
commit 60b890d367
14 changed files with 179 additions and 34 deletions

View File

@@ -88,6 +88,28 @@ Standard-Demo-User:
- Abteilung: `Musterabteilung`
- Rollen: `Admin`, `User`
Initiale DB-Settings (Seed):
- `app_title` -> `CoreCore`
- `app_locale` -> `de`
- `app_theme` -> `light`
- `app_theme_user` -> `1`
- `app_registration` -> `1` (aktiv)
- `app_primary_color` -> `#105433`
- `api_token_default_ttl_days` -> `90`
- `api_token_max_ttl_days` -> `365`
- `api_cors_allowed_origins` -> `http://localhost:8080`, `http://127.0.0.1:8080`
- `session_idle_timeout_minutes` -> `30`
- `session_absolute_timeout_hours` -> `8`
- `user_inactivity_deactivate_days` -> `180`
- `user_inactivity_delete_days` -> `365`
- `default_tenant_id` -> `MusterMandant`
- `default_department_id` -> Abteilungscode `MUSTER` (`Musterabteilung`)
- `default_role_id` -> Rolle `USER` (`User`)
- `frontend_telemetry_enabled` -> `1`
- `frontend_telemetry_sample_rate` -> `0.2`
- `frontend_telemetry_allowed_events` -> `ajax_error,warn_once`
## Projektstruktur
- `/config` Konfiguration (Routen, Settings, Themes, Assets)

View File

@@ -938,13 +938,56 @@ JOIN permissions p ON p.`key` IN ('users.self_update', 'address_book.view')
WHERE r.code = 'USER' OR r.description = 'User'
ON DUPLICATE KEY UPDATE role_id = role_id;
INSERT INTO `settings` (`key`, `value`, `description`)
SELECT 'default_tenant_id', CAST(t.id AS CHAR), 'setting.default_tenant'
FROM tenants t
WHERE t.description = 'MusterMandant'
LIMIT 1
ON DUPLICATE KEY UPDATE
`key` = `key`;
INSERT INTO `settings` (`key`, `value`, `description`)
SELECT 'default_department_id', CAST(d.id AS CHAR), 'setting.default_department'
FROM departments d
JOIN tenants t ON t.id = d.tenant_id
WHERE d.code = 'MUSTER'
AND t.description = 'MusterMandant'
LIMIT 1
ON DUPLICATE KEY UPDATE
`key` = `key`;
INSERT INTO `settings` (`key`, `value`, `description`)
SELECT 'default_role_id', CAST(r.id AS CHAR), 'setting.default_role'
FROM roles r
WHERE r.code = 'USER'
LIMIT 1
ON DUPLICATE KEY UPDATE
`key` = `key`;
INSERT INTO `settings` (`key`, `value`, `description`)
SELECT 'api_cors_allowed_origins', CONCAT('http://localhost:8080', CHAR(10), 'http://127.0.0.1:8080'), 'setting.api_cors_allowed_origins'
ON DUPLICATE KEY UPDATE
`key` = `key`;
INSERT INTO `settings` (`key`, `value`, `description`)
VALUES
('app_title', 'CoreCore', 'setting.app_title'),
('app_locale', 'de', 'setting.app_locale'),
('app_theme', 'light', 'setting.app_theme'),
('app_theme_user', '1', 'setting.app_theme_user'),
('app_registration', '1', 'setting.app_registration'),
('app_primary_color', '#105433', 'setting.app_primary_color'),
('api_token_default_ttl_days', '90', 'setting.api_token_default_ttl_days'),
('api_token_max_ttl_days', '365', 'setting.api_token_max_ttl_days'),
('session_idle_timeout_minutes', '30', 'setting.session_idle_timeout_minutes'),
('session_absolute_timeout_hours', '8', 'setting.session_absolute_timeout_hours'),
('user_inactivity_deactivate_days', '180', 'setting.user_inactivity_deactivate_days'),
('user_inactivity_delete_days', '365', 'setting.user_inactivity_delete_days'),
('system_audit_enabled', '1', 'setting.system_audit_enabled'),
('system_audit_retention_days', '365', 'setting.system_audit_retention_days'),
('frontend_telemetry_enabled', '0', 'setting.frontend_telemetry_enabled'),
('frontend_telemetry_enabled', '1', 'setting.frontend_telemetry_enabled'),
('frontend_telemetry_sample_rate', '0.2', 'setting.frontend_telemetry_sample_rate'),
('frontend_telemetry_allowed_events', 'warn_once,ajax_error', 'setting.frontend_telemetry_allowed_events'),
('frontend_telemetry_allowed_events', 'ajax_error,warn_once', 'setting.frontend_telemetry_allowed_events'),
('microsoft_auto_remember_default', '0', 'setting.microsoft_auto_remember_default'),
('remember_token_lifetime_days', '30', 'setting.remember_token_lifetime_days')
ON DUPLICATE KEY UPDATE

View File

@@ -90,6 +90,34 @@ Die Firewall begrenzt gleichzeitige Requests pro IP (Concurrency-Schutz).
---
## Initiale DB-Settings (Seed)
Beim Fresh-Setup über `db/init/init.sql` werden zusätzlich folgende Settings in der DB vorbelegt:
- `app_title` -> `CoreCore`
- `default_tenant_id` -> Seed-Mandant `MusterMandant`
- `default_department_id` -> Seed-Abteilung mit Code `MUSTER`
- `default_role_id` -> Seed-Rolle mit Code `USER`
- `app_locale` -> `de`
- `app_theme` -> `light`
- `app_theme_user` -> `1`
- `app_registration` -> `1` (Self-Registration aktiviert)
- `app_primary_color` -> `#105433`
- `api_token_default_ttl_days` -> `90`
- `api_token_max_ttl_days` -> `365`
- `api_cors_allowed_origins` -> `http://localhost:8080`, `http://127.0.0.1:8080`
- `session_idle_timeout_minutes` -> `30`
- `session_absolute_timeout_hours` -> `8`
- `user_inactivity_deactivate_days` -> `180`
- `user_inactivity_delete_days` -> `365`
- `frontend_telemetry_enabled` -> `1`
- `frontend_telemetry_sample_rate` -> `0.2`
- `frontend_telemetry_allowed_events` -> `ajax_error,warn_once`
Diese Defaults werden von Selbstregistrierung, Admin-Benutzeranlage und Import genutzt, wenn keine expliziten Zuordnungen gesetzt sind.
---
## Produktions-Checkliste
Folgende Variablen **müssen** vor Go-Live angepasst werden:

View File

@@ -14,6 +14,7 @@ $authHelpContext = [
'show_back_to_login' => true,
'show_support' => true,
];
$authLogoHref = lurl('login');
?>
<article class="login-card">

View File

@@ -33,6 +33,7 @@ $selectedTenantInitial = strtoupper(substr($selectedTenantLabel, 0, 1));
if ($selectedTenantInitial === '') {
$selectedTenantInitial = '?';
}
$canSwitchTenant = count($tenantCandidates) > 1;
$errors = is_array($errors ?? null) ? $errors : [];
$errorNoticeId = $errors ? 'auth-login-error-notice' : '';
$loginHeading = t('Login');
@@ -53,6 +54,10 @@ if ($stage === 'resolve_email') {
'show_support' => true,
];
}
$authLogoHref = lurl('login');
if ($tenantHint !== '') {
$authLogoHref .= '?tenant=' . rawurlencode($tenantHint);
}
?>
@@ -140,25 +145,30 @@ if ($stage === 'resolve_email') {
<?php Session::getCsrfInput(); ?>
</form>
<?php else: ?>
<section class="login-tenant-context" role="status" aria-live="polite">
<div class="login-tenant-context-main">
<?php if ($selectedTenantAvatarUrl !== ''): ?>
<img class="login-tenant-context-avatar" src="<?php e($selectedTenantAvatarUrl); ?>" alt="" loading="lazy">
<?php else: ?>
<span class="login-tenant-context-avatar-placeholder"><?php e($selectedTenantInitial); ?></span>
<?php endif; ?>
<div class="login-tenant-context-text">
<p class="login-tenant-context-name"><?php e($selectedTenantLabel); ?></p>
<?php $hasSelectedTenantAvatar = $selectedTenantAvatarUrl !== ''; ?>
<?php if ($canSwitchTenant): ?>
<section class="login-tenant-context" role="status" aria-live="polite">
<div class="login-tenant-context-main">
<?php if ($hasSelectedTenantAvatar): ?>
<img class="login-tenant-context-avatar" src="<?php e($selectedTenantAvatarUrl); ?>" alt="" loading="lazy">
<?php else: ?>
<span class="login-tenant-context-avatar-placeholder login-tenant-context-avatar-placeholder--name"><?php e($selectedTenantLabel); ?></span>
<?php endif; ?>
<?php if ($hasSelectedTenantAvatar): ?>
<div class="login-tenant-context-text">
<p class="login-tenant-context-name"><?php e($selectedTenantLabel); ?></p>
</div>
<?php endif; ?>
</div>
</div>
<form method="post" class="login-tenant-context-form">
<input type="hidden" name="stage" value="resolve_email">
<input type="hidden" name="email" value="<?php e($email); ?>">
<input type="hidden" name="tenant_hint" value="<?php e($tenantHint); ?>">
<button type="submit" class="secondary outline small"><?php e(t('Switch tenant')); ?></button>
<?php Session::getCsrfInput(); ?>
</form>
</section>
<form method="post" class="login-tenant-context-form">
<input type="hidden" name="stage" value="resolve_email">
<input type="hidden" name="email" value="<?php e($email); ?>">
<input type="hidden" name="tenant_hint" value="<?php e($tenantHint); ?>">
<button type="submit" class="secondary outline small"><?php e(t('Switch tenant')); ?></button>
<?php Session::getCsrfInput(); ?>
</form>
</section>
<?php endif; ?>
<div class="login-methods-stack">
<?php if (!empty($selectedTenantMethods['microsoft']) && $selectedTenantSlug !== ''): ?>

View File

@@ -21,6 +21,7 @@ $authHelpContext = [
'show_back_to_login' => true,
'show_support' => true,
];
$authLogoHref = lurl('login');
?>
<article class="login-card">
<?php require templatePath('partials/auth-logo.phtml'); ?>

View File

@@ -16,6 +16,7 @@ $authHelpContext = [
'show_back_to_login' => true,
'show_support' => true,
];
$authLogoHref = lurl('login');
?>
<article class="login-card">

View File

@@ -15,6 +15,7 @@ $authHelpContext = [
'show_back_to_login' => true,
'show_support' => true,
];
$authLogoHref = lurl('login');
?>
<article class="login-card">

View File

@@ -15,6 +15,7 @@ $authHelpContext = [
'show_back_to_login' => true,
'show_support' => true,
];
$authLogoHref = lurl('login');
?>
<article class="login-card">

View File

@@ -267,7 +267,7 @@ $csrfToken = (string) ($layoutNav['csrfToken'] ?? '');
title="<?php e($tenantName); ?>">
<?php else: ?>
<div class="app-sidebar-tenant-name">
<i class="bi bi-buildings"></i> <?php e($tenantName); ?>
<?php e($tenantName); ?>
</div>
<?php endif; ?>
</a>

View File

@@ -4,7 +4,14 @@ $authLogoUrl = appLogoUrl();
if ($authLogoUrl === '') {
return;
}
$authLogoHref = isset($authLogoHref) && is_string($authLogoHref) ? trim($authLogoHref) : '';
?>
<div class="login-logo">
<img src="<?php e($authLogoUrl); ?>" alt="<?php e(appTitle()); ?>" class="login-logo-img">
<?php if ($authLogoHref !== ''): ?>
<a href="<?php e($authLogoHref); ?>" class="login-logo-link">
<img src="<?php e($authLogoUrl); ?>" alt="<?php e(appTitle()); ?>" class="login-logo-img">
</a>
<?php else: ?>
<img src="<?php e($authLogoUrl); ?>" alt="<?php e(appTitle()); ?>" class="login-logo-img">
<?php endif; ?>
</div>

View File

@@ -64,6 +64,7 @@
padding: 0;
flex: 1;
border: 0;
text-decoration: none;
}
.app-sidebar .app-sidebar-tenant-logo img {
@@ -88,17 +89,13 @@
}
.app-sidebar-tenant-name {
flex: 1;
font-weight: 600;
font-size: var(--app-sidebar-tenant-size);
font-size: 17px;
color: var(--app-contrast);
padding: 8px 12px;
border-radius: var(--app-border-radius);
text-align: left;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
border: 1px solid var(--app-border);
}
.app-sidebar-title {
@@ -247,16 +244,26 @@
gap: 0.125rem;
}
.app-sidebar #aside-panel-admin .app-sidebar-admin-group {
.app-sidebar #aside-panel-admin .app-sidebar-admin-group {
margin-bottom: 5px;
width: 100%;
}
}
.app-sidebar #aside-panel-admin .app-sidebar-admin-group details > ul li:first-child {
.app-sidebar
#aside-panel-admin
.app-sidebar-admin-group
details
> ul
li:first-child {
margin-top: 0;
}
.app-sidebar #aside-panel-admin .app-sidebar-admin-group details > ul li:last-child {
.app-sidebar
#aside-panel-admin
.app-sidebar-admin-group
details
> ul
li:last-child {
margin-bottom: 0.35rem;
}
@@ -312,8 +319,14 @@
color: var(--app-contrast);
}
.app-sidebar .app-sidebar-saved-filter-item:hover .app-sidebar-saved-filter-delete button,
.app-sidebar .app-sidebar-saved-filter-item:focus-within .app-sidebar-saved-filter-delete button {
.app-sidebar
.app-sidebar-saved-filter-item:hover
.app-sidebar-saved-filter-delete
button,
.app-sidebar
.app-sidebar-saved-filter-item:focus-within
.app-sidebar-saved-filter-delete
button {
opacity: 1;
visibility: visible;
pointer-events: auto;

View File

@@ -556,6 +556,13 @@
margin-bottom: 1.25rem;
}
.login-logo-link:focus-visible {
outline: 2px solid var(--app-primary-focus);
outline-offset: 2px;
}
.login-logo-img {
max-height: 2.5rem;
max-width: 10rem;
@@ -798,6 +805,16 @@
color: var(--app-primary);
}
.login-tenant-context-avatar-placeholder--name {
width: auto;
max-width: 18rem;
height: auto;
justify-content: flex-start;
text-align: left;
font-weight: 700;
color: var(--app-text);
}
.login-tenant-context-text {
min-width: 0;
overflow-wrap: anywhere;

View File

@@ -20,5 +20,5 @@ import './components/app-tenant-switcher.js';
import './components/app-color-default-toggle.js';
import './components/app-custom-field-options-toggle.js';
import './components/app-tenant-sso-toggle.js';
import './components/app-standard-detail-page.js';
import './components/app-settings-telemetry.js';
import './components/app-standard-detail-page.js';