fix rev console to make it not buns.

This commit is contained in:
KingKDot
2026-05-19 20:31:36 -05:00
parent bd192f7076
commit 40c72acb2e
5 changed files with 121 additions and 66 deletions
+8 -4
View File
@@ -14,12 +14,15 @@
flex: 1;
padding: 18px;
box-sizing: border-box;
max-width: none !important;
width: 100%;
margin: 0;
}
.console-card {
max-width: 1280px;
width: 100%;
margin: 0 auto;
max-width: none !important;
margin: 0;
box-sizing: border-box;
background: rgba(8, 12, 22, 0.8);
border: 1px solid rgba(255, 255, 255, 0.05);
@@ -40,8 +43,9 @@
}
.console-meta {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, max-content));
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 14px 24px;
color: #a9b8d4;
}
+79 -37
View File
@@ -12,6 +12,7 @@ const userLabel = document.getElementById("user-label");
const osLabel = document.getElementById("os-label");
const reconnectBtn = document.getElementById("reconnect-btn");
const clearBtn = document.getElementById("clear-btn");
const interruptBtn = document.getElementById("interrupt-btn");
const clientId = decodeURIComponent(
location.pathname.split("/").filter(Boolean)[0] || "",
@@ -19,13 +20,7 @@ const clientId = decodeURIComponent(
const wsProto = location.protocol === "https:" ? "wss" : "ws";
const wsUrl = `${wsProto}://${location.host}/api/clients/${encodeURIComponent(clientId)}/console/ws`;
const term = new Terminal({
cursorBlink: true,
fontFamily: '"JetBrains Mono", ui-monospace, Menlo, Consolas, monospace',
fontSize: 13,
scrollback: 10000,
allowProposedApi: true,
theme: {
const THEME = {
background: "#050913",
foreground: "#e8edf2",
cursor: "#6ee7b7",
@@ -47,21 +42,60 @@ const term = new Terminal({
brightMagenta: "#d8b4fe",
brightCyan: "#67e8f9",
brightWhite: "#f8fafc",
},
});
const fit = new FitAddon();
term.loadAddon(fit);
term.loadAddon(new WebLinksAddon());
term.open(containerEl);
};
let term = null;
let fit = null;
let ws = null;
let connected = false;
let sessionClosed = false;
let offlineNotified = false;
let lastSize = { cols: 0, rows: 0 };
let resizeTimer = 0;
let pendingSystem = [];
function isWindowsOs(os) {
return typeof os === "string" && /win/i.test(os);
}
function initTerminal(os) {
if (term) return;
const opts = {
cursorBlink: true,
fontFamily: '"JetBrains Mono", ui-monospace, Menlo, Consolas, monospace',
fontSize: 13,
scrollback: 10000,
allowProposedApi: true,
theme: THEME,
};
if (isWindowsOs(os)) {
opts.windowsPty = { backend: "conpty", buildNumber: 22621 };
}
term = new Terminal(opts);
fit = new FitAddon();
term.loadAddon(fit);
term.loadAddon(new WebLinksAddon());
term.open(containerEl);
term.onData((d) => {
if (!ws || ws.readyState !== WebSocket.OPEN) return;
if (sessionClosed) return;
ws.send(encodeMsgpack({ type: "input", data: d }));
});
term.onResize(() => {
sendResize();
});
for (const msg of pendingSystem) term.writeln(msg);
pendingSystem.length = 0;
tryFit();
}
function tryFit() {
if (!fit) return;
try {
fit.fit();
} catch {
@@ -76,12 +110,18 @@ function setStatus(label, tone = "pill-offline") {
}
function writeSystem(msg) {
term.writeln(`\x1b[2m[${msg}]\x1b[0m`);
const line = `\x1b[2m[${msg}]\x1b[0m`;
if (term) {
term.writeln(line);
} else {
pendingSystem.push(line);
}
}
function sendResize() {
if (sessionClosed) return;
if (!ws || ws.readyState !== WebSocket.OPEN) return;
if (!term) return;
const cols = term.cols;
const rows = term.rows;
if (!cols || !rows) return;
@@ -91,7 +131,7 @@ function sendResize() {
}
function applyOutput(data) {
if (!data) return;
if (!data || !term) return;
if (data instanceof Uint8Array) {
term.write(data);
} else if (typeof data === "string") {
@@ -112,9 +152,6 @@ function connect() {
ws.addEventListener("open", () => {
connected = true;
setStatus("Connected", "pill-online");
term.options.disableStdin = false;
tryFit();
sendResize();
});
ws.addEventListener("message", (event) => {
@@ -123,10 +160,18 @@ function connect() {
switch (payload.type) {
case "ready":
if (clientLabel) clientLabel.textContent = payload.clientId || "unknown";
if (clientLabel) {
const full = payload.clientId || "unknown";
clientLabel.textContent = full.length > 7 ? full.slice(0, 7) + "…" : full;
clientLabel.title = full;
}
if (hostLabel) hostLabel.textContent = payload.host || "unknown";
if (userLabel) userLabel.textContent = payload.user || "unknown";
if (osLabel) osLabel.textContent = payload.os || "unknown";
initTerminal(payload.os);
if (term) term.options.disableStdin = false;
tryFit();
sendResize();
setStatus("Live", "pill-online");
break;
case "status":
@@ -137,12 +182,12 @@ function connect() {
writeSystem(payload.reason || "Client offline");
}
sessionClosed = true;
term.options.disableStdin = true;
if (term) term.options.disableStdin = true;
} else if (payload.status === "closed") {
setStatus("Closed", "pill-offline");
writeSystem(payload.reason || "Console closed");
sessionClosed = true;
term.options.disableStdin = true;
if (term) term.options.disableStdin = true;
} else if (payload.status === "online") {
offlineNotified = false;
}
@@ -151,10 +196,10 @@ function connect() {
applyOutput(payload.data);
if (payload.error) writeSystem(payload.error);
if (typeof payload.exitCode === "number") {
term.writeln(`\r\n\x1b[33m[Process exited (${payload.exitCode})]\x1b[0m`);
if (term) term.writeln(`\r\n\x1b[33m[Process exited (${payload.exitCode})]\x1b[0m`);
setStatus("Closed", "pill-offline");
sessionClosed = true;
term.options.disableStdin = true;
if (term) term.options.disableStdin = true;
}
break;
default:
@@ -167,7 +212,7 @@ function connect() {
connected = false;
setStatus("Disconnected", "pill-offline");
if (!sessionClosed) writeSystem("Connection closed");
term.options.disableStdin = true;
if (term) term.options.disableStdin = true;
});
ws.addEventListener("error", () => {
@@ -175,16 +220,6 @@ function connect() {
});
}
term.onData((d) => {
if (!ws || ws.readyState !== WebSocket.OPEN) return;
if (sessionClosed) return;
ws.send(encodeMsgpack({ type: "input", data: d }));
});
term.onResize(() => {
sendResize();
});
window.addEventListener("resize", () => {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(tryFit, 120);
@@ -192,16 +227,23 @@ window.addEventListener("resize", () => {
reconnectBtn?.addEventListener("click", () => {
try { ws?.close(); } catch { /* ignore */ }
term.reset();
if (term) term.reset();
lastSize = { cols: 0, rows: 0 };
connect();
});
clearBtn?.addEventListener("click", () => {
term.clear();
if (term) term.clear();
});
interruptBtn?.addEventListener("click", () => {
if (!ws || ws.readyState !== WebSocket.OPEN) return;
if (sessionClosed) return;
ws.send(encodeMsgpack({ type: "input", data: "\x03" }));
if (term) term.focus();
});
requestAnimationFrame(() => {
tryFit();
checkFeatureAccess("console", clientId).then((ok) => ok && connect());
});
+4 -1
View File
@@ -17,7 +17,7 @@
<link rel="icon" type="image/x-icon" href="/assets/favicon.ico" />
<link rel="stylesheet" href="/assets/main.css" />
<link rel="stylesheet" href="/assets/custom.css" />
<link rel="stylesheet" href="/assets/console.css" />
<link rel="stylesheet" href="/assets/console.css?v=3" />
</head>
<body class="console-body">
<header id="top-nav"></header>
@@ -47,6 +47,9 @@
<button type="button" id="reconnect-btn" class="button ghost" title="Reconnect">
<i class="fa-solid fa-rotate-right"></i><span>Reconnect</span>
</button>
<button type="button" id="interrupt-btn" class="button ghost" title="Send Ctrl+C">
<i class="fa-solid fa-hand"></i><span>Ctrl+C</span>
</button>
<button type="button" id="clear-btn" class="button ghost" title="Clear screen">
<i class="fa-solid fa-eraser"></i><span>Clear</span>
</button>
@@ -345,7 +345,7 @@ export function handleConsoleViewerOpen(ws: ServerWebSocket<SocketData>) {
const effectiveSessionId = sessionId || uuidv4();
ws.data.sessionId = effectiveSessionId;
const target = clientManager.getClient(clientId);
const session: ConsoleSession = { id: effectiveSessionId, clientId, viewer: ws, createdAt: Date.now() };
const session: ConsoleSession = { id: effectiveSessionId, clientId, viewer: ws, createdAt: Date.now(), started: false };
sessionManager.addConsoleSession(session);
safeSendViewer(ws, {
type: "ready",
@@ -360,7 +360,6 @@ export function handleConsoleViewerOpen(ws: ServerWebSocket<SocketData>) {
safeSendViewer(ws, { type: "status", status: "offline", reason: "Client is offline", sessionId: effectiveSessionId });
return;
}
startConsoleForViewer(target, effectiveSessionId);
}
export function handleRemoteDesktopViewerOpen(ws: ServerWebSocket<SocketData>) {
@@ -1219,7 +1218,13 @@ export function handleConsoleViewerMessage(ws: ServerWebSocket<SocketData>, raw:
case "resize": {
const cols = Number(payload.cols) || 120;
const rows = Number(payload.rows) || 36;
const session = sessionId ? sessionManager.getConsoleSession(sessionId) : undefined;
if (session && !session.started) {
session.started = true;
startConsoleForViewer(target, sessionId!, cols, rows);
} else {
sendConsoleCommand(target, "console_resize", { sessionId, cols, rows });
}
break;
}
case "stop": {
+1
View File
@@ -35,6 +35,7 @@ export type ConsoleSession = {
clientId: string;
viewer: ServerWebSocket<SocketData>;
createdAt: number;
started?: boolean;
};
export type RemoteDesktopViewer = {