mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 11:16:47 +00:00
The serial page had a white strip of inline styles with an untranslated status and a browser-default button above the terminal, sent input while disconnected, sized the terminal only on window load and resize, and left the keyboard elsewhere after the key menu. When a superuser opened a console open in another window, its user saw "[An administrator opened this console]" and a plain "disconnected". - console-xterm.html shares the VNC page's bar: status badge (Connecting, Connected, Disconnected, the server's refusal), Disconnect, then Reconnect (a new page for a new token). The script is static/js/console_serial.js; the token reaches it as json_script data. - The terminal takes the keyboard on connect and after the key menu; input is sent only while connected; it fits its box on connect and whenever the box changes size (ResizeObserver). - A console that writes nothing yet gets a dim local hint: press Enter. - socketiod sends the displaced client "taken_over" with the superuser's username; the page shows "Taken over by <name>" in the bar and the terminal. The superuser is not asked first (owner's decision). - Fullscreen uses the shared console_keys.js handler (keyboard lock).
89 lines
2.9 KiB
JavaScript
89 lines
2.9 KiB
JavaScript
// The serial console page: xterm.js on socketiod (Socket.IO behind nginx's
|
|
// /socket.io/), driven from the panel's bar: status, keys, reconnect.
|
|
import { bindFullscreen, bindKeyAction } from './console_keys.js';
|
|
|
|
const status = document.getElementById('status');
|
|
const connectionButton = document.getElementById('connection_button');
|
|
|
|
// the page's signed token is the credential, good for two minutes: a new
|
|
// connection takes a new page (Reconnect), not an automatic retry
|
|
const token = JSON.parse(document.getElementById('serial-token').textContent);
|
|
const socket = io({ transports: ['websocket', 'polling'], auth: { token }, reconnection: false });
|
|
|
|
const term = new Terminal({ cursorBlink: true });
|
|
const fitAddon = new FitAddon.FitAddon();
|
|
term.loadAddon(fitAddon);
|
|
term.open(document.getElementById('terminal'));
|
|
|
|
function send(input) {
|
|
if (socket.connected) {
|
|
socket.emit('pty_input', { input });
|
|
}
|
|
}
|
|
|
|
function resize() {
|
|
fitAddon.fit();
|
|
if (socket.connected) {
|
|
socket.emit('resize', { cols: term.cols, rows: term.rows });
|
|
}
|
|
}
|
|
|
|
function showStatus(text, color) {
|
|
status.textContent = text;
|
|
status.className = 'badge text-bg-' + color;
|
|
connectionButton.textContent = socket.connected ? connectionButton.dataset.disconnect : connectionButton.dataset.reconnect;
|
|
}
|
|
|
|
let output = false;
|
|
let takenBy = null;
|
|
|
|
// typed and pasted input
|
|
term.onData(send);
|
|
socket.on('pty_output', (data) => {
|
|
output = true;
|
|
term.write(data.output);
|
|
});
|
|
socket.on('connect', () => {
|
|
showStatus(status.dataset.connected, 'success');
|
|
resize();
|
|
term.focus();
|
|
// a serial console shows nothing until the VM writes
|
|
setTimeout(() => {
|
|
if (!output && socket.connected) {
|
|
term.write('\x1b[2m' + status.dataset.hint + '\x1b[0m\r\n');
|
|
}
|
|
}, 1500);
|
|
});
|
|
// a superuser opened this console: the server ends this session next
|
|
socket.on('taken_over', (data) => {
|
|
takenBy = data.by;
|
|
});
|
|
socket.on('disconnect', () => {
|
|
if (takenBy) {
|
|
const text = status.dataset.takenOver.replace('%(name)s', takenBy);
|
|
term.write('\r\n[' + text + ']\r\n');
|
|
showStatus(text, 'danger');
|
|
} else {
|
|
showStatus(status.dataset.disconnected, 'danger');
|
|
}
|
|
});
|
|
// the server says why: serial console off, an expired link, the console open elsewhere
|
|
socket.on('connect_error', (err) => showStatus(err.message, 'danger'));
|
|
|
|
connectionButton.addEventListener('click', () => {
|
|
if (socket.connected) {
|
|
socket.disconnect();
|
|
} else {
|
|
window.location.reload();
|
|
}
|
|
});
|
|
|
|
const keys = { ctrlc: '\x03', ctrld: '\x04', ctrlz: '\x1a', ctrlbackslash: '\x1c', sendtab: '\t', sendesc: '\x1b' };
|
|
for (const [id, input] of Object.entries(keys)) {
|
|
bindKeyAction(id, () => send(input));
|
|
}
|
|
bindFullscreen();
|
|
|
|
// back to the terminal after the menu
|
|
document.addEventListener('hidden.bs.dropdown', () => term.focus());
|
|
new ResizeObserver(resize).observe(document.getElementById('terminal'));
|