KVM console

- The kvm console with using novnc library

Signed-off-by: Mateusz Gapski <mateuszx.gapski@intel.com>
Change-Id: Icfb7643595d8c17231ca3671753d6de971525bd3
diff --git a/package-lock.json b/package-lock.json
index f99d516..7216709 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1527,6 +1527,11 @@
         "fastq": "^1.6.0"
       }
     },
+    "@novnc/novnc": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/@novnc/novnc/-/novnc-1.2.0.tgz",
+      "integrity": "sha512-FaUckOedGhSbwQBXk/KGyxKt9ngskg4wPw6ghbHWXOUEmQscAZr3467lTU5DSfppwHJt5k+lQiHoeYUuY90l2Q=="
+    },
     "@nuxt/opencollective": {
       "version": "0.3.0",
       "resolved": "https://registry.npmjs.org/@nuxt/opencollective/-/opencollective-0.3.0.tgz",
diff --git a/package.json b/package.json
index 8c90675..9942f12 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
   },
   "dependencies": {
     "@carbon/icons-vue": "10.6.1",
+    "@novnc/novnc": "1.2.0",
     "axios": "0.19.0",
     "bootstrap": "4.4.1",
     "bootstrap-vue": "2.12.0",
diff --git a/src/components/AppNavigation/AppNavigation.vue b/src/components/AppNavigation/AppNavigation.vue
index 37f0f71..2b773a0 100644
--- a/src/components/AppNavigation/AppNavigation.vue
+++ b/src/components/AppNavigation/AppNavigation.vue
@@ -34,6 +34,9 @@
               <icon-expand class="icon-expand" />
             </b-button>
             <b-collapse id="control-menu" tag="ul" class="nav-item__nav">
+              <b-nav-item to="/control/kvm">
+                {{ $t('appNavigation.kvm') }}
+              </b-nav-item>
               <b-nav-item to="/control/manage-power-usage">
                 {{ $t('appNavigation.managePowerUsage') }}
               </b-nav-item>
diff --git a/src/locales/en-US.json b/src/locales/en-US.json
index 02600f4..e6c508b 100644
--- a/src/locales/en-US.json
+++ b/src/locales/en-US.json
@@ -79,6 +79,7 @@
     "firmware": "@:appPageTitle.firmware",
     "hardwareStatus": "@:appPageTitle.hardwareStatus",
     "health": "Health",
+    "kvm": "@:appPageTitle.kvm",
     "ldap": "@:appPageTitle.ldap",
     "localUserManagement": "@:appPageTitle.localUserManagement",
     "managePowerUsage": "@:appPageTitle.managePowerUsage",
@@ -99,6 +100,7 @@
     "eventLogs": "Event logs",
     "firmware": "Firmware",
     "hardwareStatus": "Hardware status",
+    "kvm": "KVM",
     "ldap": "LDAP",
     "localUserManagement": "Local user management",
     "login": "Login",
@@ -198,6 +200,15 @@
       "uuid": "UUID"
     }
   },
+  "pageKvm": {
+    "openNewTab": "Open in new tab",
+    "subTitle": "Access the KVM console",
+    "buttonCtrlAltDelete": "Send Ctrl+Alt+Delete",
+    "status": "Status",
+    "connected": "Connected",
+    "connecting": "Connecting",
+    "disconnected": "Disconnected"
+  },
   "pageLdap": {
     "pageDescription": "Configure LDAP settings and manage role groups",
     "roleGroups": "Role groups",
diff --git a/src/router/index.js b/src/router/index.js
index 0da37fa..eace2bb 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -90,6 +90,14 @@
         }
       },
       {
+        path: '/control/kvm',
+        name: 'kvm',
+        component: () => import('@/views/Control/Kvm'),
+        meta: {
+          title: 'appPageTitle.kvm'
+        }
+      },
+      {
         path: '/control/manage-power-usage',
         name: 'manage-power-usage',
         component: () => import('@/views/Control/ManagePowerUsage'),
@@ -178,13 +186,21 @@
     },
     children: [
       {
-        path: '/console/serial-over-lan-console',
-        name: 'serial-over-lan',
+        path: 'serial-over-lan-console',
+        name: 'serial-over-lan-console',
         component: () =>
           import('@/views/Control/SerialOverLan/SerialOverLanConsole'),
         meta: {
           title: 'appPageTitle.serialOverLan'
         }
+      },
+      {
+        path: 'kvm',
+        name: 'kvm-console',
+        component: () => import('@/views/Control/Kvm/KvmConsole'),
+        meta: {
+          title: 'appPageTitle.kvm'
+        }
       }
     ]
   }
