dadssad
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-22 01:16:50 +02:00
parent ba8794c371
commit f83a64b854
12 changed files with 489 additions and 2 deletions

View File

@@ -503,6 +503,13 @@ h1 {
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 {
position: absolute;
inset: 0;
@@ -518,6 +525,94 @@ h1 {
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 {
height: 100%;
color: #0f172a;
@@ -1238,6 +1333,11 @@ h1 {
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 {
position: absolute;
right: 16px;
@@ -1364,4 +1464,9 @@ h1 {
display: grid;
grid-template-columns: 1fr;
}
.desktop-debug-toolbar {
display: grid;
grid-template-columns: 1fr;
}
}

View File

@@ -19,6 +19,8 @@ if (payloadNode) {
const accountMenu = document.getElementById('tray-account-menu');
const clockNode = document.getElementById('clock');
const clockTopNode = document.getElementById('clock-top');
const debugToggleNode = document.getElementById('debug-toggle');
const debugToggleTopNode = document.getElementById('debug-toggle-top');
const windows = new Map();
const activeWidgets = new Set(payload.widgets.active_ids);
const activeSkin = payload.meta.active_skin;
@@ -29,6 +31,8 @@ if (payloadNode) {
const storageScope = payload.desktop.storage_scope || 'guest';
const storageKey = `desktop.kusche.berlin.window-state.${storageScope}`;
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 topDesktopInset = 0;
let sideDesktopInset = 0;
@@ -38,6 +42,92 @@ if (payloadNode) {
const windowControlsMode = document.body.dataset.windowControls || 'windows';
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)
.replaceAll('&', '&')
.replaceAll('<', '&lt;')
@@ -45,6 +135,89 @@ if (payloadNode) {
.replaceAll('"', '&quot;')
.replaceAll("'", '&#39;');
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 safeClassName = className ? ` ${className}` : '';
@@ -292,6 +465,22 @@ if (payloadNode) {
};
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') {
return `
<div class="window-content window-content--embedded window-content--module">
@@ -660,6 +849,132 @@ if (payloadNode) {
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 { x, y, width, height } = record.layout;
record.node.style.left = `${x}px`;
@@ -736,6 +1051,13 @@ if (payloadNode) {
return;
}
if (record.definition.app_id === 'desktop-debug') {
debugState.open = false;
saveDebugState();
debugBus.setEnabled(false);
updateDebugToggleUi();
}
windows.delete(windowId);
record.node.remove();
syncTaskbar();
@@ -1052,6 +1374,10 @@ if (payloadNode) {
}
}
if (definition.content_mode === 'desktop-debug') {
initializeDesktopDebugWindow(record);
}
node.querySelectorAll('.window-action').forEach((action) => {
action.addEventListener('click', (event) => {
event.stopPropagation();
@@ -1328,6 +1654,14 @@ if (payloadNode) {
setAccountMenuOpen(accountMenu.hidden);
});
debugToggleNode?.addEventListener('click', () => {
toggleDebugWindow();
});
debugToggleTopNode?.addEventListener('click', () => {
toggleDebugWindow();
});
accountMenu?.addEventListener('click', (event) => {
const target = event.target instanceof Element ? event.target.closest('[data-account-action]') : null;
@@ -1426,9 +1760,13 @@ if (payloadNode) {
renderWidgets();
renderStartMenu();
updateDebugToggleUi();
setStartMenuOpen(false);
measureDesktopBounds();
applyStoredIconLayout();
if (isAdmin && debugState.open === true) {
openDebugWindow();
}
payload.windows.forEach(createWindow);
renderClock();
window.setInterval(renderClock, 1000);