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