Files
ssh-jumphost/static/js/rdp.js
2026-08-21 08:45:36 +02:00

111 lines
4.8 KiB
JavaScript

/*
* RDP-Session-Client: guacamole-common-js <-> /ws/rdp/{host_id}.
*
* Vollbild: native Browser-Fullscreen-API (Konzept 4.3).
* Copy & Paste: bidirektionale Synchronisation ueber Guacamole.Client
* onclipboard-Event (Ziel -> Browser) und den `paste`-Browser-Event
* (Browser -> Ziel, sendet eine "clipboard"-Instruktion). Wird serverseitig
* zusaetzlich blockiert, wenn fuer den Host clipboard_enabled=false ist
* (siehe app/rdp_proxy/ws_tunnel.py) -- das UI blendet den Hinweis dann ein.
*/
(() => {
"use strict";
const shell = document.getElementById("session-shell");
const hostId = shell.dataset.hostId;
const statusEl = document.getElementById("status");
const displayDiv = document.getElementById("rdp-display");
const proto = window.location.protocol === "https:" ? "wss:" : "ws:";
const width = Math.round(window.innerWidth);
const height = Math.round(window.innerHeight - 40);
const dpi = Math.round(window.devicePixelRatio * 96) || 96;
// WICHTIG: die Verbindungsparameter gehoeren NICHT in die Tunnel-URL.
// Guacamole.WebSocketTunnel.connect(data) baut die Socket-URL selbst als
// `tunnelURL + "?" + data` zusammen. Standen die Parameter schon in der
// URL, entstand daraus `...?width=1280&height=800&dpi=96?undefined` -- der
// letzte Query-Parameter war damit kein gueltiger Integer mehr, FastAPI
// wies den WebSocket noch vor dem Routenhandler ab (HTTP 422) und im
// Verbindungslog des Servers tauchte kein einziger Eintrag auf.
const tunnelUrl = `${proto}//${window.location.host}/ws/rdp/${hostId}`;
const connectParams = `width=${width}&height=${height}&dpi=${dpi}`;
const tunnel = new Guacamole.WebSocketTunnel(tunnelUrl);
const client = new Guacamole.Client(tunnel);
displayDiv.appendChild(client.getDisplay().getElement());
client.onstatechange = (state) => {
// 0=idle,1=connecting,2=waiting,3=connected,4=disconnecting,5=disconnected
const labels = ["Idle", "Verbinde ...", "Warte auf Server ...", "Verbunden", "Trenne ...", "Getrennt"];
statusEl.textContent = labels[state] || `Status ${state}`;
};
const showError = (err) => {
// Der Server gibt den Abbruchgrund als WebSocket-Close-Reason mit
// (siehe _reject() in app/rdp_proxy/ws_tunnel.py); guacamole-common-js
// reicht ihn als Guacamole.Status.message hierher durch.
statusEl.textContent = "Fehler: " + ((err && err.message) || "unbekannt");
statusEl.classList.add("error");
// Troubleshooting: Statuscode + Rohobjekt zusaetzlich in die
// Browser-Konsole, fuer den Fall, dass die Klartextmeldung allein nicht
// reicht (z.B. guacd/FreeRDP-interne Codes).
console.error("RDP-Sitzung beendet/Fehler:", err);
};
client.onerror = showError;
tunnel.onerror = showError;
client.onclipboard = (stream, mimetype) => {
if (!mimetype.startsWith("text/")) return;
const reader = new Guacamole.StringReader(stream);
let data = "";
reader.ontext = (text) => { data += text; };
reader.onend = () => {
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(data).catch(() => {});
}
};
};
client.connect(connectParams);
window.addEventListener("beforeunload", () => client.disconnect());
const mouse = new Guacamole.Mouse(client.getDisplay().getElement());
mouse.onmousedown = mouse.onmouseup = mouse.onmousemove = (mouseState) => {
client.sendMouseState(mouseState);
};
const keyboard = new Guacamole.Keyboard(document);
keyboard.onkeydown = (keysym) => client.sendKeyEvent(1, keysym);
keyboard.onkeyup = (keysym) => client.sendKeyEvent(0, keysym);
document.addEventListener("paste", (ev) => {
const text = (ev.clipboardData || window.clipboardData).getData("text");
if (!text) return;
const stream = client.createClipboardStream("text/plain");
const writer = new Guacamole.StringWriter(stream);
writer.sendText(text);
writer.sendEnd();
});
document.getElementById("fullscreen-btn").addEventListener("click", () => {
if (!document.fullscreenElement) {
shell.requestFullscreen().catch(() => {});
} else {
document.exitFullscreen();
}
});
// Strg+Alt+Entf: der Browser faengt diese Kombination selbst ab (Windows
// reserviert sie systemweit), sie kommt also nie als normales Tastatur-
// Event beim RDP-Ziel an. Abhilfe wie im offiziellen Guacamole-Client:
// die drei Tasten einzeln als synthetische Key-Events senden (X11-
// Keysyms: Ctrl=0xFFE3, Alt=0xFFE9, Delete=0xFFFF), erst alle drei
// herunter- dann in umgekehrter Reihenfolge wieder hochdruecken.
document.getElementById("ctrlaltdel-btn").addEventListener("click", () => {
const keys = [0xffe3, 0xffe9, 0xffff];
for (const keysym of keys) client.sendKeyEvent(1, keysym);
for (const keysym of keys.slice().reverse()) client.sendKeyEvent(0, keysym);
});
})();