Files
emailtemplate.it/public/assets/js/ui-user.js
2025-12-08 00:19:58 +01:00

793 lines
27 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { apiAction, toast } from './api.js';
const state = {
settings: {},
rotate: { bridge: false, sender: false, external: false },
users: [],
userMap: new Map(),
senders: [],
senderMap: new Map(),
bridgeTables: [],
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 bridgePreview;
let validateBridgeBtn;
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 = [];
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');
bridgePreview = document.getElementById('bridgeTablesPreview');
validateBridgeBtn = document.getElementById('btn-validate-bridge');
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);
validateBridgeBtn?.addEventListener('click', validateBridgeSettings);
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);
});
});
}
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 = '/account.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();
}
}
} 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 || '';
const tables = Array.isArray(settings.bridge_tables) ? settings.bridge_tables : [];
settingsForm.bridge_tables ? settingsForm.bridge_tables.value = tables.join(', ') : null;
applyBridgePreview(tables);
state.rotate = { bridge: false, sender: false, external: false };
}
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 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(),
rotate_bridge_token: state.rotate.bridge ? 1 : 0,
rotate_sender_token: state.rotate.sender ? 1 : 0,
rotate_external_token: state.rotate.external ? 1 : 0,
bridge_tables: parseBridgeTablesInput(),
};
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 parseBridgeTablesInput() {
if (!settingsForm) return [];
const raw = settingsForm.bridge_tables?.value || '';
return raw
.split(/[\s,]+/)
.map(part => part.trim())
.filter(Boolean);
}
function applyBridgePreview(tables) {
state.bridgeTables = Array.isArray(tables) ? tables : [];
if (!bridgePreview) return;
if (!state.bridgeTables.length) {
bridgePreview.innerHTML = '<span class="text-xs text-slate-500">Keine Einschränkung alle Tabellen erlaubt.</span>';
return;
}
bridgePreview.innerHTML = state.bridgeTables.map(name => `<span class="chip">${escapeHtml(name)}</span>`).join('');
}
async function validateBridgeSettings(ev) {
ev?.preventDefault();
if (!settingsForm) return;
const data = {
bridge_url: settingsForm.bridge_url.value.trim(),
bridge_token: settingsForm.bridge_token.value.trim(),
};
if (!data.bridge_url || !data.bridge_token) {
toast('Bitte Bridge-URL und Token angeben', false);
return;
}
try {
const res = await apiAction('account.bridge.test', { method: 'POST', data });
if (!res?.ok) throw new Error(res?.error || 'Prüfung fehlgeschlagen');
const tables = Array.isArray(res.tables) ? res.tables : [];
applyBridgePreview(tables);
if (settingsForm.bridge_tables) {
settingsForm.bridge_tables.value = tables.join(', ');
}
toast('Bridge erfolgreich geprüft', true);
} catch (err) {
toast(err.message || 'Prüfung fehlgeschlagen', false);
}
}
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 = '<tr><td colspan="5" class="text-sm text-slate-500">Keine Nutzer vorhanden.</td></tr>';
return;
}
tbody.innerHTML = state.users.map(user => {
const badge = user.is_active ? '<span class="badge bg-green-100 text-green-800">Aktiv</span>' : '<span class="badge bg-slate-200 text-slate-700">Inaktiv</span>';
return `<tr>
<td>${escapeHtml(user.name)}</td>
<td>${escapeHtml(user.email)}</td>
<td>${escapeHtml(user.role)}</td>
<td>${badge}</td>
<td class="text-right flex gap-2 justify-end">
<button class="btn" data-user-action="edit" data-user-id="${user.id}">Bearbeiten</button>
<button class="btn" data-user-action="reset" data-user-id="${user.id}">Passwort</button>
<button class="btn btn-danger" data-user-action="delete" data-user-id="${user.id}">Löschen</button>
</td>
</tr>`;
}).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 = '<tr><td colspan="5" class="text-sm text-slate-500">Keine Absender vorhanden.</td></tr>';
return;
}
tbody.innerHTML = state.senders.map(sender => `
<tr>
<td>${escapeHtml(sender.label || sender.from_name || sender.from_email)}</td>
<td>${escapeHtml(sender.from_name || '—')}</td>
<td>${escapeHtml(sender.from_email)}</td>
<td>${escapeHtml(sender.reply_to || '')}</td>
<td class="text-right flex gap-2 justify-end">
<button class="btn" data-sender-action="edit" data-sender-id="${sender.id}">Bearbeiten</button>
<button class="btn btn-danger" data-sender-action="delete" data-sender-id="${sender.id}">Löschen</button>
</td>
</tr>
`).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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
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 = `
<div class="debug-shell bg-white rounded-2xl shadow-2xl">
<div class="debug-header">
<strong>Debug Tools</strong>
<button type="button" class="btn" data-debug-close>Schließen</button>
</div>
<div class="debug-tabs">
<button type="button" data-debug-tab="php" class="active">PHP Debug</button>
<button type="button" data-debug-tab="console">Console</button>
</div>
<div class="debug-panel" data-debug-panel="php">
<div id="debugPhpContent" class="text-sm text-slate-700">Lade Daten…</div>
</div>
<div class="debug-panel hidden" data-debug-panel="console">
<pre id="debugConsoleContent"></pre>
</div>
</div>`;
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 || '<p>Keine Daten</p>';
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 `<div class="debug-console-entry ${entry.type}">[${time}] ${escapeHtml(entry.text)}</div>`;
});
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);
}
}