summaryrefslogtreecommitdiff
path: root/src/views/SecurityAndAccess/UserManagement
diff options
context:
space:
mode:
Diffstat (limited to 'src/views/SecurityAndAccess/UserManagement')
-rw-r--r--src/views/SecurityAndAccess/UserManagement/ModalSettings.vue215
-rw-r--r--src/views/SecurityAndAccess/UserManagement/ModalUser.vue386
-rw-r--r--src/views/SecurityAndAccess/UserManagement/TableRoles.vue92
-rw-r--r--src/views/SecurityAndAccess/UserManagement/UserManagement.vue388
-rw-r--r--src/views/SecurityAndAccess/UserManagement/index.js2
5 files changed, 1083 insertions, 0 deletions
diff --git a/src/views/SecurityAndAccess/UserManagement/ModalSettings.vue b/src/views/SecurityAndAccess/UserManagement/ModalSettings.vue
new file mode 100644
index 00000000..0f05123c
--- /dev/null
+++ b/src/views/SecurityAndAccess/UserManagement/ModalSettings.vue
@@ -0,0 +1,215 @@
+<template>
+ <b-modal
+ id="modal-settings"
+ ref="modal"
+ :title="$t('pageUserManagement.accountPolicySettings')"
+ @hidden="resetForm"
+ >
+ <b-form id="form-settings" novalidate @submit.prevent="handleSubmit">
+ <b-container>
+ <b-row>
+ <b-col>
+ <b-form-group
+ :label="$t('pageUserManagement.modal.maxFailedLoginAttempts')"
+ label-for="lockout-threshold"
+ >
+ <b-form-text id="lockout-threshold-help-block">
+ {{
+ $t('global.form.valueMustBeBetween', {
+ min: 0,
+ max: 65535,
+ })
+ }}
+ </b-form-text>
+ <b-form-input
+ id="lockout-threshold"
+ v-model.number="form.lockoutThreshold"
+ type="number"
+ aria-describedby="lockout-threshold-help-block"
+ data-test-id="userManagement-input-lockoutThreshold"
+ :state="getValidationState($v.form.lockoutThreshold)"
+ @input="$v.form.lockoutThreshold.$touch()"
+ />
+ <b-form-invalid-feedback role="alert">
+ <template v-if="!$v.form.lockoutThreshold.required">
+ {{ $t('global.form.fieldRequired') }}
+ </template>
+ <template
+ v-if="
+ !$v.form.lockoutThreshold.minLength ||
+ !$v.form.lockoutThreshold.maxLength
+ "
+ >
+ {{
+ $t('global.form.valueMustBeBetween', {
+ min: 0,
+ max: 65535,
+ })
+ }}
+ </template>
+ </b-form-invalid-feedback>
+ </b-form-group>
+ </b-col>
+ <b-col>
+ <b-form-group
+ :label="$t('pageUserManagement.modal.userUnlockMethod')"
+ >
+ <b-form-radio
+ v-model="form.unlockMethod"
+ name="unlock-method"
+ class="mb-2"
+ :value="0"
+ data-test-id="userManagement-radio-manualUnlock"
+ @input="$v.form.unlockMethod.$touch()"
+ >
+ {{ $t('pageUserManagement.modal.manual') }}
+ </b-form-radio>
+ <b-form-radio
+ v-model="form.unlockMethod"
+ name="unlock-method"
+ :value="1"
+ data-test-id="userManagement-radio-automaticUnlock"
+ @input="$v.form.unlockMethod.$touch()"
+ >
+ {{ $t('pageUserManagement.modal.automaticAfterTimeout') }}
+ </b-form-radio>
+ <div class="mt-3 ml-4">
+ <b-form-text id="lockout-duration-help-block">
+ {{ $t('pageUserManagement.modal.timeoutDurationSeconds') }}
+ </b-form-text>
+ <b-form-input
+ v-model.number="form.lockoutDuration"
+ aria-describedby="lockout-duration-help-block"
+ type="number"
+ data-test-id="userManagement-input-lockoutDuration"
+ :state="getValidationState($v.form.lockoutDuration)"
+ :readonly="$v.form.unlockMethod.$model === 0"
+ @input="$v.form.lockoutDuration.$touch()"
+ />
+ <b-form-invalid-feedback role="alert">
+ <template v-if="!$v.form.lockoutDuration.required">
+ {{ $t('global.form.fieldRequired') }}
+ </template>
+ <template v-else-if="!$v.form.lockoutDuration.minvalue">
+ {{ $t('global.form.mustBeAtLeast', { value: 1 }) }}
+ </template>
+ </b-form-invalid-feedback>
+ </div>
+ </b-form-group>
+ </b-col>
+ </b-row>
+ </b-container>
+ </b-form>
+ <template #modal-footer="{ cancel }">
+ <b-button
+ variant="secondary"
+ data-test-id="userManagement-button-cancel"
+ @click="cancel()"
+ >
+ {{ $t('global.action.cancel') }}
+ </b-button>
+ <b-button
+ form="form-settings"
+ type="submit"
+ variant="primary"
+ data-test-id="userManagement-button-submit"
+ @click="onOk"
+ >
+ {{ $t('global.action.save') }}
+ </b-button>
+ </template>
+ </b-modal>
+</template>
+
+<script>
+import VuelidateMixin from '@/components/Mixins/VuelidateMixin.js';
+import {
+ required,
+ requiredIf,
+ minValue,
+ maxValue,
+} from 'vuelidate/lib/validators';
+
+export default {
+ mixins: [VuelidateMixin],
+ props: {
+ settings: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ form: {
+ lockoutThreshold: 0,
+ unlockMethod: 0,
+ lockoutDuration: null,
+ },
+ };
+ },
+ watch: {
+ settings: function ({ lockoutThreshold, lockoutDuration }) {
+ this.form.lockoutThreshold = lockoutThreshold;
+ this.form.unlockMethod = lockoutDuration ? 1 : 0;
+ this.form.lockoutDuration = lockoutDuration ? lockoutDuration : null;
+ },
+ },
+ validations: {
+ form: {
+ lockoutThreshold: {
+ minValue: minValue(0),
+ maxValue: maxValue(65535),
+ required,
+ },
+ unlockMethod: { required },
+ lockoutDuration: {
+ minValue: function (value) {
+ return this.form.unlockMethod === 0 || value > 0;
+ },
+ required: requiredIf(function () {
+ return this.form.unlockMethod === 1;
+ }),
+ },
+ },
+ },
+ methods: {
+ handleSubmit() {
+ this.$v.$touch();
+ if (this.$v.$invalid) return;
+
+ let lockoutThreshold;
+ let lockoutDuration;
+ if (this.$v.form.lockoutThreshold.$dirty) {
+ lockoutThreshold = this.form.lockoutThreshold;
+ }
+ if (this.$v.form.unlockMethod.$dirty) {
+ lockoutDuration = this.form.unlockMethod
+ ? this.form.lockoutDuration
+ : 0;
+ }
+
+ this.$emit('ok', { lockoutThreshold, lockoutDuration });
+ this.closeModal();
+ },
+ onOk(bvModalEvt) {
+ // prevent modal close
+ bvModalEvt.preventDefault();
+ this.handleSubmit();
+ },
+ closeModal() {
+ this.$nextTick(() => {
+ this.$refs.modal.hide();
+ });
+ },
+ resetForm() {
+ // Reset form models
+ this.form.lockoutThreshold = this.settings.lockoutThreshold;
+ this.form.unlockMethod = this.settings.lockoutDuration ? 1 : 0;
+ this.form.lockoutDuration = this.settings.lockoutDuration
+ ? this.settings.lockoutDuration
+ : null;
+ this.$v.$reset(); // clear validations
+ },
+ },
+};
+</script>
diff --git a/src/views/SecurityAndAccess/UserManagement/ModalUser.vue b/src/views/SecurityAndAccess/UserManagement/ModalUser.vue
new file mode 100644
index 00000000..0f8757ce
--- /dev/null
+++ b/src/views/SecurityAndAccess/UserManagement/ModalUser.vue
@@ -0,0 +1,386 @@
+<template>
+ <b-modal id="modal-user" ref="modal" @hidden="resetForm">
+ <template #modal-title>
+ <template v-if="newUser">
+ {{ $t('pageUserManagement.addUser') }}
+ </template>
+ <template v-else>
+ {{ $t('pageUserManagement.editUser') }}
+ </template>
+ </template>
+ <b-form id="form-user" novalidate @submit.prevent="handleSubmit">
+ <b-container>
+ <!-- Manual unlock form control -->
+ <b-row v-if="!newUser && manualUnlockPolicy && user.Locked">
+ <b-col sm="9">
+ <alert :show="true" variant="warning" small>
+ <template v-if="!$v.form.manualUnlock.$dirty">
+ {{ $t('pageUserManagement.modal.accountLocked') }}
+ </template>
+ <template v-else>
+ {{ $t('pageUserManagement.modal.clickSaveToUnlockAccount') }}
+ </template>
+ </alert>
+ </b-col>
+ <b-col sm="3">
+ <input
+ v-model="form.manualUnlock"
+ data-test-id="userManagement-input-manualUnlock"
+ type="hidden"
+ value="false"
+ />
+ <b-button
+ variant="primary"
+ :disabled="$v.form.manualUnlock.$dirty"
+ data-test-id="userManagement-button-manualUnlock"
+ @click="$v.form.manualUnlock.$touch()"
+ >
+ {{ $t('pageUserManagement.modal.unlock') }}
+ </b-button>
+ </b-col>
+ </b-row>
+ <b-row>
+ <b-col>
+ <b-form-group :label="$t('pageUserManagement.modal.accountStatus')">
+ <b-form-radio
+ v-model="form.status"
+ name="user-status"
+ :value="true"
+ data-test-id="userManagement-radioButton-statusEnabled"
+ @input="$v.form.status.$touch()"
+ >
+ {{ $t('global.status.enabled') }}
+ </b-form-radio>
+ <b-form-radio
+ v-model="form.status"
+ name="user-status"
+ data-test-id="userManagement-radioButton-statusDisabled"
+ :value="false"
+ @input="$v.form.status.$touch()"
+ >
+ {{ $t('global.status.disabled') }}
+ </b-form-radio>
+ </b-form-group>
+ <b-form-group
+ :label="$t('pageUserManagement.modal.username')"
+ label-for="username"
+ >
+ <b-form-text id="username-help-block">
+ {{ $t('pageUserManagement.modal.cannotStartWithANumber') }}
+ <br />
+ {{
+ $t(
+ 'pageUserManagement.modal.noSpecialCharactersExceptUnderscore'
+ )
+ }}
+ </b-form-text>
+ <b-form-input
+ id="username"
+ v-model="form.username"
+ type="text"
+ aria-describedby="username-help-block"
+ data-test-id="userManagement-input-username"
+ :state="getValidationState($v.form.username)"
+ :disabled="!newUser && originalUsername === 'root'"
+ @input="$v.form.username.$touch()"
+ />
+ <b-form-invalid-feedback role="alert">
+ <template v-if="!$v.form.username.required">
+ {{ $t('global.form.fieldRequired') }}
+ </template>
+ <template v-else-if="!$v.form.username.maxLength">
+ {{
+ $t('global.form.lengthMustBeBetween', { min: 1, max: 16 })
+ }}
+ </template>
+ <template v-else-if="!$v.form.username.pattern">
+ {{ $t('global.form.invalidFormat') }}
+ </template>
+ </b-form-invalid-feedback>
+ </b-form-group>
+ <b-form-group
+ :label="$t('pageUserManagement.modal.privilege')"
+ label-for="privilege"
+ >
+ <b-form-select
+ id="privilege"
+ v-model="form.privilege"
+ :options="privilegeTypes"
+ data-test-id="userManagement-select-privilege"
+ :state="getValidationState($v.form.privilege)"
+ @input="$v.form.privilege.$touch()"
+ >
+ <template #first>
+ <b-form-select-option :value="null" disabled>
+ {{ $t('global.form.selectAnOption') }}
+ </b-form-select-option>
+ </template>
+ </b-form-select>
+ <b-form-invalid-feedback role="alert">
+ <template v-if="!$v.form.privilege.required">
+ {{ $t('global.form.fieldRequired') }}
+ </template>
+ </b-form-invalid-feedback>
+ </b-form-group>
+ </b-col>
+ <b-col>
+ <b-form-group
+ :label="$t('pageUserManagement.modal.userPassword')"
+ label-for="password"
+ >
+ <b-form-text id="password-help-block">
+ {{
+ $t('pageUserManagement.modal.passwordMustBeBetween', {
+ min: passwordRequirements.minLength,
+ max: passwordRequirements.maxLength,
+ })
+ }}
+ </b-form-text>
+ <input-password-toggle>
+ <b-form-input
+ id="password"
+ v-model="form.password"
+ type="password"
+ data-test-id="userManagement-input-password"
+ aria-describedby="password-help-block"
+ :state="getValidationState($v.form.password)"
+ class="form-control-with-button"
+ @input="$v.form.password.$touch()"
+ />
+ <b-form-invalid-feedback role="alert">
+ <template v-if="!$v.form.password.required">
+ {{ $t('global.form.fieldRequired') }}
+ </template>
+ <template
+ v-if="
+ !$v.form.password.minLength || !$v.form.password.maxLength
+ "
+ >
+ {{
+ $t('pageUserManagement.modal.passwordMustBeBetween', {
+ min: passwordRequirements.minLength,
+ max: passwordRequirements.maxLength,
+ })
+ }}
+ </template>
+ </b-form-invalid-feedback>
+ </input-password-toggle>
+ </b-form-group>
+ <b-form-group
+ :label="$t('pageUserManagement.modal.confirmUserPassword')"
+ label-for="password-confirmation"
+ >
+ <input-password-toggle>
+ <b-form-input
+ id="password-confirmation"
+ v-model="form.passwordConfirmation"
+ data-test-id="userManagement-input-passwordConfirmation"
+ type="password"
+ :state="getValidationState($v.form.passwordConfirmation)"
+ class="form-control-with-button"
+ @input="$v.form.passwordConfirmation.$touch()"
+ />
+ <b-form-invalid-feedback role="alert">
+ <template v-if="!$v.form.passwordConfirmation.required">
+ {{ $t('global.form.fieldRequired') }}
+ </template>
+ <template
+ v-else-if="!$v.form.passwordConfirmation.sameAsPassword"
+ >
+ {{ $t('pageUserManagement.modal.passwordsDoNotMatch') }}
+ </template>
+ </b-form-invalid-feedback>
+ </input-password-toggle>
+ </b-form-group>
+ </b-col>
+ </b-row>
+ </b-container>
+ </b-form>
+ <template #modal-footer="{ cancel }">
+ <b-button
+ variant="secondary"
+ data-test-id="userManagement-button-cancel"
+ @click="cancel()"
+ >
+ {{ $t('global.action.cancel') }}
+ </b-button>
+ <b-button
+ form="form-user"
+ data-test-id="userManagement-button-submit"
+ type="submit"
+ variant="primary"
+ @click="onOk"
+ >
+ <template v-if="newUser">
+ {{ $t('pageUserManagement.addUser') }}
+ </template>
+ <template v-else>
+ {{ $t('global.action.save') }}
+ </template>
+ </b-button>
+ </template>
+ </b-modal>
+</template>
+
+<script>
+import {
+ required,
+ maxLength,
+ minLength,
+ sameAs,
+ helpers,
+ requiredIf,
+} from 'vuelidate/lib/validators';
+import VuelidateMixin from '@/components/Mixins/VuelidateMixin.js';
+import InputPasswordToggle from '@/components/Global/InputPasswordToggle';
+import Alert from '@/components/Global/Alert';
+
+export default {
+ components: { Alert, InputPasswordToggle },
+ mixins: [VuelidateMixin],
+ props: {
+ user: {
+ type: Object,
+ default: null,
+ },
+ passwordRequirements: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ originalUsername: '',
+ form: {
+ status: true,
+ username: '',
+ privilege: null,
+ password: '',
+ passwordConfirmation: '',
+ manualUnlock: false,
+ },
+ };
+ },
+ computed: {
+ newUser() {
+ return this.user ? false : true;
+ },
+ accountSettings() {
+ return this.$store.getters['userManagement/accountSettings'];
+ },
+ manualUnlockPolicy() {
+ return !this.accountSettings.accountLockoutDuration;
+ },
+ privilegeTypes() {
+ return this.$store.getters['userManagement/accountRoles'];
+ },
+ },
+ watch: {
+ user: function (value) {
+ if (value === null) return;
+ this.originalUsername = value.username;
+ this.form.username = value.username;
+ this.form.status = value.Enabled;
+ this.form.privilege = value.privilege;
+ },
+ },
+ validations() {
+ return {
+ form: {
+ status: {
+ required,
+ },
+ username: {
+ required,
+ maxLength: maxLength(16),
+ pattern: helpers.regex('pattern', /^([a-zA-Z_][a-zA-Z0-9_]*)/),
+ },
+ privilege: {
+ required,
+ },
+ password: {
+ required: requiredIf(function () {
+ return this.requirePassword();
+ }),
+ minLength: minLength(this.passwordRequirements.minLength),
+ maxLength: maxLength(this.passwordRequirements.maxLength),
+ },
+ passwordConfirmation: {
+ required: requiredIf(function () {
+ return this.requirePassword();
+ }),
+ sameAsPassword: sameAs('password'),
+ },
+ manualUnlock: {},
+ },
+ };
+ },
+ methods: {
+ handleSubmit() {
+ let userData = {};
+
+ if (this.newUser) {
+ this.$v.$touch();
+ if (this.$v.$invalid) return;
+ userData.username = this.form.username;
+ userData.status = this.form.status;
+ userData.privilege = this.form.privilege;
+ userData.password = this.form.password;
+ } else {
+ if (this.$v.$invalid) return;
+ userData.originalUsername = this.originalUsername;
+ if (this.$v.form.status.$dirty) {
+ userData.status = this.form.status;
+ }
+ if (this.$v.form.username.$dirty) {
+ userData.username = this.form.username;
+ }
+ if (this.$v.form.privilege.$dirty) {
+ userData.privilege = this.form.privilege;
+ }
+ if (this.$v.form.password.$dirty) {
+ userData.password = this.form.password;
+ }
+ if (this.$v.form.manualUnlock.$dirty) {
+ // If form manualUnlock control $dirty then
+ // set user Locked property to false
+ userData.locked = false;
+ }
+ if (Object.entries(userData).length === 1) {
+ this.closeModal();
+ return;
+ }
+ }
+
+ this.$emit('ok', { isNewUser: this.newUser, userData });
+ this.closeModal();
+ },
+ closeModal() {
+ this.$nextTick(() => {
+ this.$refs.modal.hide();
+ });
+ },
+ resetForm() {
+ this.form.originalUsername = '';
+ this.form.status = true;
+ this.form.username = '';
+ this.form.privilege = null;
+ this.form.password = '';
+ this.form.passwordConfirmation = '';
+ this.$v.$reset();
+ this.$emit('hidden');
+ },
+ requirePassword() {
+ if (this.newUser) return true;
+ if (this.$v.form.password.$dirty) return true;
+ if (this.$v.form.passwordConfirmation.$dirty) return true;
+ return false;
+ },
+ onOk(bvModalEvt) {
+ // prevent modal close
+ bvModalEvt.preventDefault();
+ this.handleSubmit();
+ },
+ },
+};
+</script>
diff --git a/src/views/SecurityAndAccess/UserManagement/TableRoles.vue b/src/views/SecurityAndAccess/UserManagement/TableRoles.vue
new file mode 100644
index 00000000..61ef1ee8
--- /dev/null
+++ b/src/views/SecurityAndAccess/UserManagement/TableRoles.vue
@@ -0,0 +1,92 @@
+<template>
+ <b-table stacked="sm" hover small :items="items" :fields="fields">
+ <template #cell(administrator)="data">
+ <template v-if="data.value">
+ <checkmark20 />
+ </template>
+ </template>
+ <template #cell(operator)="data">
+ <template v-if="data.value">
+ <checkmark20 />
+ </template>
+ </template>
+ <template #cell(readonly)="data">
+ <template v-if="data.value">
+ <checkmark20 />
+ </template>
+ </template>
+ <template #cell(noaccess)="data">
+ <template v-if="data.value">
+ <checkmark20 />
+ </template>
+ </template>
+ </b-table>
+</template>
+
+<script>
+import Checkmark20 from '@carbon/icons-vue/es/checkmark/20';
+
+export default {
+ components: {
+ Checkmark20,
+ },
+ data() {
+ return {
+ items: [
+ {
+ description: this.$t(
+ 'pageUserManagement.tableRoles.configureComponentsManagedByThisService'
+ ),
+ administrator: true,
+ operator: true,
+ readonly: false,
+ noaccess: false,
+ },
+ {
+ description: this.$t(
+ 'pageUserManagement.tableRoles.configureManagerResources'
+ ),
+ administrator: true,
+ operator: false,
+ readonly: false,
+ noaccess: false,
+ },
+ {
+ description: this.$t(
+ 'pageUserManagement.tableRoles.updatePasswordForCurrentUserAccount'
+ ),
+ administrator: true,
+ operator: true,
+ readonly: true,
+ noaccess: false,
+ },
+ {
+ description: this.$t(
+ 'pageUserManagement.tableRoles.configureUsersAndTheirAccounts'
+ ),
+ administrator: true,
+ operator: false,
+ readonly: false,
+ noaccess: false,
+ },
+ {
+ description: this.$t(
+ 'pageUserManagement.tableRoles.logInToTheServiceAndReadResources'
+ ),
+ administrator: true,
+ operator: true,
+ readonly: true,
+ noaccess: false,
+ },
+ ],
+ fields: [
+ { key: 'description', label: 'Privilege' },
+ { key: 'administrator', label: 'Administrator', class: 'text-center' },
+ { key: 'operator', label: 'Operator', class: 'text-center' },
+ { key: 'readonly', label: 'ReadOnly', class: 'text-center' },
+ { key: 'noaccess', label: 'NoAccess', class: 'text-center' },
+ ],
+ };
+ },
+};
+</script>
diff --git a/src/views/SecurityAndAccess/UserManagement/UserManagement.vue b/src/views/SecurityAndAccess/UserManagement/UserManagement.vue
new file mode 100644
index 00000000..015fee91
--- /dev/null
+++ b/src/views/SecurityAndAccess/UserManagement/UserManagement.vue
@@ -0,0 +1,388 @@
+<template>
+ <b-container fluid="xl">
+ <page-title />
+ <b-row>
+ <b-col xl="9" class="text-right">
+ <b-button variant="link" @click="initModalSettings">
+ <icon-settings />
+ {{ $t('pageUserManagement.accountPolicySettings') }}
+ </b-button>
+ <b-button
+ variant="primary"
+ data-test-id="userManagement-button-addUser"
+ @click="initModalUser(null)"
+ >
+ <icon-add />
+ {{ $t('pageUserManagement.addUser') }}
+ </b-button>
+ </b-col>
+ </b-row>
+ <b-row>
+ <b-col xl="9">
+ <table-toolbar
+ ref="toolbar"
+ :selected-items-count="selectedRows.length"
+ :actions="tableToolbarActions"
+ @clear-selected="clearSelectedRows($refs.table)"
+ @batch-action="onBatchAction"
+ />
+ <b-table
+ ref="table"
+ responsive="md"
+ selectable
+ show-empty
+ no-select-on-click
+ hover
+ :fields="fields"
+ :items="tableItems"
+ :empty-text="$t('global.table.emptyMessage')"
+ @row-selected="onRowSelected($event, tableItems.length)"
+ >
+ <!-- Checkbox column -->
+ <template #head(checkbox)>
+ <b-form-checkbox
+ v-model="tableHeaderCheckboxModel"
+ data-test-id="userManagement-checkbox-tableHeaderCheckbox"
+ :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="userManagement-checkbox-toggleSelectRow"
+ @change="toggleSelectRow($refs.table, row.index)"
+ >
+ <span class="sr-only">{{ $t('global.table.selectItem') }}</span>
+ </b-form-checkbox>
+ </template>
+
+ <!-- table actions column -->
+ <template #cell(actions)="{ item }">
+ <table-row-action
+ v-for="(action, index) in item.actions"
+ :key="index"
+ :value="action.value"
+ :enabled="action.enabled"
+ :title="action.title"
+ @click-table-action="onTableRowAction($event, item)"
+ >
+ <template #icon>
+ <icon-edit
+ v-if="action.value === 'edit'"
+ :data-test-id="`userManagement-tableRowAction-edit-${index}`"
+ />
+ <icon-trashcan
+ v-if="action.value === 'delete'"
+ :data-test-id="`userManagement-tableRowAction-delete-${index}`"
+ />
+ </template>
+ </table-row-action>
+ </template>
+ </b-table>
+ </b-col>
+ </b-row>
+ <b-row>
+ <b-col xl="8">
+ <b-button
+ v-b-toggle.collapse-role-table
+ data-test-id="userManagement-button-viewPrivilegeRoleDescriptions"
+ variant="link"
+ class="mt-3"
+ >
+ <icon-chevron />
+ {{ $t('pageUserManagement.viewPrivilegeRoleDescriptions') }}
+ </b-button>
+ <b-collapse id="collapse-role-table" class="mt-3">
+ <table-roles />
+ </b-collapse>
+ </b-col>
+ </b-row>
+ <!-- Modals -->
+ <modal-settings :settings="settings" @ok="saveAccountSettings" />
+ <modal-user
+ :user="activeUser"
+ :password-requirements="passwordRequirements"
+ @ok="saveUser"
+ @hidden="activeUser = null"
+ />
+ </b-container>
+</template>
+
+<script>
+import IconTrashcan from '@carbon/icons-vue/es/trash-can/20';
+import IconEdit from '@carbon/icons-vue/es/edit/20';
+import IconAdd from '@carbon/icons-vue/es/add--alt/20';
+import IconSettings from '@carbon/icons-vue/es/settings/20';
+import IconChevron from '@carbon/icons-vue/es/chevron--up/20';
+
+import ModalUser from './ModalUser';
+import ModalSettings from './ModalSettings';
+import PageTitle from '@/components/Global/PageTitle';
+import TableRoles from './TableRoles';
+import TableToolbar from '@/components/Global/TableToolbar';
+import TableRowAction from '@/components/Global/TableRowAction';
+
+import BVTableSelectableMixin, {
+ selectedRows,
+ tableHeaderCheckboxModel,
+ tableHeaderCheckboxIndeterminate,
+} from '@/components/Mixins/BVTableSelectableMixin';
+import BVToastMixin from '@/components/Mixins/BVToastMixin';
+import LoadingBarMixin from '@/components/Mixins/LoadingBarMixin';
+
+export default {
+ name: 'UserManagement',
+ components: {
+ IconAdd,
+ IconChevron,
+ IconEdit,
+ IconSettings,
+ IconTrashcan,
+ ModalSettings,
+ ModalUser,
+ PageTitle,
+ TableRoles,
+ TableRowAction,
+ TableToolbar,
+ },
+ mixins: [BVTableSelectableMixin, BVToastMixin, LoadingBarMixin],
+ beforeRouteLeave(to, from, next) {
+ this.hideLoader();
+ next();
+ },
+ data() {
+ return {
+ activeUser: null,
+ fields: [
+ {
+ key: 'checkbox',
+ },
+ {
+ key: 'username',
+ label: this.$t('pageUserManagement.table.username'),
+ },
+ {
+ key: 'privilege',
+ label: this.$t('pageUserManagement.table.privilege'),
+ },
+ {
+ key: 'status',
+ label: this.$t('pageUserManagement.table.status'),
+ },
+ {
+ key: 'actions',
+ label: '',
+ tdClass: 'text-right text-nowrap',
+ },
+ ],
+ tableToolbarActions: [
+ {
+ value: 'delete',
+ label: this.$t('global.action.delete'),
+ },
+ {
+ value: 'enable',
+ label: this.$t('global.action.enable'),
+ },
+ {
+ value: 'disable',
+ label: this.$t('global.action.disable'),
+ },
+ ],
+ selectedRows: selectedRows,
+ tableHeaderCheckboxModel: tableHeaderCheckboxModel,
+ tableHeaderCheckboxIndeterminate: tableHeaderCheckboxIndeterminate,
+ };
+ },
+ computed: {
+ allUsers() {
+ return this.$store.getters['userManagement/allUsers'];
+ },
+ tableItems() {
+ // transform user data to table data
+ return this.allUsers.map((user) => {
+ return {
+ username: user.UserName,
+ privilege: user.RoleId,
+ status: user.Locked
+ ? 'Locked'
+ : user.Enabled
+ ? 'Enabled'
+ : 'Disabled',
+ actions: [
+ {
+ value: 'edit',
+ enabled: true,
+ title: this.$t('pageUserManagement.editUser'),
+ },
+ {
+ value: 'delete',
+ enabled: user.UserName === 'root' ? false : true,
+ title: this.$tc('pageUserManagement.deleteUser'),
+ },
+ ],
+ ...user,
+ };
+ });
+ },
+ settings() {
+ return this.$store.getters['userManagement/accountSettings'];
+ },
+ passwordRequirements() {
+ return this.$store.getters['userManagement/accountPasswordRequirements'];
+ },
+ },
+ created() {
+ this.startLoader();
+ this.$store
+ .dispatch('userManagement/getUsers')
+ .finally(() => this.endLoader());
+ this.$store.dispatch('userManagement/getAccountSettings');
+ this.$store.dispatch('userManagement/getAccountRoles');
+ },
+ methods: {
+ initModalUser(user) {
+ this.activeUser = user;
+ this.$bvModal.show('modal-user');
+ },
+ initModalDelete(user) {
+ this.$bvModal
+ .msgBoxConfirm(
+ this.$t('pageUserManagement.modal.deleteConfirmMessage', {
+ user: user.username,
+ }),
+ {
+ title: this.$tc('pageUserManagement.deleteUser'),
+ okTitle: this.$tc('pageUserManagement.deleteUser'),
+ cancelTitle: this.$t('global.action.cancel'),
+ }
+ )
+ .then((deleteConfirmed) => {
+ if (deleteConfirmed) {
+ this.deleteUser(user);
+ }
+ });
+ },
+ initModalSettings() {
+ this.$bvModal.show('modal-settings');
+ },
+ saveUser({ isNewUser, userData }) {
+ this.startLoader();
+ if (isNewUser) {
+ this.$store
+ .dispatch('userManagement/createUser', userData)
+ .then((success) => this.successToast(success))
+ .catch(({ message }) => this.errorToast(message))
+ .finally(() => this.endLoader());
+ } else {
+ this.$store
+ .dispatch('userManagement/updateUser', userData)
+ .then((success) => this.successToast(success))
+ .catch(({ message }) => this.errorToast(message))
+ .finally(() => this.endLoader());
+ }
+ },
+ deleteUser({ username }) {
+ this.startLoader();
+ this.$store
+ .dispatch('userManagement/deleteUser', username)
+ .then((success) => this.successToast(success))
+ .catch(({ message }) => this.errorToast(message))
+ .finally(() => this.endLoader());
+ },
+ onBatchAction(action) {
+ switch (action) {
+ case 'delete':
+ this.$bvModal
+ .msgBoxConfirm(
+ this.$tc(
+ 'pageUserManagement.modal.batchDeleteConfirmMessage',
+ this.selectedRows.length
+ ),
+ {
+ title: this.$tc(
+ 'pageUserManagement.deleteUser',
+ this.selectedRows.length
+ ),
+ okTitle: this.$tc(
+ 'pageUserManagement.deleteUser',
+ this.selectedRows.length
+ ),
+ cancelTitle: this.$t('global.action.cancel'),
+ }
+ )
+ .then((deleteConfirmed) => {
+ if (deleteConfirmed) {
+ this.startLoader();
+ this.$store
+ .dispatch('userManagement/deleteUsers', this.selectedRows)
+ .then((messages) => {
+ messages.forEach(({ type, message }) => {
+ if (type === 'success') this.successToast(message);
+ if (type === 'error') this.errorToast(message);
+ });
+ })
+ .finally(() => this.endLoader());
+ }
+ });
+ break;
+ case 'enable':
+ this.startLoader();
+ this.$store
+ .dispatch('userManagement/enableUsers', this.selectedRows)
+ .then((messages) => {
+ messages.forEach(({ type, message }) => {
+ if (type === 'success') this.successToast(message);
+ if (type === 'error') this.errorToast(message);
+ });
+ })
+ .finally(() => this.endLoader());
+ break;
+ case 'disable':
+ this.startLoader();
+ this.$store
+ .dispatch('userManagement/disableUsers', this.selectedRows)
+ .then((messages) => {
+ messages.forEach(({ type, message }) => {
+ if (type === 'success') this.successToast(message);
+ if (type === 'error') this.errorToast(message);
+ });
+ })
+ .finally(() => this.endLoader());
+ break;
+ }
+ },
+ onTableRowAction(action, row) {
+ switch (action) {
+ case 'edit':
+ this.initModalUser(row);
+ break;
+ case 'delete':
+ this.initModalDelete(row);
+ break;
+ default:
+ break;
+ }
+ },
+ saveAccountSettings(settings) {
+ this.startLoader();
+ this.$store
+ .dispatch('userManagement/saveAccountSettings', settings)
+ .then((message) => this.successToast(message))
+ .catch(({ message }) => this.errorToast(message))
+ .finally(() => this.endLoader());
+ },
+ },
+};
+</script>
+
+<style lang="scss" scoped>
+.btn.collapsed {
+ svg {
+ transform: rotate(180deg);
+ }
+}
+</style>
diff --git a/src/views/SecurityAndAccess/UserManagement/index.js b/src/views/SecurityAndAccess/UserManagement/index.js
new file mode 100644
index 00000000..c3aebec3
--- /dev/null
+++ b/src/views/SecurityAndAccess/UserManagement/index.js
@@ -0,0 +1,2 @@
+import UserManagement from './UserManagement.vue';
+export default UserManagement;