connectiopn fix round 2 3

This commit is contained in:
2026-08-21 16:02:23 +02:00
parent 8c9af78672
commit 763be3e7bf
12 changed files with 708 additions and 11 deletions

View File

@ -1364,6 +1364,11 @@
} else if (s.is_active) {
actions.appendChild(el("span", { className: "hint", textContent: "anderer Prozess" }));
}
if (s.watchable) {
actions.appendChild(
actionButton("Live ansehen", "btn-secondary", () => watchSession(s))
);
}
if (s.has_recording) {
actions.appendChild(
actionButton("Wiedergabe", "btn-secondary", () => showSessionRecording(s))
@ -1388,6 +1393,75 @@
);
}
// --- Live-Mitschau einer aktiven SSH-Sitzung (nur lesend) --------------
//
// Verbindet sich mit GET /ws/sessions/{id}/watch (app/ssh_proxy/
// terminal_ws.py) und schreibt jedes "output"-Frame in ein eigenes,
// schreibgeschuetztes xterm.js-Terminal -- exakt dieselben Frames, die
// auch der Sitzungsinhaber selbst bekommt (active_sessions.broadcast()).
// Es gibt hier bewusst KEINEN Eingabepfad: ein Beobachter kann die
// fremde Sitzung nicht beeinflussen.
let activeWatch = null;
function closeWatchOverlay() {
document.getElementById("watch-overlay").classList.add("hidden");
if (activeWatch) {
activeWatch.destroy();
activeWatch = null;
}
document.getElementById("watch-stage").replaceChildren();
}
document.getElementById("watch-close-btn").addEventListener("click", closeWatchOverlay);
function watchSession(session) {
closeWatchOverlay();
const overlay = document.getElementById("watch-overlay");
const title = document.getElementById("watch-title");
const statusEl = document.getElementById("watch-status");
const stage = document.getElementById("watch-stage");
overlay.classList.remove("hidden");
title.textContent = `Live-Ansicht -- Sitzung #${session.id} (${session.username} @ ${session.hostname})`;
statusEl.textContent = "verbinde ...";
const container = el("div", { className: "rec-terminal" });
stage.appendChild(container);
const term = new Terminal({
cursorBlink: false, disableStdin: true, convertEol: false,
fontFamily: "Menlo, Consolas, monospace", fontSize: 13,
theme: { background: "#000000" },
});
const fitAddon = new FitAddon.FitAddon();
term.loadAddon(fitAddon);
term.open(container);
fitAddon.fit();
const proto = window.location.protocol === "https:" ? "wss:" : "ws:";
const ws = new WebSocket(`${proto}//${window.location.host}/ws/sessions/${session.id}/watch`);
ws.addEventListener("open", () => { statusEl.textContent = "verbunden (nur lesend)"; });
ws.addEventListener("close", (ev) => {
statusEl.textContent = ev.reason
? `getrennt: ${ev.reason}`
: "getrennt (Sitzung vermutlich beendet)";
});
ws.addEventListener("error", () => { statusEl.textContent = "Verbindungsfehler"; });
ws.addEventListener("message", (ev) => {
const msg = JSON.parse(ev.data);
if (msg.type === "output") {
term.write(recB64decode(msg.data));
}
});
activeWatch = {
destroy() {
try { ws.close(); } catch (_) { /* bereits getrennt */ }
term.dispose();
},
};
}
// --- Wiedergabe (SSH: Terminal-Replay ueber xterm.js; RDP: volle
// grafische Wiedergabe ueber Guacamole.SessionRecording) --------------
//
@ -1400,8 +1474,28 @@
// die "output"-Eintraege deshalb exakt das Format, das die Bibliothek
// erwartet -- kein eigener Player noetig.
// Bugfix ("SSH-Wiedergabe funktioniert nicht"): NICHT als JS-String ueber
// decodeURIComponent(escape(atob(...))) dekodieren. Jeder aufgezeichnete
// "output"-Eintrag entspricht genau einem process.stdout.read()-Chunk auf
// dem Server (app/ssh_proxy/terminal_ws.py) -- ein Mehrbyte-UTF-8-Zeichen
// (Umlaute/Sonderzeichen in Banner, Prompt, htop, ls --color etc.) kann
// dabei GENAU an der Chunk-Grenze zerschnitten sein. decodeURIComponent()
// wirft dann "URI malformed" fuer diesen Eintrag -- und weil term.write()
// in scheduleNext()/seekTo() innerhalb eines try-losen setTimeout-Callbacks
// steht, bricht genau dort die gesamte Wiedergabe-Kette endgueltig ab
// (idx wird nie mehr erhoeht, scheduleNext() nie mehr aufgerufen): die
// Wiedergabe blieb an der ersten betroffenen Stelle fuer immer haengen,
// oft schon beim Login-Banner. term.write() nimmt daher direkt die rohen
// Bytes als Uint8Array entgegen -- xterm.js ist ein echter
// Terminal-Emulator mit eigenem, chunk-uebergreifend korrektem
// UTF-8-Parser, genau fuer diesen Fall gebaut (siehe auch
// static/js/terminal.js::b64decodeBytes fuer dieselbe Klasse Bug in der
// Live-Ansicht).
function recB64decode(b64) {
return decodeURIComponent(escape(atob(b64)));
const bin = atob(b64);
const bytes = new Uint8Array(bin.length);
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
return bytes;
}
function formatMs(ms) {