mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-11 12:46:49 +00:00
feat(console): the serial console page on the panel's bar; a taken-over user learns by whom
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).
This commit is contained in:
parent
0532471759
commit
2b54198965
7 changed files with 125 additions and 98 deletions
89
static/js/console_serial.js
Normal file
89
static/js/console_serial.js
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
// 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'));
|
||||
Loading…
Add table
Add a link
Reference in a new issue