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

This commit is contained in:
2026-06-25 00:36:14 +02:00
parent ac2d95fc56
commit 487f592ca3
12 changed files with 188 additions and 17 deletions

View File

@@ -105,8 +105,10 @@ Aus [README.md](/home/lars/Schreibtisch/Projekte/desktop.kusche.berlin/docs/READ
- `partials/desktop/` enthaelt Shell-Templates - `partials/desktop/` enthaelt Shell-Templates
- `custom/apps/` bleibt Zielort fuer installierbare Fach-Apps - `custom/apps/` bleibt Zielort fuer installierbare Fach-Apps
- `system/apps/` bleibt Zielort fuer System-Tools - `system/apps/` bleibt Zielort fuer System-Tools
- `system/shell/` bleibt Zielort fuer globale Shell-Werkzeuge und shell-nahe Desktop-Helfer
- `temp/nexus-module-import/` ist Rohbasis fuer importierte Nexus-Module - `temp/nexus-module-import/` ist Rohbasis fuer importierte Nexus-Module
- Keycloak bleibt das Auth-System - Keycloak bleibt das Auth-System
- das globale Desktop-Debug ist als Shell-Werkzeug unter `system/shell/desktop-debug/` definiert
### Module ### Module

View File

@@ -27,6 +27,7 @@ Zentraler Dokumentationsindex fuer das Projekt.
- `partials/desktop/` Shell-Template - `partials/desktop/` Shell-Template
- `custom/apps/` Zielort fuer installierbare Fach-Apps - `custom/apps/` Zielort fuer installierbare Fach-Apps
- `system/apps/` Zielort fuer nicht installierbare System-Tools - `system/apps/` Zielort fuer nicht installierbare System-Tools
- `system/shell/` Zielort fuer globale Shell-Werkzeuge und shell-nahe Desktop-Helfer
- `temp/nexus-module-import/` Rohbasis fuer importierte Nexus-Module - `temp/nexus-module-import/` Rohbasis fuer importierte Nexus-Module
## Fachliche Hinweise ## Fachliche Hinweise
@@ -37,6 +38,7 @@ Zentraler Dokumentationsindex fuer das Projekt.
- API und Desktop laufen auf demselben Host. Zielpfad ist `(staging.)desktop.kusche.berlin/api/v1/...`. - API und Desktop laufen auf demselben Host. Zielpfad ist `(staging.)desktop.kusche.berlin/api/v1/...`.
- der Theme-Handoff ist in [keycloak-theme-handoff.md](/home/lars/Schreibtisch/Projekte/desktop.kusche.berlin/docs/keycloak-theme-handoff.md) beschrieben. - der Theme-Handoff ist in [keycloak-theme-handoff.md](/home/lars/Schreibtisch/Projekte/desktop.kusche.berlin/docs/keycloak-theme-handoff.md) beschrieben.
- das globale Admin-Debugging ist Teil der Desktop-Shell und wird nicht pro App als eigene Grundfunktion dupliziert - das globale Admin-Debugging ist Teil der Desktop-Shell und wird nicht pro App als eigene Grundfunktion dupliziert
- die Definition globaler Shell-Werkzeuge liegt unter `system/shell/`; aktuelles Beispiel ist `system/shell/desktop-debug/`
## Dokumentationsregel ## Dokumentationsregel

View File

@@ -12,6 +12,7 @@ Die Ordnerstruktur soll sich so nah wie sinnvoll an der bestehenden Nexus-Strukt
- `/config/` -> Umgebungs-Configs - `/config/` -> Umgebungs-Configs
- `/custom/apps/<app>/` -> installierbare Fach-Apps - `/custom/apps/<app>/` -> installierbare Fach-Apps
- `/system/apps/<app>/` -> System-Tools und interne App-Quellen - `/system/apps/<app>/` -> System-Tools und interne App-Quellen
- `/system/shell/<tool>/` -> globale Shell-Werkzeuge und shell-nahe Desktop-Helfer
- `/partials/structure/` -> globale Layout-Bausteine - `/partials/structure/` -> globale Layout-Bausteine
- `/partials/landingpages/` -> Seiten oder Views - `/partials/landingpages/` -> Seiten oder Views
- `/tools/` -> Worker, CLI, Hilfstools - `/tools/` -> Worker, CLI, Hilfstools
@@ -54,6 +55,7 @@ Das bedeutet:
- Module dürfen nicht ungeordnet in den Desktop-Core verschoben werden - Module dürfen nicht ungeordnet in den Desktop-Core verschoben werden
- gemeinsame Desktop-Mechaniken liegen nicht in Modulen, sondern im globalen Kern - gemeinsame Desktop-Mechaniken liegen nicht in Modulen, sondern im globalen Kern
- gemeinsame Debug-Grundlogik liegt ebenfalls nicht in Modulen, sondern im globalen Desktop-Core - gemeinsame Debug-Grundlogik liegt ebenfalls nicht in Modulen, sondern im globalen Desktop-Core
- globale Shell-Werkzeuge sollen als eigene Einheiten unter `/system/shell/` abgelegt werden
## Trennung ## Trennung

View File

@@ -58,9 +58,11 @@ Verbindlich ist:
- `custom/apps/<app>/` bleibt Ort fuer installierbare Fach-Apps - `custom/apps/<app>/` bleibt Ort fuer installierbare Fach-Apps
- `system/apps/<app>/` bleibt Ort fuer System-Tools und nicht installierbare App-Quellen - `system/apps/<app>/` bleibt Ort fuer System-Tools und nicht installierbare App-Quellen
- `system/shell/<tool>/` bleibt Ort fuer globale Shell-Werkzeuge und shell-nahe Desktop-Helfer
- globale Desktop-Mechaniken liegen im gemeinsamen Kern - globale Desktop-Mechaniken liegen im gemeinsamen Kern
- globale Modul-Helfer duerfen im gemeinsamen Kern liegen, Fachlogik aber nicht - globale Modul-Helfer duerfen im gemeinsamen Kern liegen, Fachlogik aber nicht
- globale Debug-Steuerung fuer Admins liegt im Desktop-Core; Aktivierung erfolgt ueber das gemeinsame Debug-Widget neben der Uhr - globale Debug-Steuerung fuer Admins liegt im Desktop-Core; Aktivierung erfolgt ueber das gemeinsame Debug-Widget neben der Uhr
- die Definition globaler Debug- und Shell-Werkzeuge soll in `system/shell/` liegen und nicht lose in `public/` oder verstreut im JS
- API und Desktop teilen sich im aktuellen Zielbild denselben Host; offizielle Basis ist `/api/v1/...` - API und Desktop teilen sich im aktuellen Zielbild denselben Host; offizielle Basis ist `/api/v1/...`
- App, API und Widget einer Fachfunktion muessen dieselben Sperr- und Force-Regeln fuer externe Abrufe nutzen - App, API und Widget einer Fachfunktion muessen dieselben Sperr- und Force-Regeln fuer externe Abrufe nutzen
- die Trennung `core` / `system_tool` / `module` ist in Metadaten, Startmenue und Benutzer-Setup konsistent zu halten - die Trennung `core` / `system_tool` / `module` ist in Metadaten, Startmenue und Benutzer-Setup konsistent zu halten

View File

@@ -7,6 +7,8 @@ $activeSkin = $desktopPayload['meta']['active_skin'];
$baseSkinProfile = $desktopPayload['meta']['base_skin_profile']; $baseSkinProfile = $desktopPayload['meta']['base_skin_profile'];
$skinProfile = $desktopPayload['meta']['skin_profile']; $skinProfile = $desktopPayload['meta']['skin_profile'];
$wallpaper = $desktopPayload['desktop']['wallpaper']; $wallpaper = $desktopPayload['desktop']['wallpaper'];
$debugShellApp = is_array($desktopPayload['shell']['debug_app'] ?? null) ? $desktopPayload['shell']['debug_app'] : null;
$debugShellLabel = trim((string) ($debugShellApp['label'] ?? 'Debug')) ?: 'Debug';
$assetVersion = static function (string $publicPath): string { $assetVersion = static function (string $publicPath): string {
$filesystemPath = dirname(__DIR__, 2) . '/public' . $publicPath; $filesystemPath = dirname(__DIR__, 2) . '/public' . $publicPath;
@@ -88,7 +90,7 @@ $renderStartIcon = static function (array $profile): string {
</div> </div>
<div class="system-bar-right"> <div class="system-bar-right">
<?php if (($desktopPayload['session']['is_admin'] ?? false) === true): ?> <?php if (($desktopPayload['session']['is_admin'] ?? false) === true): ?>
<button class="tray-pill tray-pill-button tray-pill-debug" id="debug-toggle-top" type="button" aria-expanded="false">Debug</button> <button class="tray-pill tray-pill-button tray-pill-debug" id="debug-toggle-top" type="button" aria-expanded="false"><?= htmlspecialchars($debugShellLabel, ENT_QUOTES) ?></button>
<?php endif; ?> <?php endif; ?>
<time id="clock-top"></time> <time id="clock-top"></time>
</div> </div>
@@ -166,7 +168,7 @@ $renderStartIcon = static function (array $profile): string {
><?= htmlspecialchars($trayItem['label'], ENT_QUOTES) ?></button> ><?= htmlspecialchars($trayItem['label'], ENT_QUOTES) ?></button>
<?php endforeach; ?> <?php endforeach; ?>
<?php if (($desktopPayload['session']['is_admin'] ?? false) === true): ?> <?php if (($desktopPayload['session']['is_admin'] ?? false) === true): ?>
<button class="tray-pill tray-pill-button tray-pill-debug" id="debug-toggle" type="button" aria-expanded="false">Debug</button> <button class="tray-pill tray-pill-button tray-pill-debug" id="debug-toggle" type="button" aria-expanded="false"><?= htmlspecialchars($debugShellLabel, ENT_QUOTES) ?></button>
<?php endif; ?> <?php endif; ?>
<time id="clock"></time> <time id="clock"></time>
</div> </div>

View File

@@ -49,6 +49,13 @@ 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 shellApps = Array.isArray(payload.shell?.apps) ? payload.shell.apps : [];
const debugShellApp = (payload.shell && typeof payload.shell.debug_app === 'object' && payload.shell.debug_app)
|| shellApps.find((app) => app && app.app_id === 'desktop-debug')
|| null;
const debugShellAppId = typeof debugShellApp?.app_id === 'string' && debugShellApp.app_id !== ''
? debugShellApp.app_id
: 'desktop-debug';
let currentUserPreferences = payload.desktop.user_preferences || {}; let currentUserPreferences = payload.desktop.user_preferences || {};
const maximizeOverSystemBar = Boolean(skinProfile.maximize_over_system_bar); const maximizeOverSystemBar = Boolean(skinProfile.maximize_over_system_bar);
const storageScope = payload.desktop.storage_scope || 'guest'; const storageScope = payload.desktop.storage_scope || 'guest';
@@ -168,7 +175,9 @@ if (payloadNode) {
node.setAttribute('aria-expanded', String(debugState.open === true)); node.setAttribute('aria-expanded', String(debugState.open === true));
node.classList.toggle('is-active', debugState.open === true); node.classList.toggle('is-active', debugState.open === true);
node.textContent = debugState.open === true ? 'Debug an' : 'Debug'; node.textContent = debugState.open === true
? `${String(debugShellApp?.label || 'Debug')} an`
: String(debugShellApp?.label || 'Debug');
}); });
}; };
@@ -571,13 +580,15 @@ if (payloadNode) {
const buildWindowBody = (definition) => { const buildWindowBody = (definition) => {
if (definition.content_mode === 'desktop-debug') { if (definition.content_mode === 'desktop-debug') {
const debugCopy = escapeHtml(definition.ui?.copy || 'Live-Debug fuer Desktop und Native-Module. Tracking laeuft nur, solange dieses Fenster offen ist.');
const clearLabel = escapeHtml(definition.ui?.clear_label || 'Log leeren');
return ` return `
<div class="window-content window-content--desktop-debug"> <div class="window-content window-content--desktop-debug">
<div class="desktop-debug-window" data-debug-window="1"> <div class="desktop-debug-window" data-debug-window="1">
<div class="desktop-debug-toolbar"> <div class="desktop-debug-toolbar">
<p class="desktop-debug-copy">Live-Debug fuer Desktop und Native-Module. Tracking laeuft nur, solange dieses Fenster offen ist.</p> <p class="desktop-debug-copy">${debugCopy}</p>
<div class="desktop-debug-actions"> <div class="desktop-debug-actions">
<button class="window-app-button desktop-debug-action" type="button" data-debug-action="clear">Log leeren</button> <button class="window-app-button desktop-debug-action" type="button" data-debug-action="clear">${clearLabel}</button>
</div> </div>
</div> </div>
<div class="desktop-debug-log" data-debug-log></div> <div class="desktop-debug-log" data-debug-log></div>
@@ -1020,7 +1031,7 @@ if (payloadNode) {
} }
}; };
const findDebugWindow = () => Array.from(windows.values()).find((record) => record.definition.app_id === 'desktop-debug') || null; const findDebugWindow = () => Array.from(windows.values()).find((record) => record.definition.app_id === debugShellAppId) || null;
const closeDebugWindow = () => { const closeDebugWindow = () => {
const existing = findDebugWindow(); const existing = findDebugWindow();
@@ -1050,17 +1061,19 @@ if (payloadNode) {
return; return;
} }
const windowDefaults = debugShellApp && typeof debugShellApp.window === 'object' ? debugShellApp.window : {};
createWindow({ createWindow({
window_id: `window-desktop-debug-${Date.now()}`, window_id: `window-${debugShellAppId}-${Date.now()}`,
app_id: 'desktop-debug', app_id: debugShellAppId,
title: 'Desktop Debug', title: String(debugShellApp?.title || 'Desktop Debug'),
x: 120, x: Number(windowDefaults.x ?? 120),
y: 88, y: Number(windowDefaults.y ?? 88),
width: 720, width: Number(windowDefaults.width ?? 720),
height: 520, height: Number(windowDefaults.height ?? 520),
content: '', content: String(debugShellApp?.content || ''),
entry_route: '', entry_route: String(debugShellApp?.entry_route || ''),
content_mode: 'desktop-debug', content_mode: String(debugShellApp?.content_mode || 'desktop-debug'),
ui: debugShellApp && typeof debugShellApp.ui === 'object' ? debugShellApp.ui : {},
}); });
}; };
@@ -1153,7 +1166,7 @@ if (payloadNode) {
return; return;
} }
if (record.definition.app_id === 'desktop-debug') { if (record.definition.app_id === debugShellAppId) {
debugState.open = false; debugState.open = false;
saveDebugState(); saveDebugState();
debugBus.setEnabled(false); debugBus.setEnabled(false);

View File

@@ -8,6 +8,7 @@ use Desktop\AppRegistry;
use Desktop\AppIconResolver; use Desktop\AppIconResolver;
use Desktop\AppVisibility; use Desktop\AppVisibility;
use Desktop\DesktopState; use Desktop\DesktopState;
use Desktop\ShellAppRegistry;
use Desktop\SkinResolver; use Desktop\SkinResolver;
use Desktop\WidgetRegistry; use Desktop\WidgetRegistry;
use Desktop\WindowManager; use Desktop\WindowManager;
@@ -30,6 +31,7 @@ final class App
$moduleRegistry = new ModuleRegistry(AppPaths::customAppsRoot($this->projectRoot)); $moduleRegistry = new ModuleRegistry(AppPaths::customAppsRoot($this->projectRoot));
$registry = new AppRegistry($this->projectRoot . '/config/apps.php', $moduleRegistry->desktopApps()); $registry = new AppRegistry($this->projectRoot . '/config/apps.php', $moduleRegistry->desktopApps());
$widgetRegistry = new WidgetRegistry($this->projectRoot . '/config/widgets.php', $moduleRegistry->widgets()); $widgetRegistry = new WidgetRegistry($this->projectRoot . '/config/widgets.php', $moduleRegistry->widgets());
$shellRegistry = new ShellAppRegistry(AppPaths::systemShellRoot($this->projectRoot));
$skins = SkinResolver::all(); $skins = SkinResolver::all();
$isAuthenticated = isset($_SESSION['desktop_auth']) && is_array($_SESSION['desktop_auth']); $isAuthenticated = isset($_SESSION['desktop_auth']) && is_array($_SESSION['desktop_auth']);
$authUser = $isAuthenticated && is_array($_SESSION['desktop_auth']['user'] ?? null) $authUser = $isAuthenticated && is_array($_SESSION['desktop_auth']['user'] ?? null)
@@ -113,6 +115,10 @@ final class App
'active_ids' => $activeWidgetIds, 'active_ids' => $activeWidgetIds,
], ],
'tray' => $trayItems, 'tray' => $trayItems,
'shell' => [
'apps' => $shellRegistry->all(),
'debug_app' => $shellRegistry->find('desktop-debug'),
],
'session' => [ 'session' => [
'display_name' => $displayName, 'display_name' => $displayName,
'authenticated' => $isAuthenticated, 'authenticated' => $isAuthenticated,

View File

@@ -25,4 +25,14 @@ final class AppPaths
{ {
return self::systemAppsRoot($projectRoot) . '/' . trim($appName, '/'); return self::systemAppsRoot($projectRoot) . '/' . trim($appName, '/');
} }
public static function systemShellRoot(string $projectRoot): string
{
return rtrim($projectRoot, '/') . '/system/shell';
}
public static function systemShellPath(string $projectRoot, string $shellName): string
{
return self::systemShellRoot($projectRoot) . '/' . trim($shellName, '/');
}
} }

