Add toast component interactions

Include boostrap toast component to communicate success
and error requests on the local user management page.

- Created BVToastMixin to share initialization options
- Used async/await pattern to make sure toasts are shown
  after asynchronous calls are complete
- Followed current AngularJS pattern of manual dismiss for
  error toast and automatic dismiss for success toast

Signed-off-by: Yoshie Muranaka <yoshiemuranaka@gmail.com>
Change-Id: I5d5c037b5f41781972106fb5e9a2096cc72c39ab
diff --git a/src/assets/styles/_obmc-custom.scss b/src/assets/styles/_obmc-custom.scss
index d20e64e..2e36019 100644
--- a/src/assets/styles/_obmc-custom.scss
+++ b/src/assets/styles/_obmc-custom.scss
@@ -56,4 +56,5 @@
 @import "./buttons";
 @import "./form-components";
 @import "./modal";
-@import "./table";
\ No newline at end of file
+@import "./table";
+@import "./toast";
\ No newline at end of file
diff --git a/src/assets/styles/_toast.scss b/src/assets/styles/_toast.scss
new file mode 100644
index 0000000..3f2f08c
--- /dev/null
+++ b/src/assets/styles/_toast.scss
@@ -0,0 +1,32 @@
+.b-toaster {
+  top: 75px!important; // make sure toasts do not hide top header
+}
+
+.toast {
+  padding: $spacer/2 $spacer/2 $spacer/2 $spacer;
+  border-width: 0 0 0 5px;
+  .close {
+    font-weight: 300;
+    opacity: 1;
+  }
+}
+
+.toast-header {
+  background-color: inherit!important; //override specificity
+  border: none;
+  color: $dark!important; //override specificity
+  padding-bottom: 0;
+}
+
+.toast-body {
+  color: $dark;
+  padding-top: 0;
+}
+
+.b-toast-success .toast {
+  border-left-color: $success!important;
+}
+
+.b-toast-danger .toast {
+  border-left-color: $danger!important;
+}
\ No newline at end of file
diff --git a/src/components/Mixins/BVToastMixin.js b/src/components/Mixins/BVToastMixin.js
new file mode 100644
index 0000000..489173c
--- /dev/null
+++ b/src/components/Mixins/BVToastMixin.js
@@ -0,0 +1,24 @@
+const BVToastMixin = {
+  methods: {
+    successToast(message) {
+      this.$root.$bvToast.toast(message, {
+        title: 'Success',
+        variant: 'success',
+        autoHideDelay: 10000, //auto hide in milliseconds
+        isStatus: true,
+        solid: true
+      });
+    },
+    errorToast(message) {
+      this.$root.$bvToast.toast(message, {
+        title: 'Error',
+        variant: 'danger',
+        noAutoHide: true,
+        isStatus: true,
+        solid: true
+      });
+    }
+  }
+};
+
+export default BVToastMixin;
diff --git a/src/main.js b/src/main.js
index e32a56b..d80d201 100644
--- a/src/main.js
+++ b/src/main.js
@@ -21,7 +21,8 @@
   ModalPlugin,
   NavbarPlugin,
   NavPlugin,
-  TablePlugin
+  TablePlugin,
+  ToastPlugin
 } from 'bootstrap-vue';
 import Vuelidate from 'vuelidate';
 
@@ -52,6 +53,7 @@
 Vue.use(NavbarPlugin);
 Vue.use(NavPlugin);
 Vue.use(TablePlugin);
+Vue.use(ToastPlugin);
 Vue.use(Vuelidate);
 
 new Vue({
diff --git a/src/store/modules/AccessControl/LocalUserMangementStore.js b/src/store/modules/AccessControl/LocalUserMangementStore.js
index 815c166..bc14c73 100644
--- a/src/store/modules/AccessControl/LocalUserMangementStore.js
+++ b/src/store/modules/AccessControl/LocalUserMangementStore.js
@@ -25,21 +25,28 @@
           const userData = users.map(user => user.data);
           commit('setUsers', userData);
         })
-        .catch(error => console.log(error));
+        .catch(error => {
+          console.log(error);
+          throw new Error('Error loading local users.');
+        });
     },
-    createUser({ dispatch }, { username, password, privilege, status }) {
+    async createUser({ dispatch }, { username, password, privilege, status }) {
       const data = {
         UserName: username,
         Password: password,
         RoleId: privilege,
         Enabled: status
       };
-      api
+      return await api
         .post('/redfish/v1/AccountService/Accounts', data)
         .then(() => dispatch('getUsers'))
-        .catch(error => console.log(error));
+        .then(() => `Created user '${username}'.`)
+        .catch(error => {
+          console.log(error);
+          throw new Error(`Error creating user '${username}'.`);
+        });
     },
-    updateUser(
+    async updateUser(
       { dispatch },
       { originalUsername, username, password, privilege, status }
     ) {
@@ -48,16 +55,24 @@
       if (password) data.Password = password;
       if (privilege) data.RoleId = privilege;
       if (status !== undefined) data.Enabled = status;
-      api
+      return await api
         .patch(`/redfish/v1/AccountService/Accounts/${originalUsername}`, data)
         .then(() => dispatch('getUsers'))
-        .catch(error => console.log(error));
+        .then(() => `Updated user '${originalUsername}'.`)
+        .catch(error => {
+          console.log(error);
+          throw new Error(`Error updating user '${originalUsername}'.`);
+        });
     },
-    deleteUser({ dispatch }, username) {
-      api
+    async deleteUser({ dispatch }, username) {
+      return await api
         .delete(`/redfish/v1/AccountService/Accounts/${username}`)
         .then(() => dispatch('getUsers'))
-        .catch(error => console.log(error));
+        .then(() => `Deleted user '${username}'.`)
+        .catch(error => {
+          console.log(error);
+          throw new Error(`Error deleting user '${username}'.`);
+        });
     }
   }
 };
diff --git a/src/views/AccessControl/LocalUserManagement/LocalUserManagement.vue b/src/views/AccessControl/LocalUserManagement/LocalUserManagement.vue
index 0ca3428..e71387d 100644
--- a/src/views/AccessControl/LocalUserManagement/LocalUserManagement.vue
+++ b/src/views/AccessControl/LocalUserManagement/LocalUserManagement.vue
@@ -67,6 +67,7 @@
 import ModalUser from './ModalUser';
 import ModalSettings from './ModalSettings';
 import PageTitle from '../../../components/Global/PageTitle';
+import BVToastMixin from '../../../components/Mixins/BVToastMixin';
 
 export default {
   name: 'LocalUsers',
@@ -81,6 +82,7 @@
     TableRoles,
     PageTitle
   },
+  mixins: [BVToastMixin],
   data() {
     return {
       activeUser: null,
@@ -156,13 +158,22 @@
     },
     saveUser({ isNewUser, userData }) {
       if (isNewUser) {
-        this.$store.dispatch('localUsers/createUser', userData);
+        this.$store
+          .dispatch('localUsers/createUser', userData)
+          .then(success => this.successToast(success))
+          .catch(({ message }) => this.errorToast(message));
       } else {
-        this.$store.dispatch('localUsers/updateUser', userData);
+        this.$store
+          .dispatch('localUsers/updateUser', userData)
+          .then(success => this.successToast(success))
+          .catch(({ message }) => this.errorToast(message));
       }
     },
     deleteUser({ username }) {
-      this.$store.dispatch('localUsers/deleteUser', username);
+      this.$store
+        .dispatch('localUsers/deleteUser', username)
+        .then(success => this.successToast(success))
+        .catch(({ message }) => this.errorToast(message));
     }
   }
 };