yyxx
This commit is contained in:
@@ -26,20 +26,7 @@
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
const initialDebugMode = (() => {
|
const initialDebugMode = document.body && document.body.dataset.nexusDebugEnabled === '1';
|
||||||
try {
|
|
||||||
return window.localStorage.getItem('mining-checker-debug-enabled') === '1';
|
|
||||||
} catch (error) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
const initialDebugConsoleOpen = (() => {
|
|
||||||
try {
|
|
||||||
return window.localStorage.getItem('mining-checker-debug-console-open') === '1';
|
|
||||||
} catch (error) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
function getCookie(name) {
|
function getCookie(name) {
|
||||||
const pattern = `; ${document.cookie}`;
|
const pattern = `; ${document.cookie}`;
|
||||||
const parts = pattern.split(`; ${name}=`);
|
const parts = pattern.split(`; ${name}=`);
|
||||||
@@ -52,20 +39,12 @@
|
|||||||
function setCookie(name, value, maxAgeSeconds) {
|
function setCookie(name, value, maxAgeSeconds) {
|
||||||
document.cookie = `${name}=${encodeURIComponent(value)}; path=/; max-age=${maxAgeSeconds}; samesite=lax`;
|
document.cookie = `${name}=${encodeURIComponent(value)}; path=/; max-age=${maxAgeSeconds}; samesite=lax`;
|
||||||
}
|
}
|
||||||
const initialDebugView = (() => {
|
const debugBus = window.__nexusDebugBus || {
|
||||||
try {
|
|
||||||
const value = window.localStorage.getItem('mining-checker-debug-view');
|
|
||||||
return value === 'text' ? 'text' : 'structured';
|
|
||||||
} catch (error) {
|
|
||||||
return 'structured';
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
const debugBus = window.__miningCheckerDebugBus || {
|
|
||||||
enabled: initialDebugMode,
|
enabled: initialDebugMode,
|
||||||
listener: null,
|
listener: null,
|
||||||
sequence: 0,
|
sequence: 0,
|
||||||
};
|
};
|
||||||
window.__miningCheckerDebugBus = debugBus;
|
window.__nexusDebugBus = debugBus;
|
||||||
|
|
||||||
function emitDebug(entry) {
|
function emitDebug(entry) {
|
||||||
debugBus.sequence += 1;
|
debugBus.sequence += 1;
|
||||||
@@ -80,11 +59,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function persistDebugCookie(enabled) {
|
|
||||||
const value = enabled ? '1' : '0';
|
|
||||||
document.cookie = `mining_checker_debug=${value}; path=/; max-age=31536000; samesite=lax`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function emitServerTraceEntries(trace, meta) {
|
function emitServerTraceEntries(trace, meta) {
|
||||||
if (!Array.isArray(trace) || !trace.length) {
|
if (!Array.isArray(trace) || !trace.length) {
|
||||||
emitDebug({
|
emitDebug({
|
||||||
@@ -585,10 +559,6 @@
|
|||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [message, setMessage] = useState('');
|
const [message, setMessage] = useState('');
|
||||||
const [debugEnabled, setDebugEnabled] = useState(initialDebugMode);
|
|
||||||
const [debugConsoleOpen, setDebugConsoleOpen] = useState(initialDebugConsoleOpen);
|
|
||||||
const [debugView, setDebugView] = useState(initialDebugView);
|
|
||||||
const [debugEntries, setDebugEntries] = useState([]);
|
|
||||||
const [schemaStatus, setSchemaStatus] = useState(normalizeSchemaStatus(null));
|
const [schemaStatus, setSchemaStatus] = useState(normalizeSchemaStatus(null));
|
||||||
const [initForm, setInitForm] = useState({ drop_existing: false });
|
const [initForm, setInitForm] = useState({ drop_existing: false });
|
||||||
const [sqlImportFile, setSqlImportFile] = useState(null);
|
const [sqlImportFile, setSqlImportFile] = useState(null);
|
||||||
@@ -706,43 +676,12 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
debugBus.enabled = debugEnabled;
|
debugBus.enabled = initialDebugMode;
|
||||||
debugBus.listener = (entry) => {
|
|
||||||
setDebugEntries((current) => [entry].concat(current).slice(0, 250));
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
window.localStorage.setItem('mining-checker-debug-enabled', debugEnabled ? '1' : '0');
|
|
||||||
} catch (error) {
|
|
||||||
// Ignore localStorage write failures.
|
|
||||||
}
|
|
||||||
persistDebugCookie(debugEnabled);
|
|
||||||
|
|
||||||
emitDebug({
|
emitDebug({
|
||||||
type: 'debug:mode',
|
type: 'debug:mode',
|
||||||
enabled: debugEnabled,
|
enabled: initialDebugMode,
|
||||||
});
|
});
|
||||||
|
}, []);
|
||||||
return () => {
|
|
||||||
debugBus.listener = null;
|
|
||||||
};
|
|
||||||
}, [debugEnabled]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
try {
|
|
||||||
window.localStorage.setItem('mining-checker-debug-console-open', debugConsoleOpen ? '1' : '0');
|
|
||||||
} catch (error) {
|
|
||||||
// Ignore localStorage write failures.
|
|
||||||
}
|
|
||||||
}, [debugConsoleOpen]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
try {
|
|
||||||
window.localStorage.setItem('mining-checker-debug-view', debugView);
|
|
||||||
} catch (error) {
|
|
||||||
// Ignore localStorage write failures.
|
|
||||||
}
|
|
||||||
}, [debugView]);
|
|
||||||
|
|
||||||
const measurements = Array.isArray(payload?.measurements) ? payload.measurements : [];
|
const measurements = Array.isArray(payload?.measurements) ? payload.measurements : [];
|
||||||
const latest = payload?.summary?.latest_measurement || null;
|
const latest = payload?.summary?.latest_measurement || null;
|
||||||
@@ -1765,46 +1704,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copyDebugConsole() {
|
|
||||||
const content = debugEntries
|
|
||||||
.slice()
|
|
||||||
.reverse()
|
|
||||||
.map((entry) => `${entry.time} · ${entry.type}\n${JSON.stringify(entry, null, 2)}`)
|
|
||||||
.join('\n\n');
|
|
||||||
|
|
||||||
if (!content) {
|
|
||||||
setMessage('Keine Debug-Ausgaben zum Kopieren vorhanden.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
||||||
await navigator.clipboard.writeText(content);
|
|
||||||
} else {
|
|
||||||
const textarea = document.createElement('textarea');
|
|
||||||
textarea.value = content;
|
|
||||||
textarea.setAttribute('readonly', 'readonly');
|
|
||||||
textarea.style.position = 'absolute';
|
|
||||||
textarea.style.left = '-9999px';
|
|
||||||
document.body.appendChild(textarea);
|
|
||||||
textarea.select();
|
|
||||||
document.execCommand('copy');
|
|
||||||
document.body.removeChild(textarea);
|
|
||||||
}
|
|
||||||
|
|
||||||
setMessage('Debug-Inhalt wurde in die Zwischenablage kopiert.');
|
|
||||||
} catch (err) {
|
|
||||||
setError('Debug-Inhalt konnte nicht kopiert werden.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetReportCurrencyOverride() {
|
function resetReportCurrencyOverride() {
|
||||||
setReportCurrencyOverride('');
|
setReportCurrencyOverride('');
|
||||||
setCookie('mining_checker_report_currency', '', 0);
|
setCookie('mining_checker_report_currency', '', 0);
|
||||||
}
|
}
|
||||||
const debugConsoleText = debugEntries
|
|
||||||
.map((entry) => `${entry.time} · ${entry.type}\n${JSON.stringify(entry, null, 2)}`)
|
|
||||||
.join('\n\n');
|
|
||||||
return h('div', {
|
return h('div', {
|
||||||
className: 'mc-grid-bg',
|
className: 'mc-grid-bg',
|
||||||
}, [
|
}, [
|
||||||
@@ -1813,78 +1716,6 @@
|
|||||||
message ? h('div', { key: 'message', className: 'mc-alert mc-alert--success' }, message) : null,
|
message ? h('div', { key: 'message', className: 'mc-alert mc-alert--success' }, message) : null,
|
||||||
loading ? h('div', { key: 'loading', className: 'mc-empty' }, 'Lade Mining-Checker Daten …') : null,
|
loading ? h('div', { key: 'loading', className: 'mc-empty' }, 'Lade Mining-Checker Daten …') : null,
|
||||||
payload ? renderTab() : null,
|
payload ? renderTab() : null,
|
||||||
h('div', { key: 'debug-tools', className: 'mc-debug-tools' }, [
|
|
||||||
h('button', {
|
|
||||||
key: 'debug-toggle',
|
|
||||||
type: 'button',
|
|
||||||
className: cx('mc-button', debugEnabled ? 'mc-button--secondary' : 'mc-button--ghost'),
|
|
||||||
onClick: () => {
|
|
||||||
const next = !debugEnabled;
|
|
||||||
setDebugEnabled(next);
|
|
||||||
if (next) {
|
|
||||||
setDebugConsoleOpen(true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}, debugEnabled ? 'Debug aktiv' : 'Debug einschalten'),
|
|
||||||
h('button', {
|
|
||||||
key: 'console-toggle',
|
|
||||||
type: 'button',
|
|
||||||
className: 'mc-button mc-button--ghost',
|
|
||||||
onClick: () => setDebugConsoleOpen((current) => {
|
|
||||||
const next = !current;
|
|
||||||
if (next) {
|
|
||||||
setDebugEnabled(true);
|
|
||||||
}
|
|
||||||
return next;
|
|
||||||
}),
|
|
||||||
}, debugConsoleOpen ? 'Online-Konsole ausblenden' : 'Online-Konsole einblenden'),
|
|
||||||
debugEntries.length ? h('button', {
|
|
||||||
key: 'debug-clear',
|
|
||||||
type: 'button',
|
|
||||||
className: 'mc-button mc-button--ghost',
|
|
||||||
onClick: () => setDebugEntries([]),
|
|
||||||
}, 'Konsole leeren') : null,
|
|
||||||
debugEntries.length ? h('button', {
|
|
||||||
key: 'debug-copy',
|
|
||||||
type: 'button',
|
|
||||||
className: 'mc-button mc-button--ghost',
|
|
||||||
onClick: copyDebugConsole,
|
|
||||||
}, 'Copy content') : null,
|
|
||||||
]),
|
|
||||||
debugConsoleOpen ? h('section', { key: 'debug-console', className: 'mc-panel mc-debug-console' }, [
|
|
||||||
h(SectionTitle, {
|
|
||||||
key: 'debug-title',
|
|
||||||
title: 'Online-Konsole',
|
|
||||||
subtitle: 'Zeigt API-, Provider- und DB-Debugspuren direkt aus dem Modul.',
|
|
||||||
action: h(Badge, { tone: debugEnabled ? 'success' : 'warn' }, debugEnabled ? 'Debug aktiv' : 'Debug aus'),
|
|
||||||
}),
|
|
||||||
h('div', { key: 'debug-body', className: 'mc-panel-body' }, [
|
|
||||||
h('div', { key: 'debug-view-switch', className: 'mc-debug-view-switch' }, [
|
|
||||||
h('button', {
|
|
||||||
key: 'view-structured',
|
|
||||||
type: 'button',
|
|
||||||
className: cx('mc-button', debugView === 'structured' ? 'mc-button--tab-active' : 'mc-button--tab'),
|
|
||||||
onClick: () => setDebugView('structured'),
|
|
||||||
}, 'Strukturiert'),
|
|
||||||
h('button', {
|
|
||||||
key: 'view-text',
|
|
||||||
type: 'button',
|
|
||||||
className: cx('mc-button', debugView === 'text' ? 'mc-button--tab-active' : 'mc-button--tab'),
|
|
||||||
onClick: () => setDebugView('text'),
|
|
||||||
}, 'Text-Konsole'),
|
|
||||||
]),
|
|
||||||
debugView === 'text'
|
|
||||||
? h('pre', { className: 'mc-code-block mc-debug-text-console' }, debugConsoleText || 'Noch keine Debug-Ausgaben vorhanden.')
|
|
||||||
: h('div', { className: 'mc-debug-log' },
|
|
||||||
debugEntries.length
|
|
||||||
? debugEntries.map((entry) => h('div', { key: entry.id, className: 'mc-debug-entry' }, [
|
|
||||||
h('div', { key: 'meta', className: 'mc-kicker' }, `${entry.time} · ${entry.type}`),
|
|
||||||
h('pre', { key: 'payload', className: 'mc-code-block' }, JSON.stringify(entry, null, 2)),
|
|
||||||
]))
|
|
||||||
: h('div', { className: 'mc-empty' }, 'Noch keine Debug-Ausgaben vorhanden.')
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
]) : null,
|
|
||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -44,14 +44,7 @@ final class Router
|
|||||||
$this->config = ModuleConfig::load($this->moduleBasePath);
|
$this->config = ModuleConfig::load($this->moduleBasePath);
|
||||||
$requestUri = (string) ($_SERVER['REQUEST_URI'] ?? '');
|
$requestUri = (string) ($_SERVER['REQUEST_URI'] ?? '');
|
||||||
$requestPath = (string) (parse_url($requestUri, PHP_URL_PATH) ?: '');
|
$requestPath = (string) (parse_url($requestUri, PHP_URL_PATH) ?: '');
|
||||||
$debugConfig = $this->config->debug();
|
$debugEnabled = function_exists('nexus_debug_enabled') ? nexus_debug_enabled() : false;
|
||||||
$debugEnabled = filter_var(
|
|
||||||
$_GET['debug']
|
|
||||||
?? $_SERVER['HTTP_X_MINING_DEBUG']
|
|
||||||
?? $_COOKIE['mining_checker_debug']
|
|
||||||
?? ($debugConfig['enabled'] ?? false),
|
|
||||||
FILTER_VALIDATE_BOOL
|
|
||||||
);
|
|
||||||
$latestDebugFilePath = rtrim($this->config->debugDir(), '/') . '/latest-server.json';
|
$latestDebugFilePath = rtrim($this->config->debugDir(), '/') . '/latest-server.json';
|
||||||
$isLatestDebugRequest = str_ends_with($requestPath, '/api/mining-checker/v1/debug/latest')
|
$isLatestDebugRequest = str_ends_with($requestPath, '/api/mining-checker/v1/debug/latest')
|
||||||
|| $requestPath === 'api/mining-checker/v1/debug/latest'
|
|| $requestPath === 'api/mining-checker/v1/debug/latest'
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ $error = null;
|
|||||||
$notice = null;
|
$notice = null;
|
||||||
$testGroup = null;
|
$testGroup = null;
|
||||||
$dbTestMessages = [];
|
$dbTestMessages = [];
|
||||||
|
$nexusDebugSettings = modules()->settings(nexus_debug_settings_key());
|
||||||
|
$nexusDebugEnabled = !empty($nexusDebugSettings['enabled']);
|
||||||
|
|
||||||
require_admin();
|
require_admin();
|
||||||
|
|
||||||
@@ -15,21 +17,6 @@ if (!$module) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$fields = (array)($module['setup']['fields'] ?? []);
|
$fields = (array)($module['setup']['fields'] ?? []);
|
||||||
$hasGlobalDebugField = false;
|
|
||||||
foreach ($fields as $field) {
|
|
||||||
if ((string)($field['name'] ?? '') === 'debug_enabled') {
|
|
||||||
$hasGlobalDebugField = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!$hasGlobalDebugField) {
|
|
||||||
$fields[] = [
|
|
||||||
'name' => 'debug_enabled',
|
|
||||||
'label' => 'Modul-Debug aktivieren',
|
|
||||||
'type' => 'checkbox',
|
|
||||||
'help' => 'Wenn aktiv, darf das Modul Debug-Daten sammeln und den Debug-Bereich anzeigen.',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
$fieldTypes = [];
|
$fieldTypes = [];
|
||||||
$fieldMeta = [];
|
$fieldMeta = [];
|
||||||
foreach ($fields as $field) {
|
foreach ($fields as $field) {
|
||||||
@@ -416,6 +403,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$isSchedulerAutosave = isset($_POST['scheduler_autosave']) && (string) $_POST['scheduler_autosave'] === '1';
|
$isSchedulerAutosave = isset($_POST['scheduler_autosave']) && (string) $_POST['scheduler_autosave'] === '1';
|
||||||
$isSchedulerTest = isset($_POST['scheduler_test']) && (string) $_POST['scheduler_test'] === '1';
|
$isSchedulerTest = isset($_POST['scheduler_test']) && (string) $_POST['scheduler_test'] === '1';
|
||||||
$payload = [];
|
$payload = [];
|
||||||
|
$nexusDebugEnabled = isset($_POST['nexus_debug_enabled']);
|
||||||
|
|
||||||
if ($isSchedulerAutosave || $isSchedulerTest) {
|
if ($isSchedulerAutosave || $isSchedulerTest) {
|
||||||
if ($cronTaskDefinitions !== []) {
|
if ($cronTaskDefinitions !== []) {
|
||||||
@@ -576,13 +564,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
modules()->saveSettings($moduleName, $current);
|
modules()->saveSettings($moduleName, $current);
|
||||||
|
modules()->saveSettings(nexus_debug_settings_key(), ['enabled' => $nexusDebugEnabled]);
|
||||||
if ($isFxRatesSetup && modules()->hasFunction($moduleName, 'save_runtime_settings')) {
|
if ($isFxRatesSetup && modules()->hasFunction($moduleName, 'save_runtime_settings')) {
|
||||||
module_fn($moduleName, 'save_runtime_settings', $payload);
|
module_fn($moduleName, 'save_runtime_settings', $payload);
|
||||||
$current = modules()->settings($moduleName);
|
$current = modules()->settings($moduleName);
|
||||||
}
|
}
|
||||||
$refreshSchedulerState();
|
$refreshSchedulerState();
|
||||||
if (empty($payload['debug_enabled'])) {
|
if (!$nexusDebugEnabled) {
|
||||||
module_debug_clear($moduleName);
|
nexus_debug_clear();
|
||||||
}
|
}
|
||||||
$notice = 'Setup gespeichert.';
|
$notice = 'Setup gespeichert.';
|
||||||
$module = modules()->get($moduleName) ?: $module;
|
$module = modules()->get($moduleName) ?: $module;
|
||||||
@@ -616,6 +605,22 @@ $activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups)
|
|||||||
<option value="<?= e((string) $timezoneOption['value']) ?>"><?= e((string) $timezoneOption['label']) ?></option>
|
<option value="<?= e((string) $timezoneOption['value']) ?>"><?= e((string) $timezoneOption['label']) ?></option>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</datalist>
|
</datalist>
|
||||||
|
<section class="setup-panel">
|
||||||
|
<div class="setup-panel__head">
|
||||||
|
<div>
|
||||||
|
<span class="pill">Nexus</span>
|
||||||
|
<h2>Debug</h2>
|
||||||
|
<p class="muted">Aktiviert das projektweite Debug-Popup. Sichtbar und nutzbar nur fuer Benutzer aus der Admin-Gruppe `appadmin`.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="setup-grid">
|
||||||
|
<label class="setup-field muted">
|
||||||
|
<span>Globales Debug aktivieren</span>
|
||||||
|
<input type="checkbox" name="nexus_debug_enabled" value="1" <?= $nexusDebugEnabled ? 'checked' : '' ?>>
|
||||||
|
<small class="muted">Wenn aktiv, sammelt Nexus sitzungsbezogene Debug-Eintraege projektweit und zeigt sie ueber den Bug-Button als Popup an.</small>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
<?php if ($isFxRatesSetup): ?>
|
<?php if ($isFxRatesSetup): ?>
|
||||||
<?php
|
<?php
|
||||||
$fxCatalog = is_array($current['currency_catalog'] ?? null) ? $current['currency_catalog'] : [];
|
$fxCatalog = is_array($current['currency_catalog'] ?? null) ? $current['currency_catalog'] : [];
|
||||||
@@ -776,11 +781,6 @@ $activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups)
|
|||||||
<small class="muted">Letzter Sync: <?= e((string) $current['currency_catalog_synced_at']) ?></small>
|
<small class="muted">Letzter Sync: <?= e((string) $current['currency_catalog_synced_at']) ?></small>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<label class="setup-field muted">
|
|
||||||
<span>Modul-Debug aktivieren</span>
|
|
||||||
<input type="checkbox" name="debug_enabled" value="1" <?= !empty($current['debug_enabled']) ? 'checked' : '' ?>>
|
|
||||||
<small class="muted">Wenn aktiv, darf das Modul Debug-Daten sammeln und den Debug-Bereich anzeigen.</small>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,17 @@
|
|||||||
</main>
|
</main>
|
||||||
|
<?php if (auth_is_admin()): ?>
|
||||||
|
<?php
|
||||||
|
$nexusDebugPayload = json_encode([
|
||||||
|
'enabled' => nexus_debug_enabled(),
|
||||||
|
'entries' => nexus_debug_entries(),
|
||||||
|
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||||
|
if (!is_string($nexusDebugPayload)) {
|
||||||
|
$nexusDebugPayload = '{"enabled":false,"entries":[]}';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<div id="nexus-debug-root"></div>
|
||||||
|
<script id="nexus-debug-data" type="application/json"><?= $nexusDebugPayload ?></script>
|
||||||
|
<?php endif; ?>
|
||||||
<script src="<?= e(app()->assets()->versioned('/assets/js/app.js')) ?>" defer></script>
|
<script src="<?= e(app()->assets()->versioned('/assets/js/app.js')) ?>" defer></script>
|
||||||
<?php asset_scripts('footer'); ?>
|
<?php asset_scripts('footer'); ?>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ $headerActions = isset($GLOBALS['layout_header_actions']) && is_array($GLOBALS['
|
|||||||
: [];
|
: [];
|
||||||
$auth = app()->auth();
|
$auth = app()->auth();
|
||||||
$authUser = $auth->user();
|
$authUser = $auth->user();
|
||||||
|
$isDebugAdmin = auth_is_admin();
|
||||||
|
$isNexusDebugEnabled = $isDebugAdmin && nexus_debug_enabled();
|
||||||
?>
|
?>
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="de">
|
<html lang="de">
|
||||||
@@ -54,7 +56,7 @@ $authUser = $auth->user();
|
|||||||
<?php asset_styles(); ?>
|
<?php asset_styles(); ?>
|
||||||
<?php asset_scripts('header'); ?>
|
<?php asset_scripts('header'); ?>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body data-nexus-debug-admin="<?= $isDebugAdmin ? '1' : '0' ?>" data-nexus-debug-enabled="<?= $isNexusDebugEnabled ? '1' : '0' ?>">
|
||||||
<main class="main-shell">
|
<main class="main-shell">
|
||||||
<section class="home-hero app-header main-header-box" data-module-name="<?= e((string)($currentModuleName ?? '')) ?>">
|
<section class="home-hero app-header main-header-box" data-module-name="<?= e((string)($currentModuleName ?? '')) ?>">
|
||||||
<a class="brand-mark" href="/" aria-label="Nexus">
|
<a class="brand-mark" href="/" aria-label="Nexus">
|
||||||
|
|||||||
@@ -1378,6 +1378,180 @@ body.has-modal-open {
|
|||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nexus-debug-bug {
|
||||||
|
position: fixed;
|
||||||
|
right: 20px;
|
||||||
|
bottom: 20px;
|
||||||
|
z-index: 1200;
|
||||||
|
width: 58px;
|
||||||
|
height: 58px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 999px;
|
||||||
|
background: color-mix(in srgb, var(--surface) 88%, var(--brand-accent-2) 12%);
|
||||||
|
color: var(--text);
|
||||||
|
box-shadow: 0 18px 40px rgba(1, 22, 32, 0.18);
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nexus-debug-bug svg {
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nexus-debug-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: -4px;
|
||||||
|
right: -2px;
|
||||||
|
min-width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
padding: 0 6px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--brand-accent-2);
|
||||||
|
color: #07131a;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 700;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nexus-debug-popup {
|
||||||
|
position: fixed;
|
||||||
|
right: 20px;
|
||||||
|
bottom: 88px;
|
||||||
|
z-index: 1199;
|
||||||
|
width: min(560px, calc(100vw - 32px));
|
||||||
|
max-height: min(70vh, 760px);
|
||||||
|
display: none;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 24px;
|
||||||
|
background: var(--surface);
|
||||||
|
box-shadow: 0 28px 80px rgba(1, 22, 32, 0.24);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nexus-debug-popup.is-open {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto auto minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nexus-debug-popup__head,
|
||||||
|
.nexus-debug-popup__toolbar {
|
||||||
|
padding: 18px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nexus-debug-popup__head {
|
||||||
|
display: flex;
|
||||||
|
align-items: start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nexus-debug-popup__head h2 {
|
||||||
|
margin: 8px 0 0;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nexus-debug-popup__toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nexus-debug-popup__actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nexus-debug-popup__body {
|
||||||
|
overflow: auto;
|
||||||
|
padding: 16px 20px 20px;
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nexus-debug-empty {
|
||||||
|
padding: 14px 16px;
|
||||||
|
border: 1px dashed var(--line);
|
||||||
|
border-radius: 16px;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nexus-debug-entry {
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 16px;
|
||||||
|
background: color-mix(in srgb, var(--surface) 92%, var(--brand-accent-2) 8%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nexus-debug-entry summary {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nexus-debug-entry summary::-webkit-details-marker {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nexus-debug-entry__meta {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.82rem;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nexus-debug-entry pre {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 14px 14px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||||
|
font-size: 0.84rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
.nexus-debug-bug {
|
||||||
|
right: 14px;
|
||||||
|
bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nexus-debug-popup {
|
||||||
|
right: 12px;
|
||||||
|
left: 12px;
|
||||||
|
bottom: 82px;
|
||||||
|
width: auto;
|
||||||
|
max-height: 72vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nexus-debug-popup__head,
|
||||||
|
.nexus-debug-popup__toolbar,
|
||||||
|
.nexus-debug-popup__body {
|
||||||
|
padding-left: 16px;
|
||||||
|
padding-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nexus-debug-entry summary {
|
||||||
|
align-items: start;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nexus-debug-entry__meta {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.module-box-title {
|
.module-box-title {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
|
|||||||
@@ -188,3 +188,199 @@ window.NexusModal = (() => {
|
|||||||
|
|
||||||
return { create };
|
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();
|
||||||
|
})();
|
||||||
|
|||||||
@@ -121,6 +121,31 @@ if (preg_match('~^api/module-auth/([a-zA-Z0-9_-]+)$~', $uriPath, $moduleAuthMatc
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($uriPath === 'api/debug/entries') {
|
||||||
|
require_admin();
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
echo json_encode([
|
||||||
|
'data' => [
|
||||||
|
'enabled' => nexus_debug_enabled(),
|
||||||
|
'entries' => nexus_debug_entries(),
|
||||||
|
],
|
||||||
|
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($uriPath === 'api/debug/clear' && strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET')) === 'POST') {
|
||||||
|
require_admin();
|
||||||
|
nexus_debug_clear();
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
echo json_encode([
|
||||||
|
'data' => [
|
||||||
|
'cleared' => true,
|
||||||
|
'entries' => [],
|
||||||
|
],
|
||||||
|
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
if (preg_match('~^api/mining-checker(?:/(.*))?$~', $uriPath, $apiMatches)) {
|
if (preg_match('~^api/mining-checker(?:/(.*))?$~', $uriPath, $apiMatches)) {
|
||||||
$moduleMeta = app()->modules()->get('mining-checker') ?? ['auth' => ['required' => false]];
|
$moduleMeta = app()->modules()->get('mining-checker') ?? ['auth' => ['required' => false]];
|
||||||
if (!$auth->canAccessModule($moduleMeta)) {
|
if (!$auth->canAccessModule($moduleMeta)) {
|
||||||
|
|||||||
@@ -276,20 +276,94 @@ function module_design(string $module): array
|
|||||||
return $cache[$module];
|
return $cache[$module];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function nexus_debug_settings_key(): string
|
||||||
|
{
|
||||||
|
return '__nexus_debug__';
|
||||||
|
}
|
||||||
|
|
||||||
|
function nexus_debug_configured(): bool
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$settings = modules()->settings(nexus_debug_settings_key());
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = $settings['enabled'] ?? '0';
|
||||||
|
return $value === true || $value === 1 || $value === '1' || $value === 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
function nexus_debug_enabled(): bool
|
||||||
|
{
|
||||||
|
if (!nexus_debug_configured()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (function_exists('auth_enabled') && auth_enabled()) {
|
||||||
|
return auth_is_admin();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function nexus_debug_entries(): array
|
||||||
|
{
|
||||||
|
if (!nexus_debug_enabled()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
app()->session()->start();
|
||||||
|
$entries = $_SESSION['nexus_debug_entries'] ?? [];
|
||||||
|
return is_array($entries) ? array_values(array_filter($entries, 'is_array')) : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function nexus_debug_push(string $source, array $entry): void
|
||||||
|
{
|
||||||
|
if (!nexus_debug_enabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$source = trim($source);
|
||||||
|
if ($source === '') {
|
||||||
|
$source = 'nexus';
|
||||||
|
}
|
||||||
|
|
||||||
|
app()->session()->start();
|
||||||
|
if (!isset($_SESSION['nexus_debug_entries']) || !is_array($_SESSION['nexus_debug_entries'])) {
|
||||||
|
$_SESSION['nexus_debug_entries'] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$entry['source'] = $entry['source'] ?? $source;
|
||||||
|
$entry['at'] = $entry['at'] ?? date('Y-m-d H:i:s');
|
||||||
|
array_unshift($_SESSION['nexus_debug_entries'], $entry);
|
||||||
|
$_SESSION['nexus_debug_entries'] = array_slice($_SESSION['nexus_debug_entries'], 0, 250);
|
||||||
|
}
|
||||||
|
|
||||||
|
function nexus_debug_clear(?string $source = null): void
|
||||||
|
{
|
||||||
|
app()->session()->start();
|
||||||
|
if ($source === null || trim($source) === '') {
|
||||||
|
unset($_SESSION['nexus_debug_entries']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$entries = $_SESSION['nexus_debug_entries'] ?? [];
|
||||||
|
if (!is_array($entries)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION['nexus_debug_entries'] = array_values(array_filter($entries, static function ($entry) use ($source): bool {
|
||||||
|
return !is_array($entry) || (string) ($entry['source'] ?? '') !== $source;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
function module_debug_enabled(string $module): bool
|
function module_debug_enabled(string $module): bool
|
||||||
{
|
{
|
||||||
if (preg_match('/[^a-zA-Z0-9_\-]/', $module)) {
|
if (preg_match('/[^a-zA-Z0-9_\-]/', $module)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
return nexus_debug_enabled();
|
||||||
$settings = modules()->settings($module);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$value = $settings['debug_enabled'] ?? '0';
|
|
||||||
return $value === true || $value === 1 || $value === '1' || $value === 'true';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function module_debug_entries(string $module): array
|
function module_debug_entries(string $module): array
|
||||||
@@ -301,9 +375,9 @@ function module_debug_entries(string $module): array
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
app()->session()->start();
|
return array_values(array_filter(nexus_debug_entries(), static function ($entry) use ($module): bool {
|
||||||
$entries = $_SESSION['module_debug'][$module] ?? [];
|
return is_array($entry) && (string) ($entry['source'] ?? '') === $module;
|
||||||
return is_array($entries) ? array_values(array_filter($entries, 'is_array')) : [];
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
function module_debug_push(string $module, array $entry): void
|
function module_debug_push(string $module, array $entry): void
|
||||||
@@ -315,17 +389,7 @@ function module_debug_push(string $module, array $entry): void
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
app()->session()->start();
|
nexus_debug_push($module, $entry);
|
||||||
if (!isset($_SESSION['module_debug']) || !is_array($_SESSION['module_debug'])) {
|
|
||||||
$_SESSION['module_debug'] = [];
|
|
||||||
}
|
|
||||||
if (!isset($_SESSION['module_debug'][$module]) || !is_array($_SESSION['module_debug'][$module])) {
|
|
||||||
$_SESSION['module_debug'][$module] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$entry['at'] = $entry['at'] ?? date('Y-m-d H:i:s');
|
|
||||||
array_unshift($_SESSION['module_debug'][$module], $entry);
|
|
||||||
$_SESSION['module_debug'][$module] = array_slice($_SESSION['module_debug'][$module], 0, 25);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function module_debug_clear(string $module): void
|
function module_debug_clear(string $module): void
|
||||||
@@ -334,8 +398,7 @@ function module_debug_clear(string $module): void
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
app()->session()->start();
|
nexus_debug_clear($module);
|
||||||
unset($_SESSION['module_debug'][$module]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function module_shell_header(string $module, array $options = []): string
|
function module_shell_header(string $module, array $options = []): string
|
||||||
@@ -442,53 +505,7 @@ function module_shell_header(string $module, array $options = []): string
|
|||||||
|
|
||||||
function module_shell_footer(): string
|
function module_shell_footer(): string
|
||||||
{
|
{
|
||||||
$html = '';
|
return '</div></div></div>';
|
||||||
$module = current_module_name();
|
|
||||||
if (is_string($module) && $module !== '' && module_debug_enabled($module)) {
|
|
||||||
if ((string) ($_GET['module_debug_clear'] ?? '') === '1') {
|
|
||||||
module_debug_clear($module);
|
|
||||||
}
|
|
||||||
|
|
||||||
$entries = module_debug_entries($module);
|
|
||||||
$currentPath = app()->request()->path();
|
|
||||||
$clearHref = $currentPath . '?module_debug_clear=1';
|
|
||||||
|
|
||||||
$html .= '<section class="module-debug">';
|
|
||||||
$html .= '<details class="module-debug-details"' . ($entries !== [] ? ' open' : '') . '>';
|
|
||||||
$html .= '<summary class="module-debug-summary">';
|
|
||||||
$html .= '<span>Debug</span>';
|
|
||||||
$html .= '<span class="module-debug-meta">' . e((string) count($entries)) . ' Eintraege</span>';
|
|
||||||
$html .= '</summary>';
|
|
||||||
$html .= '<div class="module-debug-body">';
|
|
||||||
$html .= '<div class="module-debug-toolbar">';
|
|
||||||
$html .= '<div class="muted">Standard-Debugbereich des Moduls. Zeigt die letzten Request-/Response-Daten der aktuellen Sitzung.</div>';
|
|
||||||
$html .= '<a class="module-button module-button--ghost module-button--small" href="' . e($clearHref) . '">Debug leeren</a>';
|
|
||||||
$html .= '</div>';
|
|
||||||
|
|
||||||
if ($entries === []) {
|
|
||||||
$html .= '<div class="module-debug-empty">Noch keine Debug-Daten vorhanden.</div>';
|
|
||||||
} else {
|
|
||||||
foreach ($entries as $index => $entry) {
|
|
||||||
$label = trim((string) ($entry['label'] ?? ('Eintrag ' . ($index + 1))));
|
|
||||||
$at = trim((string) ($entry['at'] ?? ''));
|
|
||||||
$payload = json_encode($entry, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
||||||
if (!is_string($payload)) {
|
|
||||||
$payload = '{}';
|
|
||||||
}
|
|
||||||
|
|
||||||
$html .= '<details class="module-debug-entry"' . ($index === 0 ? ' open' : '') . '>';
|
|
||||||
$html .= '<summary><strong>' . e($label) . '</strong>' . ($at !== '' ? '<span class="module-debug-entry-time">' . e($at) . '</span>' : '') . '</summary>';
|
|
||||||
$html .= '<pre class="module-debug-pre">' . e($payload) . '</pre>';
|
|
||||||
$html .= '</details>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$html .= '</div>';
|
|
||||||
$html .= '</details>';
|
|
||||||
$html .= '</section>';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $html . '</div></div></div>';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user