|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="utf-8" /> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
|
<title>Enhanced Terminal</title> |
|
<link rel="stylesheet" href="https://unpkg.com/xterm@4.11.0/css/xterm.css" /> |
|
<style> |
|
html { |
|
font-family: Arial, sans-serif; |
|
} |
|
body { |
|
background-color: #1e1e1e; |
|
color: white; |
|
margin: 0; |
|
padding: 0; |
|
display: flex; |
|
flex-direction: column; |
|
height: 100vh; |
|
} |
|
#status-bar { |
|
background-color: #333; |
|
color: #fff; |
|
padding: 5px 10px; |
|
display: flex; |
|
justify-content: space-between; |
|
align-items: center; |
|
font-size: small; |
|
} |
|
#terminal-container { |
|
flex: 1; |
|
display: flex; |
|
flex-direction: column; |
|
padding: 10px; |
|
} |
|
#terminal { |
|
flex: 1; |
|
width: 100%; |
|
height: 100%; |
|
border: 1px solid #444; |
|
border-radius: 5px; |
|
overflow: hidden; |
|
} |
|
|
|
::-webkit-scrollbar { |
|
width: 12px; |
|
height: 12px; |
|
} |
|
::-webkit-scrollbar-track { |
|
background: #2e2e2e; |
|
border-radius: 10px; |
|
} |
|
::-webkit-scrollbar-thumb { |
|
background-color: #555; |
|
border-radius: 10px; |
|
border: 3px solid #2e2e2e; |
|
} |
|
::-webkit-scrollbar-thumb:hover { |
|
background-color: #777; |
|
} |
|
.link-container { |
|
text-align: right; |
|
font-size: small; |
|
margin-top: 5px; |
|
} |
|
.link-container a { |
|
color: #1e90ff; |
|
text-decoration: none; |
|
} |
|
.link-container a:hover { |
|
text-decoration: underline; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div id="status-bar"> |
|
<span>Status: <span id="status">connecting...</span></span> |
|
<span id="resize-status"></span> |
|
</div> |
|
<div id="terminal-container"> |
|
<div id="terminal"></div> |
|
<div class="link-container"> |
|
<a href="https://www.youtube.com/watch?v=o-YBDTqX_ZU">Get Rickrolled</a> | |
|
<a href="https://github.com">GitHub</a> |
|
</div> |
|
</div> |
|
<script src="https://unpkg.com/xterm@4.11.0/lib/xterm.js"></script> |
|
<script src="https://unpkg.com/xterm-addon-fit@0.5.0/lib/xterm-addon-fit.js"></script> |
|
<script src="https://unpkg.com/xterm-addon-web-links@0.4.0/lib/xterm-addon-web-links.js"></script> |
|
<script src="https://unpkg.com/xterm-addon-search@0.8.0/lib/xterm-addon-search.js"></script> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.min.js"></script> |
|
<script> |
|
const term = new Terminal({ |
|
cursorBlink: true, |
|
macOptionIsMeta: true, |
|
scrollback: true, |
|
}); |
|
const fit = new FitAddon.FitAddon(); |
|
term.loadAddon(fit); |
|
term.loadAddon(new WebLinksAddon.WebLinksAddon()); |
|
term.loadAddon(new SearchAddon.SearchAddon()); |
|
|
|
term.open(document.getElementById("terminal")); |
|
fit.fit(); |
|
term.resize(15, 50); |
|
console.log(`size: ${term.cols} columns, ${term.rows} rows`); |
|
fit.fit(); |
|
term.writeln("Welcome to AR-server"); |
|
term.writeln(""); |
|
term.writeln("You can copy with ctrl+shift+x"); |
|
term.writeln("You can paste with ctrl+shift+v"); |
|
term.writeln(''); |
|
|
|
term.onData((data) => { |
|
console.log("browser terminal received new data:", data); |
|
socket.emit("pty-input", { input: data }); |
|
}); |
|
|
|
const socket = io.connect("/pty"); |
|
const status = document.getElementById("status"); |
|
|
|
socket.on("pty-output", function (data) { |
|
console.log("new output received from server:", data.output); |
|
term.write(data.output); |
|
}); |
|
|
|
socket.on("connect", () => { |
|
fitToscreen(); |
|
status.innerHTML = '<span style="background-color: lightgreen; padding: 2px 4px; border-radius: 3px;">connected</span>'; |
|
}); |
|
|
|
socket.on("disconnect", () => { |
|
status.innerHTML = '<span style="background-color: #ff8383; padding: 2px 4px; border-radius: 3px;">disconnected</span>'; |
|
}); |
|
|
|
function fitToscreen() { |
|
fit.fit(); |
|
const dims = { cols: term.cols, rows: term.rows }; |
|
console.log("sending new dimensions to server's pty", dims); |
|
socket.emit("resize", dims); |
|
document.getElementById("resize-status").textContent = `Resized to: ${dims.cols}x${dims.rows}`; |
|
} |
|
|
|
function debounce(func, wait_ms) { |
|
let timeout; |
|
return function (...args) { |
|
const context = this; |
|
clearTimeout(timeout); |
|
timeout = setTimeout(() => func.apply(context, args), wait_ms); |
|
}; |
|
} |
|
|
|
|
|
|
|
|
|
function customKeyEventHandler(e) { |
|
if (e.type !== "keydown") { |
|
return true; |
|
} |
|
if (e.ctrlKey && e.shiftKey) { |
|
const key = e.key.toLowerCase(); |
|
if (key === "v") { |
|
|
|
navigator.clipboard.readText().then((toPaste) => { |
|
socket.emit("pty-input", { input: toPaste }); |
|
}); |
|
|
|
|
|
e.preventDefault(); |
|
|
|
return false; |
|
} else if (key === "c" || key === "x") { |
|
|
|
const toCopy = term.getSelection(); |
|
if (toCopy) { |
|
navigator.clipboard.writeText(toCopy).then(() => { |
|
term.focus(); |
|
}); |
|
} |
|
return false; |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
const wait_ms = 50; |
|
window.onresize = debounce(fitToscreen, wait_ms); |
|
term.attachCustomKeyEventHandler(customKeyEventHandler); |
|
</script> |
|
</body> |
|
</html> |
|
|