adds the new rebased patches, based on current novnc master notable changes: * (most of) our code is in an es6 module, opposed to patching the ui.js * removed our sendkeys menu in favor of the novnc one * you can now enter fullscreen from a popup console * when a vm resizes its resolution, the canvas should also rescale in the console tab
Signed-off-by: Dominik Csapak <d.csa...@proxmox.com> --- debian/patches/0001-add-pve-specific-js-code.patch | 639 +++++++++++++++++++++ ...2-change-scaling-when-toggling-fullscreen.patch | 45 ++ debian/patches/0003-add-pve-style.patch | 75 +++ debian/patches/0004-remove-vnc-logos.patch | 54 ++ ...-change-src-directory-for-images-js-files.patch | 232 ++++++++ debian/patches/0006-add-pve-vnc-commands.patch | 44 ++ ...0007-add-replaceable-snippets-in-vnc.html.patch | 41 ++ debian/patches/0008-focus-canvas-after-load.patch | 43 ++ debian/patches/0009-decrease-animation-time.patch | 73 +++ 9 files changed, 1246 insertions(+) create mode 100644 debian/patches/0001-add-pve-specific-js-code.patch create mode 100644 debian/patches/0002-change-scaling-when-toggling-fullscreen.patch create mode 100644 debian/patches/0003-add-pve-style.patch create mode 100644 debian/patches/0004-remove-vnc-logos.patch create mode 100644 debian/patches/0005-change-src-directory-for-images-js-files.patch create mode 100644 debian/patches/0006-add-pve-vnc-commands.patch create mode 100644 debian/patches/0007-add-replaceable-snippets-in-vnc.html.patch create mode 100644 debian/patches/0008-focus-canvas-after-load.patch create mode 100644 debian/patches/0009-decrease-animation-time.patch diff --git a/debian/patches/0001-add-pve-specific-js-code.patch b/debian/patches/0001-add-pve-specific-js-code.patch new file mode 100644 index 0000000..401e53b --- /dev/null +++ b/debian/patches/0001-add-pve-specific-js-code.patch @@ -0,0 +1,639 @@ +From ad6975daaeee0798955f1d8b36d567975f4cbae5 Mon Sep 17 00:00:00 2001 +From: Dominik Csapak <d.csa...@proxmox.com> +Date: Tue, 13 Dec 2016 16:11:35 +0100 +Subject: [PATCH 1/9] add pve specific js code + +this adds a es6 module 'PVEUI' which we use for defining the pve related +methods (API2Request, etc.) + +we also modify ui.js so that it uses this module and sets up our +autoresizing, commandstoggle, etc. + +Signed-off-by: Dominik Csapak <d.csa...@proxmox.com> +--- + app/pve.js | 417 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + app/ui.js | 95 ++++++++++---- + 2 files changed, 488 insertions(+), 24 deletions(-) + create mode 100644 app/pve.js + +diff --git a/app/pve.js b/app/pve.js +new file mode 100644 +index 0000000..668835a +--- /dev/null ++++ b/app/pve.js +@@ -0,0 +1,417 @@ ++/* ++ * PVE Utility functions for noVNC ++ * Copyright (C) 2017 Proxmox GmbH ++ */ ++ ++import * as WebUtil from "./webutil.js"; ++ ++export default function PVEUI(UI){ ++ this.consoletype = WebUtil.getQueryVar('console'); ++ this.vmid = WebUtil.getQueryVar('vmid'); ++ this.vmname = WebUtil.getQueryVar('vmname'); ++ this.nodename = WebUtil.getQueryVar('node'); ++ this.resize = WebUtil.getQueryVar('resize'); ++ this.lastFBWidth = undefined; ++ this.lastFBHeight = undefined; ++ this.sizeUpdateTimer = undefined; ++ this.UI = UI; ++ ++ var baseUrl = '/nodes/' + this.nodename; ++ var url; ++ var params = { websocket: 1 }; ++ var title; ++ ++ switch (this.consoletype) { ++ case 'kvm': ++ baseUrl += '/qemu/' + this.vmid; ++ url = baseUrl + '/vncproxy'; ++ title = "VM " + this.vmid; ++ if (this.vmname) { ++ title += " ('" + this.vmname + "')"; ++ } ++ break; ++ case 'lxc': ++ baseUrl += '/lxc/' + this.vmid; ++ url = baseUrl + '/vncproxy'; ++ title = "CT " + this.vmid; ++ if (this.vmname) { ++ title += " ('" + this.vmname + "')"; ++ } ++ break; ++ case 'shell': ++ url = baseUrl + '/vncshell'; ++ title = "node '" + this.nodename + "'"; ++ break; ++ case 'upgrade': ++ url = baseUrl + '/vncshell'; ++ params.upgrade = 1; ++ title = 'System upgrade on node ' + this.nodename; ++ break; ++ default: ++ throw 'implement me'; ++ break; ++ } ++ ++ this.baseUrl = baseUrl; ++ this.url = url; ++ this.params = params; ++ document.title = title; ++}; ++ ++PVEUI.prototype = { ++ urlEncode: function(object) { ++ var i,value, params = []; ++ ++ for (i in object) { ++ if (object.hasOwnProperty(i)) { ++ value = object[i]; ++ if (value === undefined) value = ''; ++ params.push(encodeURIComponent(i) + '=' + encodeURIComponent(String(value))); ++ } ++ } ++ ++ return params.join('&'); ++ }, ++ ++ API2Request: function(reqOpts) { ++ var me = this; ++ ++ reqOpts.method = reqOpts.method || 'GET'; ++ ++ var xhr = new XMLHttpRequest(); ++ ++ xhr.onload = function() { ++ var scope = reqOpts.scope || this; ++ var result; ++ var errmsg; ++ ++ if (xhr.readyState === 4) { ++ var ctype = xhr.getResponseHeader('Content-Type'); ++ if (xhr.status === 200) { ++ if (ctype.match(/application\/json;/)) { ++ result = JSON.parse(xhr.responseText); ++ } else { ++ errmsg = 'got unexpected content type ' + ctype; ++ } ++ } else { ++ errmsg = 'Error ' + xhr.status + ': ' + xhr.statusText; ++ } ++ } else { ++ errmsg = 'Connection error - server offline?'; ++ } ++ ++ if (errmsg !== undefined) { ++ if (reqOpts.failure) { ++ reqOpts.failure.call(scope, errmsg); ++ } ++ } else { ++ if (reqOpts.success) { ++ reqOpts.success.call(scope, result); ++ } ++ } ++ if (reqOpts.callback) { ++ reqOpts.callback.call(scope, errmsg === undefined); ++ } ++ } ++ ++ var data = me.urlEncode(reqOpts.params || {}); ++ ++ if (reqOpts.method === 'GET') { ++ xhr.open(reqOpts.method, "/api2/json" + reqOpts.url + '?' + data); ++ } else { ++ xhr.open(reqOpts.method, "/api2/json" + reqOpts.url); ++ } ++ xhr.setRequestHeader('Cache-Control', 'no-cache'); ++ if (reqOpts.method === 'POST' || reqOpts.method === 'PUT') { ++ xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); ++ xhr.setRequestHeader('CSRFPreventionToken', PVE.CSRFPreventionToken); ++ xhr.send(data); ++ } else if (reqOpts.method === 'GET') { ++ xhr.send(); ++ } else { ++ throw "unknown method"; ++ } ++ }, ++ ++ pve_detect_migrated_vm: function() { ++ var me = this; ++ if (me.consoletype === 'kvm') { ++ // try to detect migrated VM ++ me.API2Request({ ++ url: '/cluster/resources', ++ method: 'GET', ++ success: function(result) { ++ var list = result.data; ++ list.every(function(item) { ++ if (item.type === 'qemu' && item.vmid == me.vmid) { ++ var url = "?" + me.urlEncode({ ++ console: me.consoletype, ++ novnc: 1, ++ vmid: me.vmid, ++ vmname: me.vmname, ++ node: item.node, ++ resize: me.resize ++ }); ++ location.href = url; ++ return false; // break ++ } ++ return true; ++ }); ++ } ++ }); ++ } else if(me.consoletype === 'lxc') { ++ // lxc restart migration can take a while, ++ // so we need to find out if we are really migrating ++ var migrating; ++ var check = setInterval(function() { ++ if (migrating === undefined || ++ migrating === true) { ++ // check (again) if migrating ++ me.UI.showStatus('Checking for Migration', 'warning', 5000); ++ me.API2Request({ ++ url: me.baseUrl + '/config', ++ method: 'GET', ++ success: function(result) { ++ var lock = result.data.lock; ++ if (lock == 'migrate') { ++ migrating = true; ++ me.UI.showStatus('Migration detected, waiting...', 'warning', 5000); ++ } else { ++ migrating = false; ++ } ++ }, ++ failure: function() { ++ migrating = false; ++ } ++ }); ++ } else { ++ // not migrating any more ++ me.UI.showStatus('Migration finished', 'warning'); ++ clearInterval(check); ++ me.API2Request({ ++ url: '/cluster/resources', ++ method: 'GET', ++ success: function(result) { ++ var list = result.data; ++ list.every(function(item) { ++ if (item.type === 'lxc' && item.vmid == me.vmid) { ++ var url = "?" + me.urlEncode({ ++ console: me.consoletype, ++ novnc: 1, ++ vmid: me.vmid, ++ vmname: me.vmname, ++ node: item.node, ++ resize: me.resize ++ }); ++ location.href = url; ++ return false; // break ++ } ++ return true; ++ }); ++ } ++ }); ++ } ++ }, 5000); ++ } ++ ++ }, ++ ++ pve_vm_command: function(cmd, params, reload) { ++ var me = this; ++ var baseUrl; ++ var confirmMsg = ""; ++ ++ switch(cmd) { ++ case "start": ++ reload = 1; ++ case "shutdown": ++ case "stop": ++ case "reset": ++ case "suspend": ++ case "resume": ++ confirmMsg = "Do you really want to " + cmd + " VM/CT {0}?"; ++ break; ++ case "reload": ++ location.reload(); ++ break; ++ default: ++ throw "implement me " + cmd; ++ } ++ ++ confirmMsg = confirmMsg.replace('{0}', me.vmid); ++ ++ if (confirmMsg !== "" && confirm(confirmMsg) !== true) { ++ return; ++ } ++ ++ me.UI.closePVECommandPanel(); ++ ++ if (me.consoletype === 'kvm') { ++ baseUrl = '/nodes/' + me.nodename + '/qemu/' + me.vmid; ++ } else if (me.consoletype === 'lxc') { ++ baseUrl = '/nodes/' + me.nodename + '/lxc/' + me.vmid; ++ } else { ++ throw "unknown VM type"; ++ } ++ ++ me.API2Request({ ++ params: params, ++ url: baseUrl + "/status/" + cmd, ++ method: 'POST', ++ failure: function(msg) { ++ me.UI.showStatus(msg, 'warning'); ++ }, ++ success: function() { ++ me.UI.showStatus("VM command '" + cmd +"' successful", 'normal'); ++ if (reload) { ++ setTimeout(function() { ++ location.reload(); ++ }, 1000); ++ }; ++ } ++ }); ++ }, ++ ++ addPVEHandlers: function() { ++ var me = this; ++ document.getElementById('pve_commands_button') ++ .addEventListener('click', me.UI.togglePVECommandPanel); ++ ++ // show/hide the buttons ++ document.getElementById('noVNC_settings_button') ++ .classList.add('noVNC_hidden'); ++ document.getElementById('noVNC_disconnect_button') ++ .classList.add('noVNC_hidden'); ++ if (me.consoletype === 'kvm') { ++ document.getElementById('noVNC_clipboard_button') ++ .classList.add('noVNC_hidden'); ++ } ++ ++ if (me.consoletype === 'shell' || me.consoletype === 'upgrade') { ++ document.getElementById('pve_commands_button') ++ .classList.add('noVNC_hidden'); ++ } ++ ++ // add command logic ++ var commandArray = [ ++ { cmd: 'start', kvm: 1, lxc: 1}, ++ { cmd: 'stop', kvm: 1, lxc: 1}, ++ { cmd: 'shutdown', kvm: 1, lxc: 1}, ++ { cmd: 'suspend', kvm: 1}, ++ { cmd: 'resume', kvm: 1}, ++ { cmd: 'reset', kvm: 1}, ++ { cmd: 'reload', kvm: 1, lxc: 1, shell: 1}, ++ ]; ++ ++ commandArray.forEach(function(item) { ++ var el = document.getElementById('pve_command_'+item.cmd); ++ if (!el) { ++ return; ++ } ++ ++ if (item[me.consoletype] === 1) { ++ el.onclick = function() { ++ me.pve_vm_command(item.cmd); ++ }; ++ } else { ++ el.classList.add('noVNC_hidden'); ++ } ++ }); ++ ++ //edge/ie11 quirk ++ var canvas = document.getElementById('noVNC_canvas'); ++ canvas.onclick = canvas.focus; ++ }, ++ ++ getFBSize: function() { ++ var oh; ++ var ow; ++ ++ if (window.innerHeight) { ++ oh = window.innerHeight; ++ ow = window.innerWidth; ++ } else if (document.documentElement && ++ document.documentElement.clientHeight) { ++ oh = document.documentElement.clientHeight; ++ ow = document.documentElement.clientWidth; ++ } else if (document.body) { ++ oh = document.body.clientHeight; ++ ow = document.body.clientWidth; ++ } else { ++ throw "can't get window size"; ++ } ++ ++ return { width: ow, height: oh }; ++ }, ++ ++ pveStart: function(callback) { ++ var me = this; ++ me.API2Request({ ++ url: me.url, ++ method: 'POST', ++ params: me.params, ++ success: function(result) { ++ var wsparams = me.urlEncode({ ++ port: result.data.port, ++ vncticket: result.data.ticket ++ }); ++ ++ document.getElementById('noVNC_password_input').value = result.data.ticket; ++ me.UI.updateSetting('path', 'api2/json' + me.baseUrl + '/vncwebsocket' + "?" + wsparams); ++ ++ callback(); ++ }, ++ failure: function(msg) { ++ me.UI.showStatus(msg, 'error'); ++ } ++ }); ++ }, ++ ++ updateFBSize: function(rfb, width, height, clip) { ++ var me = this; ++ console.log(arguments); ++ try { ++ // Note: window size must be even number for firefox ++ me.lastFBWidth = Math.floor((width + 1)/2)*2; ++ me.lastFBHeight = Math.floor((height + 1)/2)*2; ++ ++ if (me.sizeUpdateTimer !== undefined) { ++ clearInterval(me.sizeUpdateTimer); ++ } ++ if (clip) return; ++ ++ var update_size = function() { ++ // we do not want to resize if we are in fullscreen ++ if (document.fullscreenElement || // alternative standard method ++ document.mozFullScreenElement || // currently working methods ++ document.webkitFullscreenElement || ++ document.msFullscreenElement) { ++ return; ++ } ++ ++ var oldsize = me.getFBSize(); ++ var offsetw = me.lastFBWidth - oldsize.width; ++ var offseth = me.lastFBHeight - oldsize.height; ++ if (offsetw !== 0 || offseth !== 0) { ++ //console.log("try resize by " + offsetw + " " + offseth); ++ try { ++ window.resizeBy(offsetw, offseth); ++ // wait a little an then fix the scrollbars ++ // on chrome ++ setTimeout(function() { ++ me.UI.fixScrollbars(); ++ }, 100); ++ } catch (e) { ++ console.log('resizing did not work', e); ++ } ++ } ++ }; ++ ++ update_size(); ++ me.sizeUpdateTimer = setInterval(update_size, 1000); ++ ++ } catch(e) { ++ console.log(e); ++ } ++ }, ++}; +diff --git a/app/ui.js b/app/ui.js +index 73ad2b4..def0eda 100644 +--- a/app/ui.js ++++ b/app/ui.js +@@ -20,6 +20,7 @@ import keysyms from "../core/input/keysymdef.js"; + import RFB from "../core/rfb.js"; + import Display from "../core/display.js"; + import * as WebUtil from "./webutil.js"; ++import PVEUI from "./pve.js"; + + const UI = { + +@@ -63,6 +64,7 @@ const UI = { + // Render default UI and initialize settings menu + start: function(callback) { + ++ UI.PVE = new PVEUI(UI); + // Setup global variables first + UI.isSafari = (navigator.userAgent.indexOf('Safari') !== -1 && + navigator.userAgent.indexOf('Chrome') === -1); +@@ -95,6 +97,9 @@ const UI = { + UI.addConnectionControlHandlers(); + UI.addClipboardHandlers(); + UI.addSettingsHandlers(); ++ ++ // add pve specific event handlers ++ UI.PVE.addPVEHandlers(); + document.getElementById("noVNC_status") + .addEventListener('click', UI.hideStatus); + +@@ -103,11 +108,6 @@ const UI = { + + UI.openControlbar(); + +- // Show the connect panel on first load unless autoconnecting +- if (!autoconnect) { +- UI.openConnectPanel(); +- } +- + UI.updateViewClip(); + + UI.updateVisualState(); +@@ -115,17 +115,13 @@ const UI = { + document.getElementById('noVNC_setting_host').focus(); + document.documentElement.classList.remove("noVNC_loading"); + +- var autoconnect = WebUtil.getConfigVar('autoconnect', false); +- if (autoconnect === 'true' || autoconnect == '1') { +- autoconnect = true; +- UI.connect(); +- } else { +- autoconnect = false; +- } ++ UI.PVE.pveStart(function() { ++ UI.connect(); + +- if (typeof callback === "function") { +- callback(UI.rfb); +- } ++ if (typeof callback === "function") { ++ callback(UI.rfb); ++ } ++ }); + }, + + initFullscreen: function() { +@@ -170,10 +166,14 @@ const UI = { + /* Populate the controls if defaults are provided in the URL */ + UI.initSetting('host', window.location.hostname); + UI.initSetting('port', port); +- UI.initSetting('encrypt', (window.location.protocol === "https:")); ++ UI.initSetting('encrypt', true); + UI.initSetting('cursor', !isTouchDevice); + UI.initSetting('clip', false); +- UI.initSetting('resize', 'off'); ++ // we need updateSetting because ++ // otherwise we load from browser storage ++ // we want to overwrite the resize mode from url ++ var resize = WebUtil.getQueryVar('resize'); ++ UI.updateSetting('resize', resize); + UI.initSetting('shared', true); + UI.initSetting('view_only', false); + UI.initSetting('path', 'websockify'); +@@ -434,6 +434,7 @@ const UI = { + case 'connected': + UI.connected = true; + UI.inhibit_reconnect = false; ++ UI.pveAllowMigratedTest = true; + document.documentElement.classList.add("noVNC_connected"); + if (rfb && rfb.get_encrypt()) { + msg = _("Connected (encrypted) to ") + UI.desktopName; +@@ -449,6 +450,10 @@ const UI = { + break; + case 'disconnected': + UI.showStatus(_("Disconnected")); ++ if (UI.pveAllowMigratedTest === true) { ++ UI.pveAllowMigratedTest = false; ++ UI.PVE.pve_detect_migrated_vm(); ++ } + break; + default: + msg = "Invalid UI state"; +@@ -861,6 +866,7 @@ const UI = { + UI.closeXvpPanel(); + UI.closeClipboardPanel(); + UI.closeExtraKeys(); ++ UI.closePVECommandPanel(); + }, + + /* ------^------- +@@ -1033,9 +1039,15 @@ const UI = { + password = WebUtil.getConfigVar('password'); + } + +- if (password === null) { +- password = undefined; +- } ++ var password = document.getElementById('noVNC_password_input').value; ++ ++ if (!password) { ++ password = WebUtil.getConfigVar('password'); ++ } ++ ++ if (password === null) { ++ password = undefined; ++ } + + if ((!host) || (!port)) { + var msg = _("Must set host and port"); +@@ -1608,9 +1620,36 @@ const UI = { + /* ------^------- + * /EXTRA KEYS + * ============== +- * MISC ++ * PVE + * ------v------*/ + ++ togglePVECommandPanel: function() { ++ if (document.getElementById('pve_commands').classList.contains("noVNC_open")) { ++ UI.closePVECommandPanel(); ++ } else { ++ UI.openPVECommandPanel(); ++ } ++ }, ++ ++ openPVECommandPanel: function() { ++ var me = this; ++ UI.closeAllPanels(); ++ UI.openControlbar(); ++ ++ document.getElementById('pve_commands').classList.add("noVNC_open"); ++ document.getElementById('pve_commands_button').classList.add("noVNC_selected"); ++ }, ++ ++ closePVECommandPanel: function() { ++ document.getElementById('pve_commands').classList.remove("noVNC_open"); ++ document.getElementById('pve_commands_button').classList.remove("noVNC_selected"); ++ }, ++ ++/* ------^------- ++ * /PVE ++ * ============== ++ * MISC ++ * ------v------*/ + setMouseButton: function(num) { + var view_only = UI.rfb.get_view_only(); + if (UI.rfb && !view_only) { +@@ -1658,8 +1697,16 @@ const UI = { + }, + + updateSessionSize: function(rfb, width, height) { +- UI.updateViewClip(); +- UI.fixScrollbars(); ++ var resize = UI.getSetting('resize'); ++ ++ if (resize == 'null') { ++ var clip = UI.getSetting('clip'); ++ UI.PVE.updateFBSize(rfb, width, height, clip); ++ } ++ ++ UI.applyResizeMode(); ++ UI.updateViewClip(); ++ UI.updateViewDrag(); + }, + + fixScrollbars: function() { +@@ -1704,7 +1751,7 @@ const UI = { + }, + + /* ------^------- +- * /MISC ++ * /MISC + * ============== + */ + }; +-- +2.11.0 + diff --git a/debian/patches/0002-change-scaling-when-toggling-fullscreen.patch b/debian/patches/0002-change-scaling-when-toggling-fullscreen.patch new file mode 100644 index 0000000..5bf888b --- /dev/null +++ b/debian/patches/0002-change-scaling-when-toggling-fullscreen.patch @@ -0,0 +1,45 @@ +From c1073cb0f19ebd64f6ee4b221b5aa06a2f8e5c99 Mon Sep 17 00:00:00 2001 +From: Dominik Csapak <d.csa...@proxmox.com> +Date: Thu, 11 May 2017 10:34:10 +0200 +Subject: [PATCH 2/9] change scaling when toggling fullscreen + +when activating fullscreen, we change the scaling to 'scale', +and changing it back when leaving fullscreen + +Signed-off-by: Dominik Csapak <d.csa...@proxmox.com> +--- + app/ui.js | 11 +++++++++++ + 1 file changed, 11 insertions(+) + +diff --git a/app/ui.js b/app/ui.js +index def0eda..d4b0291 100644 +--- a/app/ui.js ++++ b/app/ui.js +@@ -1174,6 +1174,15 @@ const UI = { + } else if (document.msExitFullscreen) { + document.msExitFullscreen(); + } ++ ++ // when changing from fullscreen to window, ++ // re enable auto resize ++ if (WebUtil.getQueryVar('resize') === null) { ++ UI.updateSetting('resize', null); ++ setTimeout(function() { ++ UI.fixScrollbars(); ++ }, 100); ++ } + } else { + if (document.documentElement.requestFullscreen) { + document.documentElement.requestFullscreen(); +@@ -1184,6 +1193,8 @@ const UI = { + } else if (document.body.msRequestFullscreen) { + document.body.msRequestFullscreen(); + } ++ // we want scaling in fullscreen mode ++ UI.updateSetting('resize', 'scale'); + } + UI.enableDisableViewClip(); + UI.updateFullscreenButton(); +-- +2.11.0 + diff --git a/debian/patches/0003-add-pve-style.patch b/debian/patches/0003-add-pve-style.patch new file mode 100644 index 0000000..394f8dd --- /dev/null +++ b/debian/patches/0003-add-pve-style.patch @@ -0,0 +1,75 @@ +From 1cdfeafd561bfe4d9acb7ef8a838d82c64f9c1e5 Mon Sep 17 00:00:00 2001 +From: Dominik Csapak <d.csa...@proxmox.com> +Date: Tue, 13 Dec 2016 16:03:41 +0100 +Subject: [PATCH 3/9] add pve style + +this adds the custom pve style (based on black.css) + +we hide the connect button, and add custom colors, +and fix the z-index of the connect overlay + +Signed-off-by: Dominik Csapak <d.csa...@proxmox.com> +--- + app/styles/pve.css | 37 +++++++++++++++++++++++++++++++++++++ + vnc.html | 1 + + 2 files changed, 38 insertions(+) + create mode 100644 app/styles/pve.css + +diff --git a/app/styles/pve.css b/app/styles/pve.css +new file mode 100644 +index 0000000..35002fe +--- /dev/null ++++ b/app/styles/pve.css +@@ -0,0 +1,37 @@ ++/* ++ * noVNC black CSS ++ * Copyright (C) 2012 Joel Martin ++ * Copyright (C) 2013 Samuel Mannehed for Cendio AB ++ * noVNC is licensed under the MPL 2.0 (see LICENSE.txt) ++ * This file is licensed under the 2-Clause BSD license (see LICENSE.txt). ++ */ ++ ++.noVNC_panel { ++ border: 0px; ++ background:#4c4c4c;; ++ color:#fff; ++} ++ ++#noVNC_control_bar, #noVNC_control_bar_handle, .noVNC_panel .noVNC_heading { ++ background: #4c4c4c; ++} ++ ++.noVNC_button.noVNC_selected { ++ background: #3892d4; ++} ++ ++#pve_commands > input[type=button] { ++ width: 100%; ++} ++ ++#noVNC_canvas:focus { ++ outline: none; ++} ++ ++#noVNC_transition { ++ z-index: 0; ++} ++ ++#noVNC_connect_button { ++ display: none; ++} +diff --git a/vnc.html b/vnc.html +index 6cb4be4..98a9952 100644 +--- a/vnc.html ++++ b/vnc.html +@@ -53,6 +53,7 @@ + + <!-- Stylesheets --> + <link rel="stylesheet" href="app/styles/base.css" /> ++ <link rel="stylesheet" href="/novnc/app/styles/pve.css" /> + + <!-- + <script type='text/javascript' +-- +2.11.0 + diff --git a/debian/patches/0004-remove-vnc-logos.patch b/debian/patches/0004-remove-vnc-logos.patch new file mode 100644 index 0000000..2577119 --- /dev/null +++ b/debian/patches/0004-remove-vnc-logos.patch @@ -0,0 +1,54 @@ +From a28b283a224990e1b46f304d9a2067afa7633d63 Mon Sep 17 00:00:00 2001 +From: Dominik Csapak <d.csa...@proxmox.com> +Date: Fri, 20 Jan 2017 10:35:05 +0100 +Subject: [PATCH 4/9] remove vnc logos + +to show the pve icon instead + +Signed-off-by: Dominik Csapak <d.csa...@proxmox.com> +--- + vnc.html | 24 ------------------------ + 1 file changed, 24 deletions(-) + +diff --git a/vnc.html b/vnc.html +index 98a9952..cd8e594 100644 +--- a/vnc.html ++++ b/vnc.html +@@ -23,34 +23,10 @@ + Remove this if you use the .htaccess --> + <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /> + +- <!-- Icons (see Makefile for what the sizes are for) --> +- <link rel="icon" sizes="16x16" type="image/png" href="app/images/icons/novnc-16x16.png"> +- <link rel="icon" sizes="24x24" type="image/png" href="app/images/icons/novnc-24x24.png"> +- <link rel="icon" sizes="32x32" type="image/png" href="app/images/icons/novnc-32x32.png"> +- <link rel="icon" sizes="48x48" type="image/png" href="app/images/icons/novnc-48x48.png"> +- <link rel="icon" sizes="60x60" type="image/png" href="app/images/icons/novnc-60x60.png"> +- <link rel="icon" sizes="64x64" type="image/png" href="app/images/icons/novnc-64x64.png"> +- <link rel="icon" sizes="72x72" type="image/png" href="app/images/icons/novnc-72x72.png"> +- <link rel="icon" sizes="76x76" type="image/png" href="app/images/icons/novnc-76x76.png"> +- <link rel="icon" sizes="96x96" type="image/png" href="app/images/icons/novnc-96x96.png"> +- <link rel="icon" sizes="120x120" type="image/png" href="app/images/icons/novnc-120x120.png"> +- <link rel="icon" sizes="144x144" type="image/png" href="app/images/icons/novnc-144x144.png"> +- <link rel="icon" sizes="152x152" type="image/png" href="app/images/icons/novnc-152x152.png"> +- <link rel="icon" sizes="192x192" type="image/png" href="app/images/icons/novnc-192x192.png"> +- <link rel="icon" sizes="any" type="image/svg+xml" href="app/images/icons/novnc-icon.svg"> +- <!-- Repeated last so that legacy handling will pick this --> +- <link rel="icon" sizes="16x16" type="image/png" href="app/images/icons/novnc-16x16.png"> +- + <!-- Apple iOS Safari settings --> + <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> + <meta name="apple-mobile-web-app-capable" content="yes" /> + <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> +- <!-- Home Screen Icons (favourites and bookmarks use the normal icons) --> +- <link rel="apple-touch-icon" sizes="60x60" type="image/png" href="app/images/icons/novnc-60x60.png"> +- <link rel="apple-touch-icon" sizes="76x76" type="image/png" href="app/images/icons/novnc-76x76.png"> +- <link rel="apple-touch-icon" sizes="120x120" type="image/png" href="app/images/icons/novnc-120x120.png"> +- <link rel="apple-touch-icon" sizes="152x152" type="image/png" href="app/images/icons/novnc-152x152.png"> +- + <!-- Stylesheets --> + <link rel="stylesheet" href="app/styles/base.css" /> + <link rel="stylesheet" href="/novnc/app/styles/pve.css" /> +-- +2.11.0 + diff --git a/debian/patches/0005-change-src-directory-for-images-js-files.patch b/debian/patches/0005-change-src-directory-for-images-js-files.patch new file mode 100644 index 0000000..43847c8 --- /dev/null +++ b/debian/patches/0005-change-src-directory-for-images-js-files.patch @@ -0,0 +1,232 @@ +From 0b7322fc05361366fc4e24e6d5b9d8e2254ace40 Mon Sep 17 00:00:00 2001 +From: Dominik Csapak <d.csa...@proxmox.com> +Date: Tue, 17 Jan 2017 17:24:03 +0100 +Subject: [PATCH 5/9] change src directory for images/js files + +since they will be in /novnc/ +also change the directory in the build script + +Signed-off-by: Dominik Csapak <d.csa...@proxmox.com> +--- + app/ui.js | 2 +- + utils/use_require_helpers.js | 2 +- + vnc.html | 56 ++++++++++++++++++++++---------------------- + 3 files changed, 30 insertions(+), 30 deletions(-) + +diff --git a/app/ui.js b/app/ui.js +index d4b0291..d32c789 100644 +--- a/app/ui.js ++++ b/app/ui.js +@@ -1771,7 +1771,7 @@ const UI = { + var LINGUAS = ["de", "el", "nl", "sv"]; + l10n.setup(LINGUAS); + if (l10n.language !== "en" && l10n.dictionary === undefined) { +- WebUtil.fetchJSON('app/locale/' + l10n.language + '.json', function (translations) { ++ WebUtil.fetchJSON('/novnc/app/locale/' + l10n.language + '.json', function (translations) { + l10n.dictionary = translations; + + // wait for translations to load before loading the UI +diff --git a/utils/use_require_helpers.js b/utils/use_require_helpers.js +index 990fb4d..0f44e1c 100644 +--- a/utils/use_require_helpers.js ++++ b/utils/use_require_helpers.js +@@ -22,7 +22,7 @@ module.exports = { + var browserify = require('browserify'); + var b = browserify(path.join(base_out_path, 'app/ui.js'), {}); + b.bundle().pipe(fs.createWriteStream(out_path)); +- return `<script src="${path.relative(base_out_path, out_path)}"></script>`; ++ return `<script src="/novnc/${path.relative(base_out_path, out_path)}"></script>`; + }, + noCopyOverride: () => {}, + }, +diff --git a/vnc.html b/vnc.html +index cd8e594..23fc0e4 100644 +--- a/vnc.html ++++ b/vnc.html +@@ -28,7 +28,7 @@ + <meta name="apple-mobile-web-app-capable" content="yes" /> + <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> + <!-- Stylesheets --> +- <link rel="stylesheet" href="app/styles/base.css" /> ++ <link rel="stylesheet" href="/novnc/app/styles/base.css" /> + <link rel="stylesheet" href="/novnc/app/styles/pve.css" /> + + <!-- +@@ -37,11 +37,11 @@ + --> + + <!-- this is included as a normal file in order to catch script-loading errors as well --> +- <script type="text/javascript" src="app/error-handler.js"></script> ++ <script type="text/javascript" src="/novnc/app/error-handler.js"></script> + + <!-- begin scripts --> + <!-- promise polyfills promises for IE11 --> +- <script src="vendor/promise.js"></script> ++ <script src="/novnc/vendor/promise.js"></script> + <!-- ES2015/ES6 modules polyfill --> + <script type="module"> + window._noVNC_has_module_support = true; +@@ -50,12 +50,12 @@ + window.addEventListener("load", function() { + if (window._noVNC_has_module_support) return; + var loader = document.createElement("script"); +- loader.src = "vendor/browser-es-module-loader/dist/browser-es-module-loader.js"; ++ loader.src = "/novnc/vendor/browser-es-module-loader/dist/browser-es-module-loader.js"; + document.head.appendChild(loader); + }); + </script> + <!-- actual script modules --> +- <script type="module" src="app/ui.js"></script> ++ <script type="module" src="/novnc/app/ui.js"></script> + <!-- end scripts --> + </head> + +@@ -80,49 +80,49 @@ + <h1 class="noVNC_logo" translate="no"><span>no</span><br />VNC</h1> + + <!-- Drag/Pan the viewport --> +- <input type="image" alt="viewport drag" src="app/images/drag.svg" ++ <input type="image" alt="viewport drag" src="/novnc/app/images/drag.svg" + id="noVNC_view_drag_button" class="noVNC_button noVNC_hidden" + title="Move/Drag Viewport" /> + + <!--noVNC Touch Device only buttons--> + <div id="noVNC_mobile_buttons"> +- <input type="image" alt="No mousebutton" src="app/images/mouse_none.svg" ++ <input type="image" alt="No mousebutton" src="/novnc/app/images/mouse_none.svg" + id="noVNC_mouse_button0" class="noVNC_button" + title="Active Mouse Button"/> +- <input type="image" alt="Left mousebutton" src="app/images/mouse_left.svg" ++ <input type="image" alt="Left mousebutton" src="/novnc/app/images/mouse_left.svg" + id="noVNC_mouse_button1" class="noVNC_button" + title="Active Mouse Button"/> +- <input type="image" alt="Middle mousebutton" src="app/images/mouse_middle.svg" ++ <input type="image" alt="Middle mousebutton" src="/novnc/app/images/mouse_middle.svg" + id="noVNC_mouse_button2" class="noVNC_button" + title="Active Mouse Button"/> +- <input type="image" alt="Right mousebutton" src="app/images/mouse_right.svg" ++ <input type="image" alt="Right mousebutton" src="/novnc/app/images/mouse_right.svg" + id="noVNC_mouse_button4" class="noVNC_button" + title="Active Mouse Button"/> +- <input type="image" alt="Keyboard" src="app/images/keyboard.svg" ++ <input type="image" alt="Keyboard" src="/novnc/app/images/keyboard.svg" + id="noVNC_keyboard_button" class="noVNC_button" + value="Keyboard" title="Show Keyboard" /> + </div> + + <!-- Extra manual keys --> + <div id="noVNC_extra_keys"> +- <input type="image" alt="Extra keys" src="app/images/toggleextrakeys.svg" ++ <input type="image" alt="Extra keys" src="/novnc/app/images/toggleextrakeys.svg" + id="noVNC_toggle_extra_keys_button" class="noVNC_button" + title="Show Extra Keys"/> + <div class="noVNC_vcenter"> + <div id="noVNC_modifiers" class="noVNC_panel"> +- <input type="image" alt="Ctrl" src="app/images/ctrl.svg" ++ <input type="image" alt="Ctrl" src="/novnc/app/images/ctrl.svg" + id="noVNC_toggle_ctrl_button" class="noVNC_button" + title="Toggle Ctrl"/> +- <input type="image" alt="Alt" src="app/images/alt.svg" ++ <input type="image" alt="Alt" src="/novnc/app/images/alt.svg" + id="noVNC_toggle_alt_button" class="noVNC_button" + title="Toggle Alt"/> +- <input type="image" alt="Tab" src="app/images/tab.svg" ++ <input type="image" alt="Tab" src="/novnc/app/images/tab.svg" + id="noVNC_send_tab_button" class="noVNC_button" + title="Send Tab"/> +- <input type="image" alt="Esc" src="app/images/esc.svg" ++ <input type="image" alt="Esc" src="/novnc/app/images/esc.svg" + id="noVNC_send_esc_button" class="noVNC_button" + title="Send Escape"/> +- <input type="image" alt="Ctrl+Alt+Del" src="app/images/ctrlaltdel.svg" ++ <input type="image" alt="Ctrl+Alt+Del" src="/novnc/app/images/ctrlaltdel.svg" + id="noVNC_send_ctrl_alt_del_button" class="noVNC_button" + title="Send Ctrl-Alt-Del" /> + </div> +@@ -130,13 +130,13 @@ + </div> + + <!-- XVP Shutdown/Reboot --> +- <input type="image" alt="Shutdown/Reboot" src="app/images/power.svg" ++ <input type="image" alt="Shutdown/Reboot" src="/novnc/app/images/power.svg" + id="noVNC_xvp_button" class="noVNC_button" + title="Shutdown/Reboot..." /> + <div class="noVNC_vcenter"> + <div id="noVNC_xvp" class="noVNC_panel"> + <div class="noVNC_heading"> +- <img src="app/images/power.svg"> Power ++ <img src="/novnc/app/images/power.svg"> Power + </div> + <input type="button" id="noVNC_xvp_shutdown_button" value="Shutdown" /> + <input type="button" id="noVNC_xvp_reboot_button" value="Reboot" /> +@@ -145,13 +145,13 @@ + </div> + + <!-- Clipboard --> +- <input type="image" alt="Clipboard" src="app/images/clipboard.svg" ++ <input type="image" alt="Clipboard" src="/novnc/app/images/clipboard.svg" + id="noVNC_clipboard_button" class="noVNC_button" + title="Clipboard" /> + <div class="noVNC_vcenter"> + <div id="noVNC_clipboard" class="noVNC_panel"> + <div class="noVNC_heading"> +- <img src="app/images/clipboard.svg"> Clipboard ++ <img src="/novnc/app/images/clipboard.svg"> Clipboard + </div> + <textarea id="noVNC_clipboard_text" rows=5></textarea> + <br /> +@@ -161,19 +161,19 @@ + </div> + + <!-- Toggle fullscreen --> +- <input type="image" alt="Fullscreen" src="app/images/fullscreen.svg" ++ <input type="image" alt="Fullscreen" src="/novnc/app/images/fullscreen.svg" + id="noVNC_fullscreen_button" class="noVNC_button noVNC_hidden" + title="Fullscreen" /> + + <!-- Settings --> +- <input type="image" alt="Settings" src="app/images/settings.svg" ++ <input type="image" alt="Settings" src="/novnc/app/images/settings.svg" + id="noVNC_settings_button" class="noVNC_button" + title="Settings" /> + <div class="noVNC_vcenter"> + <div id="noVNC_settings" class="noVNC_panel"> + <ul> + <li class="noVNC_heading"> +- <img src="app/images/settings.svg"> Settings ++ <img src="/novnc/app/images/settings.svg"> Settings + </li> + <li> + <label><input id="noVNC_setting_shared" type="checkbox" /> Shared Mode</label> +@@ -249,7 +249,7 @@ + </div> + + <!-- Connection Controls --> +- <input type="image" alt="Disconnect" src="app/images/disconnect.svg" ++ <input type="image" alt="Disconnect" src="/novnc/app/images/disconnect.svg" + id="noVNC_disconnect_button" class="noVNC_button" + title="Disconnect" /> + +@@ -266,7 +266,7 @@ + <div id="noVNC_connect_dlg"> + <div class="noVNC_logo" translate="no"><span>no</span>VNC</div> + <div id="noVNC_connect_button"><div> +- <img src="app/images/connect.svg"> Connect ++ <img src="/novnc/app/images/connect.svg"> Connect + </div></div> + </div> + </div> +@@ -314,8 +314,8 @@ + </div> + + <audio id="noVNC_bell"> +- <source src="app/sounds/bell.oga" type="audio/ogg"> +- <source src="app/sounds/bell.mp3" type="audio/mpeg"> ++ <source src="/novnc/app/sounds/bell.oga" type="audio/ogg"> ++ <source src="/novnc/app/sounds/bell.mp3" type="audio/mpeg"> + </audio> + </body> + </html> +-- +2.11.0 + diff --git a/debian/patches/0006-add-pve-vnc-commands.patch b/debian/patches/0006-add-pve-vnc-commands.patch new file mode 100644 index 0000000..3b83959 --- /dev/null +++ b/debian/patches/0006-add-pve-vnc-commands.patch @@ -0,0 +1,44 @@ +From 1a042fcad1f9242cafbb589a1da5b8b270e8b8bb Mon Sep 17 00:00:00 2001 +From: Dominik Csapak <d.csa...@proxmox.com> +Date: Fri, 20 Jan 2017 10:35:43 +0100 +Subject: [PATCH 6/9] add pve vnc commands + +Signed-off-by: Dominik Csapak <d.csa...@proxmox.com> +--- + vnc.html | 20 ++++++++++++++++++++ + 1 file changed, 20 insertions(+) + +diff --git a/vnc.html b/vnc.html +index 23fc0e4..e789d04 100644 +--- a/vnc.html ++++ b/vnc.html +@@ -165,6 +165,26 @@ + id="noVNC_fullscreen_button" class="noVNC_button noVNC_hidden" + title="Fullscreen" /> + ++ <!-- PVE Commands --> ++ <input type="image" alt="Commands" src="/novnc/app/images/power.svg" ++ id="pve_commands_button" class="noVNC_button" ++ title="Commands" /> ++ ++ <div class="noVNC_vcenter"> ++ <div id="pve_commands" class="noVNC_panel"> ++ <div class="noVNC_heading"> ++ <img src="/novnc/app/images/power.svg"> Commands ++ </div> ++ <input id="pve_command_start" type="button" value="Start" /> ++ <input id="pve_command_shutdown" type="button" value="Shutdown" /> ++ <input id="pve_command_stop" type="button" value="Stop" /> ++ <input id="pve_command_reset" type="button" value="Reset" /> ++ <input id="pve_command_suspend" type="button" value="Suspend" /> ++ <input id="pve_command_resume" type="button" value="Resume" /> ++ <input id="pve_command_reload" type="button" value="Reload" /> ++ </div> ++ </div> ++ + <!-- Settings --> + <input type="image" alt="Settings" src="/novnc/app/images/settings.svg" + id="noVNC_settings_button" class="noVNC_button" +-- +2.11.0 + diff --git a/debian/patches/0007-add-replaceable-snippets-in-vnc.html.patch b/debian/patches/0007-add-replaceable-snippets-in-vnc.html.patch new file mode 100644 index 0000000..6ea98a1 --- /dev/null +++ b/debian/patches/0007-add-replaceable-snippets-in-vnc.html.patch @@ -0,0 +1,41 @@ +From c3d885b78fd816201ed485eb7a681ce1fcf3a122 Mon Sep 17 00:00:00 2001 +From: Dominik Csapak <d.csa...@proxmox.com> +Date: Fri, 20 Jan 2017 10:16:09 +0100 +Subject: [PATCH 7/9] add replaceable snippets in vnc.html + +so that we can insert the username/csrftoken via search/replace + +Signed-off-by: Dominik Csapak <d.csa...@proxmox.com> +--- + vnc.html | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +diff --git a/vnc.html b/vnc.html +index e789d04..1423f78 100644 +--- a/vnc.html ++++ b/vnc.html +@@ -15,7 +15,7 @@ + or the fragment: + http://example.com/#host=HOST&port=PORT&encrypt=1 + --> +- <title>noVNC</title> ++ <title>[% nodename %] - Proxmox Console</title> + + <meta charset="utf-8" /> + +@@ -38,6 +38,12 @@ + + <!-- this is included as a normal file in order to catch script-loading errors as well --> + <script type="text/javascript" src="/novnc/app/error-handler.js"></script> ++ <script type="text/javascript"> ++ if (typeof(PVE) === 'undefined') PVE = {}; ++ PVE.UserName = '[% username %]'; ++ PVE.CSRFPreventionToken = '[% token %]'; ++ INCLUDE_URI='/novnc/include'; ++ </script> + + <!-- begin scripts --> + <!-- promise polyfills promises for IE11 --> +-- +2.11.0 + diff --git a/debian/patches/0008-focus-canvas-after-load.patch b/debian/patches/0008-focus-canvas-after-load.patch new file mode 100644 index 0000000..4c38bbb --- /dev/null +++ b/debian/patches/0008-focus-canvas-after-load.patch @@ -0,0 +1,43 @@ +From b97c51d6a2ca70a5e60209cd7ab16563455b8d13 Mon Sep 17 00:00:00 2001 +From: Dominik Csapak <d.csa...@proxmox.com> +Date: Wed, 14 Dec 2016 08:40:02 +0100 +Subject: [PATCH 8/9] focus canvas after load + +or else in some browsers, the canvas does not get the focus after +loading in an iframe + +Signed-off-by: Dominik Csapak <d.csa...@proxmox.com> +--- + app/ui.js | 2 ++ + vnc.html | 2 +- + 2 files changed, 3 insertions(+), 1 deletion(-) + +diff --git a/app/ui.js b/app/ui.js +index d32c789..ed2897b 100644 +--- a/app/ui.js ++++ b/app/ui.js +@@ -442,6 +442,8 @@ const UI = { + msg = _("Connected (unencrypted) to ") + UI.desktopName; + } + UI.showStatus(msg); ++ rfb.get_keyboard().set_focused(true); ++ document.getElementById("noVNC_canvas").focus(); + break; + case 'disconnecting': + UI.connected = false; +diff --git a/vnc.html b/vnc.html +index 1423f78..ff70e0c 100644 +--- a/vnc.html ++++ b/vnc.html +@@ -332,7 +332,7 @@ + autocorrect="off" autocomplete="off" spellcheck="false" + mozactionhint="Enter"></textarea> + +- <canvas id="noVNC_canvas" width="0" height="0"> ++ <canvas id="noVNC_canvas" width="0" height="0" tabindex="0"> + Canvas not supported. + </canvas> + </div> +-- +2.11.0 + diff --git a/debian/patches/0009-decrease-animation-time.patch b/debian/patches/0009-decrease-animation-time.patch new file mode 100644 index 0000000..ff9ba1a --- /dev/null +++ b/debian/patches/0009-decrease-animation-time.patch @@ -0,0 +1,73 @@ +From 3f13289a9f895b1ba86ad61ed085390bffda3e3a Mon Sep 17 00:00:00 2001 +From: Dominik Csapak <d.csa...@proxmox.com> +Date: Fri, 20 Jan 2017 10:55:49 +0100 +Subject: [PATCH 9/9] decrease animation time + +because 0.5s is too long + +Signed-off-by: Dominik Csapak <d.csa...@proxmox.com> +--- + app/styles/base.css | 12 ++++++------ + 1 file changed, 6 insertions(+), 6 deletions(-) + +diff --git a/app/styles/base.css b/app/styles/base.css +index ce31cf7..6db4467 100644 +--- a/app/styles/base.css ++++ b/app/styles/base.css +@@ -283,7 +283,7 @@ select:active { + position: fixed; + z-index: 10; + +- transition: 0.5s ease-in-out; ++ transition: 0.1s ease-in-out; + + /* Edge misrenders animations wihthout this */ + transform: translateX(0); +@@ -300,7 +300,7 @@ select:active { + position: relative; + left: -100%; + +- transition: 0.5s ease-in-out; ++ transition: 0.1s ease-in-out; + + background-color: rgb(110, 132, 163); + border-radius: 0 10px 10px 0; +@@ -318,7 +318,7 @@ select:active { + height: 100%; + width: 30px; + left: -30px; +- transition: box-shadow 0.5s ease-in-out; ++ transition: box-shadow 0.1s ease-in-out; + } + #noVNC_control_bar.noVNC_open::before { + box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5); +@@ -352,7 +352,7 @@ select:active { + } + #noVNC_control_bar_handle:after { + content: ""; +- transition: transform 0.5s ease-in-out; ++ transition: transform 0.1s ease-in-out; + background: url("../images/handle.svg"); + position: absolute; + top: 22px; /* (50px-6px)/2 */ +@@ -440,7 +440,7 @@ select:active { + .noVNC_panel { + transform: translateX(25px); + +- transition: 0.5s ease-in-out; ++ transition: 0.25s ease-in-out; + + max-height: 100vh; /* Chrome is buggy with 100% */ + overflow-x: hidden; +@@ -617,7 +617,7 @@ select:active { + + cursor: pointer; + +- transition: 0.5s ease-in-out; ++ transition: 0.1s ease-in-out; + + visibility: hidden; + opacity: 0; +-- +2.11.0 + -- 2.11.0 _______________________________________________ pve-devel mailing list pve-devel@pve.proxmox.com https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel