import { apiAction, toast } from './api.js'; const state = { settings: {}, rotate: { bridge: false, sender: false, external: false }, users: [], userMap: new Map(), senders: [], senderMap: new Map(), currentTab: 'profile', loading: false, }; const pageType = document.body?.dataset?.page || 'account'; const DEBUG_EMAIL = 'madmin@papa-kind-treff.info'; const MAX_CONSOLE_LINES = 200; let avatarBtn; let userMenuPanel; let profileForm; let passwordForm; let settingsForm; let teamTable; let userForm; let senderTable; let senderForm; let menuInitialized = false; let menuOpen = false; let debugButton; let debugDialog; let debugPhpLoaded = false; let debugPhpLoading = false; let debugActiveTab = 'php'; let phpInfoContainer; let consoleContainer; let debugStylesInjected = false; let consolePatched = false; const consoleBuffer = []; let adminTablesAllSelect; let adminTablesSelectedSelect; let adminTablesAddBtn; let adminTablesRemoveBtn; ensureConsoleCapture(); export function initUserPanel() { avatarBtn = document.getElementById('btn-user'); userMenuPanel = document.getElementById('userMenuPanel'); ensureConsoleCapture(); handleUserContextChange(); if (!menuInitialized && avatarBtn && userMenuPanel) { avatarBtn.addEventListener('click', toggleUserMenu); document.addEventListener('click', handleDocumentClick, true); document.addEventListener('keydown', handleMenuKeydown); userMenuPanel.addEventListener('click', handleMenuItemClick); menuInitialized = true; } } export function initAccountPage() { profileForm = document.getElementById('profileForm'); passwordForm = document.getElementById('passwordForm'); settingsForm = document.getElementById('settingsForm'); teamTable = document.getElementById('teamTable'); userForm = document.getElementById('userForm'); senderTable = document.getElementById('senderTable'); senderForm = document.getElementById('senderForm'); adminTablesAllSelect = document.getElementById('adminBridgeTablesAll'); adminTablesSelectedSelect = document.getElementById('adminBridgeTablesSelected'); adminTablesAddBtn = document.getElementById('adminBridgeTablesAdd'); adminTablesRemoveBtn = document.getElementById('adminBridgeTablesRemove'); document.getElementById('btn-user-add')?.addEventListener('click', () => openUserForm()); document.getElementById('userFormCancel')?.addEventListener('click', () => closeUserForm()); userForm?.addEventListener('submit', submitUserForm); document.getElementById('btn-sender-add')?.addEventListener('click', () => openSenderForm()); document.getElementById('senderFormCancel')?.addEventListener('click', () => closeSenderForm()); senderForm?.addEventListener('submit', submitSenderForm); profileForm?.addEventListener('submit', submitProfileForm); passwordForm?.addEventListener('submit', submitPasswordForm); settingsForm?.addEventListener('submit', submitSettingsForm); teamTable?.addEventListener('click', handleTeamTableClick); senderTable?.addEventListener('click', handleSenderTableClick); document.querySelectorAll('[data-user-tab]').forEach(btn => { btn.addEventListener('click', () => switchTab(btn.getAttribute('data-user-tab'))); }); if (settingsForm) { settingsForm.querySelectorAll('button[data-rotate]').forEach(btn => { btn.addEventListener('click', () => { const type = btn.getAttribute('data-rotate'); if (type && state.rotate[type] !== undefined) { state.rotate[type] = true; toast('Token wird nach dem Speichern erneuert.', true, { duration: 2000 }); } }); }); settingsForm.querySelectorAll('button[data-download]').forEach(btn => { btn.addEventListener('click', () => { const type = btn.getAttribute('data-download'); if (type) downloadFile(type); }); }); } adminTablesAddBtn?.addEventListener('click', () => { addAdminTables(getSelectedOptions(adminTablesAllSelect)); }); adminTablesRemoveBtn?.addEventListener('click', () => { removeAdminTables(getSelectedOptions(adminTablesSelectedSelect)); }); window.addEventListener('bridge-setup-updated', (ev) => { const setup = ev?.detail || {}; refreshAdminTables(setup.tables || [], state.settings.bridge_tables || []); }); switchTab(state.currentTab); loadAccountData(); updateRoleVisibility(); } function isOwner() { return (window.__currentUser?.role || '').toLowerCase() === 'owner'; } function isAdmin() { const role = (window.__currentUser?.role || '').toLowerCase(); return role === 'owner' || role === 'admin'; } function enforcePageAccess() { if (pageType !== 'admin') return; if (isAdmin()) return; toast('Kein Zugriff auf diesen Bereich', false, { duration: 2500 }); window.location.href = '/admin/profile.php'; } function handleUserContextChange() { updateAvatar(); updateRoleVisibility(); enforcePageAccess(); refreshDebugAccess(); } function updateAvatar() { const target = document.getElementById('userAvatar'); if (!target) return; const name = window.__currentUser?.name || window.__currentUser?.email || ''; target.textContent = name ? name.trim().charAt(0).toUpperCase() : 'U'; } function toggleUserMenu(ev) { ev?.preventDefault(); if (!userMenuPanel || !avatarBtn) return; menuOpen = !menuOpen; userMenuPanel.classList.toggle('hidden', !menuOpen); avatarBtn.setAttribute('aria-expanded', menuOpen ? 'true' : 'false'); } function closeUserMenu() { if (!menuOpen) return; menuOpen = false; if (userMenuPanel) userMenuPanel.classList.add('hidden'); if (avatarBtn) avatarBtn.setAttribute('aria-expanded', 'false'); } function handleDocumentClick(ev) { if (!userMenuPanel || !avatarBtn || !menuOpen) return; const target = ev.target; if (avatarBtn.contains(target) || userMenuPanel.contains(target)) return; closeUserMenu(); } function handleMenuKeydown(ev) { if (ev.key === 'Escape') { closeUserMenu(); } } function handleMenuItemClick(ev) { const item = ev.target.closest('.user-menu-item'); if (!item) return; closeUserMenu(); } function updateRoleVisibility() { const role = (window.__currentUser?.role || '').toLowerCase(); document.querySelectorAll('[data-role]').forEach(el => { const allowed = (el.getAttribute('data-role') || '').split(/[\s,]+/).filter(Boolean).map(r => r.toLowerCase()); if (!allowed.length) return; const visible = allowed.some(targetRole => { if (targetRole === 'owner') return role === 'owner'; if (targetRole === 'admin') return role === 'owner' || role === 'admin'; if (targetRole === 'editor') return role === 'owner' || role === 'admin' || role === 'editor'; return true; }); el.classList.toggle('hidden', !visible); }); document.querySelectorAll('.owner-only').forEach(el => { el.classList.toggle('hidden', !isOwner()); }); } function switchTab(tab) { if (!tab) return; state.currentTab = tab; document.querySelectorAll('[data-user-panel]').forEach(panel => { panel.classList.toggle('hidden', panel.getAttribute('data-user-panel') !== tab); }); document.querySelectorAll('[data-user-tab]').forEach(btn => { const isActive = btn.getAttribute('data-user-tab') === tab; btn.classList.toggle('bg-sky-50', isActive); btn.classList.toggle('text-sky-700', isActive); }); } async function loadAccountData() { try { state.loading = true; const res = await apiAction('account.profile.get', { method: 'GET' }); if (!res?.ok) throw new Error(res?.error || 'Profil konnte nicht geladen werden'); if (res.user) { window.__currentUser = res.user; handleUserContextChange(); } fillProfileForm(res.user); fillSettingsForm(res.settings || {}); if (teamTable && isOwner()) { await loadUsers(); } if (senderTable) { if (isAdmin()) { await loadSenders(); } else { state.senders = []; state.senderMap = new Map(); renderSenderList(); } } reportViewDebugInfo(res); } catch (err) { console.error(err); toast(err.message || 'Fehler beim Laden', false); } finally { state.loading = false; } } function fillProfileForm(user) { if (!profileForm) return; profileForm.name.value = user?.name || ''; profileForm.email.value = user?.email || ''; } function fillSettingsForm(settings) { state.settings = settings; if (!settingsForm) return; settingsForm.bridge_url.value = settings.bridge_url || ''; settingsForm.bridge_token.value = settings.bridge_token || ''; settingsForm.sender_token.value = settings.sender_token || ''; settingsForm.external_api_token.value = settings.external_api_token || ''; state.rotate = { bridge: false, sender: false, external: false }; refreshAdminTables(settings.bridge_setup?.tables || [], settings.bridge_tables || []); } async function submitProfileForm(ev) { ev.preventDefault(); const data = { name: profileForm.name.value.trim(), email: profileForm.email.value.trim(), }; try { const res = await apiAction('account.profile.update', { method: 'POST', data }); if (!res?.ok) throw new Error(res?.error || 'Profil konnte nicht gespeichert werden'); window.__currentUser = res.user; handleUserContextChange(); toast('Profil aktualisiert', true); } catch (err) { toast(err.message || 'Fehler beim Speichern', false); } } async function submitPasswordForm(ev) { ev.preventDefault(); const data = { current_password: passwordForm.current_password.value, new_password: passwordForm.new_password.value, }; try { const res = await apiAction('account.password.update', { method: 'POST', data }); if (!res?.ok) throw new Error(res?.error || 'Passwort konnte nicht geändert werden'); passwordForm.reset(); toast('Passwort aktualisiert', true); } catch (err) { toast(err.message || 'Fehler beim Speichern', false); } } async function submitSettingsForm(ev) { ev.preventDefault(); const bridgeTables = normalizeTableList(state.settings.bridge_tables || []); const data = { bridge_url: settingsForm.bridge_url.value.trim(), bridge_token: settingsForm.bridge_token.value.trim(), sender_token: settingsForm.sender_token.value.trim(), external_api_token: settingsForm.external_api_token.value.trim(), bridge_tables: bridgeTables, rotate_bridge_token: state.rotate.bridge ? 1 : 0, rotate_sender_token: state.rotate.sender ? 1 : 0, rotate_external_token: state.rotate.external ? 1 : 0, }; try { const res = await apiAction('account.settings.update', { method: 'POST', data }); if (!res?.ok) throw new Error(res?.error || 'Einstellungen konnten nicht gespeichert werden'); fillSettingsForm(res.settings || {}); toast('Integrationen gespeichert', true); } catch (err) { toast(err.message || 'Fehler beim Speichern', false); } } async function downloadFile(type) { try { const action = type === 'sender' ? 'downloads.sender' : 'downloads.bridge'; const res = await apiAction(action, { method: 'POST', data: {} }); if (!res?.ok || !res.content) throw new Error(res?.error || 'Download fehlgeschlagen'); const bytes = atob(res.content); const buffer = new Uint8Array(bytes.length); for (let i = 0; i < bytes.length; i++) buffer[i] = bytes.charCodeAt(i); const blob = new Blob([buffer], { type: 'application/octet-stream' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = res.file_name || `${type}.php`; document.body.appendChild(link); link.click(); link.remove(); setTimeout(() => URL.revokeObjectURL(url), 1000); } catch (err) { toast(err.message || 'Download fehlgeschlagen', false); } } function normalizeTableList(input) { const items = Array.isArray(input) ? input : (typeof input === 'string' ? input.split(/[\s,]+/) : []); const result = []; const seen = new Set(); items.forEach(entry => { const name = String(entry || '').trim(); if (name && !seen.has(name)) { seen.add(name); result.push(name); } }); return result; } function refreshAdminTables(availableTables, selectedTables) { const whitelist = normalizeTableList(availableTables); let selected = normalizeTableList(selectedTables); if (!selected.length) { selected = whitelist.slice(); } if (whitelist.length) { selected = selected.filter(name => whitelist.includes(name)); } state.settings.bridge_tables = selected; state.settings.bridge_setup = state.settings.bridge_setup || {}; state.settings.bridge_setup.tables = whitelist; updateAdminTableSelects(whitelist, selected); } function updateAdminTableSelects(availableTables, selectedTables) { const selectedSet = new Set(selectedTables); const available = availableTables.filter(name => !selectedSet.has(name)); renderSelect(adminTablesAllSelect, available, 'Keine Tabellen freigegeben.'); renderSelect(adminTablesSelectedSelect, selectedTables, 'Noch keine Tabellen ausgewaehlt.'); } function renderSelect(selectEl, list, emptyLabel) { if (!selectEl) return; selectEl.innerHTML = ''; if (!list.length) { const opt = document.createElement('option'); opt.textContent = emptyLabel; opt.disabled = true; selectEl.appendChild(opt); return; } list.forEach(name => { const opt = document.createElement('option'); opt.value = name; opt.textContent = name; selectEl.appendChild(opt); }); } function getSelectedOptions(selectEl) { if (!selectEl) return []; return Array.from(selectEl.selectedOptions || []).map(opt => opt.value); } function addAdminTables(list) { const whitelist = normalizeTableList(state.settings.bridge_setup?.tables || []); if (!whitelist.length) return; const selected = normalizeTableList(state.settings.bridge_tables || []); const merged = normalizeTableList([...selected, ...list]).filter(name => whitelist.includes(name)); state.settings.bridge_tables = merged; updateAdminTableSelects(whitelist, merged); } function removeAdminTables(list) { const whitelist = normalizeTableList(state.settings.bridge_setup?.tables || []); if (!whitelist.length) return; const removeSet = new Set(list); const next = normalizeTableList(state.settings.bridge_tables || []).filter(name => !removeSet.has(name)); state.settings.bridge_tables = next; updateAdminTableSelects(whitelist, next); } async function loadUsers() { try { const res = await apiAction('account.users.list', { method: 'GET' }); if (!res?.ok) throw new Error(res?.error || 'Team konnte nicht geladen werden'); state.users = res.items || []; state.userMap = new Map(state.users.map(u => [u.id, u])); renderUserList(); } catch (err) { toast(err.message || 'Fehler beim Laden der Nutzer', false); } } function renderUserList() { if (!teamTable) return; const tbody = teamTable.querySelector('tbody'); if (!tbody) return; if (!state.users.length) { tbody.innerHTML = 'Keine Nutzer vorhanden.'; return; } tbody.innerHTML = state.users.map(user => { const badge = user.is_active ? 'Aktiv' : 'Inaktiv'; return ` ${escapeHtml(user.name)} ${escapeHtml(user.email)} ${escapeHtml(user.role)} ${badge} `; }).join(''); } function handleTeamTableClick(ev) { const btn = ev.target.closest('button[data-user-action]'); if (!btn) return; const id = Number(btn.getAttribute('data-user-id')); const action = btn.getAttribute('data-user-action'); const user = state.userMap.get(id); if (!user) return; if (action === 'edit') { openUserForm(user); } else if (action === 'delete') { if (confirm(`Soll ${user.name} wirklich entfernt werden?`)) deleteUser(id); } else if (action === 'reset') { openUserForm(user, true); } } function openUserForm(user = null, resetOnly = false) { if (!userForm) return; userForm.classList.remove('hidden'); userForm.user_id.value = user?.id || ''; userForm.name.value = user?.name || ''; userForm.email.value = user?.email || ''; userForm.role.value = user?.role || 'user'; userForm.is_active.checked = user ? !!user.is_active : true; const resetRow = userForm.querySelector('.reset-only'); if (resetRow) resetRow.classList.toggle('hidden', !user); if (resetRow) resetRow.querySelector('input').checked = resetOnly; } function closeUserForm() { if (!userForm) return; userForm.classList.add('hidden'); userForm.reset(); userForm.user_id.value = ''; const resetInput = userForm.querySelector('.reset-only input'); if (resetInput) resetInput.checked = false; } async function submitUserForm(ev) { ev.preventDefault(); const formData = new FormData(userForm); const payload = { name: formData.get('name')?.toString().trim() || '', email: formData.get('email')?.toString().trim() || '', role: formData.get('role')?.toString() || 'user', is_active: userForm.is_active.checked ? 1 : 0, }; const userId = formData.get('user_id')?.toString(); let action = 'account.users.create'; if (userId) { action = 'account.users.update'; payload.user_id = Number(userId); payload.reset_password = userForm.querySelector('input[name="reset_password"]').checked ? 1 : 0; } try { const res = await apiAction(action, { method: 'POST', data: payload }); if (!res?.ok) throw new Error(res?.error || 'Speichern fehlgeschlagen'); closeUserForm(); await loadUsers(); toast('Nutzer gespeichert', true); if (res.temp_password) { copyToClipboard(res.temp_password); alert(`Neues Passwort: ${res.temp_password}`); } } catch (err) { toast(err.message || 'Fehler beim Speichern', false); } } async function deleteUser(userId) { try { const res = await apiAction('account.users.delete', { method: 'POST', data: { user_id: userId } }); if (!res?.ok) throw new Error(res?.error || 'Löschen fehlgeschlagen'); await loadUsers(); toast('Nutzer gelöscht', true); } catch (err) { toast(err.message || 'Fehler beim Löschen', false); } } function copyToClipboard(value) { if (navigator.clipboard?.writeText) { navigator.clipboard.writeText(value).catch(() => {}); } } async function loadSenders() { if (!senderTable) return; try { const res = await apiAction('account.senders.list', { method: 'GET' }); if (!res?.ok) throw new Error(res?.error || 'Absender konnten nicht geladen werden'); state.senders = res.items || []; state.senderMap = new Map(state.senders.map(item => [item.id, item])); renderSenderList(); } catch (err) { toast(err.message || 'Fehler beim Laden der Absender', false); } } function renderSenderList() { if (!senderTable) return; const tbody = senderTable.querySelector('tbody'); if (!tbody) return; if (!state.senders.length) { tbody.innerHTML = 'Keine Absender vorhanden.'; return; } tbody.innerHTML = state.senders.map(sender => ` ${escapeHtml(sender.label || sender.from_name || sender.from_email)} ${escapeHtml(sender.from_name || '—')} ${escapeHtml(sender.from_email)} ${escapeHtml(sender.reply_to || '')} `).join(''); } function handleSenderTableClick(ev) { const btn = ev.target.closest('button[data-sender-action]'); if (!btn) return; const id = Number(btn.getAttribute('data-sender-id')); const action = btn.getAttribute('data-sender-action'); const sender = state.senderMap.get(id); if (!sender) return; if (action === 'edit') { openSenderForm(sender); } else if (action === 'delete') { if (confirm(`Absender "${sender.label || sender.from_email}" wirklich löschen?`)) { deleteSender(id); } } } function openSenderForm(sender = null) { if (!senderForm) return; senderForm.classList.remove('hidden'); senderForm.sender_id.value = sender?.id || ''; senderForm.label.value = sender?.label || ''; senderForm.from_name.value = sender?.from_name || ''; senderForm.from_email.value = sender?.from_email || ''; senderForm.reply_to.value = sender?.reply_to || ''; } function closeSenderForm() { if (!senderForm) return; senderForm.classList.add('hidden'); senderForm.reset(); senderForm.sender_id.value = ''; } async function submitSenderForm(ev) { ev.preventDefault(); if (!senderForm) return; const payload = { sender_id: senderForm.sender_id.value ? Number(senderForm.sender_id.value) : undefined, label: senderForm.label.value.trim(), from_name: senderForm.from_name.value.trim(), from_email: senderForm.from_email.value.trim(), reply_to: senderForm.reply_to.value.trim(), }; if (!payload.from_email) { toast('Bitte eine Absenderadresse angeben', false); return; } try { const res = await apiAction('account.senders.save', { method: 'POST', data: payload }); if (!res?.ok) throw new Error(res?.error || 'Speichern fehlgeschlagen'); closeSenderForm(); await loadSenders(); toast('Absender gespeichert', true); } catch (err) { toast(err.message || 'Fehler beim Speichern', false); } } async function deleteSender(senderId) { try { const res = await apiAction('account.senders.delete', { method: 'POST', data: { sender_id: senderId } }); if (!res?.ok) throw new Error(res?.error || 'Löschen fehlgeschlagen'); await loadSenders(); toast('Absender gelöscht', true); } catch (err) { toast(err.message || 'Fehler beim Löschen', false); } } function escapeHtml(str) { return String(str || '') .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function refreshDebugAccess() { const allowed = (window.__currentUser?.email || '').toLowerCase() === DEBUG_EMAIL; if (!allowed) { debugButton?.remove(); debugButton = null; if (debugDialog?.open) { debugDialog.close(); } return; } ensureDebugStyles(); ensureConsoleCapture(); ensureDebugDialog(); if (!debugButton) { debugButton = document.createElement('button'); debugButton.id = 'debugToggleButton'; debugButton.className = 'debug-floating-btn'; debugButton.type = 'button'; debugButton.textContent = 'Debug'; debugButton.addEventListener('click', () => openDebugDialog('php')); document.body.appendChild(debugButton); } } function ensureDebugStyles() { if (debugStylesInjected) return; const style = document.createElement('style'); style.id = 'debugStyles'; style.textContent = ` .debug-floating-btn{position:fixed;left:16px;bottom:16px;padding:.5rem 1rem;border-radius:999px;border:none;background:#0ea5e9;color:#fff;font-weight:600;box-shadow:0 10px 25px rgba(15,23,42,.25);z-index:60;cursor:pointer} .debug-floating-btn:hover{background:#0284c7} dialog#debugDialog::backdrop{background:rgba(15,23,42,.45)} #debugDialog{border:none;border-radius:1rem;padding:0;width:80vw;max-width:960px} .debug-shell{display:flex;flex-direction:column;height:80vh} .debug-header{display:flex;align-items:center;justify-content:space-between;padding:1rem;border-bottom:1px solid #e2e8f0} .debug-tabs{display:flex;border-bottom:1px solid #e2e8f0} .debug-tabs button{flex:1;padding:.75rem 1rem;border:none;background:transparent;cursor:pointer;font-weight:600} .debug-tabs button.active{background:#e0f2fe;color:#0c4a6e} .debug-panel{flex:1;overflow:auto;padding:1rem;background:#f8fafc} #debugConsoleContent{font-family:monospace;font-size:.85rem;white-space:pre-wrap} .debug-console-entry{margin-bottom:.35rem} .debug-console-entry.log{color:#15803d} .debug-console-entry.warn{color:#b45309} .debug-console-entry.error{color:#b91c1c} `; document.head.appendChild(style); debugStylesInjected = true; } function ensureDebugDialog() { if (debugDialog) return; debugDialog = document.createElement('dialog'); debugDialog.id = 'debugDialog'; debugDialog.innerHTML = `
Debug Tools
Lade Daten…
`; document.body.appendChild(debugDialog); phpInfoContainer = debugDialog.querySelector('#debugPhpContent'); consoleContainer = debugDialog.querySelector('#debugConsoleContent'); debugDialog.querySelector('[data-debug-close]')?.addEventListener('click', () => closeDebugDialog()); debugDialog.addEventListener('close', () => setDebugTab('php')); debugDialog.querySelectorAll('[data-debug-tab]').forEach(btn => { btn.addEventListener('click', () => setDebugTab(btn.getAttribute('data-debug-tab'))); }); } function openDebugDialog(tab = 'php') { ensureDebugDialog(); setDebugTab(tab); if (!debugDialog.open) debugDialog.showModal(); if (tab === 'php') { loadPhpInfo(); } else { renderConsolePanel(); } } function closeDebugDialog() { if (debugDialog?.open) debugDialog.close(); } function setDebugTab(tab) { if (!debugDialog) return; debugActiveTab = tab || 'php'; debugDialog.querySelectorAll('[data-debug-tab]').forEach(btn => { const isActive = btn.getAttribute('data-debug-tab') === debugActiveTab; btn.classList.toggle('active', isActive); }); debugDialog.querySelectorAll('[data-debug-panel]').forEach(panel => { panel.classList.toggle('hidden', panel.getAttribute('data-debug-panel') !== debugActiveTab); }); if (debugActiveTab === 'php') { loadPhpInfo(); } else { renderConsolePanel(); } } async function loadPhpInfo() { if (debugPhpLoaded || debugPhpLoading || !phpInfoContainer) return; debugPhpLoading = true; phpInfoContainer.textContent = 'Lade phpinfo…'; try { const res = await apiAction('debug.phpinfo', { method: 'GET' }); if (!res?.ok) throw new Error(res?.error || 'Fehler beim Laden'); const frame = document.createElement('iframe'); frame.style.width = '100%'; frame.style.height = '100%'; frame.style.minHeight = '400px'; frame.style.border = 'none'; frame.srcdoc = res.html || '

Keine Daten

'; phpInfoContainer.innerHTML = ''; phpInfoContainer.appendChild(frame); debugPhpLoaded = true; } catch (err) { phpInfoContainer.textContent = err.message || 'Fehler beim Laden'; } finally { debugPhpLoading = false; } } function renderConsolePanel() { if (!consoleContainer) return; if (!consoleBuffer.length) { consoleContainer.textContent = 'Noch keine Konsolenmeldungen in dieser Sitzung.'; return; } const lines = consoleBuffer.map(entry => { const time = entry.time.toLocaleTimeString(); return `
[${time}] ${escapeHtml(entry.text)}
`; }); consoleContainer.innerHTML = lines.join(''); } function ensureConsoleCapture() { if (consolePatched) return; ['log', 'warn', 'error'].forEach(type => { const original = console[type]; console[type] = function (...args) { appendConsoleMessage(type, args); if (typeof original === 'function') { original.apply(console, args); } }; }); consolePatched = true; } function appendConsoleMessage(type, args) { const text = args.map(formatConsoleArg).join(' '); consoleBuffer.push({ type, text, time: new Date() }); if (consoleBuffer.length > MAX_CONSOLE_LINES) consoleBuffer.shift(); if (debugActiveTab === 'console' && debugDialog?.open) { renderConsolePanel(); } } function formatConsoleArg(arg) { if (typeof arg === 'string') return arg; try { return JSON.stringify(arg); } catch (err) { return String(arg); } } function reportViewDebugInfo(apiResponse) { const role = (window.__currentUser?.role || '').toLowerCase() || 'unknown'; const expected = { profile: true, dashboard: role === 'owner' || role === 'admin', administration: role === 'owner' || role === 'admin', downloads: role === 'owner', }; const summary = { hasUser: !!apiResponse?.user, userRole: apiResponse?.user?.role ?? null, settingsKeys: apiResponse?.settings ? Object.keys(apiResponse.settings) : [], usersCount: Array.isArray(state.users) ? state.users.length : null, sendersCount: Array.isArray(state.senders) ? state.senders.length : null, requestedTab: state.currentTab, }; console.log('[view-debug]', { expectedViews: expected, role, apiSummary: summary }); }