Add power operations page

Add route, component and Control requests to enable
power operations (power on, soft and hard reboot, soft
and hard power off).
This rewrite includes updates to use Redfish endpoints.

Signed-off-by: Yoshie Muranaka <yoshiemuranaka@gmail.com>
Change-Id: I54784b8cc1b6260e44e708c260ea4a531fc0a629
diff --git a/src/components/AppNavigation/AppNavigation.vue b/src/components/AppNavigation/AppNavigation.vue
index 62ffe98..07d1cdb 100644
--- a/src/components/AppNavigation/AppNavigation.vue
+++ b/src/components/AppNavigation/AppNavigation.vue
@@ -42,7 +42,7 @@
               <b-nav-item href="javascript:void(0)">
                 {{ $t('appNavigation.serverLed') }}
               </b-nav-item>
-              <b-nav-item href="javascript:void(0)">
+              <b-nav-item to="/control/server-power-operations">
                 {{ $t('appNavigation.serverPowerOperations') }}
               </b-nav-item>
             </b-collapse>
diff --git a/src/locales/en.json b/src/locales/en.json
index 30bfc88..0de5298 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -183,5 +183,27 @@
       "errorRebootStart": "Error rebooting BMC.",
       "successRebootStart": "Rebooting BMC."
     }
+  },
+  "pageServerPowerOperations": {
+    "currentStatus": "Current status",
+    "hostname": "Hostname",
+    "hostStatus": "Host status",
+    "immediateReboot": "Immediate – Server reboots without OS shutting down; may cause data corruption",
+    "immediateShutdown": "Immediate - Server shuts down without OS shutting down; may cause data corruption",
+    "operationInProgress": "There are no options to display while a power operation is in progress. When complete, power operations will be displayed here.",
+    "operations": "Operations",
+    "orderlyReboot": "Orderly – OS shuts down, then server reboots",
+    "orderlyShutdown": "Orderly - OS shuts down, then server shuts down",
+    "powerOn": "Power on",
+    "reboot": "Reboot",
+    "rebootServer": "Reboot server",
+    "shutDown": "Shut down",
+    "shutdownServer": "Shutdown server",
+    "modal": {
+      "confirmRebootMessage": "Are you sure you want to reboot?",
+      "confirmRebootTitle": "Server reboot will cause outage",
+      "confirmShutdownMessage": "Are you sure you want to shut down?",
+      "confirmShutdownTitle": "Server shutdown will cause outage"
+    }
   }
 }
\ No newline at end of file
diff --git a/src/router/index.js b/src/router/index.js
index dda4daf..0d246cd 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -40,6 +40,14 @@
         }
       },
       {
+        path: '/control/server-power-operations',
+        name: 'server-power-operations',
+        component: () => import('@/views/Control/ServerPowerOperations'),
+        meta: {
+          title: 'appPageTitle.serverPowerOperations'
+        }
+      },
+      {
         path: '/unauthorized',
         name: 'unauthorized',
         component: () => import('@/views/Unauthorized'),
diff --git a/src/store/modules/Control/ControlStore.js b/src/store/modules/Control/ControlStore.js
index 9b2e459..6f9ced4 100644
--- a/src/store/modules/Control/ControlStore.js
+++ b/src/store/modules/Control/ControlStore.js
@@ -1,8 +1,45 @@
 import api from '../../api';
 import i18n from '../../../i18n';
 
+/**
+ * Watch for hostStatus changes in GlobalStore module
+ * to set isOperationInProgress state
+ * Stop watching status changes and resolve Promise when
+ * hostStatus value matches passed argument or after 5 minutes
+ * @param {string} hostStatus
+ * @returns {Promise}
+ */
+const checkForHostStatus = function(hostStatus) {
+  return new Promise(resolve => {
+    const timer = setTimeout(() => {
+      resolve();
+      unwatch();
+    }, 300000 /*5mins*/);
+    const unwatch = this.watch(
+      state => state.global.hostStatus,
+      value => {
+        if (value === hostStatus) {
+          resolve();
+          unwatch();
+          clearTimeout(timer);
+        }
+      }
+    );
+  });
+};
+
 const ControlStore = {
   namespaced: true,
+  state: {
+    isOperationInProgress: false
+  },
+  getters: {
+    isOperationInProgress: state => state.isOperationInProgress
+  },
+  mutations: {
+    setOperationInProgress: (state, inProgress) =>
+      (state.isOperationInProgress = inProgress)
+  },
   actions: {
     async rebootBmc() {
       const data = { ResetType: 'GracefulRestart' };
@@ -13,6 +50,48 @@
           console.log(error);
           throw new Error(i18n.t('pageRebootBmc.toast.errorRebootStart'));
         });
+    },
+    async hostPowerOn({ dispatch, commit }) {
+      const data = { ResetType: 'On' };
+      dispatch('hostPowerChange', data);
+      await checkForHostStatus.bind(this, 'on')();
+      commit('setOperationInProgress', false);
+    },
+    async hostSoftReboot({ dispatch, commit }) {
+      const data = { ResetType: 'GracefulRestart' };
+      dispatch('hostPowerChange', data);
+      await checkForHostStatus.bind(this, 'on')();
+      commit('setOperationInProgress', false);
+    },
+    async hostHardReboot({ dispatch, commit }) {
+      // TODO: Update when ForceWarmReboot property
+      // available
+      dispatch('hostPowerChange', { ResetType: 'ForceOff' });
+      await checkForHostStatus.bind(this, 'off')();
+      dispatch('hostPowerChange', { ResetType: 'On' });
+      await checkForHostStatus.bind(this, 'on')();
+      commit('setOperationInProgress', false);
+    },
+    async hostSoftPowerOff({ dispatch, commit }) {
+      const data = { ResetType: 'GracefulShutdown' };
+      dispatch('hostPowerChange', data);
+      await checkForHostStatus.bind(this, 'off')();
+      commit('setOperationInProgress', false);
+    },
+    async hostHardPowerOff({ dispatch, commit }) {
+      const data = { ResetType: 'ForceOff' };
+      dispatch('hostPowerChange', data);
+      await checkForHostStatus.bind(this, 'off')();
+      commit('setOperationInProgress', false);
+    },
+    hostPowerChange({ commit }, data) {
+      commit('setOperationInProgress', true);
+      api
+        .post('/redfish/v1/Systems/system/Actions/ComputerSystem.Reset', data)
+        .catch(error => {
+          console.log(error);
+          commit('setOperationInProgress', false);
+        });
     }
   }
 };
