--- /dev/null
+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();
+
+var type = getQueryParameter('console');
+var vmid = getQueryParameter('vmid');
+var vmname = getQueryParameter('vmname');
+var nodename = getQueryParameter('node');
+var cmd = getQueryParameter('cmd');
+var cmdOpts = getQueryParameter('cmd-opts');
+
+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();
+const webglAddon = new WebglAddon.WebglAddon();
+
+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 + location.hostname + ((location.port) ? (':' + location.port) : '') + '/api2/json' + url + '/vncwebsocket?port=' + port + '&vncticket=' + encodeURIComponent(ticket);
+
+ 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.open(terminalContainer);
+ term.loadAddon(fitAddon);
+ try {
+ term.loadAddon(webglAddon);
+ } catch (_e) {
+ console.warn("webgl-addon loading failed, falling back to regular dom renderer");
+ }
+
+ 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) {
+ event = event || {};
+ clearEvents();
+ clearInterval(ping);
+ socket.close();
+ term.dispose();
+ updateState(states.disconnected, event.msg, event.code);
+}