]> git.proxmox.com Git - pve-xtermjs.git/blame - src/www/main.js
detect not running guests and add start button
[pve-xtermjs.git] / src / www / main.js
CommitLineData
dcf3d43b
DC
1console.log('xtermjs: starting');
2
3var states = {
4 start: 1,
5 connecting: 2,
6 connected: 3,
7 disconnecting: 4,
8 disconnected: 5,
291e1a24 9 reconnecting: 6,
dcf3d43b
DC
10};
11
12var term,
13 protocol,
14 socketURL,
15 socket,
16 ticket,
dcf3d43b
DC
17 resize,
18 ping,
291e1a24
DC
19 state = states.start,
20 starttime = new Date();
21
22var type = getQueryParameter('console');
23var vmid = getQueryParameter('vmid');
24var vmname = getQueryParameter('vmname');
25var nodename = getQueryParameter('node');
6c7c792d 26var cmd = getQueryParameter('cmd');
1d3d2919 27var cmdOpts = getQueryParameter('cmd-opts');
dcf3d43b 28
93c96ac5 29function updateState(newState, msg, code) {
dcf3d43b
DC
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:
e435736a 38 window.onbeforeunload = windowUnload;
dcf3d43b
DC
39 message = "Connected";
40 break;
41 case states.disconnecting:
f7fd260c 42 window.onbeforeunload = undefined;
dcf3d43b
DC
43 message = "Disconnecting...";
44 timeout = 0;
45 severity = severities.warning;
46 break;
291e1a24 47 case states.reconnecting:
f7fd260c 48 window.onbeforeunload = undefined;
291e1a24
DC
49 message = "Reconnecting...";
50 timeout = 0;
51 severity = severities.warning;
52 break;
dcf3d43b 53 case states.disconnected:
e435736a 54 window.onbeforeunload = undefined;
dcf3d43b
DC
55 switch (state) {
56 case states.start:
57 case states.connecting:
291e1a24 58 case states.reconnecting:
dcf3d43b
DC
59 message = "Connection failed";
60 timeout = 0;
61 severity = severities.error;
62 break;
63 case states.connected:
64 case states.disconnecting:
291e1a24
DC
65 var time_since_started = new Date() - starttime;
66 timeout = 5000;
3645b3f4 67 if (time_since_started > 5*1000 || type === 'shell') {
291e1a24
DC
68 message = "Connection closed";
69 } else {
70 message = "Connection failed";
71 severity = severities.error;
72 }
dcf3d43b
DC
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 }
93c96ac5 84 let msgArr = [];
dcf3d43b 85 if (msg) {
93c96ac5
DC
86 msgArr.push(msg);
87 }
88 if (code !== undefined) {
89 msgArr.push(`Code: ${code}`);
90 }
91 if (msgArr.length > 0) {
92 message += ` (${msgArr.join(', ')})`;
dcf3d43b
DC
93 }
94 state = newState;
95 showMsg(message, timeout, severity);
96}
97
98var terminalContainer = document.getElementById('terminal-container');
99document.getElementById('status_bar').addEventListener('click', hideMsg);
9871a3b8 100document.getElementById('connect_btn').addEventListener('click', startGuest);
b9bbd688 101const fitAddon = new FitAddon.FitAddon();
dcf3d43b
DC
102
103createTerminal();
104
9871a3b8
DC
105function 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
129function 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
dcf3d43b 153function createTerminal() {
37445374 154 term = new Terminal(getTerminalSettings());
b9bbd688 155 term.loadAddon(fitAddon);
dcf3d43b 156
b9bbd688 157 term.onResize(function (size) {
dcf3d43b
DC
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 = {};
dcf3d43b
DC
166 var url = '/nodes/' + nodename;
167 switch (type) {
168 case 'kvm':
169 url += '/qemu/' + vmid;
dcf3d43b
DC
170 break;
171 case 'lxc':
172 url += '/lxc/' + vmid;
dcf3d43b
DC
173 break;
174 case 'upgrade':
af39a6f0 175 params.cmd = 'upgrade';
dcf3d43b 176 break;
6c7c792d
TM
177 case 'cmd':
178 params.cmd = decodeURI(cmd);
bc2ce72a 179 if (cmdOpts !== undefined && cmdOpts !== null && cmdOpts !== "") {
1d3d2919
TL
180 params['cmd-opts'] = decodeURI(cmdOpts);
181 }
6c7c792d 182 break;
dcf3d43b 183 }
9871a3b8
DC
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 }
dcf3d43b
DC
202}
203
204function runTerminal() {
205 socket.onmessage = function(event) {
920ae86d 206 var answer = new Uint8Array(event.data);
dcf3d43b
DC
207 if (state === states.connected) {
208 term.write(answer);
209 } else if(state === states.connecting) {
920ae86d 210 if (answer[0] === 79 && answer[1] === 75) { // "OK"
dcf3d43b
DC
211 updateState(states.connected);
212 term.write(answer.slice(2));
213 } else {
214 socket.close();
215 }
216 }
217 };
218
b9bbd688 219 term.onData(function(data) {
dcf3d43b
DC
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
b9bbd688 233 fitAddon.fit();
dcf3d43b
DC
234 }, 250);
235 });
236
5e91985c 237 socket.send(PVE.UserName + ':' + ticket + "\n");
dcf3d43b 238
1553e6ef
DC
239 // initial focus and resize
240 setTimeout(function() {
241 term.focus();
b9bbd688 242 fitAddon.fit();
1553e6ef 243 }, 250);
dcf3d43b
DC
244}
245
291e1a24
DC
246function 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
263function 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);
8f2ac472
DC
304 } else if (started) {
305 // container was rebooted
306 location.reload();
291e1a24
DC
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
93c96ac5 337function tryReconnect(event) {
291e1a24
DC
338 var time_since_started = new Date() - starttime;
339 var type = getQueryParameter('console');
6c7c792d 340 if (time_since_started < 5*1000 || type === 'shell' || type === 'cmd') { // 5 seconds
93c96ac5 341 stopTerminal(event);
291e1a24 342 return;
291e1a24
DC
343 }
344
345 updateState(states.disconnecting, 'Detecting migration...');
346 setTimeout(checkMigration, 5000);
347}
348
b9bbd688
DC
349function clearEvents() {
350 term.onResize(() => {});
351 term.onData(() => {});
352}
353
e435736a
DC
354function 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
dcf3d43b 365function stopTerminal(event) {
8c502b81 366 event = event || {};
b9bbd688 367 clearEvents();
dcf3d43b
DC
368 clearInterval(ping);
369 socket.close();
93c96ac5 370 updateState(states.disconnected, event.reason, event.code);
dcf3d43b
DC
371}
372
373function errorTerminal(event) {
8c502b81 374 even = event || {};
b9bbd688 375 clearEvents();
dcf3d43b
DC
376 clearInterval(ping);
377 socket.close();
f21a83b7 378 term.dispose();
93c96ac5 379 updateState(states.disconnected, event.msg, event.code);
dcf3d43b 380}