diff --git a/src/views/Control/ServerPowerOperations/ServerPowerOperations.vue b/src/views/Control/ServerPowerOperations/ServerPowerOperations.vue
new file mode 100644
index 0000000..c9b02b3
--- /dev/null
+++ b/src/views/Control/ServerPowerOperations/ServerPowerOperations.vue
@@ -0,0 +1,184 @@
+<template>
+  <b-container fluid>
+    <page-title />
+    <b-row>
+      <b-col md="8" lg="8" xl="6">
+        <page-section
+          :section-title="$t('pageServerPowerOperations.currentStatus')"
+        >
+          <dl>
+            <dt>{{ $t('pageServerPowerOperations.hostname') }}</dt>
+            <dd>{{ hostname }}</dd>
+          </dl>
+          <dl>
+            <dt>{{ $t('pageServerPowerOperations.hostStatus') }}</dt>
+            <dd v-if="hostStatus === 'on'">
+              {{ $t('global.status.on') }}
+            </dd>
+            <dd v-else-if="hostStatus === 'off'">
+              {{ $t('global.status.off') }}
+            </dd>
+            <dd v-else>
+              {{ $t('global.status.notAvailable') }}
+            </dd>
+          </dl>
+        </page-section>
+      </b-col>
+    </b-row>
+    <b-row>
+      <b-col md="8" lg="7" xl="8">
+        <page-section
+          :section-title="$t('pageServerPowerOperations.operations')"
+        >
+          <template v-if="isOperationInProgress">
+            {{ $t('pageServerPowerOperations.operationInProgress') }}
+          </template>
+          <template v-else-if="hostStatus === 'off'">
+            <b-button variant="primary" @click="powerOn">
+              {{ $t('pageServerPowerOperations.powerOn') }}
+            </b-button>
+          </template>
+          <template v-else-if="hostStatus === 'on'">
+            <!-- Reboot server options -->
+            <b-form novalidate class="mb-5" @submit.prevent="rebootServer">
+              <b-form-group
+                :label="$t('pageServerPowerOperations.rebootServer')"
+              >
+                <b-form-radio
+                  v-model="form.rebootOption"
+                  name="reboot-option"
+                  value="orderly"
+                >
+                  {{ $t('pageServerPowerOperations.orderlyReboot') }}
+                </b-form-radio>
+                <b-form-radio
+                  v-model="form.rebootOption"
+                  name="reboot-option"
+                  value="immediate"
+                >
+                  {{ $t('pageServerPowerOperations.immediateReboot') }}
+                </b-form-radio>
+              </b-form-group>
+              <b-button variant="primary" type="submit">
+                {{ $t('pageServerPowerOperations.reboot') }}
+              </b-button>
+            </b-form>
+            <!-- Shutdown server options -->
+            <b-form novalidate @submit.prevent="shutdownServer">
+              <b-form-group
+                :label="$t('pageServerPowerOperations.shutdownServer')"
+              >
+                <b-form-radio
+                  v-model="form.shutdownOption"
+                  name="shutdown-option"
+                  value="orderly"
+                >
+                  {{ $t('pageServerPowerOperations.orderlyShutdown') }}
+                </b-form-radio>
+                <b-form-radio
+                  v-model="form.shutdownOption"
+                  name="shutdown-option"
+                  value="immediate"
+                >
+                  {{ $t('pageServerPowerOperations.immediateShutdown') }}
+                </b-form-radio>
+              </b-form-group>
+              <b-button variant="primary" type="submit">
+                {{ $t('pageServerPowerOperations.shutDown') }}
+              </b-button>
+            </b-form>
+          </template>
+          <template v-else>
+            {{ $t('global.status.notAvailable') }}
+          </template>
+        </page-section>
+      </b-col>
+    </b-row>
+  </b-container>
+</template>
+
+<script>
+import PageTitle from '../../../components/Global/PageTitle';
+import PageSection from '../../../components/Global/PageSection';
+import BVToastMixin from '../../../components/Mixins/BVToastMixin';
+
+export default {
+  name: 'ServerPowerOperations',
+  components: { PageTitle, PageSection },
+  mixins: [BVToastMixin],
+  data() {
+    return {
+      form: {
+        rebootOption: 'orderly',
+        shutdownOption: 'orderly'
+      }
+    };
+  },
+  computed: {
+    hostStatus() {
+      return this.$store.getters['global/hostStatus'];
+    },
+    hostname() {
+      return this.$store.getters['global/hostName'];
+    },
+    isOperationInProgress() {
+      return this.$store.getters['controls/isOperationInProgress'];
+    }
+  },
+  created() {
+    this.$store.dispatch('global/getHostName');
+  },
+  methods: {
+    powerOn() {
+      this.$store.dispatch('controls/hostPowerOn');
+    },
+    rebootServer() {
+      const modalMessage = this.$t(
+        'pageServerPowerOperations.modal.confirmRebootMessage'
+      );
+      const modalOptions = {
+        title: this.$t('pageServerPowerOperations.modal.confirmRebootTitle'),
+        okTitle: this.$t('global.action.confirm')
+      };
+
+      if (this.form.rebootOption === 'orderly') {
+        this.$bvModal
+          .msgBoxConfirm(modalMessage, modalOptions)
+          .then(confirmed => {
+            if (confirmed) this.$store.dispatch('controls/hostSoftReboot');
+          });
+      } else if (this.form.rebootOption === 'immediate') {
+        this.$bvModal
+          .msgBoxConfirm(modalMessage, modalOptions)
+          .then(confirmed => {
+            if (confirmed) this.$store.dispatch('controls/hostHardReboot');
+          });
+      }
+    },
+    shutdownServer() {
+      const modalMessage = this.$t(
+        'pageServerPowerOperations.modal.confirmShutdownMessage'
+      );
+      const modalOptions = {
+        title: this.$t('pageServerPowerOperations.modal.confirmShutdownTitle'),
+        okTitle: this.$t('global.action.confirm')
+      };
+
+      if (this.form.shutdownOption === 'orderly') {
+        this.$bvModal
+          .msgBoxConfirm(modalMessage, modalOptions)
+          .then(confirmed => {
+            if (confirmed) this.$store.dispatch('controls/hostSoftPowerOff');
+          });
+      }
+      if (this.form.shutdownOption === 'immediate') {
+        this.$bvModal
+          .msgBoxConfirm(modalMessage, modalOptions)
+          .then(confirmed => {
+            if (confirmed) this.$store.dispatch('controls/hostHardPowerOff');
+          });
+      }
+    }
+  }
+};
+</script>
diff --git a/src/views/Control/ServerPowerOperations/index.js b/src/views/Control/ServerPowerOperations/index.js
new file mode 100644
index 0000000..1043004
--- /dev/null
+++ b/src/views/Control/ServerPowerOperations/index.js
@@ -0,0 +1,2 @@
+import ServerPowerOperations from './ServerPowerOperations.vue';
+export default ServerPowerOperations;