]> git.proxmox.com Git - pve-xtermjs.git/blob - xterm.js/src/main.js
85cc39953c928bc2003323777069a49fca5078ab
[pve-xtermjs.git] / xterm.js / src / main.js
1 console.log('xtermjs: starting');
2
3 var states = {
4 start: 1,
5 connecting: 2,
6 connected: 3,
7 disconnecting: 4,
8 disconnected: 5,
9 reconnecting: 6,
10 };
11
12 var term,
13 protocol,
14 socketURL,
15 socket,
16 ticket,
17 resize,
18 ping,
19 state = states.start,
20 starttime = new Date();
21
22 var type = getQueryParameter('console');
23 var vmid = getQueryParameter('vmid');
24 var vmname = getQueryParameter('vmname');
25 var nodename = getQueryParameter('node');
26 var cmd = getQueryParameter('cmd');
27 var cmdOpts = getQueryParameter('cmd-opts');
28
29 function updateState(newState, msg, code) {
30 var timeout, severity, message;
31 switch (newState) {
32 case states.connecting:
33 message = "Connecting...";
34 timeout = 0;
35 severity = severities.warning;
36 break;
37 case states.connected:
38 window.onbeforeunload = windowUnload;
39 message = "Connected";
40 break;
41 case states.disconnecting:
42 window.onbeforeunload = undefined;
43 message = "Disconnecting...";
44 timeout = 0;
45 severity = severities.warning;
46 break;
47 case states.reconnecting:
48 window.onbeforeunload = undefined;
49 message = "Reconnecting...";
50 timeout = 0;
51 severity = severities.warning;
52 break;
53 case states.disconnected:
54 window.onbeforeunload = undefined;
55 switch (state) {
56 case states.start:
57 case states.connecting:
58 case states.reconnecting:
59 message = "Connection failed";
60 timeout = 0;
61 severity = severities.error;
62 break;
63 case states.connected:
64 case states.disconnecting:
65 var time_since_started = new Date() - starttime;
66 timeout = 5000;
67 if (time_since_started > 5*1000 || type === 'shell') {
68 message = "Connection closed";
69 } else {
70 message = "Connection failed";
71 severity = severities.error;
72 }
73 break;
74 case states.disconnected:
75 // no state change
76 break;
77 default:
78 throw "unknown state";
79 }
80 break;
81 default:
82 throw "unknown state";
83 }
84 let msgArr = [];
85 if (msg) {
86 msgArr.push(msg);
87 }
88 if (code !== undefined) {
89 msgArr.push(`Code: ${code}`);
90 }
91 if (msgArr.length > 0) {
92 message += ` (${msgArr.join(', ')})`;
93 }
94 state = newState;
95 showMsg(message, timeout, severity);
96 }
97
98 var terminalContainer = document.getElementById('terminal-container');
99 document.getElementById('status_bar').addEventListener('click', hideMsg);
100 document.getElementById('connect_btn').addEventListener('click', startGuest);
101 const fitAddon = new FitAddon.FitAddon();
102 const webglAddon = new WebglAddon.WebglAddon();
103
104 createTerminal();
105
106 function startConnection(url, params, term) {
107 API2Request({
108 method: 'POST',
109 params: params,
110 url: url + '/termproxy',
111 success: function(result) {
112 var port = encodeURIComponent(result.data.port);
113 ticket = result.data.ticket;
114 socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/api2/json' + url + '/vncwebsocket?port=' + port + '&vncticket=' + encodeURIComponent(ticket);
115
116 socket = new WebSocket(socketURL, 'binary');
117 socket.binaryType = 'arraybuffer';
118 socket.onopen = runTerminal;
119 socket.onclose = tryReconnect;
120 socket.onerror = tryReconnect;
121 updateState(states.connecting);
122 },
123 failure: function(msg) {
124 updateState(states.disconnected,msg);
125 }
126 });
127 }
128
129 function startGuest() {
130 let api_type = type === 'kvm' ? 'qemu' : 'lxc';
131 API2Request({
132 method: 'POST',
133 url: `/nodes/${nodename}/${api_type}/${vmid}/status/start`,
134 success: function(result) {
135 showMsg('Guest started successfully', 0);
136 setTimeout(function() {
137 location.reload();
138 }, 1000);
139 },
140 failure: function(msg) {
141 if (msg.match(/already running/)) {
142 showMsg('Guest started successfully', 0);
143 setTimeout(function() {
144 location.reload();
145 }, 1000);
146 } else {
147 updateState(states.disconnected,msg);
148 }
149 }
150 });
151 }
152
153 function createTerminal() {
154 term = new Terminal(getTerminalSettings());
155 term.open(terminalContainer);
156 term.loadAddon(fitAddon);
157 try {
158 term.loadAddon(webglAddon);
159 } catch (_e) {
160 console.warn("webgl-addon loading failed, falling back to regular dom renderer");
161 }
162
163 term.onResize(function (size) {
164 if (state === states.connected) {
165 socket.send("1:" + size.cols + ":" + size.rows + ":");
166 }
167 });
168
169 protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://';
170
171 var params = {};
172 var url = '/nodes/' + nodename;
173 switch (type) {
174 case 'kvm':
175 url += '/qemu/' + vmid;
176 break;
177 case 'lxc':
178 url += '/lxc/' + vmid;
179 break;
180 case 'upgrade':
181 params.cmd = 'upgrade';
182 break;
183 case 'cmd':
184 params.cmd = decodeURI(cmd);
185 if (cmdOpts !== undefined && cmdOpts !== null && cmdOpts !== "") {
186 params['cmd-opts'] = decodeURI(cmdOpts);
187 }
188 break;
189 }
190 if (type === 'kvm' || type === 'lxc') {
191 API2Request({
192 method: 'GET',
193 url: `${url}/status/current`,
194 success: function(result) {
195 if (result.data.status === 'running') {
196 startConnection(url, params, term);
197 } else {
198 document.getElementById('connect_dlg').classList.add('pve_open');
199 }
200 },
201 failure: function(msg) {
202 updateState(states.disconnected, msg);
203 },
204 });
205 } else {
206 startConnection(url, params, term);
207 }
208 }
209
210 function runTerminal() {
211 socket.onmessage = function(event) {
212 var answer = new Uint8Array(event.data);
213 if (state === states.connected) {
214 term.write(answer);
215 } else if(state === states.connecting) {
216 if (answer[0] === 79 && answer[1] === 75) { // "OK"
217 updateState(states.connected);
218 term.write(answer.slice(2));
219 } else {
220 socket.close();
221 }
222 }
223 };
224
225 term.onData(function(data) {
226 if (state === states.connected) {
227 socket.send("0:" + unescape(encodeURIComponent(data)).length.toString() + ":" + data);
228 }
229 });
230
231 ping = setInterval(function() {
232 socket.send("2");
233 }, 30*1000);
234
235 window.addEventListener('resize', function() {
236 clearTimeout(resize);
237 resize = setTimeout(function() {
238 // done resizing
239 fitAddon.fit();
240 }, 250);
241 });
242
243 socket.send(PVE.UserName + ':' + ticket + "\n");
244
245 // initial focus and resize
246 setTimeout(function() {
247 term.focus();
248 fitAddon.fit();
249 }, 250);
250 }
251
252 function getLxcStatus(callback) {
253 API2Request({
254 method: 'GET',
255 url: '/nodes/' + nodename + '/lxc/' + vmid + '/status/current',
256 success: function(result) {
257 if (typeof callback === 'function') {
258 callback(true, result);
259 }
260 },
261 failure: function(msg) {
262 if (typeof callback === 'function') {
263 callback(false, msg);
264 }
265 }
266 });
267 }
268
269 function checkMigration() {
270 var apitype = type;
271 if (apitype === 'kvm') {
272 apitype = 'qemu';
273 }
274 API2Request({
275 method: 'GET',
276 params: {
277 type: 'vm'
278 },
279 url: '/cluster/resources',
280 success: function(result) {
281 // if not yet migrated , wait and try again
282 // if not migrating and stopped, cancel
283 // if started, connect
284 result.data.forEach(function(entity) {
285 if (entity.id === (apitype + '/' + vmid)) {
286 var started = entity.status === 'running';
287 var migrated = entity.node !== nodename;
288 if (migrated) {
289 if (started) {
290 // goto different node
291 location.href = '?console=' + type +
292 '&xtermjs=1&vmid=' + vmid + '&vmname=' +
293 vmname + '&node=' + entity.node;
294 } else {
295 // wait again
296 updateState(states.reconnecting, 'waiting for migration to finish...');
297 setTimeout(checkMigration, 5000);
298 }
299 } else {
300 if (type === 'lxc') {
301 // we have to check the status of the
302 // container to know if it has the
303 // migration lock
304 getLxcStatus(function(success, result) {
305 if (success) {
306 if (result.data.lock === 'migrate') {
307 // still waiting
308 updateState(states.reconnecting, 'waiting for migration to finish...');
309 setTimeout(checkMigration, 5000);
310 } else if (started) {
311 // container was rebooted
312 location.reload();
313 } else {
314 stopTerminal();
315 }
316 } else {
317 // probably the status call failed because
318 // the ct is already somewhere else, so retry
319 setTimeout(checkMigration, 1000);
320 }
321 });
322 } else if (started) {
323 // this happens if we have old data in
324 // /cluster/resources, or the connection
325 // disconnected, so simply try to reload here
326 location.reload();
327 } else if (type === 'kvm') {
328 // it seems the guest simply stopped
329 stopTerminal();
330 }
331 }
332
333 return;
334 }
335 });
336 },
337 failure: function(msg) {
338 errorTerminal({msg: msg});
339 }
340 });
341 }
342
343 function tryReconnect(event) {
344 var time_since_started = new Date() - starttime;
345 var type = getQueryParameter('console');
346 if (time_since_started < 5*1000 || type === 'shell' || type === 'cmd') { // 5 seconds
347 stopTerminal(event);
348 return;
349 }
350
351 updateState(states.disconnecting, 'Detecting migration...');
352 setTimeout(checkMigration, 5000);
353 }
354
355 function clearEvents() {
356 term.onResize(() => {});
357 term.onData(() => {});
358 }
359
360 function windowUnload(e) {
361 let message = "Are you sure you want to leave this page?";
362
363 e = e || window.event;
364 if (e) {
365 e.returnValue = message;
366 }
367
368 return message;
369 }
370
371 function stopTerminal(event) {
372 event = event || {};
373 clearEvents();
374 clearInterval(ping);
375 socket.close();
376 updateState(states.disconnected, event.reason, event.code);
377 }
378
379 function errorTerminal(event) {
380 event = event || {};
381 clearEvents();
382 clearInterval(ping);
383 socket.close();
384 term.dispose();
385 updateState(states.disconnected, event.msg, event.code);
386 }