Files
nexus/public/assets/js/app.js
2026-03-06 21:57:33 +01:00

435 lines
14 KiB
JavaScript
Executable File
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.
(() => {
const layout = document.querySelector('.layout-body');
if (!layout) return;
const enabled = layout.dataset.sidebarEnabled === '1';
const collapsible = layout.dataset.sidebarCollapsible === '1';
const defaultState = layout.dataset.sidebarDefault || 'collapsed';
const toggles = document.querySelectorAll('[data-sidebar-toggle]');
if (!enabled || !collapsible) {
toggles.forEach((t) => t.remove());
return;
}
const saved = localStorage.getItem('sidebar-state');
const initial = saved || defaultState;
if (initial === 'open') {
layout.classList.add('sidebar-open');
} else {
layout.classList.remove('sidebar-open');
}
toggles.forEach((toggle) => {
toggle.addEventListener('click', () => {
layout.classList.toggle('sidebar-open');
localStorage.setItem('sidebar-state', layout.classList.contains('sidebar-open') ? 'open' : 'collapsed');
});
});
})();
(() => {
const openBtn = document.querySelector('[data-debug-open]');
const modal = document.getElementById('debug-modal');
if (!openBtn || !modal) return;
const listEl = document.getElementById('debug-log-list');
const contentEl = document.getElementById('debug-log-content');
const closeEls = modal.querySelectorAll('[data-debug-close]');
const open = () => {
modal.classList.add('is-open');
modal.setAttribute('aria-hidden', 'false');
loadList();
startRefresh();
};
const close = () => {
modal.classList.remove('is-open');
modal.setAttribute('aria-hidden', 'true');
if (refreshTimer) {
clearInterval(refreshTimer);
refreshTimer = null;
}
};
let activeFile = null;
let refreshTimer = null;
const loadList = async () => {
try {
const res = await fetch('/debug?list=1', { cache: 'no-store' });
if (!res.ok) {
listEl.innerHTML = `<li class=\"muted\">Fehler: ${res.status}</li>`;
return;
}
const files = await res.json();
listEl.innerHTML = '';
files.forEach((f) => {
const a = document.createElement('a');
a.href = '#';
a.textContent = f;
a.addEventListener('click', (e) => {
e.preventDefault();
loadFile(f);
});
const li = document.createElement('li');
li.appendChild(a);
listEl.appendChild(li);
});
if (files.includes('oidc_login.log')) {
loadFile('oidc_login.log');
}
} catch (e) {
listEl.innerHTML = '<li class=\"muted\">Fehler beim Laden der Logs.</li>';
}
};
const loadFile = async (name) => {
activeFile = name;
try {
const res = await fetch(`/debug?raw=1&file=${encodeURIComponent(name)}&tail=200`, { cache: 'no-store' });
if (!res.ok) {
contentEl.textContent = `Fehler: ${res.status}`;
return;
}
const text = await res.text();
contentEl.textContent = formatLog(text);
} catch (e) {
contentEl.textContent = 'Fehler beim Laden der Datei.';
}
};
const formatLog = (text) => {
const lines = text.split(/\\r?\\n/).filter(Boolean);
const pretty = lines.map((line) => {
try {
const obj = JSON.parse(line);
return JSON.stringify(obj, null, 2);
} catch (e) {
return line;
}
});
return pretty.join('\\n\\n');
};
const startRefresh = () => {
if (refreshTimer) clearInterval(refreshTimer);
refreshTimer = setInterval(() => {
if (activeFile) loadFile(activeFile);
}, 3000);
};
openBtn.addEventListener('click', open);
closeEls.forEach((el) => el.addEventListener('click', close));
if (modal.classList.contains('is-open')) {
startRefresh();
}
})();
(() => {
const tabBar = document.querySelector('[data-console-tab-bar]');
const tabPanels = document.querySelector('[data-console-tab-panels]');
if (!tabBar || !tabPanels) return;
let tabCount = 0;
const idleMs = 5 * 60 * 1000;
const idleTimers = new Map();
const storageKey = 'pi_control_console_tabs';
const saveTabs = () => {
const tabs = [];
tabBar.querySelectorAll('.console-tab').forEach((btn) => {
const id = btn.dataset.tabId;
const panel = tabPanels.querySelector(`.console-panel[data-tab-id="${id}"]`);
const iframe = panel ? panel.querySelector('iframe') : null;
if (iframe && iframe.src) {
tabs.push({
label: btn.firstChild ? btn.firstChild.textContent : btn.textContent,
url: iframe.src,
openedAt: Date.now(),
});
}
});
localStorage.setItem(storageKey, JSON.stringify(tabs));
};
const activateTab = (id) => {
tabBar.querySelectorAll('.console-tab').forEach((btn) => {
btn.classList.toggle('is-active', btn.dataset.tabId === id);
});
tabPanels.querySelectorAll('.console-panel').forEach((panel) => {
panel.classList.toggle('is-active', panel.dataset.tabId === id);
});
};
const getActiveIframe = () => {
const activePanel = tabPanels.querySelector('.console-panel.is-active');
if (!activePanel) return null;
return activePanel.querySelector('iframe');
};
const trySendToIframe = (iframe, command) => {
if (!iframe) return false;
try {
const win = iframe.contentWindow;
if (!win) return false;
const term = win.term || win.xterm || win.terminal;
if (term && typeof term.write === 'function') {
term.write(command + '\r');
return true;
}
} catch (e) {
return false;
}
return false;
};
const openTab = (label, url, persist = true) => {
const id = `tab-${++tabCount}`;
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'console-tab';
btn.textContent = label || 'Konsole';
btn.dataset.tabId = id;
btn.addEventListener('click', () => activateTab(id));
const closeBtn = document.createElement('span');
closeBtn.className = 'console-tab-close';
closeBtn.textContent = '×';
closeBtn.addEventListener('click', (e) => {
e.stopPropagation();
const panel = tabPanels.querySelector(`.console-panel[data-tab-id="${id}"]`);
if (panel) panel.remove();
btn.remove();
idleTimers.delete(id);
saveTabs();
const next = tabBar.querySelector('.console-tab');
if (next) activateTab(next.dataset.tabId);
});
btn.appendChild(closeBtn);
const panel = document.createElement('div');
panel.className = 'console-panel';
panel.dataset.tabId = id;
panel.innerHTML = `<iframe src="${url}" title="${label || 'Konsole'}"></iframe>`;
tabBar.appendChild(btn);
tabPanels.appendChild(panel);
activateTab(id);
if (persist) saveTabs();
const iframe = panel.querySelector('iframe');
const markActive = () => {
idleTimers.set(id, Date.now());
};
idleTimers.set(id, Date.now());
iframe.addEventListener('load', () => {
try {
const doc = iframe.contentWindow.document;
['keydown', 'mousedown', 'wheel', 'touchstart'].forEach((evt) => {
doc.addEventListener(evt, markActive, { passive: true });
});
const observer = new MutationObserver(markActive);
observer.observe(doc.body, { childList: true, subtree: true, characterData: true });
} catch (e) {
// cross-origin or blocked; rely on timer only
}
});
const idleCheck = setInterval(() => {
const last = idleTimers.get(id) || 0;
if (Date.now() - last > idleMs) {
const panelEl = tabPanels.querySelector(`.console-panel[data-tab-id="${id}"]`);
const btnEl = tabBar.querySelector(`.console-tab[data-tab-id="${id}"]`);
if (panelEl) panelEl.remove();
if (btnEl) btnEl.remove();
idleTimers.delete(id);
saveTabs();
clearInterval(idleCheck);
const next = tabBar.querySelector('.console-tab');
if (next) activateTab(next.dataset.tabId);
}
}, 10000);
};
document.querySelectorAll('.console-launch').forEach((el) => {
const url = el.dataset.url;
const host = el.dataset.host || 'Konsole';
if (url) {
openTab(host, url);
}
el.remove();
});
const showRestoreNotice = (text) => {
const notice = document.querySelector('[data-console-notice]');
if (!notice) return;
notice.textContent = text;
notice.style.display = 'block';
};
// Restore tabs from previous session (best-effort)
try {
const raw = localStorage.getItem(storageKey);
if (raw) {
const tabs = JSON.parse(raw);
if (Array.isArray(tabs)) {
const now = Date.now();
tabs.forEach((t) => {
if (t && t.url) {
if (t.openedAt && now - t.openedAt > (10 * 60 * 1000)) {
showRestoreNotice('Token abgelaufen, bitte Konsole neu öffnen.');
return;
}
openTab(t.label || 'Konsole', t.url, false);
}
});
}
}
} catch (e) {
// ignore
}
const queueBody = document.querySelector('[data-queue-body]');
const countdownEl = document.querySelector('[data-queue-countdown]');
const refreshBtn = document.querySelector('[data-queue-refresh]');
if (!queueBody || !countdownEl) return;
let remaining = 10;
let timer = null;
const fetchQueue = async () => {
const url = new URL(window.location.href);
url.searchParams.set('queue_json', '1');
try {
const res = await fetch(url.toString(), { cache: 'no-store' });
if (!res.ok) return;
const data = await res.json();
if (data && data.html) {
queueBody.innerHTML = data.html;
}
remaining = data && data.next ? data.next : 10;
} catch (e) {
// ignore
}
};
const tick = () => {
remaining -= 1;
if (remaining <= 0) {
fetchQueue();
remaining = 10;
}
countdownEl.textContent = String(remaining);
};
timer = setInterval(tick, 1000);
if (refreshBtn) {
refreshBtn.addEventListener('click', () => {
fetchQueue();
remaining = 10;
countdownEl.textContent = String(remaining);
});
}
const consoleForm = document.querySelector('[data-console-form]');
const consoleError = document.querySelector('[data-console-error]');
const consoleNotice = document.querySelector('[data-console-notice]');
const tokenEl = document.querySelector('[data-console-token]');
if (consoleForm) {
consoleForm.addEventListener('submit', (e) => e.preventDefault());
const presetSelect = consoleForm.querySelector('select[name="terminal_preset_id"]');
const commandTextarea = consoleForm.querySelector('textarea[name="terminal_command_text"]');
if (presetSelect && commandTextarea) {
presetSelect.addEventListener('change', () => {
const opt = presetSelect.selectedOptions[0];
const cmd = opt && opt.dataset && opt.dataset.command ? opt.dataset.command : '';
if (cmd) {
commandTextarea.value = cmd;
}
});
}
const submitOpen = async () => {
const formData = new FormData(consoleForm);
if (presetSelect) formData.set('terminal_preset_id', '');
const url = new URL(window.location.href);
url.searchParams.set('open_console_json', '1');
const res = await fetch(url.toString(), { method: 'POST', body: formData, cache: 'no-store' });
const data = await res.json();
if (!data.ok) {
if (consoleError) {
consoleError.textContent = data.error || 'Fehler beim Öffnen der Konsole.';
consoleError.style.display = 'block';
}
return;
}
if (consoleError) consoleError.style.display = 'none';
if (consoleNotice) consoleNotice.style.display = 'none';
if (tokenEl) tokenEl.textContent = data.token || '';
if (data.url) openTab(data.host || 'Konsole', data.url);
};
const submitRun = async () => {
const formData = new FormData(consoleForm);
if (presetSelect) formData.set('terminal_preset_id', '');
const url = new URL(window.location.href);
url.searchParams.set('run_command_json', '1');
const res = await fetch(url.toString(), { method: 'POST', body: formData, cache: 'no-store' });
const data = await res.json();
if (!data.ok) {
if (consoleError) {
consoleError.textContent = data.error || 'Fehler beim Ausführen.';
consoleError.style.display = 'block';
}
return;
}
if (consoleError) consoleError.style.display = 'none';
if (consoleNotice) {
consoleNotice.textContent = data.notice || '';
consoleNotice.style.display = 'block';
}
fetchQueue();
remaining = 10;
countdownEl.textContent = String(remaining);
};
const openBtn = consoleForm.querySelector('[data-open-console]');
const runBtn = consoleForm.querySelector('[data-run-command]');
const sendBtn = consoleForm.querySelector('[data-send-active]');
if (openBtn) {
openBtn.addEventListener('click', (e) => {
e.preventDefault();
submitOpen();
});
}
if (runBtn) {
runBtn.addEventListener('click', (e) => {
e.preventDefault();
submitRun();
});
}
if (sendBtn) {
sendBtn.addEventListener('click', (e) => {
e.preventDefault();
if (!commandTextarea || !commandTextarea.value.trim()) {
if (consoleError) {
consoleError.textContent = 'Bitte zuerst einen Befehl eingeben.';
consoleError.style.display = 'block';
}
return;
}
const iframe = getActiveIframe();
const ok = trySendToIframe(iframe, commandTextarea.value.trim());
if (!ok && consoleError) {
consoleError.textContent = 'Konnte den Befehl nicht an die aktive Konsole senden.';
consoleError.style.display = 'block';
} else if (consoleError) {
consoleError.style.display = 'none';
}
});
}
}
})();