/* * SSH-Terminal-Baustein: xterm.js <-> /ws/ssh/{host_id}. * * F1 (Umsetzungsauftrag_Sonnet5.md Teil F.3.1): vorher ein Dokument-Singleton * (ein Skript pro Seite, feste Element-IDs). Jetzt eine Klasse * (SshTerminalSession) mit genau einer Instanz pro Sitzung: eigenes, * dynamisch gebautes DOM-Fragment (this.element), connect(), dispose(), * setActive(bool) und ein Statusrueckruf (options.onStatusChange) fuer eine * kuenftige Seitenleiste (Teil F.3: /workspace, noch nicht Teil dieser * Stufe). Die eigenstaendige Seite /terminal/{host_id} (templates/ * terminal.html) ist ab jetzt nur noch ein duenner Rahmen, der GENAU EINE * Instanz erzeugt -- keine zweite Implementierung (F.2, letzter Absatz). * * Vollbild: native Browser-Fullscreen-API auf dem Instanz-Wurzelelement * (Konzept 4.3). * Copy & Paste: xterm.js liefert Markieren-zum-Kopieren + Strg+Umschalt+V * bereits nativ. Aufgabe #11 (vom Nutzer nachtraeglich hinzugefuegt, siehe * FORTSETZUNG_Aufgabe11.md) ergaenzt zwei konkret gewuenschte Wege: * `copyOnSelect: true` versucht, eine Markierung sofort in die System- * Zwischenablage zu kopieren (kein Strg+C/Cmd+C mehr noetig -- Strg+C bleibt * ohne Markierung weiterhin SIGINT, xterm.js' Standardverhalten). Das ist * aber eine Automatik OHNE direkte Benutzergeste (sie laeuft aus xterm.js' * eigenem mouseup-Handler heraus) und schlaegt daher in der Praxis manchmal * fehl, ohne dass die App das mitbekommt (Live-Test 2.9.: Markierung+Ziehen * hat sichtbar funktioniert, in der Zwischenablage kam aber nichts an). * Deshalb zusaetzlich ein Rechtsklick-Kontextmenue, das PuTTYs bekannte * Konvention nachbildet: Rechtsklick MIT aktiver Markierung kopiert (direkter * Versuch ueber die Clipboard-API, da ein Rechtsklick selbst eine * "transiente Aktivierung"/Benutzergeste ist -- das ist deutlich * zuverlaessiger als copyOnSelects impliziter Aufruf); schlaegt das fehl * oder ist keine Markierung aktiv, faellt es auf "Einfuegen" zurueck -- * ebenfalls zuerst direkt ueber die Clipboard-API versucht, sonst ein * winziges Kontextmenue mit "Kopieren"- bzw. "Einfuegen"-Eintrag, dessen * Klick selbst wieder eine frische Benutzergeste ist (siehe _wireClipboard() * unten). Keine serverseitige Sonderbehandlung noetig, im Gegensatz zu RDP * (siehe rdp.js). * Markieren mit gedrueckter Umschalttaste: xterm.js reicht Mausklicks an ein * Vollbild-Zielprogramm mit eigener Maussteuerung (tmux, vim, htop, mc, ...) * weiter, sobald dieses per ANSI-Escape-Sequenz Maus-Tracking anfordert -- * dann faengt ein normaler Linksklick+Ziehen KEINE lokale Textmarkierung * mehr ein (Standardverhalten jedes echten Terminals, kein Bug). Umschalt * (Shift)+Ziehen erzwingt die lokale Markierung trotzdem, xterm.js * unterstuetzt das nativ, ohne Zusatzcode. Als `title`-Tooltip auf * `.session-terminal` dokumentiert (siehe _buildElement()), da das sonst * leicht wie ein kaputtes Kopieren aussieht. * Farbschema: per ${themeOptions} Verbinde ...
`; return wrapper; } _cacheRefs() { const el = this.element; this.shellEl = el.querySelector(".session-shell"); this.statusEl = el.querySelector(".status"); this.terminalContainer = el.querySelector(".session-terminal"); this.fullscreenBtn = el.querySelector(".fullscreen-btn"); this.filetransferBtn = el.querySelector(".filetransfer-btn"); this.themeSelect = el.querySelector(".theme-select"); this.exitBtn = el.querySelector(".exit-btn"); this.ftOverlay = el.querySelector(".ft-overlay"); this.ftStatus = el.querySelector(".ft-status"); this.ftList = el.querySelector(".ft-list"); this.ftCloseBtn = el.querySelector(".ft-close-btn"); this.uploadRemotePathInput = el.querySelector(".ft-upload-remote-path"); this.uploadFileInput = el.querySelector(".ft-upload-file-input"); this.uploadFilenameLabel = el.querySelector(".ft-upload-filename"); this.uploadPickBtn = el.querySelector(".ft-upload-pick-btn"); this.uploadSubmitBtn = el.querySelector(".ft-upload-submit-btn"); this.downloadRemotePathInput = el.querySelector(".ft-download-remote-path"); this.downloadSubmitBtn = el.querySelector(".ft-download-submit-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 */ } } } // --- Terminal/WebSocket -------------------------------------------- _setupTerminal() { this._themeName = loadStoredThemeName(); this.term = new Terminal({ cursorBlink: true, fontFamily: "Menlo, Consolas, monospace", fontSize: 14, theme: TERMINAL_THEMES[this._themeName].theme, // Aufgabe #11: eine Markierung wird sofort in die System- // Zwischenablage kopiert -- kein zusaetzliches Strg+C/Cmd+C noetig. // Strg+C OHNE Markierung bleibt unveraendert SIGINT (xterm.js' // Standardverhalten, wird von copyOnSelect nicht beruehrt). copyOnSelect: true, }); this.fitAddon = new FitAddon.FitAddon(); this.term.loadAddon(this.fitAddon); this.term.open(this.terminalContainer); // Bug-Fix (Live-Test, F.6/Aufgabe#11): xterm.js' Standard-DOM-Renderer // faerbt Zellen (inkl. Markierungs-Hintergrund) teils per dynamisch // erzeugtem