aboutsummaryrefslogtreecommitdiff
path: root/modules/ext/debug-panel.js
diff options
context:
space:
mode:
Diffstat (limited to 'modules/ext/debug-panel.js')
-rw-r--r--modules/ext/debug-panel.js167
1 files changed, 167 insertions, 0 deletions
diff --git a/modules/ext/debug-panel.js b/modules/ext/debug-panel.js
new file mode 100644
index 0000000..8c37253
--- /dev/null
+++ b/modules/ext/debug-panel.js
@@ -0,0 +1,167 @@
+class DebugPanel {
+ constructor() {
+ this.commands = {};
+ this.terminal = null;
+ this._logs = [];
+ this._original = {};
+ this._panel = null;
+ this._body = null;
+ this._visible = false;
+ this.MAX_LOGS = 500;
+ }
+ requirements() {
+ return { met: true, missing: [] };
+ }
+ getHelpInfo() {
+ return { 'debug': 'debug — Toggle the debug console panel' };
+ }
+ registerCommands() {
+ this.commands['debug'] = () => this.toggle();
+ }
+ seed() {
+ this._intercept();
+ }
+ init() {}
+ _intercept() {
+ const self = this;
+ // Console
+ for (const m of ['log','warn','error','info']) {
+ this._original[m] = console[m];
+ console[m] = function(...args) {
+ self._add(m, args);
+ self._original[m].apply(console, args);
+ };
+ }
+ // window.onerror
+ this._original.onerror = window.onerror;
+ window.onerror = function(msg, url, line, col, err) {
+ self._add('error', [`${msg} (${url}:${line}:${col})`]);
+ if (self._original.onerror) self._original.onerror(msg, url, line, col, err);
+ };
+ // Unhandled rejections
+ window.addEventListener('unhandledrejection', function(e) {
+ self._add('error', ['Unhandled Promise: ' + (e.reason?.message || e.reason)]);
+ });
+ // Fetch
+ this._original.fetch = window.fetch;
+ window.fetch = function(...args) {
+ const url = typeof args[0] === 'string' ? args[0] : args[0]?.url || '?';
+ const method = (args[1]?.method || 'GET');
+ const start = performance.now();
+ return self._original.fetch.apply(window, args)
+ .then(function(resp) {
+ const ms = (performance.now() - start).toFixed(0);
+ const lvl = resp.status >= 400 ? 'error' : resp.status >= 300 ? 'warn' : 'log';
+ self._add(lvl, ['🌐 ' + method + ' ' + url + ' → ' + resp.status + ' (' + ms + 'ms)']);
+ return resp;
+ })
+ .catch(function(err) {
+ const ms = (performance.now() - start).toFixed(0);
+ self._add('error', ['🌐 ' + method + ' ' + url + ' FAIL (' + ms + 'ms): ' + err.message]);
+ throw err;
+ });
+ };
+ }
+ _add(level, args) {
+ const text = args.map(function(a) {
+ if (typeof a === 'object') try { return JSON.stringify(a, null, 2); } catch(e) { return String(a); }
+ return String(a);
+ }).join(' ');
+ this._logs.push({ level, text, time: new Date().toLocaleTimeString() });
+ if (this._logs.length > this.MAX_LOGS) this._logs.shift();
+ if (this._visible) this._appendEntry(this._logs[this._logs.length - 1]);
+ }
+ toggle() {
+ if (!this._panel) this._build();
+ this._visible = !this._visible;
+ this._panel.style.display = this._visible ? 'flex' : 'none';
+ if (this._visible) {
+ this._body.innerHTML = '';
+ for (const e of this._logs) this._appendEntry(e);
+ this._body.scrollTop = this._body.scrollHeight;
+ }
+ }
+ _build() {
+ const p = document.createElement('div');
+ p.id = 'debug-panel';
+ p.style.cssText = 'position:fixed;bottom:0;left:0;right:0;height:40vh;background:#0d0d12;border-top:2px solid #1e1e2a;z-index:9999;display:none;flex-direction:column;font-family:monospace;font-size:12px;';
+ // Header
+ const h = document.createElement('div');
+ h.style.cssText = 'display:flex;align-items:center;padding:4px 10px;background:#16161e;border-bottom:1px solid #1e1e2a;gap:6px;flex-shrink:0;';
+ h.innerHTML = '<span style="color:#00e5c8;font-weight:bold;font-size:13px;">🐞 Debug Console</span>';
+ // Copy All
+ const ca = document.createElement('button');
+ ca.textContent = '📋 Copy All';
+ ca.onclick = () => this._copyAll();
+ ca.style.cssText = 'background:#1e1e2a;color:#e8e8f0;border:1px solid #2a2a3a;padding:2px 10px;border-radius:6px;cursor:pointer;font-size:11px;font-family:inherit;';
+ // Clear
+ const cl = document.createElement('button');
+ cl.textContent = '🗑 Clear';
+ cl.onclick = () => { this._logs = []; if (this._body) this._body.innerHTML = ''; };
+ cl.style.cssText = ca.style.cssText;
+ // Close
+ const cx = document.createElement('button');
+ cx.textContent = '✕';
+ cx.onclick = () => this.toggle();
+ cx.style.cssText = 'background:none;color:#8888a0;border:none;cursor:pointer;margin-left:auto;font-size:18px;padding:0 4px;';
+ h.appendChild(ca);
+ h.appendChild(cl);
+ h.appendChild(cx);
+ p.appendChild(h);
+ // Body
+ this._body = document.createElement('div');
+ this._body.style.cssText = 'flex:1;overflow-y:auto;padding:4px 10px;background:#111118;';
+ p.appendChild(this._body);
+ document.body.appendChild(p);
+ this._panel = p;
+ }
+ _appendEntry(e) {
+ const colors = { error: '#ff6bcb', warn: '#ffa500', log: '#00e5c8', info: '#7c6bff' };
+ const line = document.createElement('div');
+ line.style.cssText = 'padding:3px 4px;border-bottom:1px solid #1a1a26;display:flex;align-items:flex-start;gap:6px;';
+ // Badge
+ const b = document.createElement('span');
+ b.textContent = e.level.toUpperCase();
+ b.style.cssText = 'color:' + (colors[e.level] || '#888') + ';font-weight:700;font-size:10px;width:40px;flex-shrink:0;';
+ // Timestamp
+ const ts = document.createElement('span');
+ ts.textContent = e.time;
+ ts.style.cssText = 'color:#555;font-size:10px;width:70px;flex-shrink:0;';
+ // Message
+ const m = document.createElement('span');
+ m.textContent = e.text;
+ m.style.cssText = 'color:#c8c8d0;flex:1;word-break:break-all;white-space:pre-wrap;';
+ // Copy
+ const cp = document.createElement('button');
+ cp.textContent = 'Copy';
+ cp.onclick = function() {
+ var ta = document.createElement('textarea');
+ ta.value = e.text;
+ ta.style.cssText = 'position:fixed;left:-999px;top:0;';
+ document.body.appendChild(ta);
+ ta.select();
+ document.execCommand('copy');
+ document.body.removeChild(ta);
+ cp.textContent = '✓';
+ setTimeout(function() { cp.textContent = 'Copy'; }, 1500);
+ };
+ cp.style.cssText = 'background:#1e1e2a;color:#8888a0;border:1px solid #2a2a3a;border-radius:4px;cursor:pointer;font-size:10px;padding:1px 6px;flex-shrink:0;font-family:inherit;';
+ line.appendChild(b);
+ line.appendChild(ts);
+ line.appendChild(m);
+ line.appendChild(cp);
+ this._body.appendChild(line);
+ this._body.scrollTop = this._body.scrollHeight;
+ }
+ _copyAll() {
+ const text = this._logs.map(function(e) { return '[' + e.level.toUpperCase() + '] ' + e.time + ' ' + e.text; }).join('\n');
+ var ta = document.createElement('textarea');
+ ta.value = text;
+ ta.style.cssText = 'position:fixed;left:-999px;top:0;';
+ document.body.appendChild(ta);
+ ta.select();
+ document.execCommand('copy');
+ document.body.removeChild(ta);
+ if (this.terminal) this.terminal.print('📋 Debug log copied (' + this._logs.length + ' entries)', 'success');
+ }
+} \ No newline at end of file