From 183c27548046e12b94354aa598b5bcf956d31103 Mon Sep 17 00:00:00 2001 From: Yoshie Muranaka Date: Wed, 12 Feb 2020 11:30:49 -0800 Subject: Add batch actions to local user table - Create TableToolbar component for table batch actions - Added Toast warning type and toast title message translations - Update vue-i18n package to latest v8.15.3 to use improved pluarlization features Signed-off-by: Yoshie Muranaka Change-Id: I455beba4f56b8209b1201bbc5ff3f616e960d189 --- src/assets/styles/_form-components.scss | 3 - src/assets/styles/_table.scss | 7 +- src/assets/styles/_toast.scss | 4 + src/components/Global/TableToolbar.vue | 119 +++++++++++++++++ src/components/Mixins/BVTableSelectableMixin.js | 44 +++++++ src/components/Mixins/BVToastMixin.js | 19 ++- src/locales/en.json | 39 ++++-- src/store/api.js | 3 + .../AccessControl/LocalUserMangementStore.js | 142 +++++++++++++++++++++ .../LocalUserManagement/LocalUserManagement.vue | 101 ++++++++++++++- 10 files changed, 458 insertions(+), 23 deletions(-) create mode 100644 src/components/Global/TableToolbar.vue create mode 100644 src/components/Mixins/BVTableSelectableMixin.js (limited to 'src') diff --git a/src/assets/styles/_form-components.scss b/src/assets/styles/_form-components.scss index 7194d9e8..89abfb3f 100644 --- a/src/assets/styles/_form-components.scss +++ b/src/assets/styles/_form-components.scss @@ -20,9 +20,6 @@ color: $gray-900 !important; font-size: 16px; border-color: $gray-400 !important; - &::before { - border-color: $primary; - } &.is-invalid, &:invalid { border-bottom: 2px solid $danger !important; diff --git a/src/assets/styles/_table.scss b/src/assets/styles/_table.scss index ff1ed302..7d265c8e 100644 --- a/src/assets/styles/_table.scss +++ b/src/assets/styles/_table.scss @@ -1,3 +1,8 @@ +table { + position: relative; + z-index: $zindex-dropdown; +} + .table-light { td { border-top: none; @@ -18,4 +23,4 @@ padding-top: 0; padding-bottom: 0; } -} +} \ No newline at end of file diff --git a/src/assets/styles/_toast.scss b/src/assets/styles/_toast.scss index 3f2f08c0..538f9968 100644 --- a/src/assets/styles/_toast.scss +++ b/src/assets/styles/_toast.scss @@ -29,4 +29,8 @@ .b-toast-danger .toast { border-left-color: $danger!important; +} + +.b-toast-warning .toast { + border-left-color: $warning!important; } \ No newline at end of file diff --git a/src/components/Global/TableToolbar.vue b/src/components/Global/TableToolbar.vue new file mode 100644 index 00000000..fc3736db --- /dev/null +++ b/src/components/Global/TableToolbar.vue @@ -0,0 +1,119 @@ + + + + + diff --git a/src/components/Mixins/BVTableSelectableMixin.js b/src/components/Mixins/BVTableSelectableMixin.js new file mode 100644 index 00000000..fba2f2b8 --- /dev/null +++ b/src/components/Mixins/BVTableSelectableMixin.js @@ -0,0 +1,44 @@ +const BVTableSelectableMixin = { + data() { + return { + tableHeaderCheckboxModel: false, + tableHeaderCheckboxIndeterminate: false, + selectedRows: [] + }; + }, + methods: { + clearSelectedRows(tableRef) { + if (tableRef) tableRef.clearSelected(); + }, + toggleSelectRow(tableRef, rowIndex) { + if (tableRef && rowIndex !== undefined) { + tableRef.isRowSelected(rowIndex) + ? tableRef.unselectRow(rowIndex) + : tableRef.selectRow(rowIndex); + } + }, + onRowSelected(selectedRows, totalRowsCount) { + if (selectedRows && totalRowsCount !== undefined) { + this.selectedRows = selectedRows; + if (selectedRows.length === 0) { + this.tableHeaderCheckboxIndeterminate = false; + this.tableHeaderCheckboxModel = false; + } else if (selectedRows.length === totalRowsCount) { + this.tableHeaderCheckboxIndeterminate = false; + this.tableHeaderCheckboxModel = true; + } else { + this.tableHeaderCheckboxIndeterminate = true; + this.tableHeaderCheckboxModel = false; + } + } + }, + onChangeHeaderCheckbox(tableRef) { + if (tableRef) { + if (this.tableHeaderCheckboxModel) tableRef.clearSelected(); + else tableRef.selectAllRows(); + } + } + } +}; + +export default BVTableSelectableMixin; diff --git a/src/components/Mixins/BVToastMixin.js b/src/components/Mixins/BVToastMixin.js index 489173c9..a46f5e50 100644 --- a/src/components/Mixins/BVToastMixin.js +++ b/src/components/Mixins/BVToastMixin.js @@ -1,22 +1,33 @@ +import i18n from '../../i18n'; + const BVToastMixin = { methods: { - successToast(message) { + successToast(message, title = i18n.t('global.response.success')) { this.$root.$bvToast.toast(message, { - title: 'Success', + title, variant: 'success', autoHideDelay: 10000, //auto hide in milliseconds isStatus: true, solid: true }); }, - errorToast(message) { + errorToast(message, title = i18n.t('global.response.error')) { this.$root.$bvToast.toast(message, { - title: 'Error', + title, variant: 'danger', noAutoHide: true, isStatus: true, solid: true }); + }, + warningToast(message, title = i18n.t('global.response.warning')) { + this.$root.$bvToast.toast(message, { + title, + variant: 'warning', + noAutoHide: true, + isStatus: true, + solid: true + }); } } }; diff --git a/src/locales/en.json b/src/locales/en.json index 2b6fa07a..9d89ce60 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -6,12 +6,27 @@ "on": "on", "off": "off", "actions": { - "confirm": "Confirm" + "confirm": "Confirm", + "cancel": "Cancel", + "delete": "Delete", + "selected": "Selected" + }, + "response": { + "success": "Success", + "error": "Error", + "warning": "Warning" } }, "ariaLabels": { "showPassword": "Show password as plain text. Note: this will visually expose your password on the screen." }, + "pageTitle": { + "localUserMgmt": "Local user management", + "login": "Login", + "overview": "Overview", + "unauthorized": "Unauthorized", + "rebootBmc": "Reboot BMC" + }, "login": { "language": { "label": "Language" @@ -67,13 +82,6 @@ "solConsole": "Serial over LAN console" } }, - "pageTitle": { - "localUserMgmt": "Local user management", - "login": "Login", - "overview": "Overview", - "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", @@ -85,5 +93,20 @@ "successRebootStart": "Rebooting BMC.", "errorRebootStart": "Error rebooting BMC." } + }, + "localUserManagement": { + "tableActions": { + "delete": "@:global.actions.delete", + "enable": "Enable", + "disable": "Disable" + }, + "toastMessages": { + "successDeleteUsers": "Successfully deleted %{count} user. | Successfully deleted %{count} users.", + "errorDeleteUsers": "Error deleting %{count} user. | Error deleting %{count} users.", + "successEnableUsers": "Successfully enabled %{count} user. | Successfully enabled %{count} users.", + "errorEnableUsers": "Error enabling %{count} user. | Error enabling %{count} users.", + "successDisableUsers": "Successfully disabled %{count} user. | Successfully disabled %{count} users.", + "errorDisableUsers": "Error disabling %{count} user. | Error disabling %{count} users." + } } } \ No newline at end of file diff --git a/src/store/api.js b/src/store/api.js index 0f8c9484..8fdbdd2f 100644 --- a/src/store/api.js +++ b/src/store/api.js @@ -40,5 +40,8 @@ export default { }, all(promises) { return Axios.all(promises); + }, + spread(callback) { + return Axios.spread(callback); } }; diff --git a/src/store/modules/AccessControl/LocalUserMangementStore.js b/src/store/modules/AccessControl/LocalUserMangementStore.js index bc14c734..eb5822e1 100644 --- a/src/store/modules/AccessControl/LocalUserMangementStore.js +++ b/src/store/modules/AccessControl/LocalUserMangementStore.js @@ -1,4 +1,20 @@ import api from '../../api'; +import i18n from '../../../i18n'; + +const getResponseCount = responses => { + let successCount = 0; + let errorCount = 0; + + responses.forEach(response => { + if (response instanceof Error) errorCount++; + else successCount++; + }); + + return { + successCount, + errorCount + }; +}; const LocalUserManagementStore = { namespaced: true, @@ -73,6 +89,132 @@ const LocalUserManagementStore = { console.log(error); throw new Error(`Error deleting user '${username}'.`); }); + }, + async deleteUsers({ dispatch }, users) { + const promises = users.map(({ username }) => { + return api + .delete(`/redfish/v1/AccountService/Accounts/${username}`) + .catch(error => { + console.log(error); + return error; + }); + }); + return await api + .all(promises) + .then(response => { + dispatch('getUsers'); + return response; + }) + .then( + api.spread((...responses) => { + const { successCount, errorCount } = getResponseCount(responses); + let toastMessages = []; + + if (successCount) { + const message = i18n.tc( + 'localUserManagement.toastMessages.successDeleteUsers', + successCount + ); + toastMessages.push({ type: 'success', message }); + } + + if (errorCount) { + const message = i18n.tc( + 'localUserManagement.toastMessages.errorDeleteUsers', + errorCount + ); + toastMessages.push({ type: 'error', message }); + } + + return toastMessages; + }) + ); + }, + async enableUsers({ dispatch }, users) { + const data = { + Enabled: true + }; + const promises = users.map(({ username }) => { + return api + .patch(`/redfish/v1/AccountService/Accounts/${username}`, data) + .catch(error => { + console.log(error); + return error; + }); + }); + return await api + .all(promises) + .then(response => { + dispatch('getUsers'); + return response; + }) + .then( + api.spread((...responses) => { + const { successCount, errorCount } = getResponseCount(responses); + let toastMessages = []; + + if (successCount) { + const message = i18n.tc( + 'localUserManagement.toastMessages.successEnableUsers', + successCount + ); + toastMessages.push({ type: 'success', message }); + } + + if (errorCount) { + const message = i18n.tc( + 'localUserManagement.toastMessages.errorEnableUsers', + errorCount + ); + toastMessages.push({ type: 'error', message }); + } + + return toastMessages; + }) + ); + }, + async disableUsers({ dispatch }, users) { + const data = { + Enabled: false + }; + const promises = users.map(({ username }) => { + return api + .patch(`/redfish/v1/AccountService/Accounts/${username}`, data) + .catch(error => { + console.log(error); + return error; + }); + }); + return await api + .all(promises) + .then(response => { + dispatch('getUsers'); + return response; + }) + .then( + api.spread((...responses) => { + const { successCount, errorCount } = getResponseCount(responses); + let toastMessages = []; + + if (successCount) { + const message = i18n.tc( + 'localUserManagement.toastMessages.successDisableUsers', + successCount + ); + toastMessages.push({ type: 'success', message }); + } + + if (errorCount) { + const message = i18n.tc( + 'localUserManagement.toastMessages.errorDisableUsers', + errorCount + ); + toastMessages.push({ type: 'error', message }); + } + + return toastMessages; + }) + ); } } }; diff --git a/src/views/AccessControl/LocalUserManagement/LocalUserManagement.vue b/src/views/AccessControl/LocalUserManagement/LocalUserManagement.vue index a5ba7ba0..d68c9534 100644 --- a/src/views/AccessControl/LocalUserManagement/LocalUserManagement.vue +++ b/src/views/AccessControl/LocalUserManagement/LocalUserManagement.vue @@ -15,7 +15,37 @@ - + + + + + + +