umbau 1.0
This commit is contained in:
456
static/js/rdp.js
456
static/js/rdp.js
@ -1,5 +1,29 @@
|
||||
/*
|
||||
* RDP-Session-Client: guacamole-common-js <-> /ws/rdp/{host_id}.
|
||||
* RDP-Sitzungs-Baustein: guacamole-common-js <-> /ws/rdp/{host_id}.
|
||||
*
|
||||
* F1 (Umsetzungsauftrag_Sonnet5.md Teil F.3.1): vorher ein Dokument-Singleton
|
||||
* (ein Skript pro Seite, feste Element-IDs). Jetzt eine Klasse
|
||||
* (RdpSession) mit genau einer Instanz pro Sitzung: eigenes, dynamisch
|
||||
* gebautes DOM-Fragment (this.element), connect(), dispose(), setActive(bool)
|
||||
* und ein Statusrueckruf (options.onStatusChange). Die eigenstaendige Seite
|
||||
* /rdp/{host_id} (templates/rdp.html) ist ab jetzt nur noch ein duenner
|
||||
* Rahmen, der GENAU EINE Instanz erzeugt (F.2, letzter Absatz).
|
||||
*
|
||||
* ACHTUNG Tastatur/Zwischenablage (F.3.2, noch NICHT geloest -- folgt in
|
||||
* Stufe F4): guacamole-common-js' Guacamole.Keyboard haengt sich immer an
|
||||
* ein von aussen uebergebenes Element -- bisher `document`, und das bleibt
|
||||
* in dieser Stufe zunaechst so, weil die Bibliothek keine saubere
|
||||
* Abmeldung/Umschaltung bietet. Bei mehreren GLEICHZEITIGEN RdpSession-
|
||||
* Instanzen wuerden ohne Gegenmassnahme alle Tastatureingaben an ALLE
|
||||
* Instanzen gehen. Deshalb: jede Instanz filtert selbst ueber setActive() --
|
||||
* Tastatur-/Paste-Ereignisse werden nur weitergeleitet, wenn genau diese
|
||||
* Instanz aktiv ist (this._active). Das ist die Grundlage fuer F.3.2, aber
|
||||
* noch NICHT die vollstaendige Loesung (fehlende Modifiertasten-Ruecksetzung
|
||||
* beim Umschalten, siehe F.3.2 Satz 2) -- die kommt mit der eigentlichen
|
||||
* Mehrfach-Sitzungs-Umschaltung in Stufe F4, wenn es ueberhaupt zwei
|
||||
* gleichzeitig existierende Instanzen geben kann. Bei genau einer Instanz
|
||||
* (heutiger Stand: Stufe F1/F2/F3, dort niemals zwei RdpSession gleichzeitig
|
||||
* auf derselben Seite) ist das Verhalten exakt wie vorher.
|
||||
*
|
||||
* Vollbild: native Browser-Fullscreen-API (Konzept 4.3).
|
||||
* Copy & Paste: bidirektionale Synchronisation ueber Guacamole.Client
|
||||
@ -11,111 +35,337 @@
|
||||
(() => {
|
||||
"use strict";
|
||||
|
||||
const shell = document.getElementById("session-shell");
|
||||
const hostId = shell.dataset.hostId;
|
||||
const statusEl = document.getElementById("status");
|
||||
const displayDiv = document.getElementById("rdp-display");
|
||||
let _nextInstanceId = 1;
|
||||
|
||||
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;
|
||||
class RdpSession {
|
||||
/**
|
||||
* @param {number|string} hostId
|
||||
* @param {object} [options]
|
||||
* @param {(text: string, isError: boolean) => void} [options.onStatusChange]
|
||||
* @param {() => void} [options.onExitRequested]
|
||||
* Wird aufgerufen, NACHDEM der interne Exit-Knopf dispose()
|
||||
* bereits ausgeloest hat.
|
||||
*/
|
||||
constructor(hostId, options = {}) {
|
||||
this.hostId = hostId;
|
||||
this.options = options;
|
||||
this._uid = _nextInstanceId++;
|
||||
this._active = false;
|
||||
this._disposed = false;
|
||||
this._intentionalDisconnect = false;
|
||||
this._resizeDebounceHandle = null;
|
||||
|
||||
// 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();
|
||||
this.element = this._buildElement();
|
||||
this._cacheRefs();
|
||||
this._wireToolbar();
|
||||
}
|
||||
});
|
||||
|
||||
// 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);
|
||||
});
|
||||
// --- Aufbau -------------------------------------------------------
|
||||
|
||||
document.getElementById("exit-btn").addEventListener("click", () => {
|
||||
statusEl.textContent = "Sitzung wird beendet ...";
|
||||
statusEl.classList.remove("error");
|
||||
try { client.disconnect(); } catch (_) { /* bereits getrennt */ }
|
||||
window.close();
|
||||
// Siehe terminal.js fuer die ausfuehrliche Begruendung: window.close()
|
||||
// schliesst den Tab nur unter bestimmten Voraussetzungen lautlos --
|
||||
// deshalb zusaetzlich immer ein Fallback zum Dashboard.
|
||||
window.setTimeout(() => { window.location.href = "/dashboard"; }, 300);
|
||||
});
|
||||
_buildElement() {
|
||||
const wrapper = document.createElement("div");
|
||||
wrapper.className = "session-instance";
|
||||
wrapper.innerHTML = `
|
||||
<div class="session-shell">
|
||||
<div class="session-toolbar">
|
||||
<button type="button" class="fullscreen-btn">Vollbild</button>
|
||||
<button type="button" class="ctrlaltdel-btn">Strg+Alt+Entf</button>
|
||||
<span class="spacer"></span>
|
||||
<span class="status">Verbinde ...</span>
|
||||
<button type="button" class="exit-btn btn-danger btn-small" title="Sitzung beenden">Exit</button>
|
||||
</div>
|
||||
<div class="session-rdp-display">
|
||||
<!-- B.1 (Umsetzungsauftrag Teil B): Overlay-Panel fuer
|
||||
Verbindungsfehler, siehe rdp-error-overlay unten. -->
|
||||
<div class="rdp-error-overlay" hidden>
|
||||
<div class="rdp-error-panel">
|
||||
<h2 class="rdp-error-title">Verbindungsfehler</h2>
|
||||
<p class="rdp-error-message"></p>
|
||||
<dl class="rdp-error-meta">
|
||||
<dt>Sitzungs-ID</dt><dd class="rdp-error-session">-</dd>
|
||||
<dt>Zeitpunkt</dt><dd class="rdp-error-time">-</dd>
|
||||
<dt>Ziel-Host</dt><dd class="rdp-error-host">-</dd>
|
||||
</dl>
|
||||
<button type="button" class="rdp-error-retry-btn">Wiederholen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
_cacheRefs() {
|
||||
const el = this.element;
|
||||
this.shellEl = el.querySelector(".session-shell");
|
||||
this.statusEl = el.querySelector(".status");
|
||||
this.displayDiv = el.querySelector(".session-rdp-display");
|
||||
this.fullscreenBtn = el.querySelector(".fullscreen-btn");
|
||||
this.ctrlAltDelBtn = el.querySelector(".ctrlaltdel-btn");
|
||||
this.exitBtn = el.querySelector(".exit-btn");
|
||||
this.errorOverlay = el.querySelector(".rdp-error-overlay");
|
||||
this.errorTitleEl = el.querySelector(".rdp-error-title");
|
||||
this.errorMessageEl = el.querySelector(".rdp-error-message");
|
||||
this.errorSessionEl = el.querySelector(".rdp-error-session");
|
||||
this.errorTimeEl = el.querySelector(".rdp-error-time");
|
||||
this.errorHostEl = el.querySelector(".rdp-error-host");
|
||||
this.errorRetryBtn = el.querySelector(".rdp-error-retry-btn");
|
||||
}
|
||||
|
||||
_setStatus(text, isError) {
|
||||
this.statusEl.textContent = text;
|
||||
this.statusEl.classList.toggle("error", !!isError);
|
||||
if (this.options.onStatusChange) {
|
||||
try {
|
||||
this.options.onStatusChange(text, !!isError);
|
||||
} catch (_) { /* Aufrufer-Fehler duerfen die Sitzung nicht stoeren */ }
|
||||
}
|
||||
}
|
||||
|
||||
// --- Verbindungsaufbau ------------------------------------------------
|
||||
|
||||
connect() {
|
||||
if (this.client) return; // bereits verbunden/am Verbinden
|
||||
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).
|
||||
const tunnelUrl = `${proto}//${window.location.host}/ws/rdp/${this.hostId}`;
|
||||
const connectParams = `width=${width}&height=${height}&dpi=${dpi}`;
|
||||
this.tunnel = new Guacamole.WebSocketTunnel(tunnelUrl);
|
||||
this.client = new Guacamole.Client(this.tunnel);
|
||||
|
||||
this.displayDiv.appendChild(this.client.getDisplay().getElement());
|
||||
|
||||
this.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"];
|
||||
this._setStatus(labels[state] || `Status ${state}`, false);
|
||||
};
|
||||
|
||||
const showError = (err) => {
|
||||
// Ein vom Benutzer ueber den Exit-Knopf ausgeloestes client.disconnect()
|
||||
// schliesst den Tunnel sauber (Guacamole.Status.Code.SUCCESS) und loest
|
||||
// damit laut guacamole-common-js KEIN onerror aus -- dieser Schutz greift
|
||||
// trotzdem zusaetzlich, falls sich das je aendert.
|
||||
if (this._intentionalDisconnect) return;
|
||||
|
||||
const message = (err && err.message) || "Unbekannter Fehler (keine Meldung vom Server empfangen)";
|
||||
this._setStatus("Fehler: " + message, true);
|
||||
|
||||
this.errorTitleEl.textContent = "Verbindungsfehler";
|
||||
this.errorMessageEl.textContent = message;
|
||||
this.errorSessionEl.textContent = this.tunnel.uuid || "unbekannt";
|
||||
this.errorTimeEl.textContent = new Date().toLocaleString("de-AT");
|
||||
this.errorHostEl.textContent = `Host-ID ${this.hostId}`;
|
||||
this.errorOverlay.hidden = false;
|
||||
|
||||
console.error("RDP-Sitzung beendet/Fehler:", err);
|
||||
};
|
||||
this.client.onerror = showError;
|
||||
this.tunnel.onerror = showError;
|
||||
|
||||
this.errorRetryBtn.addEventListener("click", () => {
|
||||
// Einfachste zuverlaessige Wiederholung: die Sitzung (Tunnel, Client,
|
||||
// Maus-/Tastatur-Listener) neu aufzubauen wuerde denselben Zustand wie
|
||||
// ein frischer Seitenaufruf erfordern -- ein Reload erreicht das direkt
|
||||
// und erzeugt serverseitig eine neue Korrelations-ID/Sitzung. Betrifft
|
||||
// bei /workspace (Stufe F3/F4) nur diese eine Kachel -- ein voller
|
||||
// Seiten-Reload dort wuerde ALLE offenen Sitzungen der Arbeitsflaeche
|
||||
// beenden (siehe F.3.7: kein Wiederanschluss nach Reload).
|
||||
window.location.reload();
|
||||
});
|
||||
|
||||
this.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(() => {});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
this.client.connect(connectParams);
|
||||
|
||||
this._onBeforeUnload = () => { try { this.client.disconnect(); } catch (_) { /* ignorieren */ } };
|
||||
window.addEventListener("beforeunload", this._onBeforeUnload);
|
||||
|
||||
// D4 (Umsetzungsauftrag Teil A): der Verbindungsaufbau setzt bereits
|
||||
// "resize-method: display-update" -- das RDP-Ziel unterstuetzt damit
|
||||
// dynamische Aufloesungsaenderungen waehrend der laufenden Sitzung.
|
||||
// Debounce: "resize" kann waehrend eines Fenster-Drags sehr haeufig
|
||||
// feuern -- ohne Verzoegerung wuerde jede Zwischengroesse eine eigene
|
||||
// "size"-Instruktion ueber den Tunnel schicken.
|
||||
// F4 (Umsetzungsauftrag_Sonnet5.md Teil F.3.3): mehrere Instanzen
|
||||
// koennen jetzt gleichzeitig existieren (Arbeitsflaeche /workspace) --
|
||||
// eine ausgeblendete Instanz hat Groesse 0 (display:none). Ein Refit
|
||||
// waehrend ausgeblendet waere sinnlos und wuerde bei Sichtbarwerden
|
||||
// ohnehin durch setActive(true) -> _sendCurrentSize() nachgeholt --
|
||||
// daher nur reagieren, wenn diese Instanz gerade sichtbar ist.
|
||||
this._onWindowResize = () => { if (this._active) this._scheduleResize(); };
|
||||
window.addEventListener("resize", this._onWindowResize);
|
||||
document.addEventListener("fullscreenchange", this._onWindowResize);
|
||||
|
||||
this.mouse = new Guacamole.Mouse(this.client.getDisplay().getElement());
|
||||
this.mouse.onmousedown = this.mouse.onmouseup = this.mouse.onmousemove = (mouseState) => {
|
||||
this.client.sendMouseState(mouseState);
|
||||
};
|
||||
|
||||
// Siehe Moduldoc oben: dokumentweit angeheftet (Bibliotheksgrenze),
|
||||
// Weiterleitung aber ueber this._active gefiltert.
|
||||
this.keyboard = new Guacamole.Keyboard(document);
|
||||
this.keyboard.onkeydown = (keysym) => { if (this._active) this.client.sendKeyEvent(1, keysym); };
|
||||
this.keyboard.onkeyup = (keysym) => { if (this._active) this.client.sendKeyEvent(0, keysym); };
|
||||
|
||||
this._onPaste = (ev) => {
|
||||
if (!this._active) return;
|
||||
const text = (ev.clipboardData || window.clipboardData).getData("text");
|
||||
if (!text) return;
|
||||
const stream = this.client.createClipboardStream("text/plain");
|
||||
const writer = new Guacamole.StringWriter(stream);
|
||||
writer.sendText(text);
|
||||
writer.sendEnd();
|
||||
};
|
||||
document.addEventListener("paste", this._onPaste);
|
||||
}
|
||||
|
||||
_sendCurrentSize() {
|
||||
const newWidth = Math.round(this.displayDiv.clientWidth || window.innerWidth);
|
||||
const newHeight = Math.round(this.displayDiv.clientHeight || (window.innerHeight - 40));
|
||||
if (newWidth > 0 && newHeight > 0) {
|
||||
this.client.sendSize(newWidth, newHeight);
|
||||
}
|
||||
}
|
||||
|
||||
_scheduleResize() {
|
||||
if (this._resizeDebounceHandle !== null) {
|
||||
window.clearTimeout(this._resizeDebounceHandle);
|
||||
}
|
||||
this._resizeDebounceHandle = window.setTimeout(() => {
|
||||
this._resizeDebounceHandle = null;
|
||||
this._sendCurrentSize();
|
||||
}, 250);
|
||||
}
|
||||
|
||||
/** Wird von einer kuenftigen Seitenleiste (Teil F.3/F.4) beim Umschalten
|
||||
* aufgerufen. Steuert, ob diese Instanz Tastatur-/Paste-Ereignisse
|
||||
* entgegennimmt (siehe Moduldoc) -- bei genau einer Instanz (heutiger
|
||||
* Stand) ohne sichtbaren Effekt ausser dem anfaenglichen Aktivieren. */
|
||||
setActive(active) {
|
||||
const wasActive = this._active;
|
||||
this._active = active;
|
||||
// F4 (F.3.2, Satz 2): beim Umschalten muessen gedrueckte Modifiertasten
|
||||
// zurueckgesetzt werden -- sonst bleibt z.B. Strg in der verlassenen
|
||||
// Sitzung haengen (dort kommt ja kein keyup mehr an, sobald diese
|
||||
// Instanz keine Ereignisse mehr weiterleitet). Keyboard.reset() ist
|
||||
// Teil der guacamole-common-js-API (dieselbe Methode, die der
|
||||
// offizielle Client bei Fokusverlust des Fensters aufruft) -- per
|
||||
// Feature-Check abgesichert, falls eine kuenftige Bibliotheksversion
|
||||
// sie umbenennt.
|
||||
if (!active && wasActive && this.keyboard && typeof this.keyboard.reset === "function") {
|
||||
this.keyboard.reset();
|
||||
}
|
||||
if (active && this.client) {
|
||||
this._sendCurrentSize();
|
||||
if (this.keyboard && typeof this.keyboard.reset === "function") {
|
||||
this.keyboard.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Toolbar --------------------------------------------------------
|
||||
|
||||
_wireToolbar() {
|
||||
this.fullscreenBtn.addEventListener("click", () => {
|
||||
if (!document.fullscreenElement) {
|
||||
this.shellEl.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.
|
||||
this.ctrlAltDelBtn.addEventListener("click", () => {
|
||||
if (!this.client) return;
|
||||
const keys = [0xffe3, 0xffe9, 0xffff];
|
||||
for (const keysym of keys) this.client.sendKeyEvent(1, keysym);
|
||||
for (const keysym of keys.slice().reverse()) this.client.sendKeyEvent(0, keysym);
|
||||
});
|
||||
|
||||
this.exitBtn.addEventListener("click", () => {
|
||||
this._intentionalDisconnect = true;
|
||||
this._setStatus("Sitzung wird beendet ...", false);
|
||||
this.dispose();
|
||||
if (this.options.onExitRequested) this.options.onExitRequested();
|
||||
});
|
||||
}
|
||||
|
||||
// --- Abbau ------------------------------------------------------------
|
||||
|
||||
/** Idempotent. Siehe terminal.js::SshTerminalSession.dispose() fuer die
|
||||
* gleiche Begruendung. Einschraenkung: guacamole-common-js bietet keine
|
||||
* saubere Abmeldung fuer Guacamole.Keyboard(document) -- der zugrunde-
|
||||
* liegende DOM-Listener bleibt technisch bestehen, sendet aber nach
|
||||
* dispose() nichts mehr (this._active bleibt false). Das ist bei genau
|
||||
* einer Instanz (heutiger Stand) folgenlos; bei mehreren gleichzeitigen
|
||||
* Instanzen (Stufe F4) muss dieser Punkt beim Kachel-Schliessen erneut
|
||||
* betrachtet werden. */
|
||||
dispose() {
|
||||
if (this._disposed) return;
|
||||
this._disposed = true;
|
||||
try {
|
||||
if (this.client) this.client.disconnect();
|
||||
} catch (_) { /* bereits getrennt */ }
|
||||
if (this._onWindowResize) {
|
||||
window.removeEventListener("resize", this._onWindowResize);
|
||||
document.removeEventListener("fullscreenchange", this._onWindowResize);
|
||||
}
|
||||
if (this._onBeforeUnload) window.removeEventListener("beforeunload", this._onBeforeUnload);
|
||||
if (this._onPaste) document.removeEventListener("paste", this._onPaste);
|
||||
if (this._resizeDebounceHandle !== null) {
|
||||
window.clearTimeout(this._resizeDebounceHandle);
|
||||
this._resizeDebounceHandle = null;
|
||||
}
|
||||
if (this.element.parentNode) this.element.parentNode.removeChild(this.element);
|
||||
}
|
||||
}
|
||||
|
||||
window.RdpSession = RdpSession;
|
||||
|
||||
// --- Bootstrap fuer die eigenstaendige Seite /rdp/{host_id} --------------
|
||||
//
|
||||
// F1: templates/rdp.html liefert nur noch einen leeren Container mit
|
||||
// data-host-id -- diese Seite bleibt als duenner Rahmen um denselben
|
||||
// Baustein bestehen (F.2), keine zweite Implementierung.
|
||||
const container = document.getElementById("session-container");
|
||||
if (container) {
|
||||
const hostId = container.dataset.hostId;
|
||||
const session = new RdpSession(hostId, {
|
||||
onExitRequested: () => {
|
||||
window.close();
|
||||
// Siehe terminal.js fuer die ausfuehrliche Begruendung: window.close()
|
||||
// schliesst den Tab nur unter bestimmten Voraussetzungen lautlos --
|
||||
// deshalb zusaetzlich immer ein Fallback zum Dashboard.
|
||||
window.setTimeout(() => { window.location.href = "/dashboard"; }, 300);
|
||||
},
|
||||
});
|
||||
container.appendChild(session.element);
|
||||
session.connect();
|
||||
session.setActive(true);
|
||||
}
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user