connectiopn fix round 2 3
This commit is contained in:
@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user