yyxx
This commit is contained in:
@@ -188,3 +188,199 @@ window.NexusModal = (() => {
|
||||
|
||||
return { create };
|
||||
})();
|
||||
|
||||
(() => {
|
||||
const root = document.getElementById('nexus-debug-root');
|
||||
const dataNode = document.getElementById('nexus-debug-data');
|
||||
if (!root || !dataNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
let payload = { enabled: false, entries: [] };
|
||||
try {
|
||||
const parsed = JSON.parse(dataNode.textContent || '{}');
|
||||
if (parsed && typeof parsed === 'object') {
|
||||
payload = parsed;
|
||||
}
|
||||
} catch (error) {
|
||||
payload = { enabled: false, entries: [] };
|
||||
}
|
||||
|
||||
const enabled = !!payload.enabled && document.body.dataset.nexusDebugEnabled === '1';
|
||||
const isAdmin = document.body.dataset.nexusDebugAdmin === '1';
|
||||
const entries = Array.isArray(payload.entries) ? payload.entries.slice(0, 250) : [];
|
||||
let sequence = entries.length;
|
||||
|
||||
const debugBus = window.__nexusDebugBus || { enabled: false, listener: null };
|
||||
debugBus.enabled = enabled;
|
||||
window.__nexusDebugBus = debugBus;
|
||||
|
||||
if (!isAdmin || !enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bugSvg = '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 4.5h6l1.2 2.2 2.5-1.4 1 1.7-2.4 1.4A6.9 6.9 0 0 1 19 13h2.5v2H19a6.9 6.9 0 0 1-.7 2.9l2.4 1.4-1 1.7-2.5-1.4L15 21.5H9l-1.2-2.2-2.5 1.4-1-1.7 2.4-1.4A6.9 6.9 0 0 1 6 15H3.5v-2H6a6.9 6.9 0 0 1 .7-2.9L4.3 8.7l1-1.7 2.5 1.4L9 4.5Zm1.2 2L9 8.8V15a3 3 0 0 0 6 0V8.8l-1.2-2.3h-3.6ZM10.5 11a1 1 0 1 1 0 2 1 1 0 0 1 0-2Zm3 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2Z" fill="currentColor"/></svg>';
|
||||
|
||||
root.innerHTML = `
|
||||
<button type="button" class="nexus-debug-bug" aria-label="Debug öffnen">
|
||||
${bugSvg}
|
||||
<span class="nexus-debug-badge">0</span>
|
||||
</button>
|
||||
<section class="nexus-debug-popup" aria-hidden="true">
|
||||
<div class="nexus-debug-popup__head">
|
||||
<div>
|
||||
<div class="pill">Debug</div>
|
||||
<h2>Nexus Debug</h2>
|
||||
</div>
|
||||
<button type="button" class="module-button module-button--ghost module-button--small" data-debug-close>Schließen</button>
|
||||
</div>
|
||||
<div class="nexus-debug-popup__toolbar">
|
||||
<div class="muted">Projektweiter Debug-Stream der aktuellen Admin-Sitzung.</div>
|
||||
<div class="nexus-debug-popup__actions">
|
||||
<button type="button" class="module-button module-button--ghost module-button--small" data-debug-reload>Neu laden</button>
|
||||
<button type="button" class="module-button module-button--ghost module-button--small" data-debug-clear>Leeren</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nexus-debug-popup__body" data-debug-list></div>
|
||||
</section>
|
||||
`;
|
||||
|
||||
const bugButton = root.querySelector('.nexus-debug-bug');
|
||||
const badge = root.querySelector('.nexus-debug-badge');
|
||||
const popup = root.querySelector('.nexus-debug-popup');
|
||||
const closeButton = root.querySelector('[data-debug-close]');
|
||||
const reloadButton = root.querySelector('[data-debug-reload]');
|
||||
const clearButton = root.querySelector('[data-debug-clear]');
|
||||
const list = root.querySelector('[data-debug-list]');
|
||||
|
||||
const updateBadge = () => {
|
||||
if (!badge) {
|
||||
return;
|
||||
}
|
||||
badge.textContent = String(entries.length);
|
||||
badge.hidden = entries.length === 0;
|
||||
};
|
||||
|
||||
const normalizeEntry = (entry) => {
|
||||
sequence += 1;
|
||||
return {
|
||||
id: entry && entry.id ? entry.id : `nexus-debug-${sequence}`,
|
||||
source: entry && entry.source ? String(entry.source) : 'nexus',
|
||||
type: entry && entry.type ? String(entry.type) : 'debug',
|
||||
label: entry && entry.label ? String(entry.label) : '',
|
||||
at: entry && (entry.at || entry.time) ? String(entry.at || entry.time) : new Date().toISOString(),
|
||||
payload: entry && typeof entry === 'object' ? entry : { value: entry },
|
||||
};
|
||||
};
|
||||
|
||||
const renderEntries = () => {
|
||||
if (!list) {
|
||||
return;
|
||||
}
|
||||
updateBadge();
|
||||
if (!entries.length) {
|
||||
list.innerHTML = '<div class="nexus-debug-empty">Noch keine Debug-Einträge vorhanden.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
list.innerHTML = entries.map((entry, index) => {
|
||||
const title = entry.label || entry.type || `Eintrag ${index + 1}`;
|
||||
const payloadText = JSON.stringify(entry.payload, null, 2);
|
||||
return `
|
||||
<details class="nexus-debug-entry"${index === 0 ? ' open' : ''}>
|
||||
<summary>
|
||||
<strong>${escapeHtml(title)}</strong>
|
||||
<span class="nexus-debug-entry__meta">${escapeHtml(entry.source)} · ${escapeHtml(entry.at)}</span>
|
||||
</summary>
|
||||
<pre>${escapeHtml(payloadText)}</pre>
|
||||
</details>
|
||||
`;
|
||||
}).join('');
|
||||
};
|
||||
|
||||
const escapeHtml = (value) => String(value ?? '')
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll("'", ''');
|
||||
|
||||
const openPopup = () => {
|
||||
popup?.classList.add('is-open');
|
||||
popup?.setAttribute('aria-hidden', 'false');
|
||||
};
|
||||
|
||||
const closePopup = () => {
|
||||
popup?.classList.remove('is-open');
|
||||
popup?.setAttribute('aria-hidden', 'true');
|
||||
};
|
||||
|
||||
const appendEntry = (entry) => {
|
||||
entries.unshift(normalizeEntry(entry));
|
||||
if (entries.length > 250) {
|
||||
entries.length = 250;
|
||||
}
|
||||
renderEntries();
|
||||
};
|
||||
|
||||
const reloadEntries = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/debug/entries', {
|
||||
credentials: 'same-origin',
|
||||
headers: { Accept: 'application/json' },
|
||||
});
|
||||
const data = await response.json().catch(() => ({}));
|
||||
const nextEntries = Array.isArray(data?.data?.entries) ? data.data.entries.map(normalizeEntry) : [];
|
||||
entries.splice(0, entries.length, ...nextEntries);
|
||||
renderEntries();
|
||||
} catch (error) {
|
||||
appendEntry({
|
||||
source: 'nexus',
|
||||
type: 'debug.reload.error',
|
||||
label: 'Reload fehlgeschlagen',
|
||||
message: error && error.message ? error.message : 'Debug-Einträge konnten nicht geladen werden.',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const clearEntries = async () => {
|
||||
try {
|
||||
await fetch('/api/debug/clear', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { Accept: 'application/json' },
|
||||
});
|
||||
} catch (error) {
|
||||
// Ignore clear failures; local UI is still reset below.
|
||||
}
|
||||
entries.splice(0, entries.length);
|
||||
renderEntries();
|
||||
};
|
||||
|
||||
const previousListener = typeof debugBus.listener === 'function' ? debugBus.listener : null;
|
||||
debugBus.listener = (entry) => {
|
||||
if (previousListener) {
|
||||
previousListener(entry);
|
||||
}
|
||||
appendEntry(entry);
|
||||
};
|
||||
|
||||
bugButton?.addEventListener('click', () => {
|
||||
if (popup?.classList.contains('is-open')) {
|
||||
closePopup();
|
||||
} else {
|
||||
openPopup();
|
||||
}
|
||||
});
|
||||
closeButton?.addEventListener('click', closePopup);
|
||||
reloadButton?.addEventListener('click', reloadEntries);
|
||||
clearButton?.addEventListener('click', clearEntries);
|
||||
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.key === 'Escape') {
|
||||
closePopup();
|
||||
}
|
||||
});
|
||||
|
||||
renderEntries();
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user