diff --git a/modules/mining-checker/assets/js/app.js b/modules/mining-checker/assets/js/app.js
index 06a61e9..365ab77 100644
--- a/modules/mining-checker/assets/js/app.js
+++ b/modules/mining-checker/assets/js/app.js
@@ -26,20 +26,7 @@
return [];
}
})();
- const initialDebugMode = (() => {
- 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;
- }
- })();
+ const initialDebugMode = document.body && document.body.dataset.nexusDebugEnabled === '1';
function getCookie(name) {
const pattern = `; ${document.cookie}`;
const parts = pattern.split(`; ${name}=`);
@@ -52,20 +39,12 @@
function setCookie(name, value, maxAgeSeconds) {
document.cookie = `${name}=${encodeURIComponent(value)}; path=/; max-age=${maxAgeSeconds}; samesite=lax`;
}
- const initialDebugView = (() => {
- try {
- const value = window.localStorage.getItem('mining-checker-debug-view');
- return value === 'text' ? 'text' : 'structured';
- } catch (error) {
- return 'structured';
- }
- })();
- const debugBus = window.__miningCheckerDebugBus || {
+ const debugBus = window.__nexusDebugBus || {
enabled: initialDebugMode,
listener: null,
sequence: 0,
};
- window.__miningCheckerDebugBus = debugBus;
+ window.__nexusDebugBus = debugBus;
function emitDebug(entry) {
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) {
if (!Array.isArray(trace) || !trace.length) {
emitDebug({
@@ -585,10 +559,6 @@
const [saving, setSaving] = useState(false);
const [error, setError] = 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 [initForm, setInitForm] = useState({ drop_existing: false });
const [sqlImportFile, setSqlImportFile] = useState(null);
@@ -706,43 +676,12 @@
});
useEffect(() => {
- debugBus.enabled = debugEnabled;
- 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);
-
+ debugBus.enabled = initialDebugMode;
emitDebug({
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 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() {
setReportCurrencyOverride('');
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', {
className: 'mc-grid-bg',
}, [
@@ -1813,78 +1716,6 @@
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,
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,
]),
]);
diff --git a/modules/mining-checker/src/Api/Router.php b/modules/mining-checker/src/Api/Router.php
index 6988913..5e7ee28 100644
--- a/modules/mining-checker/src/Api/Router.php
+++ b/modules/mining-checker/src/Api/Router.php
@@ -44,14 +44,7 @@ final class Router
$this->config = ModuleConfig::load($this->moduleBasePath);
$requestUri = (string) ($_SERVER['REQUEST_URI'] ?? '');
$requestPath = (string) (parse_url($requestUri, PHP_URL_PATH) ?: '');
- $debugConfig = $this->config->debug();
- $debugEnabled = filter_var(
- $_GET['debug']
- ?? $_SERVER['HTTP_X_MINING_DEBUG']
- ?? $_COOKIE['mining_checker_debug']
- ?? ($debugConfig['enabled'] ?? false),
- FILTER_VALIDATE_BOOL
- );
+ $debugEnabled = function_exists('nexus_debug_enabled') ? nexus_debug_enabled() : false;
$latestDebugFilePath = rtrim($this->config->debugDir(), '/') . '/latest-server.json';
$isLatestDebugRequest = str_ends_with($requestPath, '/api/mining-checker/v1/debug/latest')
|| $requestPath === 'api/mining-checker/v1/debug/latest'
diff --git a/partials/landingpages/modules/setup.php b/partials/landingpages/modules/setup.php
index 90df052..42bc341 100644
--- a/partials/landingpages/modules/setup.php
+++ b/partials/landingpages/modules/setup.php
@@ -5,6 +5,8 @@ $error = null;
$notice = null;
$testGroup = null;
$dbTestMessages = [];
+$nexusDebugSettings = modules()->settings(nexus_debug_settings_key());
+$nexusDebugEnabled = !empty($nexusDebugSettings['enabled']);
require_admin();
@@ -15,21 +17,6 @@ if (!$module) {
}
$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 = [];
$fieldMeta = [];
foreach ($fields as $field) {
@@ -416,6 +403,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$isSchedulerAutosave = isset($_POST['scheduler_autosave']) && (string) $_POST['scheduler_autosave'] === '1';
$isSchedulerTest = isset($_POST['scheduler_test']) && (string) $_POST['scheduler_test'] === '1';
$payload = [];
+ $nexusDebugEnabled = isset($_POST['nexus_debug_enabled']);
if ($isSchedulerAutosave || $isSchedulerTest) {
if ($cronTaskDefinitions !== []) {
@@ -576,13 +564,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
} else {
modules()->saveSettings($moduleName, $current);
+ modules()->saveSettings(nexus_debug_settings_key(), ['enabled' => $nexusDebugEnabled]);
if ($isFxRatesSetup && modules()->hasFunction($moduleName, 'save_runtime_settings')) {
module_fn($moduleName, 'save_runtime_settings', $payload);
$current = modules()->settings($moduleName);
}
$refreshSchedulerState();
- if (empty($payload['debug_enabled'])) {
- module_debug_clear($moduleName);
+ if (!$nexusDebugEnabled) {
+ nexus_debug_clear();
}
$notice = 'Setup gespeichert.';
$module = modules()->get($moduleName) ?: $module;
@@ -616,6 +605,22 @@ $activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups)
+
+
+
+
Nexus
+
Debug
+
Aktiviert das projektweite Debug-Popup. Sichtbar und nutzbar nur fuer Benutzer aus der Admin-Gruppe `appadmin`.
+
+
+
+
+
+
Letzter Sync: = e((string) $current['currency_catalog_synced_at']) ?>
-
diff --git a/partials/structure/layout_end.php b/partials/structure/layout_end.php
index 6f5b07c..0119fb0 100755
--- a/partials/structure/layout_end.php
+++ b/partials/structure/layout_end.php
@@ -1,4 +1,17 @@
+
+ nexus_debug_enabled(),
+ 'entries' => nexus_debug_entries(),
+ ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
+ if (!is_string($nexusDebugPayload)) {
+ $nexusDebugPayload = '{"enabled":false,"entries":[]}';
+ }
+ ?>
+
+
+
+
diff --git a/public/assets/css/app.css b/public/assets/css/app.css
index a8e98dd..47690d9 100644
--- a/public/assets/css/app.css
+++ b/public/assets/css/app.css
@@ -1378,6 +1378,180 @@ body.has-modal-open {
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 {
margin: 0;
font-size: 1.1rem;
diff --git a/public/assets/js/app.js b/public/assets/js/app.js
index adb21c5..c6aa17a 100755
--- a/public/assets/js/app.js
+++ b/public/assets/js/app.js
@@ -188,3 +188,199 @@ window.NexusModal = (() => {
return { create };
})();
+
+(() => {
+ const root = document.getElementById('nexus-debug-root');
+ const dataNode = document.getElementById('nexus-debug-data');
+ if (!root || !dataNode) {
+ return;
+ }
+
+ let payload = { enabled: false, entries: [] };
+ try {
+ const parsed = JSON.parse(dataNode.textContent || '{}');
+ if (parsed && typeof parsed === 'object') {
+ payload = parsed;
+ }
+ } catch (error) {
+ payload = { enabled: false, entries: [] };
+ }
+
+ const enabled = !!payload.enabled && document.body.dataset.nexusDebugEnabled === '1';
+ const isAdmin = document.body.dataset.nexusDebugAdmin === '1';
+ const entries = Array.isArray(payload.entries) ? payload.entries.slice(0, 250) : [];
+ let sequence = entries.length;
+
+ const debugBus = window.__nexusDebugBus || { enabled: false, listener: null };
+ debugBus.enabled = enabled;
+ window.__nexusDebugBus = debugBus;
+
+ if (!isAdmin || !enabled) {
+ return;
+ }
+
+ const bugSvg = '';
+
+ root.innerHTML = `
+
+
+ `;
+
+ 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 = 'Noch keine Debug-Einträge vorhanden.
';
+ 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 `
+
+
+ ${escapeHtml(title)}
+ ${escapeHtml(entry.source)} · ${escapeHtml(entry.at)}
+
+ ${escapeHtml(payloadText)}
+
+ `;
+ }).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();
+})();
diff --git a/public/index.php b/public/index.php
index a25a930..19804ab 100755
--- a/public/index.php
+++ b/public/index.php
@@ -121,6 +121,31 @@ if (preg_match('~^api/module-auth/([a-zA-Z0-9_-]+)$~', $uriPath, $moduleAuthMatc
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)) {
$moduleMeta = app()->modules()->get('mining-checker') ?? ['auth' => ['required' => false]];
if (!$auth->canAccessModule($moduleMeta)) {
diff --git a/src/App/functions.php b/src/App/functions.php
index 0a9dddd..4e2da4c 100644
--- a/src/App/functions.php
+++ b/src/App/functions.php
@@ -276,20 +276,94 @@ function module_design(string $module): array
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
{
if (preg_match('/[^a-zA-Z0-9_\-]/', $module)) {
return false;
}
- try {
- $settings = modules()->settings($module);
- } catch (\Throwable $e) {
- return false;
- }
-
- $value = $settings['debug_enabled'] ?? '0';
- return $value === true || $value === 1 || $value === '1' || $value === 'true';
+ return nexus_debug_enabled();
}
function module_debug_entries(string $module): array
@@ -301,9 +375,9 @@ function module_debug_entries(string $module): array
return [];
}
- app()->session()->start();
- $entries = $_SESSION['module_debug'][$module] ?? [];
- return is_array($entries) ? array_values(array_filter($entries, 'is_array')) : [];
+ return array_values(array_filter(nexus_debug_entries(), static function ($entry) use ($module): bool {
+ return is_array($entry) && (string) ($entry['source'] ?? '') === $module;
+ }));
}
function module_debug_push(string $module, array $entry): void
@@ -315,17 +389,7 @@ function module_debug_push(string $module, array $entry): void
return;
}
- app()->session()->start();
- 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);
+ nexus_debug_push($module, $entry);
}
function module_debug_clear(string $module): void
@@ -334,8 +398,7 @@ function module_debug_clear(string $module): void
return;
}
- app()->session()->start();
- unset($_SESSION['module_debug'][$module]);
+ nexus_debug_clear($module);
}
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
{
- $html = '';
- $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 .= '';
- $html .= '';
- $html .= '';
- $html .= 'Debug';
- $html .= '' . e((string) count($entries)) . ' Eintraege';
- $html .= '
';
- $html .= '';
- $html .= '
';
-
- if ($entries === []) {
- $html .= '
Noch keine Debug-Daten vorhanden.
';
- } 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 .= '
';
- $html .= '' . e($label) . '' . ($at !== '' ? '' . e($at) . '' : '') . '
';
- $html .= '' . e($payload) . '
';
- $html .= ' ';
- }
- }
-
- $html .= '
';
- $html .= ' ';
- $html .= '';
- }
-
- return $html . '';
+ return '';
}
/**