aboutsummaryrefslogtreecommitdiff
path: root/internal/net/terminal.html
diff options
context:
space:
mode:
Diffstat (limited to 'internal/net/terminal.html')
-rw-r--r--internal/net/terminal.html164
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>&gt;</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 "&amp;";
+ if (c === "<") return "&lt;";
+ if (c === ">") return "&gt;";
+ 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>