diff options
Diffstat (limited to 'internal/net/terminal.html')
| -rw-r--r-- | internal/net/terminal.html | 164 |
1 files changed, 164 insertions, 0 deletions
diff --git a/internal/net/terminal.html b/internal/net/terminal.html new file mode 100644 index 0000000..2d98178 --- /dev/null +++ b/internal/net/terminal.html @@ -0,0 +1,164 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta charset="utf-8"> +<meta name="viewport" content="width=device-width, initial-scale=1"> +<title>Third Collapse</title> +<style> +* { margin: 0; padding: 0; box-sizing: border-box; } +body { + background: #0a0a0a; + color: #c0c0c0; + font-family: "Courier New", monospace; + font-size: 15px; + line-height: 1.25; + height: 100vh; + display: flex; + flex-direction: column; +} +#output { + flex: 1; + overflow-y: auto; + padding: 10px 14px; + white-space: pre-wrap; + word-break: break-all; +} +#input-line { + display: flex; + border-top: 1px solid #222; +} +#input-line span { + padding: 8px 0 8px 14px; + color: #888; +} +#input { + flex: 1; + background: transparent; + border: none; + color: #c0c0c0; + font-family: inherit; + font-size: inherit; + padding: 8px 14px 8px 4px; + outline: none; +} +</style> +</head> +<body> +<pre id="output"></pre> +<div id="input-line"> + <span>></span> + <input type="text" id="input" autofocus autocomplete="off" spellcheck="false"> +</div> +<script> +(function () { + const out = document.getElementById("output"); + const inp = document.getElementById("input"); + const proto = location.protocol === "https:" ? "wss:" : "ws:"; + const ws = new WebSocket(proto + "//" + location.host + "/ws"); + + function ansiToHTML(text) { + let out = ""; + let i = 0; + const stack = []; + while (i < text.length) { + if (text[i] === "\x1b" && text[i + 1] === "[") { + let j = i + 2; + while (j < text.length && (text[j] < "A" || text[j] > "z" || text[j] === "[" || text[j] === ";")) j++; + if (j < text.length) j++; + const seq = text.slice(i + 2, j); + out += ansiSeq(seq, stack); + i = j; + continue; + } + out += escapeHTML(text[i]); + i++; + } + for (let k = stack.length - 1; k >= 0; k--) out += "</span>"; + return out; + } + + function escapeHTML(c) { + if (c === "&") return "&"; + if (c === "<") return "<"; + if (c === ">") return ">"; + return c; + } + + function ansiSeq(seq, stack) { + if (seq === "0" || seq === "") { + let s = ""; + for (let k = stack.length - 1; k >= 0; k--) s += "</span>"; + stack.length = 0; + return s; + } + const codes = seq.endsWith("m") ? seq.slice(0, -1).split(";") : []; + let style = ""; + for (const c of codes) { + switch (c) { + case "1": style += "font-weight:bold;"; break; + case "2": style += "opacity:0.6;"; break; + case "30": style += "color:#000;"; break; + case "31": style += "color:#c44;"; break; + case "32": style += "color:#4c4;"; break; + case "33": style += "color:#cc4;"; break; + case "34": style += "color:#44c;"; break; + case "35": style += "color:#c4c;"; break; + case "36": style += "color:#4cc;"; break; + case "37": style += "color:#ccc;"; break; + case "40": style += "background:#000;"; break; + case "41": style += "background:#c44;"; break; + case "42": style += "background:#4c4;"; break; + case "43": style += "background:#cc4;"; break; + case "44": style += "background:#44c;"; break; + case "45": style += "background:#c4c;"; break; + case "46": style += "background:#4cc;"; break; + case "47": style += "background:#ccc;"; break; + } + } + if (style) { + stack.push(1); + return '<span style="' + style + '">'; + } + return ""; + } + + ws.onmessage = function (e) { + var data = e.data; + var re = /\x1b\]10;([01])\x07/g; + var match; + while ((match = re.exec(data)) !== null) { + inp.type = match[1] === "1" ? "password" : "text"; + } + data = data.replace(re, ""); + if (data.length > 0) { + out.innerHTML += ansiToHTML(data); + out.scrollTop = out.scrollHeight; + } + }; + + inp.addEventListener("keydown", function (e) { + if (e.key === "Enter") { + e.preventDefault(); + var text = inp.value; + inp.value = ""; + if (inp.type !== "password") { + out.innerHTML += ansiToHTML(text + "\n"); + } else { + out.innerHTML += "\n"; + } + out.scrollTop = out.scrollHeight; + ws.send(text); + } + }); + + ws.onclose = function () { + out.innerHTML += ansiToHTML("\n\u001b[31mConnection lost.\u001b[0m\n"); + }; + + ws.onerror = function () { + out.innerHTML += ansiToHTML("\n\u001b[31mConnection error.\u001b[0m\n"); + }; +})(); +</script> +</body> +</html> |