diff --git a/src/views/Control/Kvm/Kvm.vue b/src/views/Control/Kvm/Kvm.vue
new file mode 100644
index 0000000..195948b
--- /dev/null
+++ b/src/views/Control/Kvm/Kvm.vue
@@ -0,0 +1,56 @@
+<template>
+  <b-container fluid="xl">
+    <page-title />
+
+    <page-section :section-title="$t('pageKvm.subTitle')">
+      <div>
+        <b-button
+          variant="link"
+          type="button"
+          class="button-launch"
+          @click="openConsoleWindow()"
+        >
+          <icon-launch />
+          {{ $t('pageKvm.openNewTab') }}
+        </b-button>
+      </div>
+      <div class="terminal-container">
+        <kvm-console />
+      </div>
+    </page-section>
+  </b-container>
+</template>
+
+<script>
+import IconLaunch from '@carbon/icons-vue/es/launch/32';
+import PageTitle from '@/components/Global/PageTitle';
+import PageSection from '@/components/Global/PageSection';
+import KvmConsole from './KvmConsole';
+
+export default {
+  name: 'Kvm',
+  components: { IconLaunch, PageSection, PageTitle, KvmConsole },
+  methods: {
+    openConsoleWindow() {
+      window.open(
+        '#/console/kvm',
+        '_blank',
+        'directories=no,titlebar=no,toolbar=no,location=no,status=no,menubar=no,scrollbars=no,resizable=yes,width=600,height=550'
+      );
+    }
+  }
+};
+</script>
+
+<style scoped>
+.button-launch > svg {
+  height: 25px;
+}
+.button-launch {
+  padding-left: 0px;
+}
+
+.terminal-container {
+  width: 100%;
+}
+</style>
diff --git a/src/views/Control/Kvm/KvmConsole.vue b/src/views/Control/Kvm/KvmConsole.vue
new file mode 100644
index 0000000..080f72e
--- /dev/null
+++ b/src/views/Control/Kvm/KvmConsole.vue
@@ -0,0 +1,74 @@
+<template>
+  <div>
+    <span class="kvm-status">{{ $t('pageKvm.status') }}: {{ status }}</span>
+    <b-button
+      v-if="isConnected"
+      variant="link"
+      type="button"
+      class="button-launch button-ctrl-alt-delete"
+      @click="sendCtrlAltDel"
+    >
+      {{ $t('pageKvm.buttonCtrlAltDelete') }}
+    </b-button>
+    <div v-show="isConnected" id="terminal" ref="panel"></div>
+  </div>
+</template>
+
+<script>
+import RFB from '@novnc/novnc/core/rfb';
+
+export default {
+  name: 'KvmConsole',
+  data() {
+    return {
+      rfb: null,
+      isConnected: false,
+      status: this.$t('pageKvm.connecting')
+    };
+  },
+  mounted() {
+    this.openTerminal();
+  },
+  methods: {
+    sendCtrlAltDel() {
+      this.rfb.sendCtrlAltDel();
+    },
+    openTerminal() {
+      const token = this.$store.getters['authentication/token'];
+      this.rfb = new RFB(
+        this.$refs.panel,
+        `wss://${window.location.host}/kvm/0`,
+        { wsProtocols: [token] }
+      );
+
+      this.rfb.scaleViewport = true;
+      const that = this;
+      this.rfb.addEventListener('connect', () => {
+        that.isConnected = true;
+        that.status = this.$t('pageKvm.connected');
+      });
+
+      this.rfb.addEventListener('disconnect', () => {
+        that.status = this.$t('pageKvm.disconnected');
+      });
+    }
+  }
+};
+</script>
+
+<style scoped lang="scss">
+@import 'src/assets/styles/helpers';
+#terminal {
+  height: calc(100vh - 42px);
+}
+
+.button-ctrl-alt-delete {
+  float: right;
+}
+
+.kvm-status {
+  padding-top: $spacer / 2;
+  padding-left: $spacer / 4;
+  display: inline-block;
+}
+</style>
diff --git a/src/views/Control/Kvm/index.js b/src/views/Control/Kvm/index.js
new file mode 100644
index 0000000..ac4f966
--- /dev/null
+++ b/src/views/Control/Kvm/index.js
@@ -0,0 +1,2 @@
+import Kvm from './Kvm.vue';
+export default Kvm;