/* * SSH-Terminal-Client: xterm.js <-> /ws/ssh/{host_id}. * * Vollbild: native Browser-Fullscreen-API auf dem Session-Container (Konzept 4.3). * Copy & Paste: xterm.js liefert dies bei SSH bereits nativ ueber die * System-Zwischenablage (Markieren-zum-Kopieren / Strg+Umschalt+V) -- keine * serverseitige Sonderbehandlung noetig, im Gegensatz zu RDP (siehe rdp.js). */ (() => { "use strict"; const hostId = window.JUMPHOST_HOST_ID; const statusEl = document.getElementById("status"); const shell = document.getElementById("session-shell"); const term = new Terminal({ cursorBlink: true, fontFamily: "Menlo, Consolas, monospace", fontSize: 14, theme: { background: "#000000" }, }); const fitAddon = new FitAddon.FitAddon(); term.loadAddon(fitAddon); term.open(document.getElementById("terminal")); fitAddon.fit(); function b64encode(str) { return btoa(unescape(encodeURIComponent(str))); } function b64decode(b64) { return decodeURIComponent(escape(atob(b64))); } const proto = window.location.protocol === "https:" ? "wss:" : "ws:"; const ws = new WebSocket(`${proto}//${window.location.host}/ws/ssh/${hostId}`); ws.addEventListener("open", () => { statusEl.textContent = "Verbunden"; sendResize(); }); ws.addEventListener("close", () => { statusEl.textContent = "Verbindung beendet"; }); ws.addEventListener("error", () => { statusEl.textContent = "Verbindungsfehler"; }); ws.addEventListener("message", (ev) => { const msg = JSON.parse(ev.data); if (msg.type === "output") { term.write(b64decode(msg.data)); } else if (msg.type === "error") { statusEl.textContent = "Fehler: " + msg.message; } }); term.onData((data) => { if (ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: "input", data: b64encode(data) })); } }); function sendResize() { fitAddon.fit(); if (ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: "resize", cols: term.cols, rows: term.rows })); } } window.addEventListener("resize", sendResize); document.getElementById("fullscreen-btn").addEventListener("click", () => { if (!document.fullscreenElement) { shell.requestFullscreen().catch(() => {}); } else { document.exitFullscreen(); } }); document.addEventListener("fullscreenchange", sendResize); const fileInput = document.getElementById("file-input"); document.getElementById("upload-btn").addEventListener("click", () => fileInput.click()); fileInput.addEventListener("change", async () => { const file = fileInput.files[0]; if (!file) return; const remotePath = prompt("Zielpfad auf dem Server:", `/tmp/${file.name}`); if (!remotePath) return; const formData = new FormData(); formData.append("file", file); statusEl.textContent = `Lade ${file.name} hoch ...`; try { const res = await fetch( `/ssh/${hostId}/files/upload?remote_path=${encodeURIComponent(remotePath)}`, { method: "POST", credentials: "same-origin", body: formData } ); const data = await res.json(); if (!res.ok) throw new Error(data.detail || "Upload fehlgeschlagen"); statusEl.textContent = `Upload ok (AV: ${data.av_scan_result})`; } catch (err) { statusEl.textContent = "Upload-Fehler: " + err.message; } fileInput.value = ""; }); })();