fix rev console to make it not buns.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
});
|
||||
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -35,6 +35,7 @@ export type ConsoleSession = {
|
||||
clientId: string;
|
||||
viewer: ServerWebSocket<SocketData>;
|
||||
createdAt: number;
|
||||
started?: boolean;
|
||||
};
|
||||
|
||||
export type RemoteDesktopViewer = {
|
||||
|
||||
Reference in New Issue
Block a user