(() => { 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\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 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); } }); } } } catch (e) { // ignore } 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]'); 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 activeIframe = getActiveIframe(); if (!activeIframe) { if (consoleNotice) { consoleNotice.textContent = 'Keine aktive Konsole – es wird eine neue Konsole geöffnet.'; consoleNotice.style.display = 'block'; } submitOpen(); return; } 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'); fetch(url.toString(), { method: 'POST', body: formData, cache: 'no-store' }) .then((res) => res.json()) .then((data) => { if (!data.ok) { if (consoleNotice) { consoleNotice.textContent = data.error || 'Befehl konnte nicht ausgeführt werden.'; consoleNotice.style.display = 'block'; } return; } if (data.url) { activeIframe.src = data.url; } if (consoleError) consoleError.style.display = 'none'; }) .catch(() => { if (consoleNotice) { consoleNotice.textContent = 'Befehl konnte nicht ausgeführt werden.'; consoleNotice.style.display = 'block'; } }); }); } } })();