]> git.proxmox.com Git - novnc-pve.git/blame - debian/patches/0001-add-pve-specific-js-code.patch
upgrade novnc to current master
[novnc-pve.git] / debian / patches / 0001-add-pve-specific-js-code.patch
CommitLineData
a375b7e5 1From e9e4fe2eab135507fd6574b2f12ddc5c9d0a532b Mon Sep 17 00:00:00 2001
fe91e9e1
DC
2From: Dominik Csapak <d.csapak@proxmox.com>
3Date: Tue, 13 Dec 2016 16:11:35 +0100
a375b7e5 4Subject: [PATCH 01/10] add pve specific js code
fe91e9e1
DC
5
6this adds a es6 module 'PVEUI' which we use for defining the pve related
7methods (API2Request, etc.)
8
9we also modify ui.js so that it uses this module and sets up our
10autoresizing, commandstoggle, etc.
11
12Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
13---
a375b7e5
DC
14 app/pve.js | 414 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
15 app/ui.js | 75 ++++++++---
16 2 files changed, 475 insertions(+), 14 deletions(-)
fe91e9e1
DC
17 create mode 100644 app/pve.js
18
19diff --git a/app/pve.js b/app/pve.js
20new file mode 100644
a375b7e5 21index 0000000..37858f5
fe91e9e1
DC
22--- /dev/null
23+++ b/app/pve.js
a375b7e5 24@@ -0,0 +1,414 @@
fe91e9e1
DC
25+/*
26+ * PVE Utility functions for noVNC
27+ * Copyright (C) 2017 Proxmox GmbH
28+ */
29+
30+import * as WebUtil from "./webutil.js";
31+
32+export default function PVEUI(UI){
33+ this.consoletype = WebUtil.getQueryVar('console');
34+ this.vmid = WebUtil.getQueryVar('vmid');
35+ this.vmname = WebUtil.getQueryVar('vmname');
36+ this.nodename = WebUtil.getQueryVar('node');
37+ this.resize = WebUtil.getQueryVar('resize');
38+ this.lastFBWidth = undefined;
39+ this.lastFBHeight = undefined;
40+ this.sizeUpdateTimer = undefined;
41+ this.UI = UI;
42+
43+ var baseUrl = '/nodes/' + this.nodename;
44+ var url;
45+ var params = { websocket: 1 };
46+ var title;
47+
48+ switch (this.consoletype) {
49+ case 'kvm':
50+ baseUrl += '/qemu/' + this.vmid;
51+ url = baseUrl + '/vncproxy';
52+ title = "VM " + this.vmid;
53+ if (this.vmname) {
54+ title += " ('" + this.vmname + "')";
55+ }
56+ break;
57+ case 'lxc':
58+ baseUrl += '/lxc/' + this.vmid;
59+ url = baseUrl + '/vncproxy';
60+ title = "CT " + this.vmid;
61+ if (this.vmname) {
62+ title += " ('" + this.vmname + "')";
63+ }
64+ break;
65+ case 'shell':
66+ url = baseUrl + '/vncshell';
67+ title = "node '" + this.nodename + "'";
68+ break;
69+ case 'upgrade':
70+ url = baseUrl + '/vncshell';
71+ params.upgrade = 1;
72+ title = 'System upgrade on node ' + this.nodename;
73+ break;
74+ default:
75+ throw 'implement me';
76+ break;
77+ }
78+
a375b7e5
DC
79+ if (this.resize == 'scale' &&
80+ (this.consoletype === 'lxc' || this.consoletype === 'shell')) {
81+ var size = this.getFBSize();
82+ params.width = size.width;
83+ params.height = size.height;
84+ }
85+
fe91e9e1
DC
86+ this.baseUrl = baseUrl;
87+ this.url = url;
88+ this.params = params;
89+ document.title = title;
90+};
91+
92+PVEUI.prototype = {
93+ urlEncode: function(object) {
94+ var i,value, params = [];
95+
96+ for (i in object) {
97+ if (object.hasOwnProperty(i)) {
98+ value = object[i];
99+ if (value === undefined) value = '';
100+ params.push(encodeURIComponent(i) + '=' + encodeURIComponent(String(value)));
101+ }
102+ }
103+
104+ return params.join('&');
105+ },
106+
107+ API2Request: function(reqOpts) {
108+ var me = this;
109+
110+ reqOpts.method = reqOpts.method || 'GET';
111+
112+ var xhr = new XMLHttpRequest();
113+
114+ xhr.onload = function() {
115+ var scope = reqOpts.scope || this;
116+ var result;
117+ var errmsg;
118+
119+ if (xhr.readyState === 4) {
120+ var ctype = xhr.getResponseHeader('Content-Type');
121+ if (xhr.status === 200) {
122+ if (ctype.match(/application\/json;/)) {
123+ result = JSON.parse(xhr.responseText);
124+ } else {
125+ errmsg = 'got unexpected content type ' + ctype;
126+ }
127+ } else {
128+ errmsg = 'Error ' + xhr.status + ': ' + xhr.statusText;
129+ }
130+ } else {
131+ errmsg = 'Connection error - server offline?';
132+ }
133+
134+ if (errmsg !== undefined) {
135+ if (reqOpts.failure) {
136+ reqOpts.failure.call(scope, errmsg);
137+ }
138+ } else {
139+ if (reqOpts.success) {
140+ reqOpts.success.call(scope, result);
141+ }
142+ }
143+ if (reqOpts.callback) {
144+ reqOpts.callback.call(scope, errmsg === undefined);
145+ }
146+ }
147+
148+ var data = me.urlEncode(reqOpts.params || {});
149+
150+ if (reqOpts.method === 'GET') {
151+ xhr.open(reqOpts.method, "/api2/json" + reqOpts.url + '?' + data);
152+ } else {
153+ xhr.open(reqOpts.method, "/api2/json" + reqOpts.url);
154+ }
155+ xhr.setRequestHeader('Cache-Control', 'no-cache');
156+ if (reqOpts.method === 'POST' || reqOpts.method === 'PUT') {
157+ xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
158+ xhr.setRequestHeader('CSRFPreventionToken', PVE.CSRFPreventionToken);
159+ xhr.send(data);
160+ } else if (reqOpts.method === 'GET') {
161+ xhr.send();
162+ } else {
163+ throw "unknown method";
164+ }
165+ },
166+
167+ pve_detect_migrated_vm: function() {
168+ var me = this;
169+ if (me.consoletype === 'kvm') {
170+ // try to detect migrated VM
171+ me.API2Request({
172+ url: '/cluster/resources',
173+ method: 'GET',
174+ success: function(result) {
175+ var list = result.data;
176+ list.every(function(item) {
177+ if (item.type === 'qemu' && item.vmid == me.vmid) {
178+ var url = "?" + me.urlEncode({
179+ console: me.consoletype,
180+ novnc: 1,
181+ vmid: me.vmid,
182+ vmname: me.vmname,
183+ node: item.node,
184+ resize: me.resize
185+ });
186+ location.href = url;
187+ return false; // break
188+ }
189+ return true;
190+ });
191+ }
192+ });
193+ } else if(me.consoletype === 'lxc') {
194+ // lxc restart migration can take a while,
195+ // so we need to find out if we are really migrating
196+ var migrating;
197+ var check = setInterval(function() {
198+ if (migrating === undefined ||
199+ migrating === true) {
200+ // check (again) if migrating
f4aecc66 201+ me.UI.showStatus('Waiting for connection...', 'warning', 5000);
fe91e9e1
DC
202+ me.API2Request({
203+ url: me.baseUrl + '/config',
204+ method: 'GET',
205+ success: function(result) {
206+ var lock = result.data.lock;
207+ if (lock == 'migrate') {
208+ migrating = true;
209+ me.UI.showStatus('Migration detected, waiting...', 'warning', 5000);
210+ } else {
211+ migrating = false;
212+ }
213+ },
214+ failure: function() {
215+ migrating = false;
216+ }
217+ });
218+ } else {
219+ // not migrating any more
f4aecc66 220+ me.UI.showStatus('Connection resumed', 'warning');
fe91e9e1
DC
221+ clearInterval(check);
222+ me.API2Request({
223+ url: '/cluster/resources',
224+ method: 'GET',
225+ success: function(result) {
226+ var list = result.data;
227+ list.every(function(item) {
228+ if (item.type === 'lxc' && item.vmid == me.vmid) {
229+ var url = "?" + me.urlEncode({
230+ console: me.consoletype,
231+ novnc: 1,
232+ vmid: me.vmid,
233+ vmname: me.vmname,
234+ node: item.node,
235+ resize: me.resize
236+ });
237+ location.href = url;
238+ return false; // break
239+ }
240+ return true;
241+ });
242+ }
243+ });
244+ }
245+ }, 5000);
246+ }
247+
248+ },
249+
250+ pve_vm_command: function(cmd, params, reload) {
251+ var me = this;
252+ var baseUrl;
253+ var confirmMsg = "";
254+
255+ switch(cmd) {
256+ case "start":
257+ reload = 1;
258+ case "shutdown":
259+ case "stop":
260+ case "reset":
261+ case "suspend":
262+ case "resume":
263+ confirmMsg = "Do you really want to " + cmd + " VM/CT {0}?";
264+ break;
265+ case "reload":
266+ location.reload();
267+ break;
268+ default:
269+ throw "implement me " + cmd;
270+ }
271+
272+ confirmMsg = confirmMsg.replace('{0}', me.vmid);
273+
274+ if (confirmMsg !== "" && confirm(confirmMsg) !== true) {
275+ return;
276+ }
277+
278+ me.UI.closePVECommandPanel();
279+
280+ if (me.consoletype === 'kvm') {
281+ baseUrl = '/nodes/' + me.nodename + '/qemu/' + me.vmid;
282+ } else if (me.consoletype === 'lxc') {
283+ baseUrl = '/nodes/' + me.nodename + '/lxc/' + me.vmid;
284+ } else {
285+ throw "unknown VM type";
286+ }
287+
288+ me.API2Request({
289+ params: params,
290+ url: baseUrl + "/status/" + cmd,
291+ method: 'POST',
292+ failure: function(msg) {
293+ me.UI.showStatus(msg, 'warning');
294+ },
295+ success: function() {
296+ me.UI.showStatus("VM command '" + cmd +"' successful", 'normal');
297+ if (reload) {
298+ setTimeout(function() {
299+ location.reload();
300+ }, 1000);
301+ };
302+ }
303+ });
304+ },
305+
306+ addPVEHandlers: function() {
307+ var me = this;
308+ document.getElementById('pve_commands_button')
309+ .addEventListener('click', me.UI.togglePVECommandPanel);
310+
311+ // show/hide the buttons
312+ document.getElementById('noVNC_settings_button')
313+ .classList.add('noVNC_hidden');
314+ document.getElementById('noVNC_disconnect_button')
315+ .classList.add('noVNC_hidden');
316+ if (me.consoletype === 'kvm') {
317+ document.getElementById('noVNC_clipboard_button')
318+ .classList.add('noVNC_hidden');
319+ }
320+
321+ if (me.consoletype === 'shell' || me.consoletype === 'upgrade') {
322+ document.getElementById('pve_commands_button')
323+ .classList.add('noVNC_hidden');
324+ }
325+
326+ // add command logic
327+ var commandArray = [
328+ { cmd: 'start', kvm: 1, lxc: 1},
329+ { cmd: 'stop', kvm: 1, lxc: 1},
330+ { cmd: 'shutdown', kvm: 1, lxc: 1},
331+ { cmd: 'suspend', kvm: 1},
332+ { cmd: 'resume', kvm: 1},
333+ { cmd: 'reset', kvm: 1},
334+ { cmd: 'reload', kvm: 1, lxc: 1, shell: 1},
335+ ];
336+
337+ commandArray.forEach(function(item) {
338+ var el = document.getElementById('pve_command_'+item.cmd);
339+ if (!el) {
340+ return;
341+ }
342+
343+ if (item[me.consoletype] === 1) {
344+ el.onclick = function() {
345+ me.pve_vm_command(item.cmd);
346+ };
347+ } else {
348+ el.classList.add('noVNC_hidden');
349+ }
350+ });
fe91e9e1
DC
351+ },
352+
353+ getFBSize: function() {
354+ var oh;
355+ var ow;
356+
357+ if (window.innerHeight) {
358+ oh = window.innerHeight;
359+ ow = window.innerWidth;
360+ } else if (document.documentElement &&
361+ document.documentElement.clientHeight) {
362+ oh = document.documentElement.clientHeight;
363+ ow = document.documentElement.clientWidth;
364+ } else if (document.body) {
365+ oh = document.body.clientHeight;
366+ ow = document.body.clientWidth;
367+ } else {
368+ throw "can't get window size";
369+ }
370+
371+ return { width: ow, height: oh };
372+ },
373+
374+ pveStart: function(callback) {
375+ var me = this;
376+ me.API2Request({
377+ url: me.url,
378+ method: 'POST',
379+ params: me.params,
380+ success: function(result) {
381+ var wsparams = me.urlEncode({
382+ port: result.data.port,
383+ vncticket: result.data.ticket
384+ });
385+
386+ document.getElementById('noVNC_password_input').value = result.data.ticket;
387+ me.UI.updateSetting('path', 'api2/json' + me.baseUrl + '/vncwebsocket' + "?" + wsparams);
388+
389+ callback();
390+ },
391+ failure: function(msg) {
392+ me.UI.showStatus(msg, 'error');
393+ }
394+ });
395+ },
396+
397+ updateFBSize: function(rfb, width, height, clip) {
398+ var me = this;
fe91e9e1
DC
399+ try {
400+ // Note: window size must be even number for firefox
401+ me.lastFBWidth = Math.floor((width + 1)/2)*2;
402+ me.lastFBHeight = Math.floor((height + 1)/2)*2;
403+
404+ if (me.sizeUpdateTimer !== undefined) {
405+ clearInterval(me.sizeUpdateTimer);
406+ }
407+ if (clip) return;
408+
409+ var update_size = function() {
410+ // we do not want to resize if we are in fullscreen
411+ if (document.fullscreenElement || // alternative standard method
412+ document.mozFullScreenElement || // currently working methods
413+ document.webkitFullscreenElement ||
414+ document.msFullscreenElement) {
415+ return;
416+ }
417+
418+ var oldsize = me.getFBSize();
419+ var offsetw = me.lastFBWidth - oldsize.width;
420+ var offseth = me.lastFBHeight - oldsize.height;
421+ if (offsetw !== 0 || offseth !== 0) {
422+ //console.log("try resize by " + offsetw + " " + offseth);
423+ try {
424+ window.resizeBy(offsetw, offseth);
fe91e9e1
DC
425+ } catch (e) {
426+ console.log('resizing did not work', e);
427+ }
428+ }
429+ };
430+
431+ update_size();
432+ me.sizeUpdateTimer = setInterval(update_size, 1000);
433+
434+ } catch(e) {
435+ console.log(e);
436+ }
437+ },
438+};
439diff --git a/app/ui.js b/app/ui.js
a375b7e5 440index 2218d24..143cb60 100644
fe91e9e1
DC
441--- a/app/ui.js
442+++ b/app/ui.js
a375b7e5 443@@ -18,6 +18,7 @@ import Keyboard from "../core/input/keyboard.js";
fe91e9e1
DC
444 import RFB from "../core/rfb.js";
445 import Display from "../core/display.js";
446 import * as WebUtil from "./webutil.js";
447+import PVEUI from "./pve.js";
448
a375b7e5 449 var UI = {
fe91e9e1 450
a375b7e5 451@@ -59,6 +60,7 @@ var UI = {
fe91e9e1
DC
452 // Render default UI and initialize settings menu
453 start: function(callback) {
454
a375b7e5 455+ UI.PVE = new PVEUI(UI);
fe91e9e1
DC
456 // Setup global variables first
457 UI.isSafari = (navigator.userAgent.indexOf('Safari') !== -1 &&
458 navigator.userAgent.indexOf('Chrome') === -1);
a375b7e5 459@@ -90,6 +92,9 @@ var UI = {
fe91e9e1
DC
460 UI.addConnectionControlHandlers();
461 UI.addClipboardHandlers();
462 UI.addSettingsHandlers();
463+
a375b7e5
DC
464+ // add pve specific event handlers
465+ UI.PVE.addPVEHandlers();
fe91e9e1
DC
466 document.getElementById("noVNC_status")
467 .addEventListener('click', UI.hideStatus);
468
a375b7e5 469@@ -98,23 +103,19 @@ var UI = {
fe91e9e1
DC
470
471 UI.openControlbar();
472
a375b7e5
DC
473+ UI.updateViewClip();
474+
475 UI.updateVisualState('init');
fe91e9e1 476
fe91e9e1
DC
477 document.documentElement.classList.remove("noVNC_loading");
478
479- var autoconnect = WebUtil.getConfigVar('autoconnect', false);
480- if (autoconnect === 'true' || autoconnect == '1') {
481- autoconnect = true;
a375b7e5
DC
482+ UI.PVE.pveStart(function() {
483 UI.connect();
fe91e9e1
DC
484- } else {
485- autoconnect = false;
a375b7e5
DC
486- // Show the connect panel on first load unless autoconnecting
487- UI.openConnectPanel();
fe91e9e1 488- }
fe91e9e1
DC
489
490- if (typeof callback === "function") {
491- callback(UI.rfb);
492- }
a375b7e5
DC
493+ if (typeof callback === "function") {
494+ callback(UI.rfb);
495+ }
496+ });
fe91e9e1
DC
497 },
498
499 initFullscreen: function() {
a375b7e5 500@@ -159,9 +160,13 @@ var UI = {
fe91e9e1
DC
501 /* Populate the controls if defaults are provided in the URL */
502 UI.initSetting('host', window.location.hostname);
503 UI.initSetting('port', port);
504- UI.initSetting('encrypt', (window.location.protocol === "https:"));
505+ UI.initSetting('encrypt', true);
a375b7e5 506 UI.initSetting('view_clip', false);
fe91e9e1 507- UI.initSetting('resize', 'off');
a375b7e5
DC
508+ // we need updateSetting because
509+ // otherwise we load from browser storage
510+ // we want to overwrite the resize mode from url
511+ var resize = WebUtil.getQueryVar('resize');
512+ UI.updateSetting('resize', resize);
fe91e9e1
DC
513 UI.initSetting('shared', true);
514 UI.initSetting('view_only', false);
515 UI.initSetting('path', 'websockify');
a375b7e5
DC
516@@ -393,6 +398,9 @@ var UI = {
517 document.documentElement.classList.add("noVNC_connecting");
518 break;
fe91e9e1 519 case 'connected':
a375b7e5
DC
520+ UI.connected = true;
521+ UI.inhibit_reconnect = false;
522+ UI.pveAllowMigratedTest = true;
fe91e9e1 523 document.documentElement.classList.add("noVNC_connected");
a375b7e5
DC
524 break;
525 case 'disconnecting':
526@@ -400,6 +408,11 @@ var UI = {
527 document.documentElement.classList.add("noVNC_disconnecting");
fe91e9e1
DC
528 break;
529 case 'disconnected':
a375b7e5
DC
530+ UI.showStatus(_("Disconnected"));
531+ if (UI.pveAllowMigratedTest === true) {
532+ UI.pveAllowMigratedTest = false;
533+ UI.PVE.pve_detect_migrated_vm();
534+ }
fe91e9e1 535 break;
a375b7e5
DC
536 case 'reconnecting':
537 transition_elem.textContent = _("Reconnecting...");
538@@ -813,6 +826,7 @@ var UI = {
539 UI.closePowerPanel();
fe91e9e1
DC
540 UI.closeClipboardPanel();
541 UI.closeExtraKeys();
a375b7e5 542+ UI.closePVECommandPanel();
fe91e9e1
DC
543 },
544
545 /* ------^-------
a375b7e5
DC
546@@ -988,6 +1002,12 @@ var UI = {
547 UI.reconnect_password = password;
fe91e9e1
DC
548 }
549
a375b7e5 550+ var password = document.getElementById('noVNC_password_input').value;
fe91e9e1 551+
a375b7e5
DC
552+ if (!password) {
553+ password = WebUtil.getConfigVar('password');
554+ }
fe91e9e1 555+
a375b7e5
DC
556 if (password === null) {
557 password = undefined;
558 }
559@@ -1567,9 +1587,36 @@ var UI = {
fe91e9e1
DC
560 /* ------^-------
561 * /EXTRA KEYS
562 * ==============
563- * MISC
564+ * PVE
565 * ------v------*/
566
567+ togglePVECommandPanel: function() {
a375b7e5
DC
568+ if (document.getElementById('pve_commands').classList.contains("noVNC_open")) {
569+ UI.closePVECommandPanel();
570+ } else {
571+ UI.openPVECommandPanel();
572+ }
fe91e9e1
DC
573+ },
574+
575+ openPVECommandPanel: function() {
a375b7e5
DC
576+ var me = this;
577+ UI.closeAllPanels();
578+ UI.openControlbar();
fe91e9e1 579+
a375b7e5
DC
580+ document.getElementById('pve_commands').classList.add("noVNC_open");
581+ document.getElementById('pve_commands_button').classList.add("noVNC_selected");
fe91e9e1
DC
582+ },
583+
584+ closePVECommandPanel: function() {
a375b7e5
DC
585+ document.getElementById('pve_commands').classList.remove("noVNC_open");
586+ document.getElementById('pve_commands_button').classList.remove("noVNC_selected");
fe91e9e1
DC
587+ },
588+
589+/* ------^-------
a375b7e5 590+ * /PVE
fe91e9e1
DC
591+ * ==============
592+ * MISC
593+ * ------v------*/
594 setMouseButton: function(num) {
a375b7e5 595 var view_only = UI.rfb.viewOnly;
fe91e9e1 596 if (UI.rfb && !view_only) {
fe91e9e1
DC
597--
5982.11.0
599