View File

@@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace Desktop;
final class ShellAppRegistry
{
/**
* @var array<int, array<string, mixed>>|null
*/
private ?array $apps = null;
public function __construct(
private readonly string $shellRoot,
) {
}
/**
* @return array<int, array<string, mixed>>
*/
public function all(): array
{
if ($this->apps !== null) {
return $this->apps;
}
if (!is_dir($this->shellRoot)) {
return $this->apps = [];
}
$apps = [];
foreach (glob($this->shellRoot . '/*/definition.php') ?: [] as $definitionPath) {
$app = require $definitionPath;
if (!is_array($app)) {
continue;
}
$appId = trim((string) ($app['app_id'] ?? ''));
if ($appId === '') {
continue;
}
$apps[] = [
'app_id' => $appId,
'title' => (string) ($app['title'] ?? $appId),
'label' => (string) ($app['label'] ?? $appId),
'content_mode' => (string) ($app['content_mode'] ?? 'shell'),
'admin_only' => (bool) ($app['admin_only'] ?? false),
'entry_route' => (string) ($app['entry_route'] ?? ''),
'content' => (string) ($app['content'] ?? ''),
'window' => is_array($app['window'] ?? null) ? $app['window'] : [],
'ui' => is_array($app['ui'] ?? null) ? $app['ui'] : [],
];
}
usort(
$apps,
static fn (array $left, array $right): int => strcmp(
(string) ($left['title'] ?? $left['app_id'] ?? ''),
(string) ($right['title'] ?? $right['app_id'] ?? '')
)
);
return $this->apps = array_values($apps);
}
/**
* @return array<string, mixed>|null
*/
public function find(string $appId): ?array
{
foreach ($this->all() as $app) {
if ((string) ($app['app_id'] ?? '') === $appId) {
return $app;
}
}
return null;
}
}

