]> git.proxmox.com Git - pve-xtermjs.git/blob - src/www/main.js
ui: fix typo in error handler
[pve-xtermjs.git] / src / www / 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
103 createTerminal();
104
105 function startConnection(url, params, term) {
106 API2Request({
107 method: 'POST',
108 params: params,
109 url: url + '/termproxy',
110 success: function(result) {
111 var port = encodeURIComponent(result.data.port);
112 ticket = result.data.ticket;
113 socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/api2/json' + url + '/vncwebsocket?port=' + port + '&vncticket=' + encodeURIComponent(ticket);
114
115 term.open(terminalContainer, true);
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.loadAddon(fitAddon);
156
157 term.onResize(function (size) {
158 if (state === states.connected) {
159 socket.send("1:" + size.cols + ":" + size.rows + ":");
160 }
161 });
162
163 protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://';
164
165 var params = {};
166 var url = '/nodes/' + nodename;
167 switch (type) {
168 case 'kvm':
169 url += '/qemu/' + vmid;
170 break;
171 case 'lxc':
172 url += '/lxc/' + vmid;
173 break;
174 case 'upgrade':
175 params.cmd = 'upgrade';
176 break;
177 case 'cmd':
178 params.cmd = decodeURI(cmd);
179 if (cmdOpts !== undefined && cmdOpts !== null && cmdOpts !== "") {
180 params['cmd-opts'] = decodeURI(cmdOpts);
181 }
182 break;
183 }
184 if (type === 'kvm' || type === 'lxc') {
185 API2Request({
186 method: 'GET',
187 url: `${url}/status/current`,
188 success: function(result) {
189 if (result.data.status === 'running') {
190 startConnection(url, params, term);
191 } else {
192 document.getElementById('connect_dlg').classList.add('pve_open');
193 }
194 },
195 failure: function(msg) {
196 updateState(states.disconnected, msg);
197 },
198 });
199 } else {
200 startConnection(url, params, term);
201 }
202 }
203
204 function runTerminal() {
205 socket.onmessage = function(event) {
206 var answer = new Uint8Array(event.data);
207 if (state === states.connected) {
208 term.write(answer);
209 } else if(state === states.connecting) {
210 if (answer[0] === 79 && answer[1] === 75) { // "OK"
211 updateState(states.connected);
212 term.write(answer.slice(2));
213 } else {
214 socket.close();
215 }
216 }
217 };
218
219 term.onData(function(data) {
220 if (state === states.connected) {
221 socket.send("0:" + unescape(encodeURIComponent(data)).length.toString() + ":" + data);
222 }
223 });
224
225 ping = setInterval(function() {
226 socket.send("2");
227 }, 30*1000);
228
229 window.addEventListener('resize', function() {
230 clearTimeout(resize);
231 resize = setTimeout(function() {
232 // done resizing
233 fitAddon.fit();
234 }, 250);
235 });
236
237 socket.send(PVE.UserName + ':' + ticket + "\n");
238
239 // initial focus and resize
240 setTimeout(function() {
241 term.focus();
242 fitAddon.fit();
243 }, 250);
244 }
245
246 function getLxcStatus(callback) {
247 API2Request({
248 method: 'GET',
249 url: '/nodes/' + nodename + '/lxc/' + vmid + '/status/current',
250 success: function(result) {
251 if (typeof callback === 'function') {
252 callback(true, result);
253 }
254 },
255 failure: function(msg) {
256 if (typeof callback === 'function') {
257 callback(false, msg);
258 }
259 }
260 });
261 }
262
263 function checkMigration() {
264 var apitype = type;
265 if (apitype === 'kvm') {
266 apitype = 'qemu';
267 }
268 API2Request({
269 method: 'GET',
270 params: {
271 type: 'vm'
272 },
273 url: '/cluster/resources',
274 success: function(result) {
275 // if not yet migrated , wait and try again
276 // if not migrating and stopped, cancel
277 // if started, connect
278 result.data.forEach(function(entity) {
279 if (entity.id === (apitype + '/' + vmid)) {
280 var started = entity.status === 'running';
281 var migrated = entity.node !== nodename;
282 if (migrated) {
283 if (started) {
284 // goto different node
285 location.href = '?console=' + type +
286 '&xtermjs=1&vmid=' + vmid + '&vmname=' +
287 vmname + '&node=' + entity.node;
288 } else {
289 // wait again
290 updateState(states.reconnecting, 'waiting for migration to finish...');
291 setTimeout(checkMigration, 5000);
292 }
293 } else {
294 if (type === 'lxc') {
295 // we have to check the status of the
296 // container to know if it has the
297 // migration lock
298 getLxcStatus(function(success, result) {
299 if (success) {
300 if (result.data.lock === 'migrate') {
301 // still waiting
302 updateState(states.reconnecting, 'waiting for migration to finish...');
303 setTimeout(checkMigration, 5000);
304 } else if (started) {
305 // container was rebooted
306 location.reload();
307 } else {
308 stopTerminal();
309 }
310 } else {
311 // probably the status call failed because
312 // the ct is already somewhere else, so retry
313 setTimeout(checkMigration, 1000);
314 }
315 });
316 } else if (started) {
317 // this happens if we have old data in
318 // /cluster/resources, or the connection
319 // disconnected, so simply try to reload here
320 location.reload();
321 } else if (type === 'kvm') {
322 // it seems the guest simply stopped
323 stopTerminal();
324 }
325 }
326
327 return;
328 }
329 });
330 },
331 failure: function(msg) {
332 errorTerminal({msg: msg});
333 }
334 });
335 }
336
337 function tryReconnect(event) {
338 var time_since_started = new Date() - starttime;
339 var type = getQueryParameter('console');
340 if (time_since_started < 5*1000 || type === 'shell' || type === 'cmd') { // 5 seconds
341 stopTerminal(event);
342 return;
343 }
344
345 updateState(states.disconnecting, 'Detecting migration...');
346 setTimeout(checkMigration, 5000);
347 }
348
349 function clearEvents() {
350 term.onResize(() => {});
351 term.onData(() => {});
352 }
353
354 function windowUnload(e) {
355 let message = "Are you sure you want to leave this page?";
356
357 e = e || window.event;
358 if (e) {
359 e.returnValue = message;
360 }
361
362 return message;
363 }
364
365 function stopTerminal(event) {
366 event = event || {};
367 clearEvents();
368 clearInterval(ping);
369 socket.close();
370 updateState(states.disconnected, event.reason, event.code);
371 }
372
373 function errorTerminal(event) {
374 event = event || {};
375 clearEvents();
376 clearInterval(ping);
377 socket.close();
378 term.dispose();
379 updateState(states.disconnected, event.msg, event.code);
380 }