Implement KVM in webui
This patchset adds the infrastructure to allow KVM sessions
through the webui. A websocket capable VNC/RFB connection
on the BMC is needed for KVM sessions.
To access, navigate to Server control -> KVM.
Tested: Ran obmc-ikvm on the BMC, added a KVM Handler to
Phosphor Rest Server, and was able to establish a
KVM session in the webui on a Witherspoon.
Change-Id: I7dda5bec41d270ae8d0913697714d4df4ec3a257
Signed-off-by: Ed Tanous <ed.tanous@intel.com>
Signed-off-by: Gunnar Mills <gmills@us.ibm.com>
diff --git a/app/common/directives/app-navigation.html b/app/common/directives/app-navigation.html
index a45a24b..e54b236 100644
--- a/app/common/directives/app-navigation.html
+++ b/app/common/directives/app-navigation.html
@@ -87,19 +87,21 @@
<a href="#/server-control/bmc-reboot" tabindex="13" ng-click="closeSubnav()">Reboot BMC</a></li>
<li ng-class="{'active': (path == '/server-control/remote-console')}">
<a href="#/server-control/remote-console" tabindex="14" ng-click="closeSubnav()">Serial over LAN console</a></li>
+ <li ng-class="{'active': (path == '/server-control/kvm')}">
+ <a href="#/server-control/kvm" tabindex="15" ng-click="closeSubnav()">KVM</a></li>
</ul>
<ul class="nav__second-level btn-firmware" ng-style="navStyle" ng-class="{opened: (showSubMenu && firstLevel == 'configuration')}">
<li ng-class="{'active': (path == '/configuration' || path == '/configuration/network')}">
- <a href="#/configuration/network" tabindex="15" ng-click="closeSubnav()">Network settings</a></li>
+ <a href="#/configuration/network" tabindex="16" ng-click="closeSubnav()">Network settings</a></li>
<li ng-class="{'active': (path == '/configuration' || path == '/configuration/snmp')}">
- <a href="#/configuration/snmp" tabindex="16" ng-click="closeSubnav()">SNMP settings</a></li>
+ <a href="#/configuration/snmp" tabindex="17" ng-click="closeSubnav()">SNMP settings</a></li>
<li ng-class="{'active': (path == '/configuration' || path == '/configuration/firmware')}">
- <a href="#/configuration/firmware" tabindex="17" ng-click="closeSubnav()">Firmware</a></li>
+ <a href="#/configuration/firmware" tabindex="18" ng-click="closeSubnav()">Firmware</a></li>
<li ng-class="{'active': (path == '/configuration' || path == '/configuration/date-time')}">
- <a href="#/configuration/date-time" tabindex="18" ng-click="closeSubnav()">Date and time settings</a></li>
+ <a href="#/configuration/date-time" tabindex="19" ng-click="closeSubnav()">Date and time settings</a></li>
</ul>
<ul class="nav__second-level btn-users" ng-style="navStyle" ng-class="{opened: (showSubMenu && firstLevel == 'users')}">
<li ng-class="{'active': (path == '/users' || path == '/users/manage-accounts')}">
- <a href="#/users/manage-accounts" tabindex="19" ng-click="closeSubnav()">Manage user account</a></li>
+ <a href="#/users/manage-accounts" tabindex="20" ng-click="closeSubnav()">Manage user account</a></li>
</ul>
</nav>
diff --git a/app/index.js b/app/index.js
index 38df0e9..a0dde4d 100644
--- a/app/index.js
+++ b/app/index.js
@@ -69,6 +69,7 @@
import power_usage_controller from './server-control/controllers/power-usage-controller.js';
import remote_console_window_controller from './server-control/controllers/remote-console-window-controller.js';
import server_led_controller from './server-control/controllers/server-led-controller.js';
+import kvm_controller from './server-control/controllers/kvm-controller.js';
import server_health_index from './server-health/index.js';
import inventory_overview_controller from './server-health/controllers/inventory-overview-controller.js';
diff --git a/app/server-control/controllers/kvm-controller.html b/app/server-control/controllers/kvm-controller.html
new file mode 100644
index 0000000..40e4d97
--- /dev/null
+++ b/app/server-control/controllers/kvm-controller.html
@@ -0,0 +1,5 @@
+<div id="noVNC_container">
+ <div id="noVNC_status_bar">
+ <div id="noVNC_left_dummy_elem"></div>
+ </div>
+</div>
diff --git a/app/server-control/controllers/kvm-controller.js b/app/server-control/controllers/kvm-controller.js
new file mode 100644
index 0000000..a43f169
--- /dev/null
+++ b/app/server-control/controllers/kvm-controller.js
@@ -0,0 +1,55 @@
+/**
+ * Controller for KVM (Kernel-based Virtual Machine)
+ *
+ * @module app/serverControl
+ * @exports kvmController
+ * @name kvmController
+ */
+
+import RFB from '@novnc/novnc/core/rfb.js';
+
+window.angular && (function(angular) {
+ 'use strict';
+
+ angular.module('app.serverControl').controller('kvmController', [
+ '$scope', '$location', '$log',
+ function($scope, $location, $log) {
+ var rfb;
+
+ $scope.$on('$destroy', function() {
+ if (rfb) {
+ rfb.disconnect();
+ }
+ });
+
+ function sendCtrlAltDel() {
+ rfb.sendCtrlAltDel();
+ return false;
+ };
+
+ function connected(e) {
+ $log.debug('RFB Connected');
+ }
+ function disconnected(e) {
+ $log.debug('RFB disconnected');
+ }
+
+ var host = $location.host();
+ var port = $location.port();
+ var target =
+ angular.element(document.querySelector('#noVNC_container'))[0];
+
+ try {
+ rfb = new RFB(target, 'wss://' + host + ':' + port + '/kvm/0', {});
+
+ rfb.addEventListener('connect', connected);
+ rfb.addEventListener('disconnect', disconnected);
+ } catch (exc) {
+ $log.error(exc);
+ updateState(
+ null, 'fatal', null, 'Unable to create RFB client -- ' + exc);
+ return; // don't continue trying to connect
+ };
+ }
+ ]);
+})(angular);
diff --git a/app/server-control/index.js b/app/server-control/index.js
index 739bd1e..1b8aad5 100644
--- a/app/server-control/index.js
+++ b/app/server-control/index.js
@@ -48,6 +48,11 @@
'controller': 'remoteConsoleWindowController',
authenticated: true
})
+ .when('/server-control/kvm', {
+ 'template': require('./controllers/kvm-controller.html'),
+ 'controller': 'kvmController',
+ authenticated: true
+ })
.when('/server-control', {
'template':
require('./controllers/power-operations-controller.html'),
diff --git a/app/server-control/styles/index.scss b/app/server-control/styles/index.scss
index f6b15ab..5e8a995 100644
--- a/app/server-control/styles/index.scss
+++ b/app/server-control/styles/index.scss
@@ -3,3 +3,4 @@
@import "./remote-console.scss";
@import "./server-led.scss";
@import "./power-usage.scss";
+@import "./kvm.scss";
diff --git a/app/server-control/styles/kvm.scss b/app/server-control/styles/kvm.scss
new file mode 100644
index 0000000..2f9e2c0
--- /dev/null
+++ b/app/server-control/styles/kvm.scss
@@ -0,0 +1,11 @@
+
+.noNVC_shown {
+ display: inline;
+}
+.noVNC_hidden {
+ display: none;
+}
+
+#noVNC_left_dummy_elem {
+ flex: 1;
+}
diff --git a/package-lock.json b/package-lock.json
index 61e9787..1f39256 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -807,6 +807,11 @@
"to-fast-properties": "2.0.0"
}
},
+ "@novnc/novnc": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@novnc/novnc/-/novnc-1.0.0.tgz",
+ "integrity": "sha1-drDonm+HOMqBVBlbr1uOaoC8kQU="
+ },
"@types/node": {
"version": "10.12.18",
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz",
diff --git a/package.json b/package.json
index 1bdf934..0a88057 100644
--- a/package.json
+++ b/package.json
@@ -30,6 +30,7 @@
"dependencies": {
"angular": "^1.7.5",
"angular-animate": "^1.7.5",
+ "@novnc/novnc": "^1.0.0",
"angular-clipboard": "^1.6.2",
"angular-cookies": "^1.7.5",
"angular-messages": "^1.7.6",
diff --git a/webpack.config.js b/webpack.config.js
index 91cbea8..6c8667c 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -113,7 +113,11 @@
'base-uri': '\'self\'',
'object-src': '\'none\'',
'script-src': ['\'self\''],
- 'style-src': ['\'self\'']
+ 'style-src': ['\'self\''],
+ // KVM requires image buffers from data: payloads, so allow that in
+ // img-src
+ // https://stackoverflow.com/questions/18447970/content-security-policy-data-not-working-for-base64-images-in-chrome-28
+ 'img-src': ['\'self\'', 'data:'],
}),
new MiniCssExtractPlugin(),