ProxmoxAAS-Dashboard/pve-xtermjs/main.js
2023-02-14 05:14:33 +00:00

409 lines
9.9 KiB
JavaScript

console.log('xtermjs: starting');
var states = {
start: 1,
connecting: 2,
connected: 3,
disconnecting: 4,
disconnected: 5,
reconnecting: 6,
};
var term,
protocol,
socketURL,
socket,
ticket,
resize,
ping,
state = states.start,
starttime = new Date();
let uridata = getURIData();
var type = uridata.type;
var vmid = uridata.vmid;
var vmname = uridata.name;
var nodename = uridata.node;
if (typeof(PVE) === 'undefined') PVE = {};
PVE.UserName = uridata.user;
PVE.CSRFPreventionToken = getCookie("CSRFPreventionToken");
PVE.url = uridata.url;
document.title = document.title.replace("{{ VMName }}", vmid).replace("{{ NodeName }}", nodename);
function getCookie(cname) {
let name = cname + "=";
let decodedCookie = decodeURIComponent(document.cookie);
let ca = decodedCookie.split(";");
for(let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) === " ") {
c = c.substring(1);
}
if (c.indexOf(name) === 0) {
return c.substring(name.length, c.length);
}
}
return "";
}
function getURIData () {
let url = new URL(window.location.href);
return Object.fromEntries(url.searchParams);
}
function updateState(newState, msg, code) {
var timeout, severity, message;
switch (newState) {
case states.connecting:
message = "Connecting...";
timeout = 0;
severity = severities.warning;
break;
case states.connected:
window.onbeforeunload = windowUnload;
message = "Connected";
break;
case states.disconnecting:
window.onbeforeunload = undefined;
message = "Disconnecting...";
timeout = 0;
severity = severities.warning;
break;
case states.reconnecting:
window.onbeforeunload = undefined;
message = "Reconnecting...";
timeout = 0;
severity = severities.warning;
break;
case states.disconnected:
window.onbeforeunload = undefined;
switch (state) {
case states.start:
case states.connecting:
case states.reconnecting:
message = "Connection failed";
timeout = 0;
severity = severities.error;
break;
case states.connected:
case states.disconnecting:
var time_since_started = new Date() - starttime;
timeout = 5000;
if (time_since_started > 5*1000 || type === 'shell') {
message = "Connection closed";
} else {
message = "Connection failed";
severity = severities.error;
}
break;
case states.disconnected:
// no state change
break;
default:
throw "unknown state";
}
break;
default:
throw "unknown state";
}
let msgArr = [];
if (msg) {
msgArr.push(msg);
}
if (code !== undefined) {
msgArr.push(`Code: ${code}`);
}
if (msgArr.length > 0) {
message += ` (${msgArr.join(', ')})`;
}
state = newState;
showMsg(message, timeout, severity);
}
var terminalContainer = document.getElementById('terminal-container');
document.getElementById('status_bar').addEventListener('click', hideMsg);
document.getElementById('connect_btn').addEventListener('click', startGuest);
const fitAddon = new FitAddon.FitAddon();
createTerminal();
function startConnection(url, params, term) {
API2Request({
method: 'POST',
params: params,
url: url + '/termproxy',
success: function(result) {
var port = encodeURIComponent(result.data.port);
ticket = result.data.ticket;
socketURL = protocol + "pve.tronnet.net" + ((location.port) ? (':' + location.port) : '') + '/api2/json' + url + '/vncwebsocket?port=' + port + '&vncticket=' + encodeURIComponent(ticket);
term.open(terminalContainer, true);
socket = new WebSocket(socketURL, 'binary');
socket.binaryType = 'arraybuffer';
socket.onopen = runTerminal;
socket.onclose = tryReconnect;
socket.onerror = tryReconnect;
updateState(states.connecting);
},
failure: function(msg) {
updateState(states.disconnected,msg);
}
});
}
function startGuest() {
let api_type = type === 'kvm' ? 'qemu' : 'lxc';
API2Request({
method: 'POST',
url: `/nodes/${nodename}/${api_type}/${vmid}/status/start`,
success: function(result) {
showMsg('Guest started successfully', 0);
setTimeout(function() {
location.reload();
}, 1000);
},
failure: function(msg) {
if (msg.match(/already running/)) {
showMsg('Guest started successfully', 0);
setTimeout(function() {
location.reload();
}, 1000);
} else {
updateState(states.disconnected,msg);
}
}
});
}
function createTerminal() {
term = new Terminal(getTerminalSettings());
term.loadAddon(fitAddon);
term.onResize(function (size) {
if (state === states.connected) {
socket.send("1:" + size.cols + ":" + size.rows + ":");
}
});
protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://';
var params = {};
var url = '/nodes/' + nodename;
switch (type) {
case 'kvm':
url += '/qemu/' + vmid;
break;
case 'lxc':
url += '/lxc/' + vmid;
break;
case 'upgrade':
params.cmd = 'upgrade';
break;
case 'cmd':
params.cmd = decodeURI(cmd);
if (cmdOpts !== undefined && cmdOpts !== null && cmdOpts !== "") {
params['cmd-opts'] = decodeURI(cmdOpts);
}
break;
}
if (type === 'kvm' || type === 'lxc') {
API2Request({
method: 'GET',
url: `${url}/status/current`,
success: function(result) {
if (result.data.status === 'running') {
startConnection(url, params, term);
} else {
document.getElementById('connect_dlg').classList.add('pve_open');
}
},
failure: function(msg) {
updateState(states.disconnected, msg);
},
});
} else {
startConnection(url, params, term);
}
}
function runTerminal() {
socket.onmessage = function(event) {
var answer = new Uint8Array(event.data);
if (state === states.connected) {
term.write(answer);
} else if(state === states.connecting) {
if (answer[0] === 79 && answer[1] === 75) { // "OK"
updateState(states.connected);
term.write(answer.slice(2));
} else {
socket.close();
}
}
};
term.onData(function(data) {
if (state === states.connected) {
socket.send("0:" + unescape(encodeURIComponent(data)).length.toString() + ":" + data);
}
});
ping = setInterval(function() {
socket.send("2");
}, 30*1000);
window.addEventListener('resize', function() {
clearTimeout(resize);
resize = setTimeout(function() {
// done resizing
fitAddon.fit();
}, 250);
});
socket.send(PVE.UserName + ':' + ticket + "\n");
// initial focus and resize
setTimeout(function() {
term.focus();
fitAddon.fit();
}, 250);
}
function getLxcStatus(callback) {
API2Request({
method: 'GET',
url: '/nodes/' + nodename + '/lxc/' + vmid + '/status/current',
success: function(result) {
if (typeof callback === 'function') {
callback(true, result);
}
},
failure: function(msg) {
if (typeof callback === 'function') {
callback(false, msg);
}
}
});
}
function checkMigration() {
var apitype = type;
if (apitype === 'kvm') {
apitype = 'qemu';
}
API2Request({
method: 'GET',
params: {
type: 'vm'
},
url: '/cluster/resources',
success: function(result) {
// if not yet migrated , wait and try again
// if not migrating and stopped, cancel
// if started, connect
result.data.forEach(function(entity) {
if (entity.id === (apitype + '/' + vmid)) {
var started = entity.status === 'running';
var migrated = entity.node !== nodename;
if (migrated) {
if (started) {
// goto different node
location.href = '?console=' + type +
'&xtermjs=1&vmid=' + vmid + '&vmname=' +
vmname + '&node=' + entity.node;
} else {
// wait again
updateState(states.reconnecting, 'waiting for migration to finish...');
setTimeout(checkMigration, 5000);
}
} else {
if (type === 'lxc') {
// we have to check the status of the
// container to know if it has the
// migration lock
getLxcStatus(function(success, result) {
if (success) {
if (result.data.lock === 'migrate') {
// still waiting
updateState(states.reconnecting, 'waiting for migration to finish...');
setTimeout(checkMigration, 5000);
} else if (started) {
// container was rebooted
location.reload();
} else {
stopTerminal();
}
} else {
// probably the status call failed because
// the ct is already somewhere else, so retry
setTimeout(checkMigration, 1000);
}
});
} else if (started) {
// this happens if we have old data in
// /cluster/resources, or the connection
// disconnected, so simply try to reload here
location.reload();
} else if (type === 'kvm') {
// it seems the guest simply stopped
stopTerminal();
}
}
return;
}
});
},
failure: function(msg) {
errorTerminal({msg: msg});
}
});
}
function tryReconnect(event) {
var time_since_started = new Date() - starttime;
var type = getQueryParameter('console');
if (time_since_started < 5*1000 || type === 'shell' || type === 'cmd') { // 5 seconds
stopTerminal(event);
return;
}
updateState(states.disconnecting, 'Detecting migration...');
setTimeout(checkMigration, 5000);
}
function clearEvents() {
term.onResize(() => {});
term.onData(() => {});
}
function windowUnload(e) {
let message = "Are you sure you want to leave this page?";
e = e || window.event;
if (e) {
e.returnValue = message;
}
return message;
}
function stopTerminal(event) {
event = event || {};
clearEvents();
clearInterval(ping);
socket.close();
updateState(states.disconnected, event.reason, event.code);
}
function errorTerminal(event) {
even = event || {};
clearEvents();
clearInterval(ping);
socket.close();
term.dispose();
updateState(states.disconnected, event.msg, event.code);
}