This commit is contained in:
2025-12-08 00:14:39 +01:00
parent b3f10164de
commit 65e028d6e4
2 changed files with 224 additions and 6 deletions

View File

@@ -13,6 +13,8 @@ const state = {
}; };
const pageType = document.body?.dataset?.page || 'account'; const pageType = document.body?.dataset?.page || 'account';
const DEBUG_EMAIL = 'madmin@papa-kind-treff.info';
const MAX_CONSOLE_LINES = 200;
let avatarBtn; let avatarBtn;
let userMenuPanel; let userMenuPanel;
@@ -27,12 +29,22 @@ let bridgePreview;
let validateBridgeBtn; let validateBridgeBtn;
let menuInitialized = false; let menuInitialized = false;
let menuOpen = 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 = [];
export function initUserPanel() { export function initUserPanel() {
avatarBtn = document.getElementById('btn-user'); avatarBtn = document.getElementById('btn-user');
userMenuPanel = document.getElementById('userMenuPanel'); userMenuPanel = document.getElementById('userMenuPanel');
updateAvatar(); ensureConsoleCapture();
updateRoleVisibility(); handleUserContextChange();
if (!menuInitialized && avatarBtn && userMenuPanel) { if (!menuInitialized && avatarBtn && userMenuPanel) {
avatarBtn.addEventListener('click', toggleUserMenu); avatarBtn.addEventListener('click', toggleUserMenu);
document.addEventListener('click', handleDocumentClick, true); document.addEventListener('click', handleDocumentClick, true);
@@ -112,6 +124,13 @@ function enforcePageAccess() {
window.location.href = '/account.php'; window.location.href = '/account.php';
} }
function handleUserContextChange() {
updateAvatar();
updateRoleVisibility();
enforcePageAccess();
refreshDebugAccess();
}
function updateAvatar() { function updateAvatar() {
const target = document.getElementById('userAvatar'); const target = document.getElementById('userAvatar');
if (!target) return; if (!target) return;
@@ -191,9 +210,7 @@ async function loadAccountData() {
if (!res?.ok) throw new Error(res?.error || 'Profil konnte nicht geladen werden'); if (!res?.ok) throw new Error(res?.error || 'Profil konnte nicht geladen werden');
if (res.user) { if (res.user) {
window.__currentUser = res.user; window.__currentUser = res.user;
updateAvatar(); handleUserContextChange();
updateRoleVisibility();
enforcePageAccess();
} }
fillProfileForm(res.user); fillProfileForm(res.user);
fillSettingsForm(res.settings || {}); fillSettingsForm(res.settings || {});
@@ -246,7 +263,7 @@ async function submitProfileForm(ev) {
const res = await apiAction('account.profile.update', { method: 'POST', data }); const res = await apiAction('account.profile.update', { method: 'POST', data });
if (!res?.ok) throw new Error(res?.error || 'Profil konnte nicht gespeichert werden'); if (!res?.ok) throw new Error(res?.error || 'Profil konnte nicht gespeichert werden');
window.__currentUser = res.user; window.__currentUser = res.user;
updateAvatar(); handleUserContextChange();
toast('Profil aktualisiert', true); toast('Profil aktualisiert', true);
} catch (err) { } catch (err) {
toast(err.message || 'Fehler beim Speichern', false); toast(err.message || 'Fehler beim Speichern', false);
@@ -591,3 +608,183 @@ function escapeHtml(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 = `
<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);
}
}

View File

@@ -859,6 +859,9 @@ class ApiKernel
case 'placeholders.schema': case 'placeholders.schema':
$this->handlePlaceholderSchema(); $this->handlePlaceholderSchema();
break; break;
case 'debug.phpinfo':
$this->handleDebugPhpInfo();
break;
case 'templates.test_send': case 'templates.test_send':
$this->handleTemplateTestSend(); $this->handleTemplateTestSend();
break; break;
@@ -1820,6 +1823,16 @@ class ApiKernel
]); ]);
} }
private function handleDebugPhpInfo(): void
{
$user = $this->authService->requireAuth();
$this->ensureDebugUser($user);
ob_start();
phpinfo(INFO_GENERAL | INFO_CONFIGURATION | INFO_MODULES | INFO_ENVIRONMENT);
$html = ob_get_clean() ?: '';
$this->respond(['ok' => true, 'html' => $html]);
}
private function resolveBridgeConfig(?int $customerId): array private function resolveBridgeConfig(?int $customerId): array
{ {
$fileConf = $this->conf['placeholders']['bridge'] ?? []; $fileConf = $this->conf['placeholders']['bridge'] ?? [];
@@ -2146,6 +2159,14 @@ class ApiKernel
} }
} }
private function ensureDebugUser(array $user): void
{
$email = strtolower((string)($user['email'] ?? ''));
if ($email !== 'madmin@papa-kind-treff.info') {
$this->fail('Debug nicht erlaubt', null, 403);
}
}
private function defaultApiBase(): string private function defaultApiBase(): string
{ {
$base = $this->conf['base_url'] ?? ''; $base = $this->conf['base_url'] ?? '';