Add Reboot BMC page

Created a ControlStore with the intention to consolidate actions
across multiple subnav pages under the 'Control' tab, instead of
creating a dedicated RebootBmc store with one action.

- Update PageSection component to make sectionTitle prop optional
- Changed PageTitle computed property to data since the value
  doesn't change during the component lifecycle
- Change PageSection <section> element to <div> to avoid
  accessibility issues

Signed-off-by: Yoshie Muranaka <yoshiemuranaka@gmail.com>
Change-Id: I2877e2a7b9bfee245c48d52c70859978b74be7f3
diff --git a/src/components/AppNavigation/AppNavigation.vue b/src/components/AppNavigation/AppNavigation.vue
index 2847e66..4c858e8 100644
--- a/src/components/AppNavigation/AppNavigation.vue
+++ b/src/components/AppNavigation/AppNavigation.vue
@@ -26,6 +26,7 @@
               <b-nav-item href="javascript:void(0)">
                 Manage power usage
               </b-nav-item>
+              <b-nav-item to="/control/reboot-bmc">Reboot BMC</b-nav-item>
               <b-nav-item href="javascript:void(0)">Server LED</b-nav-item>
               <b-nav-item href="javascript:void(0)">
                 Server power operations
diff --git a/src/components/Global/PageSection.vue b/src/components/Global/PageSection.vue
index 03040b2..dcd85e6 100644
--- a/src/components/Global/PageSection.vue
+++ b/src/components/Global/PageSection.vue
@@ -1,8 +1,8 @@
 <template>
-  <section class="page-section">
-    <h2>{{ sectionTitle }}</h2>
+  <div class="page-section">
+    <h2 v-if="sectionTitle">{{ sectionTitle }}</h2>
     <slot />
-  </section>
+  </div>
 </template>
 
 <script>
@@ -11,7 +11,7 @@
   props: {
     sectionTitle: {
       type: String,
-      required: true
+      default: ''
     }
   }
 };
@@ -21,6 +21,7 @@
 .page-section {
   margin-bottom: $spacer * 2;
 }
+
 h2 {
   @include font-size($h4-font-size);
   margin-bottom: $spacer;
diff --git a/src/components/Global/PageTitle.vue b/src/components/Global/PageTitle.vue
index 59bb6a1..17c0884 100644
--- a/src/components/Global/PageTitle.vue
+++ b/src/components/Global/PageTitle.vue
@@ -14,10 +14,10 @@
       default: ''
     }
   },
-  computed: {
-    title() {
-      return this.$t(this.$route.meta.title);
-    }
+  data() {
+    return {
+      title: this.$t(this.$route.meta.title)
+    };
   }
 };
 </script>
diff --git a/src/locales/en.json b/src/locales/en.json
index db3a87b..2b6fa07 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -4,7 +4,10 @@
       "validator": "Field required"
     },
     "on": "on",
-    "off": "off"
+    "off": "off",
+    "actions": {
+      "confirm": "Confirm"
+    }
   },
   "ariaLabels": {
     "showPassword": "Show password as plain text. Note: this will visually expose your password on the screen."
@@ -68,6 +71,19 @@
     "localUserMgmt": "Local user management",
     "login": "Login",
     "overview": "Overview",
-    "unauthorized": "Unauthorized"
+    "unauthorized": "Unauthorized",
+    "rebootBmc": "Reboot BMC"
+  },
+  "pageRebootBmc": {
+    "rebootInformation": "When you reboot the BMC, your web browser loses contact with the BMC for several minutes. When the BMC is back online, you may need to log in again.",
+    "rebootBmc": "Reboot BMC",
+    "modal": {
+      "confirmTitle": "Confirm BMC reboot",
+      "confirmMessage": "Are you sure you want to reboot the BMC?"
+    },
+    "toastMessages": {
+      "successRebootStart": "Rebooting BMC.",
+      "errorRebootStart": "Error rebooting BMC."
+    }
   }
 }
