dadssad
This commit is contained in:
@@ -35,6 +35,7 @@ Zentraler Dokumentationsindex fuer das Projekt.
|
|||||||
- Keycloak bleibt das Auth-System.
|
- Keycloak bleibt das Auth-System.
|
||||||
- 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
|
||||||
|
|
||||||
## Dokumentationsregel
|
## Dokumentationsregel
|
||||||
|
|
||||||
@@ -51,3 +52,4 @@ Aktuell wichtig:
|
|||||||
- der Mining-Checker ist das erste als echtes Desktop-Modul angebundene Modul unter `modules/mining-checker/`
|
- der Mining-Checker ist das erste als echtes Desktop-Modul angebundene Modul unter `modules/mining-checker/`
|
||||||
- Modul-Assets koennen ueber einen gemeinsamen Auslieferungsweg aus dem Modul selbst geladen werden
|
- Modul-Assets koennen ueber einen gemeinsamen Auslieferungsweg aus dem Modul selbst geladen werden
|
||||||
- die API bleibt vorerst auf demselben Host und wird nicht auf `api.desktop.kusche.berlin` ausgelagert
|
- die API bleibt vorerst auf demselben Host und wird nicht auf `api.desktop.kusche.berlin` ausgelagert
|
||||||
|
- das gemeinsame Admin-Debug-Widget neben der Uhr ist der Standard fuer Live-Debugging in Desktop und Native-Apps
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ Empfohlene Ergänzungen:
|
|||||||
- `/public/assets/desktop/`
|
- `/public/assets/desktop/`
|
||||||
- globale Desktop-CSS
|
- globale Desktop-CSS
|
||||||
- JavaScript für Fenster, Dock, Tray, Desktop-Interaktion
|
- JavaScript für Fenster, Dock, Tray, Desktop-Interaktion
|
||||||
|
- globale Debug-Infrastruktur fuer Admin-Widget, Debug-Bus und Debug-Fenster
|
||||||
- `/partials/desktop/`
|
- `/partials/desktop/`
|
||||||
- Desktop Shell
|
- Desktop Shell
|
||||||
- Window Templates
|
- Window Templates
|
||||||
@@ -51,6 +52,7 @@ Das bedeutet:
|
|||||||
- der neue Desktop-Layer ist nur die neue Oberfläche
|
- der neue Desktop-Layer ist nur die neue Oberfläche
|
||||||
- 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
|
||||||
|
|
||||||
## Trennung
|
## Trennung
|
||||||
|
|
||||||
@@ -64,6 +66,7 @@ Es gibt drei Ebenen:
|
|||||||
|
|
||||||
- keine Vermischung von Fenster-Manager-Code mit Modulbusinesslogik
|
- keine Vermischung von Fenster-Manager-Code mit Modulbusinesslogik
|
||||||
- keine Skin-spezifischen Sonderlösungen in einzelnen Modulen
|
- keine Skin-spezifischen Sonderlösungen in einzelnen Modulen
|
||||||
|
- keine eigene globale Debug-Basis je Modul oder App
|
||||||
- keine harte Kopplung zwischen alter Seitenstruktur und neuem Desktop-Verhalten
|
- keine harte Kopplung zwischen alter Seitenstruktur und neuem Desktop-Verhalten
|
||||||
- keine technische Abhängigkeit zu `Old-Nexus/`
|
- keine technische Abhängigkeit zu `Old-Nexus/`
|
||||||
- keine Includes, Imports, Autoloading-Pfade oder Assets aus `Old-Nexus/`
|
- keine Includes, Imports, Autoloading-Pfade oder Assets aus `Old-Nexus/`
|
||||||
@@ -75,6 +78,7 @@ Die neue Oberfläche muss so gebaut sein, dass:
|
|||||||
- Module später als Fenster gerendert werden können
|
- Module später als Fenster gerendert werden können
|
||||||
- Apps, Widgets und Seitenmodule aus einer gemeinsamen Registry kommen
|
- Apps, Widgets und Seitenmodule aus einer gemeinsamen Registry kommen
|
||||||
- Skins ausschließlich Layout, Stil und Interaktionsdetails definieren
|
- Skins ausschließlich Layout, Stil und Interaktionsdetails definieren
|
||||||
|
- globale Admin-Werkzeuge wie Debug als gemeinsame Desktop-Funktion bereitgestellt werden
|
||||||
|
|
||||||
## Sonderregel für `Old-Nexus/`
|
## Sonderregel für `Old-Nexus/`
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ Rahmenbedingungen:
|
|||||||
- Die neue Oberfläche ist eine Desktop-Shell, keine klassische Website.
|
- Die neue Oberfläche ist eine Desktop-Shell, keine klassische Website.
|
||||||
- Root `/` soll die Desktop-Oberfläche sein.
|
- Root `/` soll die Desktop-Oberfläche sein.
|
||||||
- Skins sollen als `Windows`, `Apple`, `Linux` angelegt werden, technisch aber auf einer gemeinsamen Shell beruhen.
|
- Skins sollen als `Windows`, `Apple`, `Linux` angelegt werden, technisch aber auf einer gemeinsamen Shell beruhen.
|
||||||
|
- Globale Desktop-Funktionen wie Admin-Debugging sollen zentral in der Shell liegen und nicht pro App neu gebaut werden.
|
||||||
|
- Der Standard fuer Live-Debugging ist ein gemeinsames Admin-Debug-Widget neben der Uhr mit eigenem Debug-Fenster und gemeinsamem Debug-Bus.
|
||||||
- Spätere Modul-Anbindung muss vorbereitet werden, aber die Shell ist zuerst zu bauen.
|
- Spätere Modul-Anbindung muss vorbereitet werden, aber die Shell ist zuerst zu bauen.
|
||||||
|
|
||||||
Erste Aufgabe:
|
Erste Aufgabe:
|
||||||
@@ -40,5 +42,6 @@ Wichtig:
|
|||||||
- keine Arbeiten mehr im alten Nexus durchführen
|
- keine Arbeiten mehr im alten Nexus durchführen
|
||||||
- keine Modulmigration im Blindflug
|
- keine Modulmigration im Blindflug
|
||||||
- keine Vermischung von alter Website-Navigation und neuer Desktop-Interaktion
|
- keine Vermischung von alter Website-Navigation und neuer Desktop-Interaktion
|
||||||
|
- keine neue globale Debug-UI pro App bauen, wenn die Desktop-Shell diese Funktion zentral bereitstellen soll
|
||||||
- `Old-Nexus/` nur lesen, nie technisch verwenden
|
- `Old-Nexus/` nur lesen, nie technisch verwenden
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ Verbindlich ist:
|
|||||||
- Desktop-Mechaniken zentral halten, nicht pro App duplizieren
|
- Desktop-Mechaniken zentral halten, nicht pro App duplizieren
|
||||||
- Fensterverhalten konsistent halten
|
- Fensterverhalten konsistent halten
|
||||||
- gemeinsame Standards fuer Fensterinhalte nutzen, sofern eine App kein bewusst eigenes UI benoetigt
|
- gemeinsame Standards fuer Fensterinhalte nutzen, sofern eine App kein bewusst eigenes UI benoetigt
|
||||||
|
- globale Debug-Infrastruktur zentral halten und ueber alle Apps wiederverwenden
|
||||||
- Begriffe in UI und Doku konsistent nach `CONTENT.md` verwenden
|
- Begriffe in UI und Doku konsistent nach `CONTENT.md` verwenden
|
||||||
- Apps als bereitgestellte Systemfunktionen denken, nicht nur als sichtbare Fenster
|
- Apps als bereitgestellte Systemfunktionen denken, nicht nur als sichtbare Fenster
|
||||||
- Unterschiede zwischen `App`, `Infobereich` und `Widget-Bereich` sauber trennen
|
- Unterschiede zwischen `App`, `Infobereich` und `Widget-Bereich` sauber trennen
|
||||||
@@ -41,6 +42,7 @@ Verbindlich ist:
|
|||||||
- keine Rueckbauten bestehender Desktop-Mechaniken ohne ausdrueckliche Entscheidung
|
- keine Rueckbauten bestehender Desktop-Mechaniken ohne ausdrueckliche Entscheidung
|
||||||
- keine Vermischung von Modul-Businesslogik mit globalem Desktop-Core
|
- keine Vermischung von Modul-Businesslogik mit globalem Desktop-Core
|
||||||
- keine Skin-Sonderlogik direkt in einzelnen Apps, wenn sie global loesbar ist
|
- keine Skin-Sonderlogik direkt in einzelnen Apps, wenn sie global loesbar ist
|
||||||
|
- keine neue globale Debug-Grundlogik pro App, wenn der Desktop-Debug-Bus und das zentrale Debug-Fenster genutzt werden koennen
|
||||||
- keine nur lokalen README-Informationen ohne zentrale Uebernahme der wichtigen Punkte
|
- keine nur lokalen README-Informationen ohne zentrale Uebernahme der wichtigen Punkte
|
||||||
- keine Hilfeinhalte nur in Chatverlaeufen oder Ad-hoc-Notizen belassen
|
- keine Hilfeinhalte nur in Chatverlaeufen oder Ad-hoc-Notizen belassen
|
||||||
- keine produktive Architekturabhaengigkeit zu `Old-Nexus/` oder vergleichbaren Altbestaenden
|
- keine produktive Architekturabhaengigkeit zu `Old-Nexus/` oder vergleichbaren Altbestaenden
|
||||||
@@ -51,6 +53,7 @@ Verbindlich ist:
|
|||||||
- `modules/<modul>/` bleibt Ort fuer klassische Module
|
- `modules/<modul>/` bleibt Ort fuer klassische Module
|
||||||
- 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
|
||||||
- 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/...`
|
||||||
- Skins definieren Darstellung und Interaktionsdetails, nicht die Fachlogik
|
- Skins definieren Darstellung und Interaktionsdetails, nicht die Fachlogik
|
||||||
- Hilfe- und Inhaltsdateien sollen spaeter maschinenlesbar oder zumindest klar strukturierbar in einen Hilfebereich ueberfuehrt werden koennen
|
- Hilfe- und Inhaltsdateien sollen spaeter maschinenlesbar oder zumindest klar strukturierbar in einen Hilfebereich ueberfuehrt werden koennen
|
||||||
@@ -76,3 +79,5 @@ Bei jeder groesseren Aenderung ist zu pruefen:
|
|||||||
- Benutzerdaten sollen spaeter sauber in LDAP oder Keycloak geschrieben werden
|
- Benutzerdaten sollen spaeter sauber in LDAP oder Keycloak geschrieben werden
|
||||||
- Standard-Fensterinhalte sollen ein gemeinsames Design nutzen
|
- Standard-Fensterinhalte sollen ein gemeinsames Design nutzen
|
||||||
- Desktop-Nutzereinstellungen liegen in einer eigenen Tabelle mit erkennbarem `_user_data`-Suffix
|
- Desktop-Nutzereinstellungen liegen in einer eigenen Tabelle mit erkennbarem `_user_data`-Suffix
|
||||||
|
- das globale Admin-Debug-Fenster ist der Standardweg fuer Live-Debugging in Desktop und Native-Apps
|
||||||
|
- Debug-Tracking soll nur waehrend eines geoeffneten globalen Debug-Fensters laufen; der letzte Status soll einen Reload pro Benutzer ueberstehen
|
||||||
|
|||||||
@@ -15,6 +15,15 @@ Ein Desktop-Modul kann zusaetzlich diese Projektdateien besitzen:
|
|||||||
|
|
||||||
Module-Assets werden in diesem Projekt nicht direkt aus `public/assets/apps/...` dupliziert, sondern ueber den generischen Endpoint `/module-assets/index.php` aus dem jeweiligen Modul ausgeliefert.
|
Module-Assets werden in diesem Projekt nicht direkt aus `public/assets/apps/...` dupliziert, sondern ueber den generischen Endpoint `/module-assets/index.php` aus dem jeweiligen Modul ausgeliefert.
|
||||||
|
|
||||||
|
## Globale Desktop-Standards fuer Module
|
||||||
|
|
||||||
|
- gemeinsame Desktop-Mechaniken wie Fenster, Tray, globale Persistenz und Debug-Infrastruktur liegen im Desktop-Core
|
||||||
|
- Module und andere Apps sollen keine eigene Grund-Debug-Oberflaeche im Stil eines separaten globalen Debuggers bauen
|
||||||
|
- fuer Debugging im Desktop gilt das zentrale Admin-Debug-Widget neben der Uhr mit eigenem Debug-Fenster als Standard
|
||||||
|
- Debug-Events aus Modulen sollen in den gemeinsamen Desktop-Debug-Bus geschrieben werden, damit sie im globalen Debug-Fenster sichtbar sind
|
||||||
|
- wenn das globale Debug-Fenster geschlossen ist, sollen Module kein dauerhaft aktives Live-Debugging erzwingen
|
||||||
|
- app-spezifische Debug-Darstellungen sind nur zulaessig, wenn sie fachliche Zusatzinformationen zeigen, die ueber den globalen Stream hinausgehen
|
||||||
|
|
||||||
## Pflegehinweis
|
## Pflegehinweis
|
||||||
|
|
||||||
Diese Datei muss gepflegt bleiben, wenn sich Modulstruktur oder Modulregeln aendern.
|
Diese Datei muss gepflegt bleiben, wenn sich Modulstruktur oder Modulregeln aendern.
|
||||||
|
|||||||
@@ -46,7 +46,11 @@
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
const initialDebugMode = normalizedOptions.moduleDebugEnabled === true
|
const desktopDebugBus = window.__desktopDebugBus && typeof window.__desktopDebugBus === 'object'
|
||||||
|
? window.__desktopDebugBus
|
||||||
|
: null;
|
||||||
|
const initialDebugMode = (desktopDebugBus && desktopDebugBus.enabled === true)
|
||||||
|
|| normalizedOptions.moduleDebugEnabled === true
|
||||||
|| (document.body && document.body.dataset.moduleDebugEnabled === '1');
|
|| (document.body && document.body.dataset.moduleDebugEnabled === '1');
|
||||||
const fallbackSections = [
|
const fallbackSections = [
|
||||||
['overview', 'Übersicht'],
|
['overview', 'Übersicht'],
|
||||||
@@ -88,11 +92,12 @@
|
|||||||
function setCookie(name, value, maxAgeSeconds) {
|
function setCookie(name, value, maxAgeSeconds) {
|
||||||
document.cookie = `${name}=${encodeURIComponent(value)}; path=/; max-age=${maxAgeSeconds}; samesite=lax`;
|
document.cookie = `${name}=${encodeURIComponent(value)}; path=/; max-age=${maxAgeSeconds}; samesite=lax`;
|
||||||
}
|
}
|
||||||
const debugBus = window.__nexusDebugBus || {
|
const debugBus = desktopDebugBus || window.__nexusDebugBus || {
|
||||||
enabled: initialDebugMode,
|
enabled: initialDebugMode,
|
||||||
listener: null,
|
listener: null,
|
||||||
sequence: 0,
|
sequence: 0,
|
||||||
};
|
};
|
||||||
|
window.__desktopDebugBus = debugBus;
|
||||||
window.__nexusDebugBus = debugBus;
|
window.__nexusDebugBus = debugBus;
|
||||||
const browserTimezone = (() => {
|
const browserTimezone = (() => {
|
||||||
try {
|
try {
|
||||||
@@ -103,6 +108,10 @@
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
function emitDebug(entry) {
|
function emitDebug(entry) {
|
||||||
|
if (typeof debugBus.emit === 'function') {
|
||||||
|
return debugBus.emit(entry);
|
||||||
|
}
|
||||||
|
|
||||||
debugBus.sequence += 1;
|
debugBus.sequence += 1;
|
||||||
const payload = {
|
const payload = {
|
||||||
id: debugBus.sequence,
|
id: debugBus.sequence,
|
||||||
|
|||||||
@@ -280,6 +280,7 @@ final class AnalyticsService
|
|||||||
}
|
}
|
||||||
|
|
||||||
$payouts = is_array($settings['payouts'] ?? null) ? $settings['payouts'] : [];
|
$payouts = is_array($settings['payouts'] ?? null) ? $settings['payouts'] : [];
|
||||||
|
$walletWithdrawals = is_array($settings['wallet_withdrawals'] ?? null) ? $settings['wallet_withdrawals'] : [];
|
||||||
$purchasedMiners = is_array($settings['purchased_miners'] ?? null) ? $settings['purchased_miners'] : [];
|
$purchasedMiners = is_array($settings['purchased_miners'] ?? null) ? $settings['purchased_miners'] : [];
|
||||||
$minerOffers = is_array($settings['miner_offers'] ?? null) ? $settings['miner_offers'] : [];
|
$minerOffers = is_array($settings['miner_offers'] ?? null) ? $settings['miner_offers'] : [];
|
||||||
$currentHashrateMh = $this->totalHashrateMh(array_merge(
|
$currentHashrateMh = $this->totalHashrateMh(array_merge(
|
||||||
|
|||||||
@@ -87,6 +87,9 @@ $renderStartIcon = static function (array $profile): string {
|
|||||||
<span>Fenster</span>
|
<span>Fenster</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="system-bar-right">
|
<div class="system-bar-right">
|
||||||
|
<?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>
|
||||||
|
<?php endif; ?>
|
||||||
<time id="clock-top"></time>
|
<time id="clock-top"></time>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@@ -157,6 +160,9 @@ $renderStartIcon = static function (array $profile): string {
|
|||||||
<?php foreach ($desktopPayload['tray'] as $trayItem): ?>
|
<?php foreach ($desktopPayload['tray'] as $trayItem): ?>
|
||||||
<button class="tray-pill tray-pill-button" type="button" data-tray-id="<?= htmlspecialchars((string) $trayItem['id'], ENT_QUOTES) ?>"><?= htmlspecialchars($trayItem['label'], ENT_QUOTES) ?></button>
|
<button class="tray-pill tray-pill-button" type="button" data-tray-id="<?= htmlspecialchars((string) $trayItem['id'], ENT_QUOTES) ?>"><?= htmlspecialchars($trayItem['label'], ENT_QUOTES) ?></button>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
<?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>
|
||||||
|
<?php endif; ?>
|
||||||
<time id="clock"></time>
|
<time id="clock"></time>
|
||||||
</div>
|
</div>
|
||||||
<div class="tray-menu" id="tray-account-menu" hidden>
|
<div class="tray-menu" id="tray-account-menu" hidden>
|
||||||
|
|||||||
@@ -503,6 +503,13 @@ h1 {
|
|||||||
background: #f8fafc;
|
background: #f8fafc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.window-content--desktop-debug {
|
||||||
|
padding: 0;
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, rgba(241, 245, 249, 0.98), rgba(226, 232, 240, 0.98)),
|
||||||
|
radial-gradient(circle at top right, rgba(34, 197, 94, 0.12), transparent 36%);
|
||||||
|
}
|
||||||
|
|
||||||
.window-module-host {
|
.window-module-host {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
@@ -518,6 +525,94 @@ h1 {
|
|||||||
color: #991b1b;
|
color: #991b1b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.desktop-debug-window {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto minmax(0, 1fr);
|
||||||
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desktop-debug-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 18px;
|
||||||
|
border-bottom: 1px solid rgba(148, 163, 184, 0.22);
|
||||||
|
background: rgba(255, 255, 255, 0.72);
|
||||||
|
}
|
||||||
|
|
||||||
|
.desktop-debug-copy {
|
||||||
|
margin: 0;
|
||||||
|
color: #475569;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desktop-debug-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desktop-debug-action {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desktop-debug-log {
|
||||||
|
min-height: 0;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 18px;
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
background: rgba(15, 23, 42, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.desktop-debug-entry,
|
||||||
|
.desktop-debug-empty {
|
||||||
|
padding: 14px 16px;
|
||||||
|
border-radius: 14px;
|
||||||
|
background: rgba(255, 255, 255, 0.92);
|
||||||
|
border: 1px solid rgba(148, 163, 184, 0.2);
|
||||||
|
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.desktop-debug-entry-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desktop-debug-entry-meta,
|
||||||
|
.desktop-debug-entry-url,
|
||||||
|
.desktop-debug-entry-message {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #475569;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desktop-debug-entry-url {
|
||||||
|
color: #0f172a;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desktop-debug-entry-message {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desktop-debug-entry-context {
|
||||||
|
margin: 10px 0 0;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #0f172a;
|
||||||
|
color: #e2e8f0;
|
||||||
|
overflow: auto;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
|
||||||
.window-app-shell {
|
.window-app-shell {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
color: #0f172a;
|
color: #0f172a;
|
||||||
@@ -1238,6 +1333,11 @@ h1 {
|
|||||||
background: rgba(255, 255, 255, 0.16);
|
background: rgba(255, 255, 255, 0.16);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tray-pill-debug.is-active {
|
||||||
|
background: linear-gradient(135deg, rgba(34, 197, 94, 0.34), rgba(59, 130, 246, 0.28));
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
.tray-menu {
|
.tray-menu {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 16px;
|
right: 16px;
|
||||||
@@ -1364,4 +1464,9 @@ h1 {
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.desktop-debug-toolbar {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ if (payloadNode) {
|
|||||||
const accountMenu = document.getElementById('tray-account-menu');
|
const accountMenu = document.getElementById('tray-account-menu');
|
||||||
const clockNode = document.getElementById('clock');
|
const clockNode = document.getElementById('clock');
|
||||||
const clockTopNode = document.getElementById('clock-top');
|
const clockTopNode = document.getElementById('clock-top');
|
||||||
|
const debugToggleNode = document.getElementById('debug-toggle');
|
||||||
|
const debugToggleTopNode = document.getElementById('debug-toggle-top');
|
||||||
const windows = new Map();
|
const windows = new Map();
|
||||||
const activeWidgets = new Set(payload.widgets.active_ids);
|
const activeWidgets = new Set(payload.widgets.active_ids);
|
||||||
const activeSkin = payload.meta.active_skin;
|
const activeSkin = payload.meta.active_skin;
|
||||||
@@ -29,6 +31,8 @@ if (payloadNode) {
|
|||||||
const storageScope = payload.desktop.storage_scope || 'guest';
|
const storageScope = payload.desktop.storage_scope || 'guest';
|
||||||
const storageKey = `desktop.kusche.berlin.window-state.${storageScope}`;
|
const storageKey = `desktop.kusche.berlin.window-state.${storageScope}`;
|
||||||
const iconStorageKey = `desktop.kusche.berlin.icon-state.${storageScope}.${activeSkin}`;
|
const iconStorageKey = `desktop.kusche.berlin.icon-state.${storageScope}.${activeSkin}`;
|
||||||
|
const debugStorageKey = `desktop.kusche.berlin.debug-state.${storageScope}`;
|
||||||
|
const isAdmin = payload.session?.is_admin === true;
|
||||||
let zIndex = 20;
|
let zIndex = 20;
|
||||||
let topDesktopInset = 0;
|
let topDesktopInset = 0;
|
||||||
let sideDesktopInset = 0;
|
let sideDesktopInset = 0;
|
||||||
@@ -38,6 +42,92 @@ if (payloadNode) {
|
|||||||
const windowControlsMode = document.body.dataset.windowControls || 'windows';
|
const windowControlsMode = document.body.dataset.windowControls || 'windows';
|
||||||
let wallpaperImagePromise = null;
|
let wallpaperImagePromise = null;
|
||||||
|
|
||||||
|
const loadDebugState = () => {
|
||||||
|
try {
|
||||||
|
const raw = window.localStorage.getItem(debugStorageKey);
|
||||||
|
const parsed = raw ? JSON.parse(raw) : null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
open: parsed?.open === true,
|
||||||
|
};
|
||||||
|
} catch {
|
||||||
|
return { open: false };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const debugState = loadDebugState();
|
||||||
|
|
||||||
|
const saveDebugState = () => {
|
||||||
|
try {
|
||||||
|
window.localStorage.setItem(debugStorageKey, JSON.stringify({
|
||||||
|
open: debugState.open === true,
|
||||||
|
}));
|
||||||
|
} catch {
|
||||||
|
// Ignore storage failures for now.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const debugBus = window.__desktopDebugBus || {
|
||||||
|
enabled: isAdmin && debugState.open === true,
|
||||||
|
sequence: 0,
|
||||||
|
entries: [],
|
||||||
|
listener: null,
|
||||||
|
maxEntries: 400,
|
||||||
|
emit(entry) {
|
||||||
|
if (!this.enabled) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sequence += 1;
|
||||||
|
const payloadEntry = {
|
||||||
|
id: this.sequence,
|
||||||
|
time: new Date().toISOString(),
|
||||||
|
...entry,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.entries.push(payloadEntry);
|
||||||
|
if (this.entries.length > this.maxEntries) {
|
||||||
|
this.entries.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof this.listener === 'function') {
|
||||||
|
this.listener(payloadEntry, this.entries.slice());
|
||||||
|
}
|
||||||
|
|
||||||
|
return payloadEntry;
|
||||||
|
},
|
||||||
|
subscribe(listener) {
|
||||||
|
this.listener = typeof listener === 'function' ? listener : null;
|
||||||
|
if (typeof this.listener === 'function') {
|
||||||
|
this.listener(null, this.entries.slice());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setEnabled(nextEnabled) {
|
||||||
|
this.enabled = nextEnabled === true;
|
||||||
|
if (!this.enabled) {
|
||||||
|
this.entries = [];
|
||||||
|
} else {
|
||||||
|
this.emit({
|
||||||
|
type: 'debug:enabled',
|
||||||
|
source: 'desktop',
|
||||||
|
message: 'Desktop-Debug aktiviert.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof this.listener === 'function') {
|
||||||
|
this.listener(null, this.entries.slice());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clear() {
|
||||||
|
this.entries = [];
|
||||||
|
if (typeof this.listener === 'function') {
|
||||||
|
this.listener(null, this.entries.slice());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
window.__desktopDebugBus = debugBus;
|
||||||
|
window.__nexusDebugBus = debugBus;
|
||||||
|
|
||||||
const escapeHtml = (value) => String(value)
|
const escapeHtml = (value) => String(value)
|
||||||
.replaceAll('&', '&')
|
.replaceAll('&', '&')
|
||||||
.replaceAll('<', '<')
|
.replaceAll('<', '<')
|
||||||
@@ -45,6 +135,89 @@ if (payloadNode) {
|
|||||||
.replaceAll('"', '"')
|
.replaceAll('"', '"')
|
||||||
.replaceAll("'", ''');
|
.replaceAll("'", ''');
|
||||||
|
|
||||||
|
const emitDebug = (entry) => debugBus.emit(entry);
|
||||||
|
|
||||||
|
const updateDebugToggleUi = () => {
|
||||||
|
[debugToggleNode, debugToggleTopNode].forEach((node) => {
|
||||||
|
if (!(node instanceof HTMLElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
node.setAttribute('aria-expanded', String(debugState.open === true));
|
||||||
|
node.classList.toggle('is-active', debugState.open === true);
|
||||||
|
node.textContent = debugState.open === true ? 'Debug an' : 'Debug';
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const ensureFetchDebugHook = () => {
|
||||||
|
if (window.__desktopDebugFetchHookInstalled === true || typeof window.fetch !== 'function') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalFetch = window.fetch.bind(window);
|
||||||
|
window.fetch = async (input, init) => {
|
||||||
|
const method = String(init?.method || 'GET').toUpperCase();
|
||||||
|
const url = typeof input === 'string' ? input : (input instanceof Request ? input.url : String(input));
|
||||||
|
const startedAt = performance.now();
|
||||||
|
const requestId = emitDebug({
|
||||||
|
type: 'fetch:start',
|
||||||
|
source: 'desktop',
|
||||||
|
method,
|
||||||
|
url,
|
||||||
|
})?.id ?? null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await originalFetch(input, init);
|
||||||
|
emitDebug({
|
||||||
|
type: 'fetch:end',
|
||||||
|
source: 'desktop',
|
||||||
|
request_id: requestId,
|
||||||
|
method,
|
||||||
|
url,
|
||||||
|
status: response.status,
|
||||||
|
ok: response.ok,
|
||||||
|
duration_ms: Math.round((performance.now() - startedAt) * 100) / 100,
|
||||||
|
});
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
emitDebug({
|
||||||
|
type: 'fetch:error',
|
||||||
|
source: 'desktop',
|
||||||
|
request_id: requestId,
|
||||||
|
method,
|
||||||
|
url,
|
||||||
|
duration_ms: Math.round((performance.now() - startedAt) * 100) / 100,
|
||||||
|
message: error instanceof Error ? error.message : 'Fetch fehlgeschlagen',
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.__desktopDebugFetchHookInstalled = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
ensureFetchDebugHook();
|
||||||
|
|
||||||
|
window.addEventListener('error', (event) => {
|
||||||
|
emitDebug({
|
||||||
|
type: 'window:error',
|
||||||
|
source: 'desktop',
|
||||||
|
message: event.message || 'Unbekannter Fehler',
|
||||||
|
file: event.filename || null,
|
||||||
|
line: event.lineno || null,
|
||||||
|
column: event.colno || null,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('unhandledrejection', (event) => {
|
||||||
|
const reason = event.reason;
|
||||||
|
emitDebug({
|
||||||
|
type: 'window:unhandledrejection',
|
||||||
|
source: 'desktop',
|
||||||
|
message: reason instanceof Error ? reason.message : String(reason || 'Promise wurde verworfen'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const buildAppIconMarkup = (app, className = '') => {
|
const buildAppIconMarkup = (app, className = '') => {
|
||||||
const safeClassName = className ? ` ${className}` : '';
|
const safeClassName = className ? ` ${className}` : '';
|
||||||
|
|
||||||
@@ -292,6 +465,22 @@ if (payloadNode) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const buildWindowBody = (definition) => {
|
const buildWindowBody = (definition) => {
|
||||||
|
if (definition.content_mode === 'desktop-debug') {
|
||||||
|
return `
|
||||||
|
<div class="window-content window-content--desktop-debug">
|
||||||
|
<div class="desktop-debug-window" data-debug-window="1">
|
||||||
|
<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>
|
||||||
|
<div class="desktop-debug-actions">
|
||||||
|
<button class="window-app-button desktop-debug-action" type="button" data-debug-action="clear">Log leeren</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="desktop-debug-log" data-debug-log></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
if (definition.content_mode === 'native-module' && definition.app_id === 'mining-checker') {
|
if (definition.content_mode === 'native-module' && definition.app_id === 'mining-checker') {
|
||||||
return `
|
return `
|
||||||
<div class="window-content window-content--embedded window-content--module">
|
<div class="window-content window-content--embedded window-content--module">
|
||||||
@@ -660,6 +849,132 @@ if (payloadNode) {
|
|||||||
setAccountMenuOpen(false);
|
setAccountMenuOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderDebugEntries = (container, entries) => {
|
||||||
|
if (!(container instanceof HTMLElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(entries) || entries.length === 0) {
|
||||||
|
container.innerHTML = '<div class="desktop-debug-empty">Noch keine Debug-Eintraege.</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.innerHTML = entries.slice().reverse().map((entry) => {
|
||||||
|
const meta = [];
|
||||||
|
if (entry.source) {
|
||||||
|
meta.push(String(entry.source));
|
||||||
|
}
|
||||||
|
if (entry.method) {
|
||||||
|
meta.push(String(entry.method));
|
||||||
|
}
|
||||||
|
if (entry.status !== undefined && entry.status !== null) {
|
||||||
|
meta.push(`HTTP ${entry.status}`);
|
||||||
|
}
|
||||||
|
if (entry.duration_ms !== undefined && entry.duration_ms !== null) {
|
||||||
|
meta.push(`${entry.duration_ms} ms`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const reservedKeys = new Set([
|
||||||
|
'id', 'time', 'type', 'source', 'message', 'method', 'url', 'status',
|
||||||
|
'ok', 'duration_ms', 'request_id', 'server_time', 'file', 'line', 'column',
|
||||||
|
]);
|
||||||
|
const context = Object.fromEntries(
|
||||||
|
Object.entries(entry).filter(([key]) => !reservedKeys.has(key))
|
||||||
|
);
|
||||||
|
const contextText = Object.keys(context).length > 0
|
||||||
|
? escapeHtml(JSON.stringify(context, null, 2))
|
||||||
|
: '';
|
||||||
|
|
||||||
|
return `
|
||||||
|
<article class="desktop-debug-entry">
|
||||||
|
<div class="desktop-debug-entry-head">
|
||||||
|
<strong>${escapeHtml(entry.type || 'debug')}</strong>
|
||||||
|
<span>${escapeHtml(new Date(entry.time || Date.now()).toLocaleTimeString('de-DE'))}</span>
|
||||||
|
</div>
|
||||||
|
${meta.length ? `<p class="desktop-debug-entry-meta">${escapeHtml(meta.join(' · '))}</p>` : ''}
|
||||||
|
${entry.url ? `<p class="desktop-debug-entry-url">${escapeHtml(String(entry.url))}</p>` : ''}
|
||||||
|
${entry.message ? `<p class="desktop-debug-entry-message">${escapeHtml(String(entry.message))}</p>` : ''}
|
||||||
|
${contextText ? `<pre class="desktop-debug-entry-context">${contextText}</pre>` : ''}
|
||||||
|
</article>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
container.scrollTop = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const initializeDesktopDebugWindow = (record) => {
|
||||||
|
const host = record.node.querySelector('[data-debug-window="1"]');
|
||||||
|
const logNode = record.node.querySelector('[data-debug-log]');
|
||||||
|
const clearButton = record.node.querySelector('[data-debug-action="clear"]');
|
||||||
|
|
||||||
|
if (!(host instanceof HTMLElement) || !(logNode instanceof HTMLElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderDebugEntries(logNode, debugBus.entries);
|
||||||
|
debugBus.subscribe((_entry, entries) => {
|
||||||
|
renderDebugEntries(logNode, entries);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (clearButton instanceof HTMLButtonElement) {
|
||||||
|
clearButton.addEventListener('click', () => {
|
||||||
|
debugBus.clear();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const findDebugWindow = () => Array.from(windows.values()).find((record) => record.definition.app_id === 'desktop-debug') || null;
|
||||||
|
|
||||||
|
const closeDebugWindow = () => {
|
||||||
|
const existing = findDebugWindow();
|
||||||
|
if (existing) {
|
||||||
|
closeWindow(existing.id);
|
||||||
|
} else {
|
||||||
|
debugState.open = false;
|
||||||
|
saveDebugState();
|
||||||
|
debugBus.setEnabled(false);
|
||||||
|
updateDebugToggleUi();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const openDebugWindow = () => {
|
||||||
|
if (!isAdmin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const existing = findDebugWindow();
|
||||||
|
debugState.open = true;
|
||||||
|
saveDebugState();
|
||||||
|
debugBus.setEnabled(true);
|
||||||
|
updateDebugToggleUi();
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
|
focusWindow(existing.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
createWindow({
|
||||||
|
window_id: `window-desktop-debug-${Date.now()}`,
|
||||||
|
app_id: 'desktop-debug',
|
||||||
|
title: 'Desktop Debug',
|
||||||
|
x: 120,
|
||||||
|
y: 88,
|
||||||
|
width: 720,
|
||||||
|
height: 520,
|
||||||
|
content: '',
|
||||||
|
entry_route: '',
|
||||||
|
content_mode: 'desktop-debug',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleDebugWindow = () => {
|
||||||
|
if (debugState.open === true) {
|
||||||
|
closeDebugWindow();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
openDebugWindow();
|
||||||
|
};
|
||||||
|
|
||||||
const applyWindowLayout = (record) => {
|
const applyWindowLayout = (record) => {
|
||||||
const { x, y, width, height } = record.layout;
|
const { x, y, width, height } = record.layout;
|
||||||
record.node.style.left = `${x}px`;
|
record.node.style.left = `${x}px`;
|
||||||
@@ -736,6 +1051,13 @@ if (payloadNode) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (record.definition.app_id === 'desktop-debug') {
|
||||||
|
debugState.open = false;
|
||||||
|
saveDebugState();
|
||||||
|
debugBus.setEnabled(false);
|
||||||
|
updateDebugToggleUi();
|
||||||
|
}
|
||||||
|
|
||||||
windows.delete(windowId);
|
windows.delete(windowId);
|
||||||
record.node.remove();
|
record.node.remove();
|
||||||
syncTaskbar();
|
syncTaskbar();
|
||||||
@@ -1052,6 +1374,10 @@ if (payloadNode) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (definition.content_mode === 'desktop-debug') {
|
||||||
|
initializeDesktopDebugWindow(record);
|
||||||
|
}
|
||||||
|
|
||||||
node.querySelectorAll('.window-action').forEach((action) => {
|
node.querySelectorAll('.window-action').forEach((action) => {
|
||||||
action.addEventListener('click', (event) => {
|
action.addEventListener('click', (event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
@@ -1328,6 +1654,14 @@ if (payloadNode) {
|
|||||||
setAccountMenuOpen(accountMenu.hidden);
|
setAccountMenuOpen(accountMenu.hidden);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
debugToggleNode?.addEventListener('click', () => {
|
||||||
|
toggleDebugWindow();
|
||||||
|
});
|
||||||
|
|
||||||
|
debugToggleTopNode?.addEventListener('click', () => {
|
||||||
|
toggleDebugWindow();
|
||||||
|
});
|
||||||
|
|
||||||
accountMenu?.addEventListener('click', (event) => {
|
accountMenu?.addEventListener('click', (event) => {
|
||||||
const target = event.target instanceof Element ? event.target.closest('[data-account-action]') : null;
|
const target = event.target instanceof Element ? event.target.closest('[data-account-action]') : null;
|
||||||
|
|
||||||
@@ -1426,9 +1760,13 @@ if (payloadNode) {
|
|||||||
|
|
||||||
renderWidgets();
|
renderWidgets();
|
||||||
renderStartMenu();
|
renderStartMenu();
|
||||||
|
updateDebugToggleUi();
|
||||||
setStartMenuOpen(false);
|
setStartMenuOpen(false);
|
||||||
measureDesktopBounds();
|
measureDesktopBounds();
|
||||||
applyStoredIconLayout();
|
applyStoredIconLayout();
|
||||||
|
if (isAdmin && debugState.open === true) {
|
||||||
|
openDebugWindow();
|
||||||
|
}
|
||||||
payload.windows.forEach(createWindow);
|
payload.windows.forEach(createWindow);
|
||||||
renderClock();
|
renderClock();
|
||||||
window.setInterval(renderClock, 1000);
|
window.setInterval(renderClock, 1000);
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ final class App
|
|||||||
? $_SESSION['desktop_auth']['user']
|
? $_SESSION['desktop_auth']['user']
|
||||||
: [];
|
: [];
|
||||||
$authGroups = array_values(array_map('strval', (array) ($authUser['groups'] ?? [])));
|
$authGroups = array_values(array_map('strval', (array) ($authUser['groups'] ?? [])));
|
||||||
|
$isAdmin = in_array('administrators', $authGroups, true);
|
||||||
$visibleApps = AppVisibility::filterByGroups($registry->all(), $authGroups);
|
$visibleApps = AppVisibility::filterByGroups($registry->all(), $authGroups);
|
||||||
$userSelfManagement = new UserSelfManagementService($this->projectRoot);
|
$userSelfManagement = new UserSelfManagementService($this->projectRoot);
|
||||||
$resolvedSkin = $userSelfManagement->resolveActiveSkin(
|
$resolvedSkin = $userSelfManagement->resolveActiveSkin(
|
||||||
@@ -97,6 +98,8 @@ final class App
|
|||||||
'session' => [
|
'session' => [
|
||||||
'display_name' => $displayName,
|
'display_name' => $displayName,
|
||||||
'authenticated' => $isAuthenticated,
|
'authenticated' => $isAuthenticated,
|
||||||
|
'is_admin' => $isAdmin,
|
||||||
|
'groups' => $authGroups,
|
||||||
'logout_url' => $isAuthenticated && $auth->isConfigured() ? '/auth/logout' : null,
|
'logout_url' => $isAuthenticated && $auth->isConfigured() ? '/auth/logout' : null,
|
||||||
],
|
],
|
||||||
'menu' => [
|
'menu' => [
|
||||||
|
|||||||
2
start.md
2
start.md
@@ -40,6 +40,8 @@ Danach bei Bedarf die fachlichen Projektanweisungen lesen:
|
|||||||
- `UMSETZUNGSSTATUS.md` nur dann auf `ERLEDIGT` setzen, wenn es ausdruecklich freigegeben wurde
|
- `UMSETZUNGSSTATUS.md` nur dann auf `ERLEDIGT` setzen, wenn es ausdruecklich freigegeben wurde
|
||||||
- Desktop-Mechaniken nicht versehentlich zurueckbauen
|
- Desktop-Mechaniken nicht versehentlich zurueckbauen
|
||||||
- Standard-Design fuer Fensterinhalte beibehalten, sofern eine App kein eigenes UI braucht
|
- Standard-Design fuer Fensterinhalte beibehalten, sofern eine App kein eigenes UI braucht
|
||||||
|
- globale Debug-Infrastruktur nicht pro App neu bauen; fuer Admin-Debugging ist das gemeinsame Desktop-Debug-Widget mit Debug-Fenster zu verwenden
|
||||||
|
- Debug-Tracking soll nur laufen, wenn das globale Debug-Fenster aktiv geoeffnet ist; der letzte Debug-Status soll pro Benutzer einen Reload ueberstehen
|
||||||
- Begriffe und offizielle Benennungen aus `docs/CONTENT.md` verwenden
|
- Begriffe und offizielle Benennungen aus `docs/CONTENT.md` verwenden
|
||||||
- `README.md`-Dateien in Teilbereichen pflegen
|
- `README.md`-Dateien in Teilbereichen pflegen
|
||||||
- wichtige Informationen aus dezentralen `README.md`-Dateien auch zentral in `docs/` halten
|
- wichtige Informationen aus dezentralen `README.md`-Dateien auch zentral in `docs/` halten
|
||||||
|
|||||||
Reference in New Issue
Block a user