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 @@
+
+
+
+
+