(() => { // Disable any beforeunload prompts on this page window.onbeforeunload = null; window.addEventListener( 'beforeunload', (e) => { e.stopImmediatePropagation(); }, { capture: true } ); const tabBar = document.querySelector('[data-console-tab-bar]'); const tabPanels = document.querySelector('[data-console-tab-panels]'); if (!tabBar || !tabPanels) return; const consoleFab = document.querySelector('[data-console-fab]'); const consoleModal = document.querySelector('[data-console-modal]'); const consoleClose = document.querySelector('[data-console-close]'); 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)); if (consoleFab) { consoleFab.classList.toggle('is-visible', tabs.length > 0); } }; 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\n'); 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 = ``; 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 disableBeforeUnload = (win) => { if (!win) return; try { win.onbeforeunload = null; win.addEventListener( 'beforeunload', (e) => { e.stopImmediatePropagation(); }, { capture: true } ); if (win.document) { win.document.onbeforeunload = null; win.document.addEventListener( 'beforeunload', (e) => { e.stopImmediatePropagation(); }, { capture: true } ); } } catch (err) { // ignore } }; disableBeforeUnload(iframe.contentWindow); setInterval(() => disableBeforeUnload(iframe.contentWindow), 2000); 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'; }; 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); } }); if (consoleFab && tabs.length > 0) { consoleFab.classList.add('is-visible'); } } } } catch (e) { // ignore } const openModal = () => { if (!consoleModal) return; consoleModal.classList.add('is-open'); consoleModal.setAttribute('aria-hidden', 'false'); }; const closeModal = () => { if (!consoleModal) return; consoleModal.classList.remove('is-open'); consoleModal.setAttribute('aria-hidden', 'true'); }; if (consoleFab) { consoleFab.addEventListener('click', openModal); } if (consoleClose) { consoleClose.addEventListener('click', closeModal); } if (consoleModal) { consoleModal.addEventListener('click', (e) => { if (e.target === consoleModal) closeModal(); }); } const queueBody = document.querySelector('[data-queue-body]'); const countdownEl = document.querySelector('[data-queue-countdown]'); const refreshBtn = document.querySelector('[data-queue-refresh]'); const queueBtn = document.querySelector('[data-queue-button]'); const queueCount = document.querySelector('[data-queue-count]'); const queueModal = document.querySelector('[data-queue-modal]'); const queueClose = document.querySelector('[data-queue-close]'); if (!queueBody || !countdownEl) return; let remaining = 10; 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; } if (queueCount && typeof data.count === 'number') { queueCount.textContent = String(data.count); queueCount.style.display = data.count > 0 ? 'inline-flex' : 'none'; } remaining = data && data.next ? data.next : 10; } catch (e) { // ignore } }; const tick = () => { remaining -= 1; if (remaining <= 0) { fetchQueue(); remaining = 10; } countdownEl.textContent = String(remaining); }; setInterval(tick, 1000); fetchQueue(); if (refreshBtn) { refreshBtn.addEventListener('click', () => { fetchQueue(); remaining = 10; countdownEl.textContent = String(remaining); }); } if (queueBtn && queueModal) { queueBtn.addEventListener('click', () => { queueModal.classList.add('is-open'); queueModal.setAttribute('aria-hidden', 'false'); fetchQueue(); }); } if (queueClose && queueModal) { queueClose.addEventListener('click', () => { queueModal.classList.remove('is-open'); queueModal.setAttribute('aria-hidden', 'true'); }); } if (queueModal) { queueModal.addEventListener('click', (e) => { if (e.target === queueModal) { queueModal.classList.remove('is-open'); queueModal.setAttribute('aria-hidden', 'true'); } }); } queueBody.addEventListener('click', async (e) => { const btn = e.target.closest('[data-queue-action]'); if (!btn) return; const runId = btn.getAttribute('data-run-id'); const action = btn.getAttribute('data-queue-action'); if (!runId || !action) return; const url = new URL(window.location.href); url.searchParams.set('queue_action_json', '1'); const formData = new FormData(); formData.set('run_id', runId); formData.set('action', action); try { const res = await fetch(url.toString(), { method: 'POST', body: formData, cache: 'no-store' }); const data = await res.json(); if (!data.ok) { if (consoleNotice) { consoleNotice.textContent = data.error || 'Aktion fehlgeschlagen.'; consoleNotice.style.display = 'block'; } } } catch (err) { if (consoleNotice) { consoleNotice.textContent = 'Aktion fehlgeschlagen.'; consoleNotice.style.display = 'block'; } } 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]'); if (openBtn) { openBtn.addEventListener('click', (e) => { e.preventDefault(); submitOpen(); }); } if (runBtn) { runBtn.addEventListener('click', (e) => { e.preventDefault(); submitRun(); }); } // send-active removed } })();