asdasd
This commit is contained in:
@@ -65,6 +65,8 @@ Dort koennen derzeit insbesondere verwaltet werden:
|
|||||||
|
|
||||||
Administratoren nutzen zusaetzlich `Admin Apps` fuer den globalen App-Bestand, Installationswege, LDAP-Gruppenberechtigungen und erste Integrations-Einstellungen wie den Gitea-Deploy-Status.
|
Administratoren nutzen zusaetzlich `Admin Apps` fuer den globalen App-Bestand, Installationswege, LDAP-Gruppenberechtigungen und erste Integrations-Einstellungen wie den Gitea-Deploy-Status.
|
||||||
|
|
||||||
|
Vor dem Login erscheint nun zusaetzlich ein offener `Logoff-Desktop`. Dort gibt es unten nur die Login-Lasche. Sichtbar sind dort ausschliesslich Desktop-Apps, die ein Admin explizit fuer die Nutzung ohne Login freigegeben hat.
|
||||||
|
|
||||||
## Module
|
## Module
|
||||||
|
|
||||||
Installierbare Fach-Apps liegen unter `custom/apps/<app>/` und koennen als normale `App` im Desktop erscheinen.
|
Installierbare Fach-Apps liegen unter `custom/apps/<app>/` und koennen als normale `App` im Desktop erscheinen.
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ Diese Benennungen gelten projektweit und sollen in UI, Doku und Weiterentwicklun
|
|||||||
- `Setup-Bereich`
|
- `Setup-Bereich`
|
||||||
Der zentrale Einstellungsbereich fuer Benutzer, Desktop-Typ, Apps und Bereichsauswahl. Aktuell umgesetzt ueber `User Self Management`.
|
Der zentrale Einstellungsbereich fuer Benutzer, Desktop-Typ, Apps und Bereichsauswahl. Aktuell umgesetzt ueber `User Self Management`.
|
||||||
|
|
||||||
|
- `Logoff-Desktop`
|
||||||
|
Die offene Desktop-Oberflaeche vor dem Login. Dort erscheinen nur die Desktop-Apps, die ein Admin explizit fuer die Nutzung ohne Login freigibt.
|
||||||
|
|
||||||
### Menue-Bereiche
|
### Menue-Bereiche
|
||||||
|
|
||||||
- `User Setting Bereich`
|
- `User Setting Bereich`
|
||||||
@@ -56,7 +59,7 @@ Diese Benennungen gelten projektweit und sollen in UI, Doku und Weiterentwicklun
|
|||||||
### Systemobjekte
|
### Systemobjekte
|
||||||
|
|
||||||
- `App`
|
- `App`
|
||||||
Eine allgemeine Anwendung. Eine App erscheint standardmaessig als Menue-Eintrag, kann zusaetzlich ein Desktop-Icon bekommen und weitere Darstellungen wie Tray-App oder Widget bereitstellen. Admins koennen die LDAP-Gruppenberechtigung pro App verwalten.
|
Eine allgemeine Anwendung. Eine App erscheint standardmaessig als Menue-Eintrag, kann zusaetzlich ein Desktop-Icon bekommen und weitere Darstellungen wie Tray-App oder Widget bereitstellen. Admins koennen die LDAP-Gruppenberechtigung pro App verwalten und festlegen, ob sie bereits ohne Login auf dem Logoff-Desktop verfuegbar ist.
|
||||||
|
|
||||||
- `Tray-App`
|
- `Tray-App`
|
||||||
Eine App oder Teilfunktion im Bereich neben der Uhr. Tray-Apps sind fuer Schnellaktionen oder Visualisierungen gedacht und koennen eigenstaendig oder zusammen mit der Haupt-App genutzt werden.
|
Eine App oder Teilfunktion im Bereich neben der Uhr. Tray-Apps sind fuer Schnellaktionen oder Visualisierungen gedacht und koennen eigenstaendig oder zusammen mit der Haupt-App genutzt werden.
|
||||||
@@ -78,6 +81,7 @@ Diese Benennungen gelten projektweit und sollen in UI, Doku und Weiterentwicklun
|
|||||||
Stand dieser Datei:
|
Stand dieser Datei:
|
||||||
|
|
||||||
- gemeinsame Desktop-Shell mit den Skins `Apple`, `Windows` und `Linux`
|
- gemeinsame Desktop-Shell mit den Skins `Apple`, `Windows` und `Linux`
|
||||||
|
- offene Desktop-Oberflaeche vor dem Login mit eigener Login-Lasche
|
||||||
- frei verschiebbare Desktop-Icons
|
- frei verschiebbare Desktop-Icons
|
||||||
- Icon-Positionen lokal gespeichert, pro Benutzer und pro Skin
|
- Icon-Positionen lokal gespeichert, pro Benutzer und pro Skin
|
||||||
- automatische Schriftfarbanpassung fuer Desktop-Icons anhand des Hintergrunds
|
- automatische Schriftfarbanpassung fuer Desktop-Icons anhand des Hintergrunds
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ Verbindlich ist:
|
|||||||
- importierte Module mit eigener Fensterseite ueber konsistente `public/apps/<modul>/index.php`-Shims anbinden
|
- importierte Module mit eigener Fensterseite ueber konsistente `public/apps/<modul>/index.php`-Shims anbinden
|
||||||
- Widget-Logik einer App muss dieselben fachlichen Regeln wie die App-API verwenden, nicht eigene Sonderwege
|
- Widget-Logik einer App muss dieselben fachlichen Regeln wie die App-API verwenden, nicht eigene Sonderwege
|
||||||
- App-Berechtigungen fuer LDAP-Gruppen muessen zentral und pro App nachvollziehbar pflegbar bleiben
|
- App-Berechtigungen fuer LDAP-Gruppen muessen zentral und pro App nachvollziehbar pflegbar bleiben
|
||||||
|
- die Freigabe fuer den offenen Logoff-Desktop muss pro App zentral pflegbar bleiben
|
||||||
- Systemtools duerfen nicht als installierbare Module modelliert werden
|
- Systemtools duerfen nicht als installierbare Module modelliert werden
|
||||||
- neue Modul-Widgets und Modul-Cronjobs sollen ueber Manifest-Metadaten registriert werden statt ueber versteckte Sonderlisten
|
- neue Modul-Widgets und Modul-Cronjobs sollen ueber Manifest-Metadaten registriert werden statt ueber versteckte Sonderlisten
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ $wallpaper = $desktopPayload['desktop']['wallpaper'];
|
|||||||
$debugShellApp = is_array($desktopPayload['shell']['debug_app'] ?? null) ? $desktopPayload['shell']['debug_app'] : null;
|
$debugShellApp = is_array($desktopPayload['shell']['debug_app'] ?? null) ? $desktopPayload['shell']['debug_app'] : null;
|
||||||
$debugShellLabel = trim((string) ($debugShellApp['label'] ?? 'Debug')) ?: 'Debug';
|
$debugShellLabel = trim((string) ($debugShellApp['label'] ?? 'Debug')) ?: 'Debug';
|
||||||
$sessionDisplayName = (string) ($desktopPayload['session']['display_name'] ?? 'Gast');
|
$sessionDisplayName = (string) ($desktopPayload['session']['display_name'] ?? 'Gast');
|
||||||
|
$publicDesktopMode = (string) ($desktopPayload['desktop']['login']['mode'] ?? '') === 'public-desktop';
|
||||||
|
$loginUrl = (string) ($desktopPayload['desktop']['login']['login_url'] ?? '');
|
||||||
|
$loginAvailable = !empty($desktopPayload['desktop']['login']['login_available']) && $loginUrl !== '';
|
||||||
|
|
||||||
$assetVersion = static function (string $publicPath): string {
|
$assetVersion = static function (string $publicPath): string {
|
||||||
$filesystemPath = dirname(__DIR__, 2) . '/public' . $publicPath;
|
$filesystemPath = dirname(__DIR__, 2) . '/public' . $publicPath;
|
||||||
@@ -153,76 +156,91 @@ $renderTrayVisual = static function (array $trayItem, string $displayName = '')
|
|||||||
|
|
||||||
<div class="window-layer" id="window-layer"></div>
|
<div class="window-layer" id="window-layer"></div>
|
||||||
|
|
||||||
<section class="start-menu" id="start-menu" hidden>
|
<?php if (!$publicDesktopMode): ?>
|
||||||
<div class="start-menu-sidebar" id="start-menu-user-settings">
|
<section class="start-menu" id="start-menu" hidden>
|
||||||
<button class="start-menu-user-card" id="start-menu-user-card" type="button">
|
<div class="start-menu-sidebar" id="start-menu-user-settings">
|
||||||
<span class="start-menu-user-avatar" id="start-menu-user-avatar">GU</span>
|
<button class="start-menu-user-card" id="start-menu-user-card" type="button">
|
||||||
<span class="start-menu-user-copy">
|
<span class="start-menu-user-avatar" id="start-menu-user-avatar">GU</span>
|
||||||
<strong id="start-menu-user-name"><?= htmlspecialchars((string) ($desktopPayload['session']['display_name'] ?? 'Gast'), ENT_QUOTES) ?></strong>
|
<span class="start-menu-user-copy">
|
||||||
<small>Benutzer</small>
|
<strong id="start-menu-user-name"><?= htmlspecialchars((string) ($desktopPayload['session']['display_name'] ?? 'Gast'), ENT_QUOTES) ?></strong>
|
||||||
</span>
|
<small>Benutzer</small>
|
||||||
</button>
|
|
||||||
<div class="start-menu-function-list" id="start-menu-functions">
|
|
||||||
<button class="start-menu-function is-active" type="button" data-function-id="programs">
|
|
||||||
<span class="start-menu-function-icon" aria-hidden="true">📁</span>
|
|
||||||
<span class="start-menu-function-copy">
|
|
||||||
<strong>Programme</strong>
|
|
||||||
<small>Installierte Apps</small>
|
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
<div class="start-menu-function-list" id="start-menu-functions">
|
||||||
|
<button class="start-menu-function is-active" type="button" data-function-id="programs">
|
||||||
|
<span class="start-menu-function-icon" aria-hidden="true">📁</span>
|
||||||
|
<span class="start-menu-function-copy">
|
||||||
|
<strong>Programme</strong>
|
||||||
|
<small>Installierte Apps</small>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<?php if (($desktopPayload['session']['authenticated'] ?? false) === true && !empty($desktopPayload['session']['logout_url'])): ?>
|
||||||
|
<a class="start-menu-session-action is-logout" id="start-menu-session-action" href="<?= htmlspecialchars((string) $desktopPayload['session']['logout_url'], ENT_QUOTES) ?>">
|
||||||
|
<span class="start-menu-session-icon" aria-hidden="true">⏻</span>
|
||||||
|
<span>Abmelden</span>
|
||||||
|
</a>
|
||||||
|
<?php else: ?>
|
||||||
|
<button class="start-menu-session-action" id="start-menu-session-action" type="button" data-session-action="login">
|
||||||
|
<span class="start-menu-session-icon" aria-hidden="true">→</span>
|
||||||
|
<span>Anmelden</span>
|
||||||
|
</button>
|
||||||
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<?php if (($desktopPayload['session']['authenticated'] ?? false) === true && !empty($desktopPayload['session']['logout_url'])): ?>
|
<div class="start-menu-selection-area start-menu-selection-area--programs">
|
||||||
<a class="start-menu-session-action is-logout" id="start-menu-session-action" href="<?= htmlspecialchars((string) $desktopPayload['session']['logout_url'], ENT_QUOTES) ?>">
|
<div class="start-menu-selection-header" id="start-menu-selection-header"></div>
|
||||||
<span class="start-menu-session-icon" aria-hidden="true">⏻</span>
|
<div class="start-menu-selection-list" id="start-menu-apps"></div>
|
||||||
<span>Abmelden</span>
|
</div>
|
||||||
</a>
|
</section>
|
||||||
<?php else: ?>
|
<?php endif; ?>
|
||||||
<button class="start-menu-session-action" id="start-menu-session-action" type="button" data-session-action="login">
|
|
||||||
<span class="start-menu-session-icon" aria-hidden="true">→</span>
|
|
||||||
<span>Anmelden</span>
|
|
||||||
</button>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
<div class="start-menu-selection-area start-menu-selection-area--programs">
|
|
||||||
<div class="start-menu-selection-header" id="start-menu-selection-header"></div>
|
|
||||||
<div class="start-menu-selection-list" id="start-menu-apps"></div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<footer class="taskbar">
|
<footer class="taskbar<?= $publicDesktopMode ? ' taskbar--public' : '' ?>">
|
||||||
<button class="start-button" id="start-button" type="button">
|
<?php if ($publicDesktopMode): ?>
|
||||||
<span class="start-button-icon"><?= $renderStartIcon($skinProfile) ?></span>
|
<div class="taskbar-public-spacer" aria-hidden="true"></div>
|
||||||
<span class="start-button-label"><?= htmlspecialchars((string) ($skinProfile['taskbar_label'] ?? 'Menu'), ENT_QUOTES) ?></span>
|
<button
|
||||||
</button>
|
class="public-login-tab"
|
||||||
<div class="taskbar-apps" id="taskbar-apps"></div>
|
id="public-login-tab"
|
||||||
<div class="tray">
|
type="button"
|
||||||
<?php foreach ($desktopPayload['tray'] as $trayItem): ?>
|
<?= $loginAvailable ? 'data-login-url="' . htmlspecialchars($loginUrl, ENT_QUOTES) . '"' : 'disabled' ?>
|
||||||
<button
|
>
|
||||||
class="tray-pill tray-pill-button<?= (($trayItem['id'] ?? '') === 'gitea-deploy-status') ? ' tray-pill-status tray-pill-status--idle' : '' ?><?= (($trayItem['id'] ?? '') === 'account') ? ' tray-pill-account' : '' ?>"
|
<span class="public-login-tab-kicker">Desktop Login</span>
|
||||||
type="button"
|
<strong><?= $loginAvailable ? 'Anmelden' : 'Login nicht verfuegbar' ?></strong>
|
||||||
data-tray-id="<?= htmlspecialchars((string) $trayItem['id'], ENT_QUOTES) ?>"
|
</button>
|
||||||
<?php if (!empty($trayItem['app_id'])): ?>data-tray-app-id="<?= htmlspecialchars((string) $trayItem['app_id'], ENT_QUOTES) ?>" title="<?= htmlspecialchars((string) ($trayItem['title'] ?? $trayItem['label'] ?? ''), ENT_QUOTES) ?>"<?php endif; ?>
|
<?php else: ?>
|
||||||
<?php if (($trayItem['id'] ?? '') === 'account'): ?>title="<?= htmlspecialchars($sessionDisplayName, ENT_QUOTES) ?>"<?php elseif (empty($trayItem['app_id']) && !empty($trayItem['title'])): ?>title="<?= htmlspecialchars((string) $trayItem['title'], ENT_QUOTES) ?>"<?php endif; ?>
|
<button class="start-button" id="start-button" type="button">
|
||||||
><?= $renderTrayVisual($trayItem, $sessionDisplayName) ?></button>
|
<span class="start-button-icon"><?= $renderStartIcon($skinProfile) ?></span>
|
||||||
<?php endforeach; ?>
|
<span class="start-button-label"><?= htmlspecialchars((string) ($skinProfile['taskbar_label'] ?? 'Menu'), ENT_QUOTES) ?></span>
|
||||||
<?php if (($desktopPayload['session']['is_admin'] ?? false) === true): ?>
|
</button>
|
||||||
<button class="tray-pill tray-pill-button tray-pill-debug" id="debug-toggle" type="button" aria-expanded="false" title="<?= htmlspecialchars($debugShellLabel, ENT_QUOTES) ?>"><span class="tray-icon tray-icon--bug" aria-hidden="true"><svg viewBox="0 0 24 24" focusable="false"><path d="M9 7.5V6a3 3 0 0 1 6 0v1.5"></path><path d="M8 10.5h8a2 2 0 0 1 2 2V15a6 6 0 0 1-12 0v-2.5a2 2 0 0 1 2-2Z"></path><path d="M4 13h3M17 13h3M5.5 8.5l2 1.5M18.5 8.5l-2 1.5M7 18l-2 1.5M17 18l2 1.5"></path></svg></span><span class="sr-only"><?= htmlspecialchars($debugShellLabel, ENT_QUOTES) ?></span></button>
|
<div class="taskbar-apps" id="taskbar-apps"></div>
|
||||||
<?php endif; ?>
|
<div class="tray">
|
||||||
<time id="clock"></time>
|
<?php foreach ($desktopPayload['tray'] as $trayItem): ?>
|
||||||
</div>
|
<button
|
||||||
<div class="tray-menu" id="tray-account-menu" hidden>
|
class="tray-pill tray-pill-button<?= (($trayItem['id'] ?? '') === 'gitea-deploy-status') ? ' tray-pill-status tray-pill-status--idle' : '' ?><?= (($trayItem['id'] ?? '') === 'account') ? ' tray-pill-account' : '' ?>"
|
||||||
<button class="tray-menu-entry" type="button" data-account-action="settings">Einstellungen</button>
|
type="button"
|
||||||
<?php if (($desktopPayload['session']['authenticated'] ?? false) === true && !empty($desktopPayload['session']['logout_url'])): ?>
|
data-tray-id="<?= htmlspecialchars((string) $trayItem['id'], ENT_QUOTES) ?>"
|
||||||
<a class="tray-menu-entry" data-account-action="logout" href="<?= htmlspecialchars((string) $desktopPayload['session']['logout_url'], ENT_QUOTES) ?>">Logout</a>
|
<?php if (!empty($trayItem['app_id'])): ?>data-tray-app-id="<?= htmlspecialchars((string) $trayItem['app_id'], ENT_QUOTES) ?>" title="<?= htmlspecialchars((string) ($trayItem['title'] ?? $trayItem['label'] ?? ''), ENT_QUOTES) ?>"<?php endif; ?>
|
||||||
<?php else: ?>
|
<?php if (($trayItem['id'] ?? '') === 'account'): ?>title="<?= htmlspecialchars($sessionDisplayName, ENT_QUOTES) ?>"<?php elseif (empty($trayItem['app_id']) && !empty($trayItem['title'])): ?>title="<?= htmlspecialchars((string) $trayItem['title'], ENT_QUOTES) ?>"<?php endif; ?>
|
||||||
<button class="tray-menu-entry" type="button" data-account-action="login">Login</button>
|
><?= $renderTrayVisual($trayItem, $sessionDisplayName) ?></button>
|
||||||
<?php endif; ?>
|
<?php endforeach; ?>
|
||||||
</div>
|
<?php if (($desktopPayload['session']['is_admin'] ?? false) === true): ?>
|
||||||
<div class="tray-menu" id="tray-fx-menu" hidden>
|
<button class="tray-pill tray-pill-button tray-pill-debug" id="debug-toggle" type="button" aria-expanded="false" title="<?= htmlspecialchars($debugShellLabel, ENT_QUOTES) ?>"><span class="tray-icon tray-icon--bug" aria-hidden="true"><svg viewBox="0 0 24 24" focusable="false"><path d="M9 7.5V6a3 3 0 0 1 6 0v1.5"></path><path d="M8 10.5h8a2 2 0 0 1 2 2V15a6 6 0 0 1-12 0v-2.5a2 2 0 0 1 2-2Z"></path><path d="M4 13h3M17 13h3M5.5 8.5l2 1.5M18.5 8.5l-2 1.5M7 18l-2 1.5M17 18l2 1.5"></path></svg></span><span class="sr-only"><?= htmlspecialchars($debugShellLabel, ENT_QUOTES) ?></span></button>
|
||||||
<p class="tray-menu-copy" id="tray-fx-status">Kursstatus wird geladen ...</p>
|
<?php endif; ?>
|
||||||
<button class="tray-menu-entry" type="button" data-fx-action="refresh">Refresh</button>
|
<time id="clock"></time>
|
||||||
<button class="tray-menu-entry" type="button" data-fx-action="force-refresh">Force-Refresh</button>
|
</div>
|
||||||
</div>
|
<div class="tray-menu" id="tray-account-menu" hidden>
|
||||||
|
<button class="tray-menu-entry" type="button" data-account-action="settings">Einstellungen</button>
|
||||||
|
<?php if (($desktopPayload['session']['authenticated'] ?? false) === true && !empty($desktopPayload['session']['logout_url'])): ?>
|
||||||
|
<a class="tray-menu-entry" data-account-action="logout" href="<?= htmlspecialchars((string) $desktopPayload['session']['logout_url'], ENT_QUOTES) ?>">Logout</a>
|
||||||
|
<?php else: ?>
|
||||||
|
<button class="tray-menu-entry" type="button" data-account-action="login">Login</button>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<div class="tray-menu" id="tray-fx-menu" hidden>
|
||||||
|
<p class="tray-menu-copy" id="tray-fx-status">Kursstatus wird geladen ...</p>
|
||||||
|
<button class="tray-menu-entry" type="button" data-fx-action="refresh">Refresh</button>
|
||||||
|
<button class="tray-menu-entry" type="button" data-fx-action="force-refresh">Force-Refresh</button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
</footer>
|
</footer>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1420,6 +1420,52 @@ h1 {
|
|||||||
padding: 6px;
|
padding: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.taskbar--public {
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.taskbar-public-spacer {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.public-login-tab {
|
||||||
|
display: grid;
|
||||||
|
gap: 2px;
|
||||||
|
min-width: 180px;
|
||||||
|
padding: 12px 18px 13px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 18px 18px 0 0;
|
||||||
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.18), rgba(255, 255, 255, 0.08));
|
||||||
|
color: inherit;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
font: inherit;
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.public-login-tab:hover {
|
||||||
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.24), rgba(255, 255, 255, 0.12));
|
||||||
|
}
|
||||||
|
|
||||||
|
.public-login-tab:disabled {
|
||||||
|
cursor: default;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.public-login-tab-kicker {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: rgba(248, 250, 252, 0.74);
|
||||||
|
}
|
||||||
|
|
||||||
|
.public-login-tab strong {
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
.tray-avatar {
|
.tray-avatar {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ if (payloadNode) {
|
|||||||
const startMenuUserName = document.getElementById('start-menu-user-name');
|
const startMenuUserName = document.getElementById('start-menu-user-name');
|
||||||
const startMenuUserAvatar = document.getElementById('start-menu-user-avatar');
|
const startMenuUserAvatar = document.getElementById('start-menu-user-avatar');
|
||||||
const startMenuSessionAction = document.getElementById('start-menu-session-action');
|
const startMenuSessionAction = document.getElementById('start-menu-session-action');
|
||||||
|
const publicLoginButton = document.getElementById('public-login-tab');
|
||||||
const accountMenu = document.getElementById('tray-account-menu');
|
const accountMenu = document.getElementById('tray-account-menu');
|
||||||
const fxTrayMenu = document.getElementById('tray-fx-menu');
|
const fxTrayMenu = document.getElementById('tray-fx-menu');
|
||||||
const fxTrayStatusNode = document.getElementById('tray-fx-status');
|
const fxTrayStatusNode = document.getElementById('tray-fx-status');
|
||||||
@@ -50,6 +51,7 @@ if (payloadNode) {
|
|||||||
const activeSkin = payload.meta.active_skin;
|
const activeSkin = payload.meta.active_skin;
|
||||||
const skinProfile = payload.meta.skin_profile || {};
|
const skinProfile = payload.meta.skin_profile || {};
|
||||||
const settingsApi = payload.desktop.settings_api || '';
|
const settingsApi = payload.desktop.settings_api || '';
|
||||||
|
const loginUrl = typeof payload.desktop?.login?.login_url === 'string' ? payload.desktop.login.login_url : '';
|
||||||
const shellApps = Array.isArray(payload.shell?.apps) ? payload.shell.apps : [];
|
const shellApps = Array.isArray(payload.shell?.apps) ? payload.shell.apps : [];
|
||||||
const debugShellApp = (payload.shell && typeof payload.shell.debug_app === 'object' && payload.shell.debug_app)
|
const debugShellApp = (payload.shell && typeof payload.shell.debug_app === 'object' && payload.shell.debug_app)
|
||||||
|| shellApps.find((app) => app && app.app_id === 'desktop-debug')
|
|| shellApps.find((app) => app && app.app_id === 'desktop-debug')
|
||||||
@@ -2148,7 +2150,9 @@ if (payloadNode) {
|
|||||||
|
|
||||||
if (action === 'login') {
|
if (action === 'login') {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
window.location.href = '/auth/keycloak';
|
if (loginUrl) {
|
||||||
|
window.location.href = loginUrl;
|
||||||
|
}
|
||||||
setAccountMenuOpen(false);
|
setAccountMenuOpen(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -2165,10 +2169,19 @@ if (payloadNode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
window.location.href = '/auth/keycloak';
|
if (loginUrl) {
|
||||||
|
window.location.href = loginUrl;
|
||||||
|
}
|
||||||
closeStartMenu();
|
closeStartMenu();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
publicLoginButton?.addEventListener('click', () => {
|
||||||
|
const targetUrl = publicLoginButton.getAttribute('data-login-url') || loginUrl;
|
||||||
|
if (targetUrl) {
|
||||||
|
window.location.href = targetUrl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
startMenuFunctions?.addEventListener('click', (event) => {
|
startMenuFunctions?.addEventListener('click', (event) => {
|
||||||
const target = event.target instanceof Element ? event.target.closest('[data-function-id]') : null;
|
const target = event.target instanceof Element ? event.target.closest('[data-function-id]') : null;
|
||||||
|
|
||||||
|
|||||||
@@ -41,17 +41,6 @@ if ($auth->isAuthenticated()) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$auth->shouldShowDesktop()) {
|
|
||||||
if ($auth->isConfigured()) {
|
|
||||||
header('Location: /auth/keycloak', true, 302);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
http_response_code(503);
|
|
||||||
echo 'Keycloak ist nicht konfiguriert.';
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$app = new App($projectRoot);
|
$app = new App($projectRoot);
|
||||||
$desktopPayload = $app->desktopPayload();
|
$desktopPayload = $app->desktopPayload();
|
||||||
|
|
||||||
|
|||||||
@@ -92,9 +92,9 @@ final class AdminAppsService
|
|||||||
* @param array<int, string> $requiredGroups
|
* @param array<int, string> $requiredGroups
|
||||||
* @return array<string, array{required_groups: array<int, string>}>
|
* @return array<string, array{required_groups: array<int, string>}>
|
||||||
*/
|
*/
|
||||||
public function saveAppAccess(string $appId, array $requiredGroups): array
|
public function saveAppAccess(string $appId, array $requiredGroups, bool $availableWithoutLogin = false): array
|
||||||
{
|
{
|
||||||
return AppAccessConfig::saveRequiredGroups($this->projectRoot, $appId, $requiredGroups);
|
return AppAccessConfig::saveRequiredGroups($this->projectRoot, $appId, $requiredGroups, $availableWithoutLogin);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -118,6 +118,7 @@ final class AdminAppsService
|
|||||||
'supports_widget' => !empty($app['supports_widget']),
|
'supports_widget' => !empty($app['supports_widget']),
|
||||||
'supports_tray' => !empty($app['supports_tray']),
|
'supports_tray' => !empty($app['supports_tray']),
|
||||||
'enabled_by_default' => !empty($app['enabled_by_default']),
|
'enabled_by_default' => !empty($app['enabled_by_default']),
|
||||||
|
'available_without_login' => !empty($app['available_without_login']),
|
||||||
'required_groups' => $requiredGroups,
|
'required_groups' => $requiredGroups,
|
||||||
'required_groups_display' => $requiredGroups === [] ? 'Alle sichtbaren Benutzer' : implode(', ', $requiredGroups),
|
'required_groups_display' => $requiredGroups === [] ? 'Alle sichtbaren Benutzer' : implode(', ', $requiredGroups),
|
||||||
'admin_only' => in_array('administrators', array_map(
|
'admin_only' => in_array('administrators', array_map(
|
||||||
|
|||||||
@@ -49,7 +49,10 @@ final class App
|
|||||||
array_map('strval', (array) ($authUser['groups'] ?? []))
|
array_map('strval', (array) ($authUser['groups'] ?? []))
|
||||||
));
|
));
|
||||||
$isAdmin = in_array('administrators', $authGroups, true);
|
$isAdmin = in_array('administrators', $authGroups, true);
|
||||||
$visibleApps = AppVisibility::filterByGroups($registry->all(), $authGroups);
|
$publicDesktopMode = !$isAuthenticated;
|
||||||
|
$visibleApps = $publicDesktopMode
|
||||||
|
? AppVisibility::filterForPublicDesktop($registry->all())
|
||||||
|
: AppVisibility::filterByGroups($registry->all(), $authGroups);
|
||||||
$userSelfManagement = new UserSelfManagementService($this->projectRoot);
|
$userSelfManagement = new UserSelfManagementService($this->projectRoot);
|
||||||
$resolvedSkin = $userSelfManagement->resolveActiveSkin(
|
$resolvedSkin = $userSelfManagement->resolveActiveSkin(
|
||||||
isset($_GET['skin']) ? (string) $_GET['skin'] : null,
|
isset($_GET['skin']) ? (string) $_GET['skin'] : null,
|
||||||
@@ -60,16 +63,27 @@ final class App
|
|||||||
);
|
);
|
||||||
$userSettings = $resolvedSkin['settings'];
|
$userSettings = $resolvedSkin['settings'];
|
||||||
$activeSkin = (string) $resolvedSkin['skin'];
|
$activeSkin = (string) $resolvedSkin['skin'];
|
||||||
$menuAppIds = array_values(array_map('strval', (array) ($userSettings['apps']['enabled_ids'] ?? [])));
|
$menuAppIds = $publicDesktopMode
|
||||||
$desktopIconIds = array_values(array_map('strval', (array) ($userSettings['apps']['desktop_icon_ids'] ?? [])));
|
? []
|
||||||
$trayAppIds = array_values(array_map('strval', (array) ($userSettings['tray']['active_ids'] ?? [])));
|
: array_values(array_map('strval', (array) ($userSettings['apps']['enabled_ids'] ?? [])));
|
||||||
|
$desktopIconIds = $publicDesktopMode
|
||||||
|
? array_values(array_filter(array_map(
|
||||||
|
static fn (array $app): ?string => (($app['show_on_desktop'] ?? false) !== false) ? (string) ($app['app_id'] ?? '') : null,
|
||||||
|
$visibleApps
|
||||||
|
)))
|
||||||
|
: array_values(array_map('strval', (array) ($userSettings['apps']['desktop_icon_ids'] ?? [])));
|
||||||
|
$trayAppIds = $publicDesktopMode
|
||||||
|
? []
|
||||||
|
: array_values(array_map('strval', (array) ($userSettings['tray']['active_ids'] ?? [])));
|
||||||
$visibleAppIds = array_values(array_filter(array_map(
|
$visibleAppIds = array_values(array_filter(array_map(
|
||||||
static fn (array $app): string => (string) ($app['app_id'] ?? ''),
|
static fn (array $app): string => (string) ($app['app_id'] ?? ''),
|
||||||
$visibleApps
|
$visibleApps
|
||||||
)));
|
)));
|
||||||
$availableWidgets = $widgetRegistry->availableForApps($visibleAppIds);
|
$availableWidgets = $publicDesktopMode ? [] : $widgetRegistry->availableForApps($visibleAppIds);
|
||||||
$activeWidgetIds = array_values(array_map('strval', (array) ($userSettings['widgets']['active_ids'] ?? DesktopState::defaultWidgetIds())));
|
$activeWidgetIds = $publicDesktopMode
|
||||||
$activeWidgets = $widgetRegistry->active($activeWidgetIds, $visibleAppIds);
|
? []
|
||||||
|
: array_values(array_map('strval', (array) ($userSettings['widgets']['active_ids'] ?? DesktopState::defaultWidgetIds())));
|
||||||
|
$activeWidgets = $publicDesktopMode ? [] : $widgetRegistry->active($activeWidgetIds, $visibleAppIds);
|
||||||
$widgetLaunchAppIds = array_values(array_filter(array_unique(array_map(
|
$widgetLaunchAppIds = array_values(array_filter(array_unique(array_map(
|
||||||
static fn (array $widget): string => (string) (($widget['launch_app_id'] ?? '') ?: ($widget['source_app_id'] ?? '')),
|
static fn (array $widget): string => (string) (($widget['launch_app_id'] ?? '') ?: ($widget['source_app_id'] ?? '')),
|
||||||
$activeWidgets
|
$activeWidgets
|
||||||
@@ -79,44 +93,50 @@ final class App
|
|||||||
$desktopIconIds,
|
$desktopIconIds,
|
||||||
$trayAppIds,
|
$trayAppIds,
|
||||||
$widgetLaunchAppIds,
|
$widgetLaunchAppIds,
|
||||||
[UserSelfManagementService::APP_ID]
|
$publicDesktopMode ? [] : [UserSelfManagementService::APP_ID]
|
||||||
))));
|
))));
|
||||||
$payloadApps = AppVisibility::filterByEnabledIds(
|
$payloadApps = $publicDesktopMode
|
||||||
$visibleApps,
|
? array_values(array_filter($visibleApps, static fn (array $app): bool => in_array((string) ($app['app_id'] ?? ''), $payloadAppIds, true)))
|
||||||
$payloadAppIds,
|
: AppVisibility::filterByEnabledIds(
|
||||||
[UserSelfManagementService::APP_ID]
|
$visibleApps,
|
||||||
);
|
$payloadAppIds,
|
||||||
$apps = AppIconResolver::decorate(array_map(static function (array $app) use ($menuAppIds, $desktopIconIds, $trayAppIds): array {
|
[UserSelfManagementService::APP_ID]
|
||||||
|
);
|
||||||
|
$apps = AppIconResolver::decorate(array_map(static function (array $app) use ($menuAppIds, $desktopIconIds, $trayAppIds, $publicDesktopMode): array {
|
||||||
$appId = (string) ($app['app_id'] ?? '');
|
$appId = (string) ($app['app_id'] ?? '');
|
||||||
$installable = !array_key_exists('installable', $app) || (bool) $app['installable'];
|
$installable = !array_key_exists('installable', $app) || (bool) $app['installable'];
|
||||||
$app['user_show_in_start_menu'] = $installable
|
$app['user_show_in_start_menu'] = $publicDesktopMode
|
||||||
|
? false
|
||||||
|
: ($installable
|
||||||
? in_array($appId, $menuAppIds, true)
|
? in_array($appId, $menuAppIds, true)
|
||||||
: (($app['show_in_start_menu'] ?? true) !== false);
|
: (($app['show_in_start_menu'] ?? true) !== false));
|
||||||
$app['user_show_on_desktop'] = in_array($appId, $desktopIconIds, true);
|
$app['user_show_on_desktop'] = in_array($appId, $desktopIconIds, true);
|
||||||
$app['user_show_in_tray'] = in_array($appId, $trayAppIds, true);
|
$app['user_show_in_tray'] = in_array($appId, $trayAppIds, true);
|
||||||
|
|
||||||
return $app;
|
return $app;
|
||||||
}, $payloadApps), $activeSkin);
|
}, $payloadApps), $activeSkin);
|
||||||
$windows = (new WindowManager($registry))->defaultWindows();
|
$windows = (new WindowManager($registry))->defaultWindows();
|
||||||
$trayItems = DesktopState::trayItems();
|
$trayItems = $publicDesktopMode ? [] : DesktopState::trayItems();
|
||||||
foreach ($apps as $app) {
|
if (!$publicDesktopMode) {
|
||||||
if (empty($app['supports_tray']) || empty($app['user_show_in_tray'])) {
|
foreach ($apps as $app) {
|
||||||
continue;
|
if (empty($app['supports_tray']) || empty($app['user_show_in_tray'])) {
|
||||||
}
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$trayItems[] = [
|
$trayItems[] = [
|
||||||
'id' => 'app-' . (string) $app['app_id'],
|
'id' => 'app-' . (string) $app['app_id'],
|
||||||
'label' => (string) ($app['icon'] ?? 'FX'),
|
'label' => (string) ($app['icon'] ?? 'FX'),
|
||||||
'app_id' => (string) ($app['app_id'] ?? ''),
|
'app_id' => (string) ($app['app_id'] ?? ''),
|
||||||
'title' => (string) ($app['title'] ?? ''),
|
'title' => (string) ($app['title'] ?? ''),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
if ($giteaStatus->shouldShowInTray($isAdmin)) {
|
if ($giteaStatus->shouldShowInTray($isAdmin)) {
|
||||||
$trayItems[] = [
|
$trayItems[] = [
|
||||||
'id' => 'gitea-deploy-status',
|
'id' => 'gitea-deploy-status',
|
||||||
'label' => '●',
|
'label' => '●',
|
||||||
'title' => 'Gitea-Deploy-Status',
|
'title' => 'Gitea-Deploy-Status',
|
||||||
];
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$displayName = (string) ($authUser['name'] ?? $authUser['username'] ?? 'Gast');
|
$displayName = (string) ($authUser['name'] ?? $authUser['username'] ?? 'Gast');
|
||||||
$storageScope = $isAuthenticated
|
$storageScope = $isAuthenticated
|
||||||
@@ -140,7 +160,9 @@ final class App
|
|||||||
'user_preferences' => $userSettings,
|
'user_preferences' => $userSettings,
|
||||||
'login' => [
|
'login' => [
|
||||||
'authenticated' => $isAuthenticated,
|
'authenticated' => $isAuthenticated,
|
||||||
'mode' => $isAuthenticated ? 'authenticated' : 'login-required',
|
'mode' => $isAuthenticated ? 'authenticated' : 'public-desktop',
|
||||||
|
'login_url' => $auth->isConfigured() ? '/auth/keycloak' : null,
|
||||||
|
'login_available' => $auth->isConfigured(),
|
||||||
'keycloak_theme_handoff' => '/docs/keycloak-theme-handoff.md',
|
'keycloak_theme_handoff' => '/docs/keycloak-theme-handoff.md',
|
||||||
'user' => [
|
'user' => [
|
||||||
'name' => (string) ($authUser['name'] ?? ''),
|
'name' => (string) ($authUser['name'] ?? ''),
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace App;
|
|||||||
final class AppAccessConfig
|
final class AppAccessConfig
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @return array<string, array{required_groups: array<int, string>}>
|
* @return array<string, array{required_groups: array<int, string>, available_without_login: bool}>
|
||||||
*/
|
*/
|
||||||
public static function load(string $projectRoot): array
|
public static function load(string $projectRoot): array
|
||||||
{
|
{
|
||||||
@@ -22,6 +22,7 @@ final class AppAccessConfig
|
|||||||
|
|
||||||
$normalized[$appId] = [
|
$normalized[$appId] = [
|
||||||
'required_groups' => self::sanitizeGroups($definition['required_groups'] ?? []),
|
'required_groups' => self::sanitizeGroups($definition['required_groups'] ?? []),
|
||||||
|
'available_without_login' => (bool) ($definition['available_without_login'] ?? false),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,9 +64,9 @@ final class AppAccessConfig
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<int, string> $requiredGroups
|
* @param array<int, string> $requiredGroups
|
||||||
* @return array<string, array{required_groups: array<int, string>}>
|
* @return array<string, array{required_groups: array<int, string>, available_without_login: bool}>
|
||||||
*/
|
*/
|
||||||
public static function saveRequiredGroups(string $projectRoot, string $appId, array $requiredGroups): array
|
public static function saveRequiredGroups(string $projectRoot, string $appId, array $requiredGroups, bool $availableWithoutLogin = false): array
|
||||||
{
|
{
|
||||||
$appId = trim($appId);
|
$appId = trim($appId);
|
||||||
if ($appId === '') {
|
if ($appId === '') {
|
||||||
@@ -75,6 +76,7 @@ final class AppAccessConfig
|
|||||||
$current = self::load($projectRoot);
|
$current = self::load($projectRoot);
|
||||||
$current[$appId] = [
|
$current[$appId] = [
|
||||||
'required_groups' => self::sanitizeGroups($requiredGroups),
|
'required_groups' => self::sanitizeGroups($requiredGroups),
|
||||||
|
'available_without_login' => $availableWithoutLogin,
|
||||||
];
|
];
|
||||||
|
|
||||||
self::writeEnvironmentConfig($projectRoot, 'app_access', ConfigLoader::currentEnvironment(), $current);
|
self::writeEnvironmentConfig($projectRoot, 'app_access', ConfigLoader::currentEnvironment(), $current);
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ final class AppRegistry
|
|||||||
if (is_array($override) && array_key_exists('required_groups', $override)) {
|
if (is_array($override) && array_key_exists('required_groups', $override)) {
|
||||||
$app['required_groups'] = array_values(array_map('strval', (array) ($override['required_groups'] ?? [])));
|
$app['required_groups'] = array_values(array_map('strval', (array) ($override['required_groups'] ?? [])));
|
||||||
}
|
}
|
||||||
|
if (is_array($override) && array_key_exists('available_without_login', $override)) {
|
||||||
|
$app['available_without_login'] = (bool) ($override['available_without_login'] ?? false);
|
||||||
|
}
|
||||||
|
|
||||||
return $app;
|
return $app;
|
||||||
}, $mergedApps);
|
}, $mergedApps);
|
||||||
|
|||||||
@@ -61,4 +61,15 @@ final class AppVisibility
|
|||||||
return isset($lookup[$appId]) || isset($mandatoryLookup[$appId]);
|
return isset($lookup[$appId]) || isset($mandatoryLookup[$appId]);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<int, array<string, mixed>> $apps
|
||||||
|
* @return array<int, array<string, mixed>>
|
||||||
|
*/
|
||||||
|
public static function filterForPublicDesktop(array $apps): array
|
||||||
|
{
|
||||||
|
return array_values(array_filter($apps, static function (array $app): bool {
|
||||||
|
return !empty($app['available_without_login']);
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -231,6 +231,81 @@
|
|||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#admin-apps-app .aa-inline-access-form {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
min-width: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#admin-apps-app .aa-inline-access-current {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #0f172a;
|
||||||
|
}
|
||||||
|
|
||||||
|
#admin-apps-app .aa-inline-access-toggle {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto minmax(0, 1fr);
|
||||||
|
gap: 8px;
|
||||||
|
align-items: start;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.45;
|
||||||
|
color: #334155;
|
||||||
|
}
|
||||||
|
|
||||||
|
#admin-apps-app .aa-inline-access-toggle input {
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#admin-apps-app .aa-inline-access-details {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#admin-apps-app .aa-inline-access-details summary {
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1d4ed8;
|
||||||
|
}
|
||||||
|
|
||||||
|
#admin-apps-app .aa-inline-access-groups {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#admin-apps-app .aa-inline-access-option {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto minmax(0, 1fr);
|
||||||
|
gap: 8px;
|
||||||
|
align-items: start;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border: 1px solid rgba(148, 163, 184, 0.22);
|
||||||
|
border-radius: 12px;
|
||||||
|
background: rgba(255, 255, 255, 0.92);
|
||||||
|
}
|
||||||
|
|
||||||
|
#admin-apps-app .aa-inline-access-option input {
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#admin-apps-app .aa-inline-access-option strong,
|
||||||
|
#admin-apps-app .aa-inline-access-option small {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#admin-apps-app .aa-inline-access-option small {
|
||||||
|
margin-top: 3px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
#admin-apps-app .aa-inline-access-submit {
|
||||||
|
justify-self: start;
|
||||||
|
}
|
||||||
|
|
||||||
#admin-apps-app .aa-message.is-success {
|
#admin-apps-app .aa-message.is-success {
|
||||||
background: rgba(34, 197, 94, 0.12);
|
background: rgba(34, 197, 94, 0.12);
|
||||||
color: #166534;
|
color: #166534;
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ $service = new AdminAppsService($projectRoot);
|
|||||||
$messages = [];
|
$messages = [];
|
||||||
$errors = [];
|
$errors = [];
|
||||||
$section = trim((string) ($_GET['section'] ?? $_POST['active_section'] ?? 'overview'));
|
$section = trim((string) ($_GET['section'] ?? $_POST['active_section'] ?? 'overview'));
|
||||||
$allowedSections = ['overview', 'installation', 'access', 'widgets', 'integrations'];
|
$allowedSections = ['overview', 'installation', 'widgets', 'integrations'];
|
||||||
if (!in_array($section, $allowedSections, true)) {
|
if (!in_array($section, $allowedSections, true)) {
|
||||||
$section = 'overview';
|
$section = 'overview';
|
||||||
}
|
}
|
||||||
@@ -81,10 +81,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
} elseif ($action === 'save-app-access') {
|
} elseif ($action === 'save-app-access') {
|
||||||
$service->saveAppAccess(
|
$service->saveAppAccess(
|
||||||
(string) ($_POST['app_id'] ?? ''),
|
(string) ($_POST['app_id'] ?? ''),
|
||||||
array_values(array_map('strval', (array) ($_POST['required_groups'] ?? [])))
|
array_values(array_map('strval', (array) ($_POST['required_groups'] ?? []))),
|
||||||
|
!empty($_POST['available_without_login'])
|
||||||
);
|
);
|
||||||
$messages[] = 'LDAP-Gruppen fuer die App wurden gespeichert.';
|
$messages[] = 'LDAP-Gruppen fuer die App wurden gespeichert.';
|
||||||
$section = 'access';
|
$section = 'overview';
|
||||||
}
|
}
|
||||||
} catch (\Throwable $exception) {
|
} catch (\Throwable $exception) {
|
||||||
$errors[] = $exception->getMessage();
|
$errors[] = $exception->getMessage();
|
||||||
@@ -117,12 +118,6 @@ $sectionMeta = [
|
|||||||
'description' => 'Installierbare Fach-Apps liegen unter custom/apps/. Upload- und Importwege werden hier als naechster Ausbauschritt vorbereitet.',
|
'description' => 'Installierbare Fach-Apps liegen unter custom/apps/. Upload- und Importwege werden hier als naechster Ausbauschritt vorbereitet.',
|
||||||
'nav' => 'ZIP-, Verzeichnis- und Server-Workflow beschreiben.',
|
'nav' => 'ZIP-, Verzeichnis- und Server-Workflow beschreiben.',
|
||||||
],
|
],
|
||||||
'access' => [
|
|
||||||
'label' => 'Berechtigungen',
|
|
||||||
'title' => 'LDAP-Gruppen pro App',
|
|
||||||
'description' => 'Admins steuern hier, welche LDAP-Gruppen eine App benutzen duerfen. Leere Auswahl bedeutet: fuer alle sichtbaren Benutzer.',
|
|
||||||
'nav' => 'LDAP-Gruppen verwalten.',
|
|
||||||
],
|
|
||||||
'widgets' => [
|
'widgets' => [
|
||||||
'label' => 'Widgets',
|
'label' => 'Widgets',
|
||||||
'title' => 'Widgets und Quellen',
|
'title' => 'Widgets und Quellen',
|
||||||
@@ -264,7 +259,50 @@ ob_start();
|
|||||||
</td>
|
</td>
|
||||||
<td><?= $h((string) ($app['app_scope'] ?? '')) ?></td>
|
<td><?= $h((string) ($app['app_scope'] ?? '')) ?></td>
|
||||||
<td><?= $h(!empty($app['installable']) ? 'Custom App' : 'System') ?></td>
|
<td><?= $h(!empty($app['installable']) ? 'Custom App' : 'System') ?></td>
|
||||||
<td><?= $h((string) ($app['required_groups_display'] ?? '')) ?></td>
|
<td>
|
||||||
|
<form class="aa-inline-access-form" method="post" action="/admin/apps/">
|
||||||
|
<input type="hidden" name="csrf_token" value="<?= $h($csrfToken) ?>">
|
||||||
|
<input type="hidden" name="action" value="save-app-access">
|
||||||
|
<input type="hidden" name="active_section" value="overview">
|
||||||
|
<input type="hidden" name="app_id" value="<?= $h((string) ($app['app_id'] ?? '')) ?>">
|
||||||
|
|
||||||
|
<p class="aa-inline-access-current"><?= $h((string) ($app['required_groups_display'] ?? '')) ?></p>
|
||||||
|
<label class="aa-inline-access-toggle">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
name="available_without_login"
|
||||||
|
value="1"
|
||||||
|
<?= !empty($app['available_without_login']) ? 'checked' : '' ?>
|
||||||
|
>
|
||||||
|
<span>Auch ohne Login auf dem Logoff-Desktop anzeigen</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<?php if ($availableGroups !== []): ?>
|
||||||
|
<details class="aa-inline-access-details">
|
||||||
|
<summary>Bearbeiten</summary>
|
||||||
|
<div class="aa-inline-access-groups">
|
||||||
|
<?php foreach ($availableGroups as $group): ?>
|
||||||
|
<label class="aa-inline-access-option">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
name="required_groups[]"
|
||||||
|
value="<?= $h((string) ($group['id'] ?? '')) ?>"
|
||||||
|
<?= in_array((string) ($group['id'] ?? ''), array_values(array_map('strval', (array) ($app['required_groups'] ?? []))), true) ? 'checked' : '' ?>
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<strong><?= $h((string) ($group['label'] ?? '')) ?></strong>
|
||||||
|
<small><?= $h((string) ($group['id'] ?? '')) ?></small>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<button class="window-app-button aa-primary aa-inline-access-submit" type="submit">Speichern</button>
|
||||||
|
</details>
|
||||||
|
<?php else: ?>
|
||||||
|
<p class="aa-table-copy">Keine LDAP-Gruppen gefunden.</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
<td><code><?= $h((string) ($app['entry_route'] ?? '')) ?></code></td>
|
<td><code><?= $h((string) ($app['entry_route'] ?? '')) ?></code></td>
|
||||||
<td>
|
<td>
|
||||||
<div class="aa-flag-list">
|
<div class="aa-flag-list">
|
||||||
@@ -272,6 +310,7 @@ ob_start();
|
|||||||
<?php if (!empty($app['supports_widget'])): ?><span class="window-app-pill">Widget</span><?php endif; ?>
|
<?php if (!empty($app['supports_widget'])): ?><span class="window-app-pill">Widget</span><?php endif; ?>
|
||||||
<?php if (!empty($app['supports_tray'])): ?><span class="window-app-pill">Tray</span><?php endif; ?>
|
<?php if (!empty($app['supports_tray'])): ?><span class="window-app-pill">Tray</span><?php endif; ?>
|
||||||
<?php if (!empty($app['enabled_by_default'])): ?><span class="window-app-pill">Default</span><?php endif; ?>
|
<?php if (!empty($app['enabled_by_default'])): ?><span class="window-app-pill">Default</span><?php endif; ?>
|
||||||
|
<?php if (!empty($app['available_without_login'])): ?><span class="window-app-pill">Logoff Desktop</span><?php endif; ?>
|
||||||
<?php if (!empty($app['admin_only'])): ?><span class="window-app-pill">Admin only</span><?php endif; ?>
|
<?php if (!empty($app['admin_only'])): ?><span class="window-app-pill">Admin only</span><?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -291,6 +330,17 @@ ob_start();
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="window-app-card aa-card">
|
||||||
|
<div class="aa-section-head">
|
||||||
|
<div>
|
||||||
|
<p class="window-app-kicker aa-kicker">Flags</p>
|
||||||
|
<h3 class="aa-section-title">Bedeutung der Flags</h3>
|
||||||
|
<p class="aa-copy"><strong>Default</strong> bedeutet: Die App wird fuer neue Benutzer oder fuer Benutzer ohne explizite Auswahl initial vorausgewaehlt. <strong>Logoff Desktop</strong> bedeutet: Die App darf bereits ohne Login auf der offenen Desktop-Oberflaeche erscheinen, sofern sie als Desktop-App darstellbar ist.</p>
|
||||||
|
<p class="aa-copy">Gespeichert wird in <?= $h((string) ($meta['app_access_config_path'] ?? '')) ?>. Dort liegen sowohl LDAP-Gruppen als auch die Freigabe fuer den offenen Desktop.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if ($section === 'installation'): ?>
|
<?php if ($section === 'installation'): ?>
|
||||||
@@ -331,60 +381,6 @@ ob_start();
|
|||||||
</section>
|
</section>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if ($section === 'access'): ?>
|
|
||||||
<section class="window-app-card aa-card">
|
|
||||||
<div class="aa-section-head">
|
|
||||||
<div>
|
|
||||||
<p class="window-app-kicker aa-kicker">Access Control</p>
|
|
||||||
<h3 class="aa-section-title">LDAP-Gruppen pro App</h3>
|
|
||||||
<p class="aa-copy">Gespeichert wird in <?= $h((string) ($meta['app_access_config_path'] ?? '')) ?>. Die kurzen Gruppennamen entsprechen den LDAP-/Auth-Gruppen im Desktop.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="aa-grid">
|
|
||||||
<?php foreach ($apps as $app): ?>
|
|
||||||
<form class="window-app-card aa-card aa-access-card" method="post" action="/admin/apps/">
|
|
||||||
<input type="hidden" name="csrf_token" value="<?= $h($csrfToken) ?>">
|
|
||||||
<input type="hidden" name="action" value="save-app-access">
|
|
||||||
<input type="hidden" name="active_section" value="access">
|
|
||||||
<input type="hidden" name="app_id" value="<?= $h((string) ($app['app_id'] ?? '')) ?>">
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<strong><?= $h((string) ($app['title'] ?? '')) ?></strong>
|
|
||||||
<p class="aa-table-copy"><?= $h((string) ($app['summary'] ?? '')) ?></p>
|
|
||||||
<p class="aa-table-copy">Aktuell: <?= $h((string) ($app['required_groups_display'] ?? '')) ?></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="aa-access-groups">
|
|
||||||
<?php if ($availableGroups === []): ?>
|
|
||||||
<p class="aa-copy">Keine LDAP-Gruppen aus der Registration-Konfiguration gefunden.</p>
|
|
||||||
<?php else: ?>
|
|
||||||
<?php foreach ($availableGroups as $group): ?>
|
|
||||||
<label class="window-app-item aa-group-item">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
name="required_groups[]"
|
|
||||||
value="<?= $h((string) ($group['id'] ?? '')) ?>"
|
|
||||||
<?= in_array((string) ($group['id'] ?? ''), array_values(array_map('strval', (array) ($app['required_groups'] ?? []))), true) ? 'checked' : '' ?>
|
|
||||||
>
|
|
||||||
<span class="window-app-item-main aa-group-main">
|
|
||||||
<strong><?= $h((string) ($group['label'] ?? '')) ?></strong>
|
|
||||||
<span class="aa-meta"><?= $h((string) ($group['id'] ?? '')) ?></span>
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="aa-actions">
|
|
||||||
<button class="window-app-button aa-primary" type="submit">Berechtigungen speichern</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<?php if ($section === 'widgets'): ?>
|
<?php if ($section === 'widgets'): ?>
|
||||||
<section class="window-app-card aa-card">
|
<section class="window-app-card aa-card">
|
||||||
<div class="aa-section-head">
|
<div class="aa-section-head">
|
||||||
|
|||||||
Reference in New Issue
Block a user