added help menu.
This commit is contained in:
parent
82b6670838
commit
9e33737e67
29
main.go
29
main.go
|
@ -5,6 +5,7 @@ import (
|
|||
// "bufio"
|
||||
"bytes"
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
|
@ -509,6 +510,7 @@ func terminalHandler (w http.ResponseWriter, r *http.Request) {
|
|||
defer ws.Close()
|
||||
|
||||
cmd := exec.Command("bash")
|
||||
cmd.Env = append(os.Environ(), "TERM=xterm-256color")
|
||||
ptmx, err := pty.Start(cmd)
|
||||
if err != nil {
|
||||
log.Printf("Error starting PTY: %v", err)
|
||||
|
@ -533,10 +535,35 @@ func terminalHandler (w http.ResponseWriter, r *http.Request) {
|
|||
}()
|
||||
|
||||
for {
|
||||
_, message, err := ws.ReadMessage()
|
||||
_, message, err := ws.ReadMessage()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
// Check if the message is binary and starts with control prefix 0xFF.
|
||||
if len(message) > 0 && message[0] == 0xFF {
|
||||
var resizeMsg struct {
|
||||
Type string `json:"type"`
|
||||
Cols uint16 `json:"cols"`
|
||||
Rows uint16 `json:"rows"`
|
||||
}
|
||||
if err := json.Unmarshal(message[1:], &resizeMsg); err == nil && resizeMsg.Type == "resize" {
|
||||
err := pty.Setsize(ptmx, &pty.Winsize{
|
||||
Cols: resizeMsg.Cols,
|
||||
Rows: resizeMsg.Rows,
|
||||
X: 0,
|
||||
Y: 0,
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("Error resizing PTY: %v", err)
|
||||
} else {
|
||||
log.Printf("Resized PTY to cols: %d, rows: %d", resizeMsg.Cols, resizeMsg.Rows)
|
||||
}
|
||||
continue // Do not write this message to the PTY.
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, treat it as normal input.
|
||||
if _, err := ptmx.Write(message); err != nil {
|
||||
break
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
const helpMsg = `
|
||||
This is a non interactive Webshell including some additional features to ease
|
||||
communications between server and client.
|
||||
Available Commands:
|
||||
upload Upload files to the server through the file selector of the browser.
|
||||
download <file> Download files from the server to your local download directory.
|
||||
theme <theme> Change the colorscheme of the shell. Type theme to get an overview of all colorschemes.
|
||||
start-interactive Opens a bash shell in an interactive terminal. Type ctrl+d to exit the interactive shell.
|
||||
`
|
||||
// const helpMsg = 'This is a non interactive Webshell including some additional features to ease communications between server and client.\n Available Commands:\n upload\t\t\t\tUpload files to the server through the file selector of the browser.\n download <file>\t\t\tDownload files from the server to your local download directory.\n theme <theme>\t\t\tChange the colorscheme of the shell. Type theme to get an overview of all colorschemes.\n start-interactive\t\t\tOpens a bash shell in an interactive terminal. Type ctrl+d to exi the interactive shell.'
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const input = document.getElementById("command-input");
|
||||
const terminal = document.getElementById("terminal");
|
||||
|
||||
input.addEventListener("keydown", function(event) {
|
||||
if (event.key === "Enter") {
|
||||
const command = input.value.trim();
|
||||
if (command.startsWith("help")) {
|
||||
event.preventDefault();
|
||||
addLogEntry(helpMsg, 'info');
|
||||
input.value = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function addLogEntry(message, type) {
|
||||
const logEntry = document.createElement("div");
|
||||
logEntry.classList.add(type === 'error' ? 'error' : 'info');
|
||||
logEntry.textContent = message;
|
||||
terminal.appendChild(logEntry);
|
||||
terminal.scrollTop = terminal.scrollHeight;
|
||||
}
|
||||
|
||||
});
|
|
@ -6,7 +6,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||
let interactiveMode = false;
|
||||
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();
|
||||
|
@ -21,77 +20,113 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||
});
|
||||
|
||||
function startInteractiveSession() {
|
||||
// Hide the normal terminal and input.
|
||||
normalTerminal.style.display = "none";
|
||||
input.style.display = "none";
|
||||
interactiveMode = true;
|
||||
// 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);
|
||||
// Create a new container for xterm.js.
|
||||
const xtermContainer = document.createElement("div");
|
||||
xtermContainer.id = "xterm-container";
|
||||
xtermContainer.style.position = "fixed";
|
||||
xtermContainer.style.top = "0";
|
||||
xtermContainer.style.left = "0";
|
||||
xtermContainer.style.width = window.innerWidth + "px";
|
||||
xtermContainer.style.height = window.innerHeight + "px";
|
||||
xtermContainer.style.zIndex = "1000";
|
||||
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);
|
||||
const term = new Terminal({
|
||||
cursorBlink: true,
|
||||
cursorStyle: 'block',
|
||||
scrollback: 1000,
|
||||
fontSize: 18,
|
||||
theme: {
|
||||
background: "#222",
|
||||
foreground: "#eee"
|
||||
}
|
||||
});
|
||||
const fitAddon = new FitAddon.FitAddon();
|
||||
term.loadAddon(fitAddon);
|
||||
term.open(xtermContainer);
|
||||
setTimeout(() => {
|
||||
fitAddon.fit();
|
||||
term.focus();
|
||||
console.log("Initial fit: container width =", xtermContainer.offsetWidth, "cols =", term.cols);
|
||||
}, 100);
|
||||
|
||||
// Establish the WebSocket connection.
|
||||
interactiveWS = new WebSocket("ws://" + location.host + "/terminal");
|
||||
interactiveWS.binaryType = "arraybuffer";
|
||||
interactiveWS = new WebSocket("ws://" + location.host + "/terminal");
|
||||
interactiveWS.binaryType = "arraybuffer";
|
||||
|
||||
interactiveWS.onmessage = function (event) {
|
||||
const text = new TextDecoder("utf-8").decode(event.data);
|
||||
term.write(text);
|
||||
};
|
||||
interactiveWS.onopen = function () {
|
||||
sendResize();
|
||||
};
|
||||
|
||||
interactiveWS.onclose = function () {
|
||||
interactiveMode = false;
|
||||
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();
|
||||
};
|
||||
interactiveWS.onmessage = function (event) {
|
||||
const text = new TextDecoder("utf-8").decode(event.data);
|
||||
term.write(text);
|
||||
};
|
||||
|
||||
interactiveWS.onerror = function (err) {
|
||||
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";
|
||||
};
|
||||
interactiveWS.onclose = function () {
|
||||
interactiveMode = false;
|
||||
term.write("\r\n--- Interactive session ended ---\r\n");
|
||||
if (xtermContainer.parentNode) {
|
||||
xtermContainer.parentNode.removeChild(xtermContainer);
|
||||
}
|
||||
window.removeEventListener("resize", handleResize);
|
||||
normalTerminal.style.display = "block";
|
||||
input.style.display = "block";
|
||||
input.focus();
|
||||
};
|
||||
|
||||
// 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 {
|
||||
interactiveWS.send(data);
|
||||
}
|
||||
});
|
||||
interactiveWS.onerror = function (err) {
|
||||
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);
|
||||
}
|
||||
window.removeEventListener("resize", handleResize);
|
||||
normalTerminal.style.display = "block";
|
||||
input.style.display = "block";
|
||||
};
|
||||
|
||||
interactiveMode = true;
|
||||
term.onData(function (data) {
|
||||
if (data === "\x04") {
|
||||
term.write("\r\n--- Exiting interactive session ---\r\n");
|
||||
interactiveWS.close();
|
||||
} else {
|
||||
interactiveWS.send(data);
|
||||
}
|
||||
});
|
||||
|
||||
function handleResize() {
|
||||
const newWidth = window.innerWidth;
|
||||
const newHeight = window.innerHeight;
|
||||
xtermContainer.style.width = newWidth + "px";
|
||||
xtermContainer.style.height = newHeight + "px";
|
||||
console.log("Resizing: new width =", newWidth, "new height =", newHeight);
|
||||
fitAddon.fit();
|
||||
sendResize();
|
||||
}
|
||||
window.addEventListener("resize", handleResize);
|
||||
|
||||
// Send a resize message using a custom control prefix (0xFF).
|
||||
function sendResize() {
|
||||
const resizeData = {
|
||||
type: "resize",
|
||||
cols: term.cols,
|
||||
rows: term.rows
|
||||
};
|
||||
const jsonStr = JSON.stringify(resizeData);
|
||||
const encoder = new TextEncoder();
|
||||
const jsonBuffer = encoder.encode(jsonStr);
|
||||
// Create a Uint8Array with one extra byte for the prefix.
|
||||
const buffer = new Uint8Array(jsonBuffer.length + 1);
|
||||
buffer[0] = 0xFF; // Control prefix.
|
||||
buffer.set(jsonBuffer, 1);
|
||||
interactiveWS.send(buffer.buffer);
|
||||
console.log("Sent resize: cols =", term.cols, "rows =", term.rows);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -8,14 +8,15 @@
|
|||
}
|
||||
|
||||
/* Default is pwny theme */
|
||||
body {
|
||||
html, body {
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
font-family: monospace;
|
||||
font-size: 14pt;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100vh;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
@ -80,6 +81,25 @@ span.ps1 {
|
|||
color: var(--ps1-color);
|
||||
}
|
||||
|
||||
|
||||
.info {
|
||||
/* text-align: justify; */
|
||||
/* color: var(--text-color); */
|
||||
/* animation: 0.75s 2 changeColor; */
|
||||
animation: changeColor 1.75s forwards;
|
||||
/* max-width: 80ch; */
|
||||
}
|
||||
@keyframes changeColor {
|
||||
from { color: var(--error-color) };
|
||||
to { color: var(--text-color) };
|
||||
}
|
||||
|
||||
.error {
|
||||
/* text-align: justify; */
|
||||
color: var(--error-color);
|
||||
/* max-width: 80ch; */
|
||||
}
|
||||
|
||||
.light-theme {
|
||||
--bg-color: #fff;
|
||||
--text-color: #222;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<title>Go Web Shell</title>
|
||||
<script type="text/javascript" src="static/keyboard-shortcuts.js"></script>
|
||||
<script type="text/javascript" src="static/download-command.js"></script>
|
||||
<script type="text/javascript" src="static/help-command.js"></script>
|
||||
<script type="text/javascript" src="static/switch-themes.js"></script>
|
||||
<script type="text/javascript" src="static/start-interactive.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/ansi_up@5.0.0/ansi_up.min.js"></script>
|
||||
|
|
Loading…
Reference in New Issue