View File

@@ -20,3 +20,8 @@ Aktuelle Beispiele:
- `user-management` - `user-management`
- `user-self-management` - `user-self-management`
- `cron-management` - `cron-management`
Verwandte, aber getrennte Struktur:
- globale Shell-Werkzeuge und shell-nahe Desktop-Helfer liegen unter `system/shell/`
- Beispiel: `system/shell/desktop-debug/`

24
system/shell/README.md Normal file
View File

@@ -0,0 +1,24 @@
# System Shell
Shell-nahe Werkzeuge und Shell-Skripte liegen in diesem Projekt unter `system/shell/<tool>/`.
Ziel der Struktur:
- globale Desktop-Werkzeuge klar von `system/apps/` und `custom/apps/` trennen
- Shell-spezifische Helfer als eigenstaendige Einheiten sichtbar machen
- Metadaten fuer den Desktop zentral ueber `definition.php` ablegen
Typische Bestandteile eines Shell-Tools:
- `definition.php` fuer Titel, Kennung, Content-Mode und Fenster-Defaults
- optionale Zusatzdateien wie `README.md`, Shell-Skripte oder Hilfsdateien
Aktuelles Beispiel:
- `desktop-debug`
Regel:
- `system/shell/` ist fuer globale Shell-Werkzeuge reserviert
- installierbare Fach-Apps bleiben unter `custom/apps/`
- nicht installierbare Oberflaechen-Apps bleiben unter `system/apps/`

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
return [
'app_id' => 'desktop-debug',
'title' => 'Desktop Debug',
'label' => 'Debug',
'content_mode' => 'desktop-debug',
'admin_only' => true,
'window' => [
'x' => 120,
'y' => 88,
'width' => 720,
'height' => 520,
],
'ui' => [
'copy' => 'Live-Debug fuer Desktop und Native-Module. Tracking laeuft nur, solange dieses Fenster offen ist.',
'clear_label' => 'Log leeren',
],
];