conosle
This commit is contained in:
@@ -12,6 +12,10 @@ $terminalToken = null;
|
|||||||
$terminalHostLabel = null;
|
$terminalHostLabel = null;
|
||||||
|
|
||||||
$settings = modules()->settings('pi_control');
|
$settings = modules()->settings('pi_control');
|
||||||
|
$assets = app()->assets();
|
||||||
|
if ($assets) {
|
||||||
|
$assets->addScript('/assets/js/pi_control_console.js', 'footer', true);
|
||||||
|
}
|
||||||
$ttydUrl = trim((string)($settings['ttyd_url'] ?? '/ttyd'));
|
$ttydUrl = trim((string)($settings['ttyd_url'] ?? '/ttyd'));
|
||||||
$defaultProvider = 'ttyd';
|
$defaultProvider = 'ttyd';
|
||||||
$defaultTimeout = (int)($settings['exec_default_timeout'] ?? (getenv('PI_CONTROL_EXEC_DEFAULT_TIMEOUT') !== false ? (int)getenv('PI_CONTROL_EXEC_DEFAULT_TIMEOUT') : 300));
|
$defaultTimeout = (int)($settings['exec_default_timeout'] ?? (getenv('PI_CONTROL_EXEC_DEFAULT_TIMEOUT') !== false ? (int)getenv('PI_CONTROL_EXEC_DEFAULT_TIMEOUT') : 300));
|
||||||
|
|||||||
@@ -125,313 +125,3 @@
|
|||||||
startRefresh();
|
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) {
|
|
||||||
if (consoleNotice) {
|
|
||||||
consoleNotice.textContent = 'Aktive Konsole nicht steuerbar – Befehl wird in neuer Konsole ausgeführt.';
|
|
||||||
consoleNotice.style.display = 'block';
|
|
||||||
}
|
|
||||||
submitOpen();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (consoleError) consoleError.style.display = 'none';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|||||||
308
public/assets/js/pi_control_console.js
Normal file
308
public/assets/js/pi_control_console.js
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
(() => {
|
||||||
|
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';
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
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) {
|
||||||
|
if (consoleNotice) {
|
||||||
|
consoleNotice.textContent = 'Aktive Konsole nicht steuerbar – Befehl wird in neuer Konsole ausgeführt.';
|
||||||
|
consoleNotice.style.display = 'block';
|
||||||
|
}
|
||||||
|
submitOpen();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (consoleError) consoleError.style.display = 'none';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
Reference in New Issue
Block a user