]> git.proxmox.com Git - pve-xtermjs.git/blame - src/www/main.js
fix #2980: do not prompt for leaving on reconnect/disconnect
[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);
b9bbd688 100const fitAddon = new FitAddon.FitAddon();
dcf3d43b
DC
101
102createTerminal();
103
104function createTerminal() {
37445374 105 term = new Terminal(getTerminalSettings());
b9bbd688 106 term.loadAddon(fitAddon);
dcf3d43b 107
b9bbd688 108 term.onResize(function (size) {
dcf3d43b
DC
109 if (state === states.connected) {
110 socket.send("1:" + size.cols + ":" + size.rows + ":");
111 }
112 });
113
114 protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://';
115
116 var params = {};
dcf3d43b
DC
117 var url = '/nodes/' + nodename;
118 switch (type) {
119 case 'kvm':
120 url += '/qemu/' + vmid;
dcf3d43b
DC
121 break;
122 case 'lxc':
123 url += '/lxc/' + vmid;
dcf3d43b
DC
124 break;
125 case 'upgrade':
af39a6f0 126 params.cmd = 'upgrade';
dcf3d43b 127 break;
6c7c792d
TM
128 case 'cmd':
129 params.cmd = decodeURI(cmd);
bc2ce72a 130 if (cmdOpts !== undefined && cmdOpts !== null && cmdOpts !== "") {
1d3d2919
TL
131 params['cmd-opts'] = decodeURI(cmdOpts);
132 }
6c7c792d 133 break;
dcf3d43b
DC
134 }
135 API2Request({
136 method: 'POST',
137 params: params,
138 url: url + '/termproxy',
139 success: function(result) {
140 var port = encodeURIComponent(result.data.port);
141 ticket = result.data.ticket;
142 socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/api2/json' + url + '/vncwebsocket?port=' + port + '&vncticket=' + encodeURIComponent(ticket);
143
144 term.open(terminalContainer, true);
145 socket = new WebSocket(socketURL, 'binary');
146 socket.binaryType = 'arraybuffer';
147 socket.onopen = runTerminal;
291e1a24
DC
148 socket.onclose = tryReconnect;
149 socket.onerror = tryReconnect;
dcf3d43b
DC
150 updateState(states.connecting);
151 },
152 failure: function(msg) {
153 updateState(states.disconnected,msg);
154 }
155 });
156
157}
158
159function runTerminal() {
160 socket.onmessage = function(event) {
920ae86d 161 var answer = new Uint8Array(event.data);
dcf3d43b
DC
162 if (state === states.connected) {
163 term.write(answer);
164 } else if(state === states.connecting) {
920ae86d 165 if (answer[0] === 79 && answer[1] === 75) { // "OK"
dcf3d43b
DC
166 updateState(states.connected);
167 term.write(answer.slice(2));
168 } else {
169 socket.close();
170 }
171 }
172 };
173
b9bbd688 174 term.onData(function(data) {
dcf3d43b
DC
175 if (state === states.connected) {
176 socket.send("0:" + unescape(encodeURIComponent(data)).length.toString() + ":" + data);
177 }
178 });
179
180 ping = setInterval(function() {
181 socket.send("2");
182 }, 30*1000);
183
184 window.addEventListener('resize', function() {
185 clearTimeout(resize);
186 resize = setTimeout(function() {
187 // done resizing
b9bbd688 188 fitAddon.fit();
dcf3d43b
DC
189 }, 250);
190 });
191
5e91985c 192 socket.send(PVE.UserName + ':' + ticket + "\n");
dcf3d43b 193
1553e6ef
DC
194 // initial focus and resize
195 setTimeout(function() {
196 term.focus();
b9bbd688 197 fitAddon.fit();
1553e6ef 198 }, 250);
dcf3d43b
DC
199}
200
291e1a24
DC
201function getLxcStatus(callback) {
202 API2Request({
203 method: 'GET',
204 url: '/nodes/' + nodename + '/lxc/' + vmid + '/status/current',
205 success: function(result) {
206 if (typeof callback === 'function') {
207 callback(true, result);
208 }
209 },
210 failure: function(msg) {
211 if (typeof callback === 'function') {
212 callback(false, msg);
213 }
214 }
215 });
216}
217
218function checkMigration() {
219 var apitype = type;
220 if (apitype === 'kvm') {
221 apitype = 'qemu';
222 }
223 API2Request({
224 method: 'GET',
225 params: {
226 type: 'vm'
227 },
228 url: '/cluster/resources',
229 success: function(result) {
230 // if not yet migrated , wait and try again
231 // if not migrating and stopped, cancel
232 // if started, connect
233 result.data.forEach(function(entity) {
234 if (entity.id === (apitype + '/' + vmid)) {
235 var started = entity.status === 'running';
236 var migrated = entity.node !== nodename;
237 if (migrated) {
238 if (started) {
239 // goto different node
240 location.href = '?console=' + type +
241 '&xtermjs=1&vmid=' + vmid + '&vmname=' +
242 vmname + '&node=' + entity.node;
243 } else {
244 // wait again
245 updateState(states.reconnecting, 'waiting for migration to finish...');
246 setTimeout(checkMigration, 5000);
247 }
248 } else {
249 if (type === 'lxc') {
250 // we have to check the status of the
251 // container to know if it has the
252 // migration lock
253 getLxcStatus(function(success, result) {
254 if (success) {
255 if (result.data.lock === 'migrate') {
256 // still waiting
257 updateState(states.reconnecting, 'waiting for migration to finish...');
258 setTimeout(checkMigration, 5000);
8f2ac472
DC
259 } else if (started) {
260 // container was rebooted
261 location.reload();
291e1a24
DC
262 } else {
263 stopTerminal();
264 }
265 } else {
266 // probably the status call failed because
267 // the ct is already somewhere else, so retry
268 setTimeout(checkMigration, 1000);
269 }
270 });
271 } else if (started) {
272 // this happens if we have old data in
273 // /cluster/resources, or the connection
274 // disconnected, so simply try to reload here
275 location.reload();
276 } else if (type === 'kvm') {
277 // it seems the guest simply stopped
278 stopTerminal();
279 }
280 }
281
282 return;
283 }
284 });
285 },
286 failure: function(msg) {
287 errorTerminal({msg: msg});
288 }
289 });
290}
291
93c96ac5 292function tryReconnect(event) {
291e1a24
DC
293 var time_since_started = new Date() - starttime;
294 var type = getQueryParameter('console');
6c7c792d 295 if (time_since_started < 5*1000 || type === 'shell' || type === 'cmd') { // 5 seconds
93c96ac5 296 stopTerminal(event);
291e1a24 297 return;
291e1a24
DC
298 }
299
300 updateState(states.disconnecting, 'Detecting migration...');
301 setTimeout(checkMigration, 5000);
302}
303
b9bbd688
DC
304function clearEvents() {
305 term.onResize(() => {});
306 term.onData(() => {});
307}
308
e435736a
DC
309function windowUnload(e) {
310 let message = "Are you sure you want to leave this page?";
311
312 e = e || window.event;
313 if (e) {
314 e.returnValue = message;
315 }
316
317 return message;
318}
319
dcf3d43b 320function stopTerminal(event) {
8c502b81 321 event = event || {};
b9bbd688 322 clearEvents();
dcf3d43b
DC
323 clearInterval(ping);
324 socket.close();
93c96ac5 325 updateState(states.disconnected, event.reason, event.code);
dcf3d43b
DC
326}
327
328function errorTerminal(event) {
8c502b81 329 even = event || {};
b9bbd688 330 clearEvents();
dcf3d43b
DC
331 clearInterval(ping);
332 socket.close();
f21a83b7 333 term.dispose();
93c96ac5 334 updateState(states.disconnected, event.msg, event.code);
dcf3d43b 335}