\ No newline at end of file
diff --git a/src/router/index.js b/src/router/index.js
index bec7f54..9a30e97 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -32,6 +32,14 @@
         }
       },
       {
+        path: '/control/reboot-bmc',
+        name: 'reboot-bmc',
+        component: () => import('@/views/Control/RebootBmc'),
+        meta: {
+          title: 'pageTitle.rebootBmc'
+        }
+      },
+      {
         path: '/unauthorized',
         name: 'unauthorized',
         component: () => import('@/views/Unauthorized'),
diff --git a/src/store/index.js b/src/store/index.js
index cb63e54..8a444a6 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -6,6 +6,7 @@
 import LocalUserManagementStore from './modules/AccessControl/LocalUserMangementStore';
 import OverviewStore from './modules/Overview/OverviewStore';
 import FirmwareStore from './modules/Configuration/FirmwareStore';
+import ControlStore from './modules/Control/ControlStore';
 import PowerConsumptionStore from './modules/Control/PowerConsumptionStore';
 import PowerCapStore from './modules/Control/PowerCapStore';
 import NetworkSettingStore from './modules/Configuration/NetworkSettingsStore';
@@ -25,6 +26,7 @@
     localUsers: LocalUserManagementStore,
     overview: OverviewStore,
     firmware: FirmwareStore,
+    controls: ControlStore,
     powerConsumption: PowerConsumptionStore,
     powerCap: PowerCapStore,
     networkSettings: NetworkSettingStore,
diff --git a/src/store/modules/Control/ControlStore.js b/src/store/modules/Control/ControlStore.js
new file mode 100644
index 0000000..f641577
--- /dev/null
+++ b/src/store/modules/Control/ControlStore.js
@@ -0,0 +1,22 @@
+import api from '../../api';
+import i18n from '../../../i18n';
+
+const ControlStore = {
+  namespaced: true,
+  actions: {
+    async rebootBmc() {
+      const data = { ResetType: 'GracefulRestart' };
+      return await api
+        .post('/redfish/v1/Managers/bmc/Actions/Manager.Reset', data)
+        .then(() => i18n.t('pageRebootBmc.toastMessages.successRebootStart'))
+        .catch(error => {
+          console.log(error);
+          throw new Error(
+            i18n.t('pageRebootBmc.toastMessages.errorRebootStart')
+          );
+        });
+    }
+  }
+};
+
+export default ControlStore;
diff --git a/src/views/Control/RebootBmc/RebootBmc.vue b/src/views/Control/RebootBmc/RebootBmc.vue
new file mode 100644
index 0000000..e301f0d
--- /dev/null
+++ b/src/views/Control/RebootBmc/RebootBmc.vue
@@ -0,0 +1,47 @@
+<template>
+  <b-container fluid>
+    <page-title />
+    <b-row>
+      <b-col md="8" lg="8" xl="6">
+        <page-section>
+          {{ $t('pageRebootBmc.rebootInformation') }}
+          <b-button variant="primary" class="d-block mt-5" @click="onClick">
+            {{ $t('pageRebootBmc.rebootBmc') }}
+          </b-button>
+        </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: 'RebootBmc',
+  components: { PageTitle, PageSection },
+  mixins: [BVToastMixin],
+  methods: {
+    onClick() {
+      this.$bvModal
+        .msgBoxConfirm(this.$t('pageRebootBmc.modal.confirmMessage'), {
+          title: this.$t('pageRebootBmc.modal.confirmTitle'),
+          okTitle: this.$t('global.actions.confirm')
+        })
+        .then(confirmed => {
+          if (confirmed) this.rebootBmc();
+        });
+    },
+    rebootBmc() {
+      this.$store
+        .dispatch('controls/rebootBmc')
+        .then(message => this.successToast(message))
+        .catch(({ message }) => this.errorToast(message));
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/src/views/Control/RebootBmc/index.js b/src/views/Control/RebootBmc/index.js
new file mode 100644
index 0000000..ac31417
--- /dev/null
+++ b/src/views/Control/RebootBmc/index.js
@@ -0,0 +1,2 @@
+import RebootBmc from './RebootBmc.vue';
+export default RebootBmc;