]> git.proxmox.com Git - novnc-pve.git/blame - debian/patches/0001-add-PVE-specific-JS-code.patch
upgrade novnc and patches to 1.3.0
[novnc-pve.git] / debian / patches / 0001-add-PVE-specific-JS-code.patch
CommitLineData
2b8dde16 1From 0000000000000000000000000000000000000000 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
bf74ff33 4Subject: [PATCH] add PVE specific JS code
fe91e9e1 5
bf74ff33
TL
6Add a ES6 module named `PVEUI` which defines the Proxmox VE
7related helper methods, like for example, API2Request.
fe91e9e1 8
bf74ff33
TL
9Hook the `PVEUI` module into the upstream `ui.js`, so that handlers
10for `autoresizing`, `commandstoggle`, etc., get setup.
fe91e9e1
DC
11
12Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
bf74ff33 13Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
fe91e9e1 14---
2b8dde16 15 app/pve.js | 418 +++++++++++++++++++++++++++++++++++++++++++++++++++++
e5e59874 16 app/ui.js | 66 +++++++--
4cc9ed08 17 vnc.html | 10 +-
e5e59874 18 3 files changed, 480 insertions(+), 14 deletions(-)
fe91e9e1
DC
19 create mode 100644 app/pve.js
20
21diff --git a/app/pve.js b/app/pve.js
22new file mode 100644
4cc9ed08 23index 0000000..e2c37fb
fe91e9e1
DC
24--- /dev/null
25+++ b/app/pve.js
4cc9ed08 26@@ -0,0 +1,418 @@
fe91e9e1
DC
27+/*
28+ * PVE Utility functions for noVNC
29+ * Copyright (C) 2017 Proxmox GmbH
30+ */
31+
32+import * as WebUtil from "./webutil.js";
33+
34+export default function PVEUI(UI){
35+ this.consoletype = WebUtil.getQueryVar('console');
36+ this.vmid = WebUtil.getQueryVar('vmid');
37+ this.vmname = WebUtil.getQueryVar('vmname');
38+ this.nodename = WebUtil.getQueryVar('node');
39+ this.resize = WebUtil.getQueryVar('resize');
40+ this.lastFBWidth = undefined;
41+ this.lastFBHeight = undefined;
42+ this.sizeUpdateTimer = undefined;
43+ this.UI = UI;
44+
45+ var baseUrl = '/nodes/' + this.nodename;
46+ var url;
47+ var params = { websocket: 1 };
48+ var title;
49+
50+ switch (this.consoletype) {
51+ case 'kvm':
52+ baseUrl += '/qemu/' + this.vmid;
53+ url = baseUrl + '/vncproxy';
54+ title = "VM " + this.vmid;
55+ if (this.vmname) {
56+ title += " ('" + this.vmname + "')";
57+ }
58+ break;
59+ case 'lxc':
60+ baseUrl += '/lxc/' + this.vmid;
61+ url = baseUrl + '/vncproxy';
62+ title = "CT " + this.vmid;
63+ if (this.vmname) {
64+ title += " ('" + this.vmname + "')";
65+ }
66+ break;
67+ case 'shell':
68+ url = baseUrl + '/vncshell';
69+ title = "node '" + this.nodename + "'";
70+ break;
71+ case 'upgrade':
72+ url = baseUrl + '/vncshell';
73+ params.upgrade = 1;
74+ title = 'System upgrade on node ' + this.nodename;
75+ break;
76+ default:
77+ throw 'implement me';
78+ break;
79+ }
80+
a375b7e5
DC
81+ if (this.resize == 'scale' &&
82+ (this.consoletype === 'lxc' || this.consoletype === 'shell')) {
83+ var size = this.getFBSize();
84+ params.width = size.width;
85+ params.height = size.height;
86+ }
87+
fe91e9e1
DC
88+ this.baseUrl = baseUrl;
89+ this.url = url;
90+ this.params = params;
91+ document.title = title;
92+};
93+
94+PVEUI.prototype = {
95+ urlEncode: function(object) {
96+ var i,value, params = [];
97+
98+ for (i in object) {
99+ if (object.hasOwnProperty(i)) {
100+ value = object[i];
101+ if (value === undefined) value = '';
102+ params.push(encodeURIComponent(i) + '=' + encodeURIComponent(String(value)));
103+ }
104+ }
105+
106+ return params.join('&');
107+ },
108+
109+ API2Request: function(reqOpts) {
110+ var me = this;
111+
112+ reqOpts.method = reqOpts.method || 'GET';
113+
114+ var xhr = new XMLHttpRequest();
115+
116+ xhr.onload = function() {
117+ var scope = reqOpts.scope || this;
118+ var result;
119+ var errmsg;
120+
121+ if (xhr.readyState === 4) {
122+ var ctype = xhr.getResponseHeader('Content-Type');
123+ if (xhr.status === 200) {
124+ if (ctype.match(/application\/json;/)) {
125+ result = JSON.parse(xhr.responseText);
126+ } else {
127+ errmsg = 'got unexpected content type ' + ctype;
128+ }
129+ } else {
130+ errmsg = 'Error ' + xhr.status + ': ' + xhr.statusText;
131+ }
132+ } else {
133+ errmsg = 'Connection error - server offline?';
134+ }
135+
136+ if (errmsg !== undefined) {
137+ if (reqOpts.failure) {
138+ reqOpts.failure.call(scope, errmsg);
139+ }
140+ } else {
141+ if (reqOpts.success) {
142+ reqOpts.success.call(scope, result);
143+ }
144+ }
145+ if (reqOpts.callback) {
146+ reqOpts.callback.call(scope, errmsg === undefined);
147+ }
148+ }
149+
150+ var data = me.urlEncode(reqOpts.params || {});
151+
152+ if (reqOpts.method === 'GET') {
153+ xhr.open(reqOpts.method, "/api2/json" + reqOpts.url + '?' + data);
154+ } else {
155+ xhr.open(reqOpts.method, "/api2/json" + reqOpts.url);
156+ }
157+ xhr.setRequestHeader('Cache-Control', 'no-cache');
158+ if (reqOpts.method === 'POST' || reqOpts.method === 'PUT') {
159+ xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
160+ xhr.setRequestHeader('CSRFPreventionToken', PVE.CSRFPreventionToken);
161+ xhr.send(data);
162+ } else if (reqOpts.method === 'GET') {
163+ xhr.send();
164+ } else {
165+ throw "unknown method";
166+ }
167+ },
168+
169+ pve_detect_migrated_vm: function() {
170+ var me = this;
171+ if (me.consoletype === 'kvm') {
172+ // try to detect migrated VM
173+ me.API2Request({
174+ url: '/cluster/resources',
175+ method: 'GET',
176+ success: function(result) {
177+ var list = result.data;
178+ list.every(function(item) {
179+ if (item.type === 'qemu' && item.vmid == me.vmid) {
180+ var url = "?" + me.urlEncode({
181+ console: me.consoletype,
182+ novnc: 1,
183+ vmid: me.vmid,
184+ vmname: me.vmname,
185+ node: item.node,
186+ resize: me.resize
187+ });
188+ location.href = url;
189+ return false; // break
190+ }
191+ return true;
192+ });
193+ }
194+ });
195+ } else if(me.consoletype === 'lxc') {
196+ // lxc restart migration can take a while,
197+ // so we need to find out if we are really migrating
198+ var migrating;
199+ var check = setInterval(function() {
200+ if (migrating === undefined ||
201+ migrating === true) {
202+ // check (again) if migrating
f4aecc66 203+ me.UI.showStatus('Waiting for connection...', 'warning', 5000);
fe91e9e1
DC
204+ me.API2Request({
205+ url: me.baseUrl + '/config',
206+ method: 'GET',
207+ success: function(result) {
208+ var lock = result.data.lock;
209+ if (lock == 'migrate') {
210+ migrating = true;
211+ me.UI.showStatus('Migration detected, waiting...', 'warning', 5000);
212+ } else {
213+ migrating = false;
214+ }
215+ },
216+ failure: function() {
217+ migrating = false;
218+ }
219+ });
220+ } else {
221+ // not migrating any more
f4aecc66 222+ me.UI.showStatus('Connection resumed', 'warning');
fe91e9e1
DC
223+ clearInterval(check);
224+ me.API2Request({
225+ url: '/cluster/resources',
226+ method: 'GET',
227+ success: function(result) {
228+ var list = result.data;
229+ list.every(function(item) {
230+ if (item.type === 'lxc' && item.vmid == me.vmid) {
231+ var url = "?" + me.urlEncode({
232+ console: me.consoletype,
233+ novnc: 1,
234+ vmid: me.vmid,
235+ vmname: me.vmname,
236+ node: item.node,
237+ resize: me.resize
238+ });
239+ location.href = url;
240+ return false; // break
241+ }
242+ return true;
243+ });
244+ }
245+ });
246+ }
247+ }, 5000);
248+ }
249+
250+ },
251+
252+ pve_vm_command: function(cmd, params, reload) {
253+ var me = this;
254+ var baseUrl;
255+ var confirmMsg = "";
256+
257+ switch(cmd) {
258+ case "start":
259+ reload = 1;
260+ case "shutdown":
261+ case "stop":
262+ case "reset":
263+ case "suspend":
264+ case "resume":
265+ confirmMsg = "Do you really want to " + cmd + " VM/CT {0}?";
266+ break;
267+ case "reload":
268+ location.reload();
269+ break;
270+ default:
271+ throw "implement me " + cmd;
272+ }
273+
274+ confirmMsg = confirmMsg.replace('{0}', me.vmid);
275+
276+ if (confirmMsg !== "" && confirm(confirmMsg) !== true) {
277+ return;
278+ }
279+
280+ me.UI.closePVECommandPanel();
281+
282+ if (me.consoletype === 'kvm') {
283+ baseUrl = '/nodes/' + me.nodename + '/qemu/' + me.vmid;
284+ } else if (me.consoletype === 'lxc') {
285+ baseUrl = '/nodes/' + me.nodename + '/lxc/' + me.vmid;
286+ } else {
287+ throw "unknown VM type";
288+ }
289+
290+ me.API2Request({
291+ params: params,
292+ url: baseUrl + "/status/" + cmd,
293+ method: 'POST',
294+ failure: function(msg) {
295+ me.UI.showStatus(msg, 'warning');
296+ },
297+ success: function() {
298+ me.UI.showStatus("VM command '" + cmd +"' successful", 'normal');
299+ if (reload) {
300+ setTimeout(function() {
301+ location.reload();
302+ }, 1000);
303+ };
304+ }
305+ });
306+ },
307+
308+ addPVEHandlers: function() {
309+ var me = this;
310+ document.getElementById('pve_commands_button')
311+ .addEventListener('click', me.UI.togglePVECommandPanel);
312+
313+ // show/hide the buttons
fe91e9e1
DC
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;
4cc9ed08 387+ me.UI.forceSetting('path', 'api2/json' + me.baseUrl + '/vncwebsocket' + "?" + wsparams);
fe91e9e1
DC
388+
389+ callback();
390+ },
391+ failure: function(msg) {
392+ me.UI.showStatus(msg, 'error');
393+ }
394+ });
395+ },
396+
4cc9ed08 397+ updateFBSize: function(rfb, width, height) {
fe91e9e1 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+ }
fe91e9e1
DC
407+
408+ var update_size = function() {
4cc9ed08
DC
409+ var clip = me.UI.getSetting('view_clip');
410+ var resize = me.UI.getSetting('resize');
411+ var autoresize = me.UI.getSetting('autoresize');
412+ if (clip || resize === 'scale' || !autoresize) {
413+ return;
414+ }
415+
fe91e9e1
DC
416+ // we do not want to resize if we are in fullscreen
417+ if (document.fullscreenElement || // alternative standard method
418+ document.mozFullScreenElement || // currently working methods
419+ document.webkitFullscreenElement ||
420+ document.msFullscreenElement) {
421+ return;
422+ }
423+
424+ var oldsize = me.getFBSize();
425+ var offsetw = me.lastFBWidth - oldsize.width;
426+ var offseth = me.lastFBHeight - oldsize.height;
427+ if (offsetw !== 0 || offseth !== 0) {
428+ //console.log("try resize by " + offsetw + " " + offseth);
429+ try {
430+ window.resizeBy(offsetw, offseth);
fe91e9e1
DC
431+ } catch (e) {
432+ console.log('resizing did not work', e);
433+ }
434+ }
435+ };
436+
437+ update_size();
438+ me.sizeUpdateTimer = setInterval(update_size, 1000);
439+
440+ } catch(e) {
441+ console.log(e);
442+ }
443+ },
444+};
445diff --git a/app/ui.js b/app/ui.js
e5e59874 446index cb6a9fd..6b4442f 100644
fe91e9e1
DC
447--- a/app/ui.js
448+++ b/app/ui.js
2b8dde16 449@@ -16,6 +16,7 @@ import keysyms from "../core/input/keysymdef.js";
4cc9ed08 450 import Keyboard from "../core/input/keyboard.js";
fe91e9e1 451 import RFB from "../core/rfb.js";
fe91e9e1
DC
452 import * as WebUtil from "./webutil.js";
453+import PVEUI from "./pve.js";
454
bf74ff33 455 const PAGE_TITLE = "noVNC";
fe91e9e1 456
bf74ff33 457@@ -56,6 +57,8 @@ const UI = {
fe91e9e1 458 // Render default UI and initialize settings menu
2b8dde16 459 start() {
fe91e9e1 460
a375b7e5 461+ UI.PVE = new PVEUI(UI);
2b8dde16
DC
462+
463 UI.initSettings();
464
465 // Translate the DOM
e5e59874 466@@ -100,6 +103,9 @@ const UI = {
fe91e9e1
DC
467 UI.addConnectionControlHandlers();
468 UI.addClipboardHandlers();
469 UI.addSettingsHandlers();
470+
a375b7e5
DC
471+ // add pve specific event handlers
472+ UI.PVE.addPVEHandlers();
fe91e9e1
DC
473 document.getElementById("noVNC_status")
474 .addEventListener('click', UI.hideStatus);
475
e5e59874 476@@ -108,19 +114,15 @@ const UI = {
fe91e9e1
DC
477
478 UI.openControlbar();
479
a375b7e5
DC
480+ UI.updateViewClip();
481+
482 UI.updateVisualState('init');
fe91e9e1 483
fe91e9e1
DC
484 document.documentElement.classList.remove("noVNC_loading");
485
4cc9ed08 486- let autoconnect = WebUtil.getConfigVar('autoconnect', false);
fe91e9e1
DC
487- if (autoconnect === 'true' || autoconnect == '1') {
488- autoconnect = true;
a375b7e5
DC
489+ UI.PVE.pveStart(function() {
490 UI.connect();
fe91e9e1
DC
491- } else {
492- autoconnect = false;
a375b7e5
DC
493- // Show the connect panel on first load unless autoconnecting
494- UI.openConnectPanel();
fe91e9e1 495- }
a375b7e5 496+ });
fe91e9e1 497
2b8dde16
DC
498 return Promise.resolve(UI.rfb);
499 },
e5e59874 500@@ -164,11 +166,12 @@ const 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);
4cc9ed08 507 UI.initSetting('resize', 'off');
bf74ff33
TL
508 UI.initSetting('quality', 6);
509 UI.initSetting('compression', 2);
4cc9ed08 510+ UI.initSetting('autoresize', true);
fe91e9e1
DC
511 UI.initSetting('shared', true);
512 UI.initSetting('view_only', false);
2b8dde16 513 UI.initSetting('show_dot', false);
e5e59874 514@@ -347,6 +350,7 @@ const UI = {
4cc9ed08 515 UI.addSettingChangeHandler('resize');
4cc9ed08 516 UI.addSettingChangeHandler('resize', UI.applyResizeMode);
2b8dde16 517 UI.addSettingChangeHandler('resize', UI.updateViewClip);
4cc9ed08 518+ UI.addSettingChangeHandler('autoresize');
bf74ff33
TL
519 UI.addSettingChangeHandler('quality');
520 UI.addSettingChangeHandler('quality', UI.updateQuality);
521 UI.addSettingChangeHandler('compression');
e5e59874 522@@ -401,6 +405,9 @@ const UI = {
a375b7e5
DC
523 document.documentElement.classList.add("noVNC_connecting");
524 break;
fe91e9e1 525 case 'connected':
a375b7e5
DC
526+ UI.connected = true;
527+ UI.inhibit_reconnect = false;
528+ UI.pveAllowMigratedTest = true;
fe91e9e1 529 document.documentElement.classList.add("noVNC_connected");
a375b7e5
DC
530 break;
531 case 'disconnecting':
e5e59874 532@@ -408,6 +415,11 @@ const UI = {
a375b7e5 533 document.documentElement.classList.add("noVNC_disconnecting");
fe91e9e1
DC
534 break;
535 case 'disconnected':
a375b7e5
DC
536+ UI.showStatus(_("Disconnected"));
537+ if (UI.pveAllowMigratedTest === true) {
538+ UI.pveAllowMigratedTest = false;
539+ UI.PVE.pve_detect_migrated_vm();
540+ }
fe91e9e1 541 break;
a375b7e5 542 case 'reconnecting':
bf74ff33 543 transitionElem.textContent = _("Reconnecting...");
e5e59874 544@@ -821,6 +833,7 @@ const UI = {
a375b7e5 545 UI.closePowerPanel();
fe91e9e1
DC
546 UI.closeClipboardPanel();
547 UI.closeExtraKeys();
a375b7e5 548+ UI.closePVECommandPanel();
fe91e9e1
DC
549 },
550
551 /* ------^-------
e5e59874 552@@ -998,6 +1011,12 @@ const UI = {
bf74ff33 553 UI.reconnectPassword = password;
fe91e9e1
DC
554 }
555
a375b7e5 556+ var password = document.getElementById('noVNC_password_input').value;
fe91e9e1 557+
a375b7e5
DC
558+ if (!password) {
559+ password = WebUtil.getConfigVar('password');
560+ }
fe91e9e1 561+
a375b7e5
DC
562 if (password === null) {
563 password = undefined;
564 }
e5e59874 565@@ -1622,9 +1641,36 @@ const UI = {
fe91e9e1
DC
566 /* ------^-------
567 * /EXTRA KEYS
568 * ==============
569- * MISC
570+ * PVE
571 * ------v------*/
572
573+ togglePVECommandPanel: function() {
a375b7e5
DC
574+ if (document.getElementById('pve_commands').classList.contains("noVNC_open")) {
575+ UI.closePVECommandPanel();
576+ } else {
577+ UI.openPVECommandPanel();
578+ }
fe91e9e1
DC
579+ },
580+
581+ openPVECommandPanel: function() {
a375b7e5
DC
582+ var me = this;
583+ UI.closeAllPanels();
584+ UI.openControlbar();
fe91e9e1 585+
a375b7e5
DC
586+ document.getElementById('pve_commands').classList.add("noVNC_open");
587+ document.getElementById('pve_commands_button').classList.add("noVNC_selected");
fe91e9e1
DC
588+ },
589+
590+ closePVECommandPanel: function() {
a375b7e5
DC
591+ document.getElementById('pve_commands').classList.remove("noVNC_open");
592+ document.getElementById('pve_commands_button').classList.remove("noVNC_selected");
fe91e9e1
DC
593+ },
594+
595+/* ------^-------
a375b7e5 596+ * /PVE
fe91e9e1
DC
597+ * ==============
598+ * MISC
599+ * ------v------*/
bf74ff33
TL
600 updateViewOnly() {
601 if (!UI.rfb) return;
602 UI.rfb.viewOnly = UI.getSetting('view_only');
4cc9ed08 603diff --git a/vnc.html b/vnc.html
e5e59874 604index 8d4b497..7ce9ba7 100644
4cc9ed08
DC
605--- a/vnc.html
606+++ b/vnc.html
e5e59874 607@@ -163,7 +163,7 @@
4cc9ed08 608 <li class="noVNC_heading">
2b8dde16 609 <img alt="" src="app/images/settings.svg"> Settings
4cc9ed08
DC
610 </li>
611- <li>
612+ <li style="display:none;">
2b8dde16 613 <label><input id="noVNC_setting_shared" type="checkbox"> Shared Mode</label>
4cc9ed08
DC
614 </li>
615 <li>
e5e59874 616@@ -173,16 +173,18 @@
4cc9ed08 617 <li>
2b8dde16
DC
618 <label><input id="noVNC_setting_view_clip" type="checkbox"> Clip to Window</label>
619 </li>
620+ <li>
4cc9ed08
DC
621+ <label><input id="noVNC_setting_autoresize" type="checkbox" /> Autoresize Window</label>
622+ </li>
2b8dde16 623 <li>
4cc9ed08
DC
624 <label for="noVNC_setting_resize">Scaling Mode:</label>
625 <select id="noVNC_setting_resize" name="vncResize">
626- <option value="off">None</option>
627+ <option value="off">Off</option>
628 <option value="scale">Local Scaling</option>
629- <option value="remote">Remote Resizing</option>
630 </select>
631 </li>
632 <li><hr></li>
633- <li>
634+ <li style="display:none;">
635 <div class="noVNC_expander">Advanced</div>
636 <div><ul>
637 <li>