From 82b66708387920ae626eb7d0be19011888f7611f Mon Sep 17 00:00:00 2001 From: Stefan Friese Date: Wed, 12 Feb 2025 16:22:59 +0000 Subject: [PATCH] changed interactive mode to xterm.js --- main.go | 3 +- static/start-interactive.js | 182 +++++++++++------------------------- templates/index.html | 5 + 3 files changed, 62 insertions(+), 128 deletions(-) diff --git a/main.go b/main.go index 0680fbe..1223b34 100644 --- a/main.go +++ b/main.go @@ -515,7 +515,8 @@ func terminalHandler (w http.ResponseWriter, r *http.Request) { return } defer func() { _ = ptmx.Close() }() - ptmx.Write([]byte("stty raw -echo\n")) + // ptmx.Write([]byte("stty raw -echo\n")) + // ptmx.Write([]byte("stty -echo\n")) // ptmx.Write([]byte("export SHELL=bash 1>&2 2>/dev/null; export TERM=xterm-256color 1>&2 2>/dev/null")) go func() { diff --git a/static/start-interactive.js b/static/start-interactive.js index 024de9b..d314e65 100644 --- a/static/start-interactive.js +++ b/static/start-interactive.js @@ -1,16 +1,12 @@ document.addEventListener("DOMContentLoaded", function () { const input = document.getElementById("command-input"); - const terminal = document.getElementById("terminal"); + // We assume your normal terminal UI is in the element with id "terminal". + const normalTerminal = document.getElementById("terminal"); let interactiveWS = null; let interactiveMode = false; - let lastKeySentTime = 0; - const throttleInterval = 50; // milliseconds - - // Holds the user’s input on the current line. - let currentLineBuffer = ""; - let currentLineElem = null; const ansi_up = new AnsiUp; + // Listen for the "start-interactive" command in normal mode. input.addEventListener("keydown", function (event) { if (!interactiveMode && event.key === "Enter") { const command = input.value.trim(); @@ -20,150 +16,82 @@ document.addEventListener("DOMContentLoaded", function () { event.preventDefault(); return; } - // Otherwise, normal HTTP submission… + // Otherwise, your normal HTTP submission… } }); function startInteractiveSession() { - interactiveWS = new WebSocket("ws://" + location.host + "/terminal"); - interactiveMode = true; - terminal.insertAdjacentHTML('beforeend', "\n--- Interactive session started ---\n"); - input.style.display = "none"; // Hide normal input. - currentLineBuffer = ""; - createCurrentLineElem(); + // Hide the normal terminal and input. + normalTerminal.style.display = "none"; + input.style.display = "none"; + // Create a new container for xterm.js. + const xtermContainer = document.createElement("div"); + xtermContainer.id = "xterm-container"; + xtermContainer.style.width = "100%"; + xtermContainer.style.height = "100vh"; + // Optionally set additional styling (e.g. background color). + document.body.appendChild(xtermContainer); + + // Initialize xterm.js Terminal. + const term = new Terminal({ + cursorBlink: true, + scrollback: 1000, + theme: { + background: "#222", + foreground: "#eee" + } + }); + const fitAddon = new FitAddon.FitAddon(); + term.loadAddon(fitAddon); + term.open(xtermContainer); + fitAddon.fit(); + term.focus(); + + // Establish the WebSocket connection. + interactiveWS = new WebSocket("ws://" + location.host + "/terminal"); interactiveWS.binaryType = "arraybuffer"; interactiveWS.onmessage = function (event) { - // When the shell returns output (for example, after Enter), process it. - let text = new TextDecoder("utf-8").decode(event.data); - text = removeOSCTitle(text); - processPTYOutput(text); + const text = new TextDecoder("utf-8").decode(event.data); + term.write(text); }; interactiveWS.onclose = function () { interactiveMode = false; - terminal.insertAdjacentHTML('beforeend', "\n--- Interactive session ended ---\n"); + term.write("\r\n--- Interactive session ended ---\r\n"); + // Remove the xterm container. + if (xtermContainer.parentNode) { + xtermContainer.parentNode.removeChild(xtermContainer); + } + // Restore the normal terminal UI. + normalTerminal.style.display = "block"; input.style.display = "block"; input.focus(); - document.removeEventListener("keydown", handleInteractiveKey, true); }; interactiveWS.onerror = function (err) { - terminal.insertAdjacentHTML('beforeend', "\n--- Error in interactive session ---\n"); + term.write("\r\n--- Error in interactive session ---\r\n"); console.error("Interactive WS error:", err); interactiveMode = false; + if (xtermContainer.parentNode) { + xtermContainer.parentNode.removeChild(xtermContainer); + } + normalTerminal.style.display = "block"; input.style.display = "block"; - document.removeEventListener("keydown", handleInteractiveKey, true); }; - // Listen for all key events while in interactive mode. - document.addEventListener("keydown", handleInteractiveKey, true); - } - - // Process output received from the PTY. - // We expect output to include completed lines (with newline) as command results. - function processPTYOutput(text) { - // Split output into lines. - const parts = text.split("\n"); - for (let i = 0; i < parts.length; i++) { - const processedPart = processBackspaces(parts[i]); - if (i < parts.length - 1) { - // Completed line. - currentLineBuffer += processedPart; - const finishedLine = document.createElement("div"); - finishedLine.innerHTML = ansi_up.ansi_to_html(currentLineBuffer); - terminal.appendChild(finishedLine); - currentLineBuffer = ""; - createCurrentLineElem(); + // When the user types in xterm, send the data to the server. + term.onData(function (data) { + // If the user presses Ctrl+D (ASCII 4), exit the session. + if (data === "\x04") { + term.write("\r\n--- Exiting interactive session ---\r\n"); + interactiveWS.close(); } else { - // Incomplete line. - currentLineBuffer += processedPart; + interactiveWS.send(data); } - } - updateCurrentLine(); - } + }); - // Update the current line element with the current buffer and a blinking cursor. - function updateCurrentLine() { - if (currentLineElem) { - currentLineElem.innerHTML = ansi_up.ansi_to_html(currentLineBuffer); - updateCursor(); - } - terminal.scrollTop = terminal.scrollHeight; - } - - // Create (or recreate) the current line element. - function createCurrentLineElem() { - if (currentLineElem && currentLineElem.parentNode) { - currentLineElem.parentNode.removeChild(currentLineElem); - } - currentLineElem = document.createElement("span"); - currentLineElem.id = "current-line"; - // Ensure the current line element is inline so the cursor stays on the same line. - currentLineElem.style.display = "inline"; - terminal.appendChild(currentLineElem); - } - - // Append a blinking cursor to the current line. - function updateCursor() { - const existingCursor = currentLineElem.querySelector(".cursor"); - if (existingCursor) { - existingCursor.parentNode.removeChild(existingCursor); - } - currentLineElem.insertAdjacentHTML('beforeend', '█'); - } - - // Throttled key handler that updates the local current line buffer and sends keys to the PTY. - function handleInteractiveKey(event) { - if (!interactiveMode || !interactiveWS || interactiveWS.readyState !== WebSocket.OPEN) - return; - const now = Date.now(); - if (now - lastKeySentTime < throttleInterval) { - event.preventDefault(); - return; - } - lastKeySentTime = now; - let data = ""; - if (event.key === "Enter") { - data = "\n"; - // Append newline to the local buffer and clear it - currentLineBuffer += "\n"; - updateCurrentLine(); - currentLineBuffer = ""; - } else if (event.key === "Backspace") { - data = "\x7f"; // DEL - // Update the local buffer: remove the last character. - currentLineBuffer = currentLineBuffer.slice(0, -1); - updateCurrentLine(); - } else if (event.key.length === 1) { - data = event.key; - currentLineBuffer += event.key; - updateCurrentLine(); - } else { - return; - } - interactiveWS.send(data); - event.preventDefault(); - } - - // Remove OSC sequences (for terminal title updates). - function removeOSCTitle(text) { - const oscRegex = /\x1b\]0;.*?(?:\x07|\x1b\\)/g; - return text.replace(oscRegex, ""); - } - - // Process backspace characters in a string (for PTY output). - function processBackspaces(text) { - let result = ""; - for (let i = 0; i < text.length; i++) { - const ch = text[i]; - if (ch === "\x7f" || ch === "\b") { - result = result.slice(0, -1); - } else { - result += ch; - } - } - return result; + interactiveMode = true; } }); diff --git a/templates/index.html b/templates/index.html index 6a50930..8cbe5ba 100644 --- a/templates/index.html +++ b/templates/index.html @@ -9,6 +9,11 @@ + + + + +