diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index cf2355ab..a371f7ff 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -56,6 +56,8 @@ jobs: --exclude=".ci_config_deploy/***" \ --exclude="docs/***" \ --exclude="temp/***" \ + --exclude="CopyToKeycloak/***" \ + --exclude="Umsetzungsanweisung/*** --exclude="Umsetzungsanweisung/***" \ --exclude="config/staging/***" \ --exclude="config/prod/***" \ @@ -137,6 +139,7 @@ jobs: --exclude=".ci_config_deploy/***" \ --exclude="docs/***" \ --exclude="temp/***" \ + --exclude="CopyToKeycloak/***" \ --exclude="Umsetzungsanweisung/***" \ --exclude="config/staging/***" \ --exclude="config/prod/***" \ diff --git a/CopyToKeycloak/README.md b/CopyToKeycloak/README.md new file mode 100644 index 00000000..65790abf --- /dev/null +++ b/CopyToKeycloak/README.md @@ -0,0 +1,24 @@ +# CopyToKeycloak + +Dieser Ordner enthaelt ein kopierbares Keycloak-Login-Theme fuer `desktop.kusche.berlin`. + +## Inhalt + +- `theme/kusche-desktop/login/theme.properties` +- `theme/kusche-desktop/login/login.ftl` +- `theme/kusche-desktop/login/resources/css/desktop-kusche.css` +- `theme/kusche-desktop/login/resources/img/desktop-glow.svg` +- `theme/kusche-desktop/login/messages/messages_de.properties` +- `theme/kusche-desktop/login/messages/messages_en.properties` +- `WAS_DU_TUN_MUSST.md` + +## Zweck + +Dieses Theme ist fuer den Client `desktop-kusche-berlin` gedacht. + +Damit gilt die angepasste Login-Seite nur fuer: + +- `desktop.kusche.berlin` +- `staging.desktop.kusche.berlin` + +und nicht automatisch fuer andere Clients im selben Realm. diff --git a/CopyToKeycloak/WAS_DU_TUN_MUSST.md b/CopyToKeycloak/WAS_DU_TUN_MUSST.md new file mode 100644 index 00000000..066d457e --- /dev/null +++ b/CopyToKeycloak/WAS_DU_TUN_MUSST.md @@ -0,0 +1,92 @@ +# Was Du Tun Musst + +## 1. Theme nach Keycloak kopieren + +Den Ordner + +- `CopyToKeycloak/theme/kusche-desktop` + +in das Keycloak-Themes-Verzeichnis kopieren. + +Typische Ziele: + +- Container: `/opt/keycloak/themes/` +- VM/Bare Metal: `/themes/` + +Danach muss dort existieren: + +- `/opt/keycloak/themes/kusche-desktop/login/theme.properties` + +## 2. Keycloak neu laden + +Wenn du in Entwicklung arbeitest: + +- Keycloak neu starten + +Wenn du in Produktion Theme-Caches aktiv hast: + +1. Keycloak stoppen oder neu starten +2. falls noetig Theme-Cache loeschen: + - `data/tmp/kc-gzip-cache` + +## 3. Theme nur fuer den Desktop-Client setzen + +In Keycloak: + +1. Realm `KuscheBerlin` oeffnen +2. Client `desktop-kusche-berlin` oeffnen +3. Die Login-Theme-Override fuer den Client auf `kusche-desktop` setzen + +Wichtig: + +- Das Theme soll **nicht** pauschal als Realm-Login-Theme fuer alle Clients gesetzt werden, wenn andere Clients das Standard-Keycloak-Login behalten sollen. + +Falls deine Keycloak-Version im Client-UI keine direkte Login-Theme-Auswahl anzeigt: + +- pruefen, ob die Client-spezifische Login-Theme-Option in den Client-Einstellungen / Advanced Settings vorhanden ist +- falls nicht vorhanden, bleibt als saubere Alternative nur: + - eigener Realm fuer Desktop + - oder spaeter ein Theme Selector SPI + +## 4. Testen + +Danach testen: + +1. `https://staging.desktop.kusche.berlin` +2. `https://desktop.kusche.berlin` + +Erwartung: + +- fuer den Client `desktop-kusche-berlin` erscheint das neue Desktop-Login-Theme +- nach erfolgreichem Login geht es zur Desktop-Shell zurueck + +## 5. Falls das Theme nicht erscheint + +Dann diese Punkte pruefen: + +- Theme-Ordner liegt wirklich im Keycloak-`themes/`-Verzeichnis +- Verzeichnisname ist exakt `kusche-desktop` +- `theme.properties` liegt unter `kusche-desktop/login/` +- Keycloak wurde nach dem Kopieren neu gestartet +- Cache wurde geleert +- wirklich der Client `desktop-kusche-berlin` wird verwendet + +## 6. Welche Datei du spaeter bei Aenderungen ersetzt + +Wenn du nur Styling aendern willst, reicht meist: + +- `theme/kusche-desktop/login/resources/css/desktop-kusche.css` + +Wenn du die Struktur aendern willst: + +- `theme/kusche-desktop/login/login.ftl` + +## 7. Was ich bewusst nicht fuer dich serverseitig tue + +Diese Schritte musst du selbst auf dem Keycloak-System machen: + +- Dateien nach Keycloak kopieren +- Keycloak neu starten +- Client-Theme-Override setzen + +Das Projekt hier erzeugt nur den fertigen Theme-Ordner. diff --git a/CopyToKeycloak/theme/kusche-desktop/login/login.ftl b/CopyToKeycloak/theme/kusche-desktop/login/login.ftl new file mode 100644 index 00000000..3acdedea --- /dev/null +++ b/CopyToKeycloak/theme/kusche-desktop/login/login.ftl @@ -0,0 +1,140 @@ +<#import "template.ftl" as layout> +<#import "passkeys.ftl" as passkeys> + +<@layout.registrationLayout displayMessage=!messagesPerField.existsError('username','password') displayInfo=realm.password && realm.registrationAllowed && !registrationDisabled??; section> + +<#if section = "header"> + ${msg("loginAccountTitle")} + +<#elseif section = "form"> +
+ + + +
+ + <@passkeys.conditionalUIData /> + + +<#elseif section = "info" > + <#if realm.password && realm.registrationAllowed && !registrationDisabled??> +
+
+ ${msg("noAccount")} ${msg("doRegister")} +
+
+ + +<#elseif section = "socialProviders" > + <#if realm.password && social?? && social.providers?has_content> +
+
+

${msg("identity-provider-login-label")}

+ +
+ + + + diff --git a/CopyToKeycloak/theme/kusche-desktop/login/messages/messages_de.properties b/CopyToKeycloak/theme/kusche-desktop/login/messages/messages_de.properties new file mode 100644 index 00000000..91d70f03 --- /dev/null +++ b/CopyToKeycloak/theme/kusche-desktop/login/messages/messages_de.properties @@ -0,0 +1,2 @@ +kbDesktopLoginHeadline=Desktop Login +kbDesktopLoginSubheadline=Geschuetzter Zugang zur Desktop-Shell und kuenftigen Modulen. diff --git a/CopyToKeycloak/theme/kusche-desktop/login/messages/messages_en.properties b/CopyToKeycloak/theme/kusche-desktop/login/messages/messages_en.properties new file mode 100644 index 00000000..bbe12f3f --- /dev/null +++ b/CopyToKeycloak/theme/kusche-desktop/login/messages/messages_en.properties @@ -0,0 +1,2 @@ +kbDesktopLoginHeadline=Desktop Login +kbDesktopLoginSubheadline=Protected access to the desktop shell and upcoming modules. diff --git a/CopyToKeycloak/theme/kusche-desktop/login/resources/css/desktop-kusche.css b/CopyToKeycloak/theme/kusche-desktop/login/resources/css/desktop-kusche.css new file mode 100644 index 00000000..ed80058f --- /dev/null +++ b/CopyToKeycloak/theme/kusche-desktop/login/resources/css/desktop-kusche.css @@ -0,0 +1,179 @@ +html, +body { + min-height: 100%; +} + +body { + background: + radial-gradient(circle at top, rgba(255, 221, 177, 0.30), transparent 26%), + radial-gradient(circle at 80% 18%, rgba(132, 216, 166, 0.18), transparent 20%), + linear-gradient(145deg, #101827 0%, #18253d 52%, #0f172a 100%) !important; + color: #f8fafc; +} + +#kc-header-wrapper, +#kc-header, +.login-pf-header, +#kc-page-title { + display: none !important; +} + +#kc-content, +#kc-content-wrapper, +#kc-locale { + position: relative; + z-index: 2; +} + +#kc-content-wrapper { + padding-top: 48px; +} + +#kc-form-login .pf-v5-c-form__label-text, +#kc-form-login label, +#kc-form-options, +#kc-form-options a, +#kc-registration, +#kc-registration a, +#kc-social-providers h2, +#kc-social-providers a { + color: #e2e8f0 !important; +} + +.kb-login-shell { + position: relative; + display: grid; + place-items: center; + min-height: calc(100vh - 80px); + padding: 24px; +} + +.kb-login-ambient { + position: absolute; + inset: 0; + background: + radial-gradient(circle at 50% 10%, rgba(255, 255, 255, 0.12), transparent 30%), + url("../img/desktop-glow.svg") center/cover no-repeat; + opacity: 0.9; + pointer-events: none; +} + +.kb-login-panel { + position: relative; + z-index: 1; + display: grid; + gap: 22px; + width: min(540px, 100%); + padding: 28px; + border-radius: 28px; + background: rgba(15, 23, 42, 0.62); + border: 1px solid rgba(255, 255, 255, 0.12); + box-shadow: 0 24px 80px rgba(2, 6, 23, 0.42); + backdrop-filter: blur(24px); +} + +.kb-login-copy { + display: grid; + gap: 12px; +} + +.kb-login-kicker { + margin: 0; + font-size: 12px; + letter-spacing: 0.18em; + text-transform: uppercase; + color: #cbd5e1; +} + +.kb-login-title { + margin: 0; + font-size: clamp(30px, 4vw, 40px); + line-height: 1.04; + color: #f8fafc; +} + +.kb-login-subtitle { + margin: 0; + color: #dbe5f1; + line-height: 1.55; +} + +.kb-login-form-shell { + margin: 0; +} + +#kc-form-wrapper { + padding: 0 !important; +} + +#kc-form-login input[type="text"], +#kc-form-login input[type="password"] { + min-height: 48px; + border-radius: 14px !important; + border: 1px solid rgba(255, 255, 255, 0.12) !important; + background: rgba(255, 255, 255, 0.08) !important; + color: #f8fafc !important; + box-shadow: none !important; +} + +#kc-form-login input::placeholder { + color: rgba(226, 232, 240, 0.72); +} + +#kc-form-login .pf-v5-c-button, +#kc-login { + min-height: 50px; + border: 0 !important; + border-radius: 14px !important; + background: linear-gradient(180deg, #a7f3d0, #6ee7b7) !important; + color: #082032 !important; + font-weight: 700; + box-shadow: none !important; +} + +#kc-form-login .pf-v5-c-button:hover, +#kc-login:hover { + filter: brightness(1.03); +} + +#kc-form-options { + padding-top: 4px; +} + +#kc-form-options .checkbox label { + display: inline-flex; + align-items: center; + gap: 8px; +} + +#kc-social-providers { + margin-top: 10px; +} + +#kc-social-providers hr { + border-color: rgba(255, 255, 255, 0.12); +} + +#kc-registration { + margin-top: 18px; +} + +.pf-v5-c-alert { + border-radius: 14px !important; +} + +@media (max-width: 640px) { + #kc-content-wrapper { + padding-top: 20px; + } + + .kb-login-shell { + min-height: auto; + padding: 16px; + } + + .kb-login-panel { + padding: 22px 18px; + border-radius: 22px; + } +} diff --git a/CopyToKeycloak/theme/kusche-desktop/login/resources/img/desktop-glow.svg b/CopyToKeycloak/theme/kusche-desktop/login/resources/img/desktop-glow.svg new file mode 100644 index 00000000..e77ef931 --- /dev/null +++ b/CopyToKeycloak/theme/kusche-desktop/login/resources/img/desktop-glow.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/CopyToKeycloak/theme/kusche-desktop/login/theme.properties b/CopyToKeycloak/theme/kusche-desktop/login/theme.properties new file mode 100644 index 00000000..69921edd --- /dev/null +++ b/CopyToKeycloak/theme/kusche-desktop/login/theme.properties @@ -0,0 +1,4 @@ +parent=keycloak +import=common/keycloak +styles=css/desktop-kusche.css +locales=de,en diff --git a/partials/desktop/shell.php b/partials/desktop/shell.php index a67a46e8..f64069be 100644 --- a/partials/desktop/shell.php +++ b/partials/desktop/shell.php @@ -110,8 +110,11 @@ $renderStartIcon = static function (array $profile): string {
- + + + Logout +
diff --git a/public/assets/desktop/desktop.css b/public/assets/desktop/desktop.css index 512140ca..8df19fda 100644 --- a/public/assets/desktop/desktop.css +++ b/public/assets/desktop/desktop.css @@ -691,6 +691,24 @@ h1 { font-size: 13px; } +.tray-action { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px 12px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.08); + color: inherit; + font-size: 13px; + text-decoration: none; + transition: background-color 140ms ease, transform 140ms ease; +} + +.tray-action:hover { + background: rgba(255, 255, 255, 0.16); + transform: translateY(-1px); +} + .window-content p:last-child { margin-bottom: 0; } diff --git a/public/assets/desktop/desktop.js b/public/assets/desktop/desktop.js index 7ebd5582..f0ac33d2 100644 --- a/public/assets/desktop/desktop.js +++ b/public/assets/desktop/desktop.js @@ -46,7 +46,7 @@ if (payloadNode) { }; const updateSessionDisplay = () => { - const accountNode = document.querySelector('.tray-pill:last-of-type'); + const accountNode = document.querySelector('.tray-pill[data-tray-id="account"]'); if (!accountNode) { return; diff --git a/src/App/App.php b/src/App/App.php index ede795a3..cf8ed6e9 100644 --- a/src/App/App.php +++ b/src/App/App.php @@ -23,6 +23,8 @@ final class App */ public function desktopPayload(): array { + $keycloakConfig = ConfigLoader::load($this->projectRoot, 'keycloak'); + $auth = new KeycloakAuth($keycloakConfig); $registry = new AppRegistry($this->projectRoot . '/config/apps.php'); $widgetRegistry = new WidgetRegistry($this->projectRoot . '/config/widgets.php'); $skins = SkinResolver::all(); @@ -70,6 +72,8 @@ final class App 'tray' => DesktopState::trayItems(), 'session' => [ 'display_name' => $displayName, + 'authenticated' => $isAuthenticated, + 'logout_url' => $isAuthenticated && $auth->isConfigured() ? '/auth/logout' : null, ], 'menu' => [ 'quick_actions' => DesktopState::quickActions(),