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