diff options
author | Sukanya Pandey <sukapan1@in.ibm.com> | 2020-12-02 16:34:09 +0300 |
---|---|---|
committer | Derick Montague <derick.montague@ibm.com> | 2020-12-16 02:12:18 +0300 |
commit | 34efde291781c01c78e4363d919cdf4d5c90ae43 (patch) | |
tree | 02463d010d1ecafec1ec423638cac99d04f768e7 | |
parent | 8f030bac11637fcd0a005907b558d7accbff68bd (diff) | |
download | webui-vue-34efde291781c01c78e4363d919cdf4d5c90ae43.tar.xz |
Add client sessions page
- This page will show the list of sessions that are
currently connected to the BMC.
APIs used:
- To get all the sessions API used is
`/redfish/v1/SessionService/Sessions`
- To delete the sessions API used is
`/redfish/v1/SessionService/Sessions/<session id>`
Signed-off-by: Sukanya Pandey <sukapan1@in.ibm.com>
Change-Id: Ia81f62cbbea749809b9b7f7e62356cfe2db7fc18
-rw-r--r-- | src/components/AppNavigation/AppNavigationMixin.js | 5 | ||||
-rw-r--r-- | src/locales/en-US.json | 21 | ||||
-rw-r--r-- | src/router/routes.js | 9 | ||||
-rw-r--r-- | src/store/index.js | 2 | ||||
-rw-r--r-- | src/store/modules/AccessControl/ClientSessionsStore.js | 80 | ||||
-rw-r--r-- | src/views/AccessControl/ClientSessions/ClientSessions.vue | 292 | ||||
-rw-r--r-- | src/views/AccessControl/ClientSessions/index.js | 2 |
7 files changed, 411 insertions, 0 deletions
diff --git a/src/components/AppNavigation/AppNavigationMixin.js b/src/components/AppNavigation/AppNavigationMixin.js index b163d75e..7fe63a0a 100644 --- a/src/components/AppNavigation/AppNavigationMixin.js +++ b/src/components/AppNavigation/AppNavigationMixin.js @@ -125,6 +125,11 @@ const AppNavigationMixin = { icon: 'iconAccessControl', children: [ { + id: 'client-sessions', + label: this.$t('appNavigation.clientSessions'), + route: '/access-control/client-sessions', + }, + { id: 'ldap', label: this.$t('appNavigation.ldap'), route: '/access-control/ldap', diff --git a/src/locales/en-US.json b/src/locales/en-US.json index 0e28de51..dcb52a68 100644 --- a/src/locales/en-US.json +++ b/src/locales/en-US.json @@ -95,6 +95,7 @@ }, "appNavigation": { "accessControl": "Access control", + "clientSessions": "@:appPageTitle.clientSessions", "configuration": "Configuration", "control": "Control", "dateTimeSettings": "@:appPageTitle.dateTimeSettings", @@ -121,6 +122,7 @@ }, "appPageTitle": { "changePassword": "Change password", + "clientSessions": "Client sessions", "dateTimeSettings": "Date and time settings", "eventLogs": "Event logs", "firmware": "Firmware", @@ -153,6 +155,25 @@ "newPassword": "New password", "username": "Username" }, + "pageClientSessions" : { + "action": { + "disconnect" : "Disconnect" + }, + "modal": { + "disconnectTitle": "Disconnect session| Disconnect sessions", + "disconnectMessage": "Are you sure you want to disconnect %{count} session? This action cannot be undone. | Are you sure you want to disconnect %{count} sessions? This action cannot be undone." + }, + "table": { + "clientID": "Client ID", + "username": "Username", + "ipAddress": "IP address", + "searchSessions": "Search sessions" + }, + "toast": { + "errorDelete": "Error disconnecting %{count} session. | Error disconnecting %{count} sessions.", + "successDelete": "Successfully disconnected %{count} session. | Successfully disconnected %{count} sessions." + } + }, "pageDateTimeSettings": { "alert": { "message": "To change how date and time are displayed (either UTC or browser offset) throughout the application, visit ", diff --git a/src/router/routes.js b/src/router/routes.js index a82833ad..9a9b7137 100644 --- a/src/router/routes.js +++ b/src/router/routes.js @@ -7,6 +7,7 @@ import Firmware from '@/views/Configuration/Firmware'; import HardwareStatus from '@/views/Health/HardwareStatus'; import Kvm from '@/views/Control/Kvm'; import KvmConsole from '@/views/Control/Kvm/KvmConsole'; +import ClientSessions from '../views/AccessControl/ClientSessions'; import Ldap from '@/views/AccessControl/Ldap'; import LocalUserManagement from '@/views/AccessControl/LocalUserManagement'; import Login from '@/views/Login'; @@ -124,6 +125,14 @@ const routes = [ }, }, { + path: '/access-control/client-sessions', + name: 'client-sessions', + component: ClientSessions, + meta: { + title: i18n.t('appPageTitle.clientSessions'), + }, + }, + { path: '/access-control/ldap', name: 'ldap', component: Ldap, diff --git a/src/store/index.js b/src/store/index.js index b4a77d82..151eb68c 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -3,6 +3,7 @@ import Vuex from 'vuex'; import GlobalStore from './modules/GlobalStore'; import AuthenticationStore from './modules/Authentication/AuthenticanStore'; +import ClientSessions from './modules/AccessControl/ClientSessionsStore'; import LdapStore from './modules/AccessControl/LdapStore'; import LocalUserManagementStore from './modules/AccessControl/LocalUserMangementStore'; import SslCertificatesStore from './modules/AccessControl/SslCertificatesStore'; @@ -36,6 +37,7 @@ export default new Vuex.Store({ modules: { global: GlobalStore, authentication: AuthenticationStore, + clientSessions: ClientSessions, dateTime: DateTimeStore, ldap: LdapStore, localUsers: LocalUserManagementStore, diff --git a/src/store/modules/AccessControl/ClientSessionsStore.js b/src/store/modules/AccessControl/ClientSessionsStore.js new file mode 100644 index 00000000..a09f766e --- /dev/null +++ b/src/store/modules/AccessControl/ClientSessionsStore.js @@ -0,0 +1,80 @@ +import api, { getResponseCount } from '@/store/api'; +import i18n from '@/i18n'; + +const ClientSessionsStore = { + namespaced: true, + state: { + allConnections: [], + }, + getters: { + allConnections: (state) => state.allConnections, + }, + mutations: { + setAllConnections: (state, allConnections) => + (state.allConnections = allConnections), + }, + actions: { + async getClientSessionsData({ commit }) { + return await api + .get('/redfish/v1/SessionService/Sessions') + .then((response) => + response.data.Members.map((sessionLogs) => sessionLogs['@odata.id']) + ) + .then((sessionUris) => + api.all(sessionUris.map((sessionUri) => api.get(sessionUri))) + ) + .then((sessionUris) => { + const allConnectionsData = sessionUris.map((sessionUri) => { + return { + clientID: sessionUri.data?.Id, + username: sessionUri.data?.UserName, + ipAddress: sessionUri.data?.Oem?.OpenBMC.ClientID.slice(2), + uri: sessionUri.data['@odata.id'], + }; + }); + commit('setAllConnections', allConnectionsData); + }) + .catch((error) => { + console.log('Client Session Data:', error); + }); + }, + async disconnectSessions({ dispatch }, uris = []) { + const promises = uris.map((uri) => + api.delete(uri).catch((error) => { + console.log(error); + return error; + }) + ); + return await api + .all(promises) + .then((response) => { + dispatch('getClientSessionsData'); + return response; + }) + .then( + api.spread((...responses) => { + const { successCount, errorCount } = getResponseCount(responses); + const toastMessages = []; + + if (successCount) { + const message = i18n.tc( + 'pageClientSessions.toast.successDelete', + successCount + ); + toastMessages.push({ type: 'success', message }); + } + + if (errorCount) { + const message = i18n.tc( + 'pageClientSessions.toast.errorDelete', + errorCount + ); + toastMessages.push({ type: 'error', message }); + } + return toastMessages; + }) + ); + }, + }, +}; +export default ClientSessionsStore; diff --git a/src/views/AccessControl/ClientSessions/ClientSessions.vue b/src/views/AccessControl/ClientSessions/ClientSessions.vue new file mode 100644 index 00000000..04dd052f --- /dev/null +++ b/src/views/AccessControl/ClientSessions/ClientSessions.vue @@ -0,0 +1,292 @@ +<template> + <b-container fluid="xl"> + <page-title /> + <b-row class="align-items-end"> + <b-col sm="6" md="5" xl="4"> + <search + :placeholder="$t('pageClientSessions.table.searchSessions')" + @change-search="onChangeSearchInput" + @clear-search="onClearSearchInput" + /> + </b-col> + <b-col sm="3" md="3" xl="2"> + <table-cell-count + :filtered-items-count="filteredRows" + :total-number-of-cells="allConnections.length" + ></table-cell-count> + </b-col> + </b-row> + <b-row> + <b-col> + <table-toolbar + ref="toolbar" + :selected-items-count="selectedRows.length" + :actions="batchActions" + @clear-selected="clearSelectedRows($refs.table)" + @batch-action="onBatchAction" + > + </table-toolbar> + <b-table + id="table-session-logs" + ref="table" + responsive="md" + selectable + no-select-on-click + hover + show-empty + sort-by="clientID" + :fields="fields" + :items="allConnections" + :filter="searchFilter" + :empty-text="$t('global.table.emptyMessage')" + :per-page="perPage" + :current-page="currentPage" + @filtered="onFiltered" + @row-selected="onRowSelected($event, allConnections.length)" + > + <!-- Checkbox column --> + <template #head(checkbox)> + <b-form-checkbox + v-model="tableHeaderCheckboxModel" + data-test-id="sessionLogs-checkbox-selectAll" + :indeterminate="tableHeaderCheckboxIndeterminate" + @change="onChangeHeaderCheckbox($refs.table)" + > + <span class="sr-only">{{ $t('global.table.selectAll') }}</span> + </b-form-checkbox> + </template> + <template #cell(checkbox)="row"> + <b-form-checkbox + v-model="row.rowSelected" + :data-test-id="`sessionLogs-checkbox-selectRow-${row.index}`" + @change="toggleSelectRow($refs.table, row.index)" + > + <span class="sr-only">{{ $t('global.table.selectItem') }}</span> + </b-form-checkbox> + </template> + + <!-- Actions column --> + <template #cell(actions)="row" class="ml-3"> + <table-row-action + v-for="(action, index) in row.item.actions" + :key="index" + :value="action.value" + :title="action.title" + :row-data="row.item" + :data-test-id="`sessionLogs-button-deleteRow-${row.index}`" + @click-table-action="onTableRowAction($event, row.item)" + ></table-row-action> + </template> + </b-table> + </b-col> + </b-row> + + <!-- Table pagination --> + <b-row> + <b-col sm="6"> + <b-form-group + class="table-pagination-select" + :label="$t('global.table.itemsPerPage')" + label-for="pagination-items-per-page" + > + <b-form-select + id="pagination-items-per-page" + v-model="perPage" + :options="itemsPerPageOptions" + /> + </b-form-group> + </b-col> + <b-col sm="6"> + <b-pagination + v-model="currentPage" + first-number + last-number + :per-page="perPage" + :total-rows="getTotalRowCount(allConnections.length)" + aria-controls="table-session-logs" + /> + </b-col> + </b-row> + </b-container> +</template> + +<script> +import PageTitle from '@/components/Global/PageTitle'; +import Search from '@/components/Global/Search'; +import TableCellCount from '@/components/Global/TableCellCount'; +import TableRowAction from '@/components/Global/TableRowAction'; +import TableToolbar from '@/components/Global/TableToolbar'; + +import LoadingBarMixin from '@/components/Mixins/LoadingBarMixin'; +import BVPaginationMixin, { + currentPage, + perPage, + itemsPerPageOptions, +} from '@/components/Mixins/BVPaginationMixin'; +import BVTableSelectableMixin, { + selectedRows, + tableHeaderCheckboxModel, + tableHeaderCheckboxIndeterminate, +} from '@/components/Mixins/BVTableSelectableMixin'; +import BVToastMixin from '@/components/Mixins/BVToastMixin'; +import SearchFilterMixin, { + searchFilter, +} from '@/components/Mixins/SearchFilterMixin'; + +export default { + components: { + PageTitle, + Search, + TableCellCount, + TableRowAction, + TableToolbar, + }, + mixins: [ + BVPaginationMixin, + BVTableSelectableMixin, + BVToastMixin, + LoadingBarMixin, + SearchFilterMixin, + ], + beforeRouteLeave(to, from, next) { + // Hide loader if the user navigates to another page + // before request is fulfilled. + this.hideLoader(); + next(); + }, + data() { + return { + fields: [ + { + key: 'checkbox', + }, + { + key: 'clientID', + label: this.$t('pageClientSessions.table.clientID'), + }, + { + key: 'username', + label: this.$t('pageClientSessions.table.username'), + }, + { + key: 'ipAddress', + label: this.$t('pageClientSessions.table.ipAddress'), + }, + { + key: 'actions', + label: '', + }, + ], + batchActions: [ + { + value: 'disconnect', + label: this.$t('pageClientSessions.action.disconnect'), + }, + ], + currentPage: currentPage, + itemsPerPageOptions: itemsPerPageOptions, + perPage: perPage, + selectedRows: selectedRows, + searchTotalFilteredRows: 0, + tableHeaderCheckboxModel: tableHeaderCheckboxModel, + tableHeaderCheckboxIndeterminate: tableHeaderCheckboxIndeterminate, + searchFilter: searchFilter, + }; + }, + computed: { + filteredRows() { + return this.searchFilter + ? this.searchTotalFilteredRows + : this.allConnections.length; + }, + allConnections() { + return this.$store.getters['clientSessions/allConnections'].map( + (session) => { + return { + ...session, + actions: [ + { + value: 'disconnect', + title: this.$t('pageClientSessions.action.disconnect'), + }, + ], + }; + } + ); + }, + }, + created() { + this.startLoader(); + this.$store + .dispatch('clientSessions/getClientSessionsData') + .finally(() => this.endLoader()); + }, + methods: { + onFiltered(filteredItems) { + this.searchTotalFilteredRows = filteredItems.length; + }, + onChangeSearchInput(event) { + this.searchFilter = event; + }, + disconnectSessions(uris) { + this.$store + .dispatch('clientSessions/disconnectSessions', uris) + .then((messages) => { + messages.forEach(({ type, message }) => { + if (type === 'success') { + this.successToast(message); + } else if (type === 'error') { + this.errorToast(message); + } + }); + }); + }, + onTableRowAction(action, { uri }) { + if (action === 'disconnect') { + this.$bvModal + .msgBoxConfirm( + this.$tc('pageClientSessions.modal.disconnectMessage'), + { + title: this.$tc('pageClientSessions.modal.disconnectTitle'), + okTitle: this.$t('pageClientSessions.action.disconnect'), + } + ) + .then((deleteConfirmed) => { + if (deleteConfirmed) this.disconnectSessions([uri]); + }); + } + }, + onBatchAction(action) { + if (action === 'disconnect') { + const uris = this.selectedRows.map((row) => row.uri); + this.$bvModal + .msgBoxConfirm( + this.$tc( + 'pageClientSessions.modal.disconnectMessage', + this.selectedRows.length + ), + { + title: this.$tc( + 'pageClientSessions.modal.disconnectTitle', + this.selectedRows.length + ), + okTitle: this.$t('pageClientSessions.action.disconnect'), + } + ) + .then((deleteConfirmed) => { + if (deleteConfirmed) { + this.disconnectSessions(uris); + } + }); + } + }, + }, +}; +</script> +<style lang="scss"> +#table-session-logs { + td .btn-link { + width: auto !important; + } +} +</style> diff --git a/src/views/AccessControl/ClientSessions/index.js b/src/views/AccessControl/ClientSessions/index.js new file mode 100644 index 00000000..6000ab75 --- /dev/null +++ b/src/views/AccessControl/ClientSessions/index.js @@ -0,0 +1,2 @@ +import ClientSessions from './ClientSessions.vue'; +export default ClientSessions; |