]> git.proxmox.com Git - pve-xtermjs.git/blob - src/www/main.js
implement reconnect logic
[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
27 function updateState(newState, msg) {
28 var timeout, severity, message;
29 switch (newState) {
30 case states.connecting:
31 message = "Connecting...";
32 timeout = 0;
33 severity = severities.warning;
34 break;
35 case states.connected:
36 message = "Connected";
37 break;
38 case states.disconnecting:
39 message = "Disconnecting...";
40 timeout = 0;
41 severity = severities.warning;
42 break;
43 case states.reconnecting:
44 message = "Reconnecting...";
45 timeout = 0;
46 severity = severities.warning;
47 break;
48 case states.disconnected:
49 switch (state) {
50 case states.start:
51 case states.connecting:
52 case states.reconnecting:
53 message = "Connection failed";
54 timeout = 0;
55 severity = severities.error;
56 break;
57 case states.connected:
58 case states.disconnecting:
59 var time_since_started = new Date() - starttime;
60 timeout = 5000;
61 if (time_since_started > 5*1000) {
62 message = "Connection closed";
63 } else {
64 message = "Connection failed";
65 severity = severities.error;
66 }
67 break;
68 case states.disconnected:
69 // no state change
70 break;
71 default:
72 throw "unknown state";
73 }
74 break;
75 default:
76 throw "unknown state";
77 }
78 if (msg) {
79 message += " (" + msg + ")";
80 }
81 state = newState;
82 showMsg(message, timeout, severity);
83 }
84
85 var terminalContainer = document.getElementById('terminal-container');
86 document.getElementById('status_bar').addEventListener('click', hideMsg);
87 Terminal.applyAddon(fit);
88
89 createTerminal();
90
91 function createTerminal() {
92 term = new Terminal(getTerminalSettings());
93
94 term.on('resize', function (size) {
95 if (state === states.connected) {
96 socket.send("1:" + size.cols + ":" + size.rows + ":");
97 }
98 });
99
100 protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://';
101
102 var params = {};
103 var url = '/nodes/' + nodename;
104 switch (type) {
105 case 'kvm':
106 url += '/qemu/' + vmid;
107 break;
108 case 'lxc':
109 url += '/lxc/' + vmid;
110 break;
111 case 'upgrade':
112 params.upgrade = 1;
113 break;
114 }
115 API2Request({
116 method: 'POST',
117 params: params,
118 url: url + '/termproxy',
119 success: function(result) {
120 var port = encodeURIComponent(result.data.port);
121 ticket = result.data.ticket;
122 socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/api2/json' + url + '/vncwebsocket?port=' + port + '&vncticket=' + encodeURIComponent(ticket);
123
124 term.open(terminalContainer, true);
125 socket = new WebSocket(socketURL, 'binary');
126 socket.binaryType = 'arraybuffer';
127 socket.onopen = runTerminal;
128 socket.onclose = tryReconnect;
129 socket.onerror = tryReconnect;
130 window.onbeforeunload = stopTerminal;
131 updateState(states.connecting);
132 },
133 failure: function(msg) {
134 updateState(states.disconnected,msg);
135 }
136 });
137
138 }
139
140 function runTerminal() {
141 socket.onmessage = function(event) {
142 var answer = Utf8ArrayToStr(event.data);
143 if (state === states.connected) {
144 term.write(answer);
145 } else if(state === states.connecting) {
146 if (answer.slice(0,2) === "OK") {
147 updateState(states.connected);
148 term.write(answer.slice(2));
149 } else {
150 socket.close();
151 }
152 }
153 };
154
155 term.on('data', function(data) {
156 if (state === states.connected) {
157 socket.send("0:" + unescape(encodeURIComponent(data)).length.toString() + ":" + data);
158 }
159 });
160
161 ping = setInterval(function() {
162 socket.send("2");
163 }, 30*1000);
164
165 window.addEventListener('resize', function() {
166 clearTimeout(resize);
167 resize = setTimeout(function() {
168 // done resizing
169 term.fit();
170 }, 250);
171 });
172
173 socket.send(PVE.UserName + ':' + ticket + "\n");
174
175 setTimeout(function() {term.fit();}, 250);
176 }
177
178 function getLxcStatus(callback) {
179 API2Request({
180 method: 'GET',
181 url: '/nodes/' + nodename + '/lxc/' + vmid + '/status/current',
182 success: function(result) {
183 if (typeof callback === 'function') {
184 callback(true, result);
185 }
186 },
187 failure: function(msg) {
188 if (typeof callback === 'function') {
189 callback(false, msg);
190 }
191 }
192 });
193 }
194
195 function checkMigration() {
196 var apitype = type;
197 if (apitype === 'kvm') {
198 apitype = 'qemu';
199 }
200 API2Request({
201 method: 'GET',
202 params: {
203 type: 'vm'
204 },
205 url: '/cluster/resources',
206 success: function(result) {
207 // if not yet migrated , wait and try again
208 // if not migrating and stopped, cancel
209 // if started, connect
210 result.data.forEach(function(entity) {
211 if (entity.id === (apitype + '/' + vmid)) {
212 var started = entity.status === 'running';
213 var migrated = entity.node !== nodename;
214 if (migrated) {
215 if (started) {
216 // goto different node
217 location.href = '?console=' + type +
218 '&xtermjs=1&vmid=' + vmid + '&vmname=' +
219 vmname + '&node=' + entity.node;
220 } else {
221 // wait again
222 updateState(states.reconnecting, 'waiting for migration to finish...');
223 setTimeout(checkMigration, 5000);
224 }
225 } else {
226 if (type === 'lxc') {
227 // we have to check the status of the
228 // container to know if it has the
229 // migration lock
230 getLxcStatus(function(success, result) {
231 if (success) {
232 if (result.data.lock === 'migrate') {
233 // still waiting
234 updateState(states.reconnecting, 'waiting for migration to finish...');
235 setTimeout(checkMigration, 5000);
236 } else {
237 stopTerminal();
238 }
239 } else {
240 // probably the status call failed because
241 // the ct is already somewhere else, so retry
242 setTimeout(checkMigration, 1000);
243 }
244 });
245 } else if (started) {
246 // this happens if we have old data in
247 // /cluster/resources, or the connection
248 // disconnected, so simply try to reload here
249 location.reload();
250 } else if (type === 'kvm') {
251 // it seems the guest simply stopped
252 stopTerminal();
253 }
254 }
255
256 return;
257 }
258 });
259 },
260 failure: function(msg) {
261 errorTerminal({msg: msg});
262 }
263 });
264 }
265
266 function tryReconnect() {
267 var time_since_started = new Date() - starttime;
268 var type = getQueryParameter('console');
269 if (time_since_started < 5*1000) { // 5 seconds
270 stopTerminal({});
271 return;
272 } else if (type === 'shell') {
273 updateState(states.reconnecting, 'trying to reconnect...');
274 setTimeout(function() {
275 location.reload();
276 }, 1000);
277 }
278
279 updateState(states.disconnecting, 'Detecting migration...');
280 setTimeout(checkMigration, 5000);
281 }
282
283 function stopTerminal(event) {
284 term.off('resize');
285 term.off('data');
286 clearInterval(ping);
287 socket.close();
288 updateState(states.disconnected, event.msg + event.code);
289 }
290
291 function errorTerminal(event) {
292 term.off('resize');
293 term.off('data');
294 clearInterval(ping);
295 socket.close();
296 term.destroy();
297 updateState(states.disconnected, event.msg + event.code);
298 }