xref: /openbmc/webui-vue/src/views/SecurityAndAccess/UserManagement/UserManagement.vue (revision d36ac8a8be8636ddd0e64ce005d507b21bcdeb00)
1<template>
2  <b-container fluid="xl">
3    <page-title />
4    <b-row>
5      <b-col xl="9" class="text-end">
6        <b-button variant="link" @click="initModalSettings">
7          <icon-settings />
8          {{ $t('pageUserManagement.accountPolicySettings') }}
9        </b-button>
10        <b-button
11          variant="primary"
12          data-test-id="userManagement-button-addUser"
13          @click="initModalUser(null)"
14        >
15          <icon-add />
16          {{ $t('pageUserManagement.addUser') }}
17        </b-button>
18      </b-col>
19    </b-row>
20    <b-row>
21      <b-col xl="9">
22        <table-toolbar
23          ref="toolbar"
24          :selected-items-count="
25            Array.isArray(selectedRows) ? selectedRows.length : 0
26          "
27          :actions="tableToolbarActions"
28          @clear-selected="clearSelectedRows($refs.table)"
29          @batch-action="onBatchAction"
30        />
31        <b-table
32          ref="table"
33          responsive="md"
34          selectable
35          show-empty
36          no-select-on-click
37          hover
38          thead-class="table-light"
39          :busy="isBusy"
40          :fields="fields"
41          :items="tableItems"
42          :empty-text="$t('global.table.emptyMessage')"
43          @row-selected="onRowSelected($event, tableItems.length)"
44        >
45          <!-- Checkbox column -->
46          <template #head(checkbox)>
47            <b-form-checkbox
48              v-model="tableHeaderCheckboxModel"
49              data-test-id="userManagement-checkbox-tableHeaderCheckbox"
50              :indeterminate="tableHeaderCheckboxIndeterminate"
51              @change="onChangeHeaderCheckbox($refs.table, $event)"
52            >
53              <span class="visually-hidden-focusable">
54                {{ $t('global.table.selectAll') }}
55              </span>
56            </b-form-checkbox>
57          </template>
58          <template #cell(checkbox)="row">
59            <b-form-checkbox
60              v-model="row.rowSelected"
61              data-test-id="userManagement-checkbox-toggleSelectRow"
62              @change="toggleSelectRow($refs.table, row.index)"
63            >
64              <span class="visually-hidden-focusable">
65                {{ $t('global.table.selectItem') }}
66              </span>
67            </b-form-checkbox>
68          </template>
69
70          <!-- table actions column -->
71          <template #cell(actions)="{ item }">
72            <table-row-action
73              v-for="(action, index) in item.actions"
74              :key="index"
75              :value="action.value"
76              :enabled="action.enabled"
77              :title="action.title"
78              @click-table-action="onTableRowAction($event, item)"
79            >
80              <template #icon>
81                <icon-edit
82                  v-if="action.value === 'edit'"
83                  :data-test-id="`userManagement-tableRowAction-edit-${index}`"
84                />
85                <icon-trashcan
86                  v-if="action.value === 'delete'"
87                  :data-test-id="`userManagement-tableRowAction-delete-${index}`"
88                />
89              </template>
90            </table-row-action>
91          </template>
92        </b-table>
93      </b-col>
94    </b-row>
95    <b-row>
96      <b-col xl="8">
97        <b-button
98          data-test-id="userManagement-button-viewPrivilegeRoleDescriptions"
99          variant="link"
100          class="mt-3"
101          @click="showRoles = !showRoles"
102        >
103          <icon-chevron />
104          {{ $t('pageUserManagement.viewPrivilegeRoleDescriptions') }}
105        </b-button>
106        <b-collapse id="collapse-role-table" :visible="showRoles" class="mt-3">
107          <table-roles />
108        </b-collapse>
109      </b-col>
110    </b-row>
111    <!-- Modals -->
112    <modal-settings
113      v-model="showSettingsModal"
114      :settings="setting"
115      @ok="saveAccountSettings"
116    />
117    <modal-user
118      v-model="showUserModal"
119      :user="activeUser"
120      :password-requirements="passwordRequirements"
121      @ok="saveUser"
122      @hidden="activeUser = null"
123    />
124  </b-container>
125</template>
126
127<script>
128import IconTrashcan from '@carbon/icons-vue/es/trash-can/20';
129import IconEdit from '@carbon/icons-vue/es/edit/20';
130import IconAdd from '@carbon/icons-vue/es/add--alt/20';
131import IconSettings from '@carbon/icons-vue/es/settings/20';
132import IconChevron from '@carbon/icons-vue/es/chevron--up/20';
133
134import ModalUser from './ModalUser';
135import ModalSettings from './ModalSettings';
136import PageTitle from '@/components/Global/PageTitle';
137import TableRoles from './TableRoles';
138import TableToolbar from '@/components/Global/TableToolbar';
139import TableRowAction from '@/components/Global/TableRowAction';
140
141import BVTableSelectableMixin, {
142  selectedRows,
143  tableHeaderCheckboxModel,
144  tableHeaderCheckboxIndeterminate,
145} from '@/components/Mixins/BVTableSelectableMixin';
146import BVToastMixin from '@/components/Mixins/BVToastMixin';
147import LoadingBarMixin from '@/components/Mixins/LoadingBarMixin';
148import { useI18n } from 'vue-i18n';
149import i18n from '@/i18n';
150import { useModal } from 'bootstrap-vue-next';
151
152export default {
153  name: 'UserManagement',
154  components: {
155    IconAdd,
156    IconChevron,
157    IconEdit,
158    IconSettings,
159    IconTrashcan,
160    ModalSettings,
161    ModalUser,
162    PageTitle,
163    TableRoles,
164    TableRowAction,
165    TableToolbar,
166  },
167  mixins: [BVTableSelectableMixin, BVToastMixin, LoadingBarMixin],
168  beforeRouteLeave(to, from, next) {
169    this.hideLoader();
170    next();
171  },
172  setup() {
173    const bvModal = useModal();
174    return { bvModal };
175  },
176  data() {
177    return {
178      $t: useI18n().t,
179      isBusy: true,
180      activeUser: null,
181      setting: {},
182      fields: [
183        {
184          key: 'checkbox',
185        },
186        {
187          key: 'username',
188          label: i18n.global.t('pageUserManagement.table.username'),
189        },
190        {
191          key: 'privilege',
192          label: i18n.global.t('pageUserManagement.table.privilege'),
193        },
194        {
195          key: 'status',
196          label: i18n.global.t('pageUserManagement.table.status'),
197        },
198        {
199          key: 'actions',
200          label: '',
201          tdClass: 'text-end text-nowrap',
202        },
203      ],
204      tableToolbarActions: [
205        {
206          value: 'delete',
207          label: i18n.global.t('global.action.delete'),
208        },
209        {
210          value: 'enable',
211          label: i18n.global.t('global.action.enable'),
212        },
213        {
214          value: 'disable',
215          label: i18n.global.t('global.action.disable'),
216        },
217      ],
218      selectedRows: selectedRows,
219      tableHeaderCheckboxModel: tableHeaderCheckboxModel,
220      tableHeaderCheckboxIndeterminate: tableHeaderCheckboxIndeterminate,
221      showUserModal: false,
222      showSettingsModal: false,
223      showRoles: false,
224    };
225  },
226  computed: {
227    allUsers() {
228      return this.$store.getters['userManagement/allUsers'];
229    },
230    tableItems() {
231      // transform user data to table data
232      return this.allUsers.map((user) => {
233        return {
234          username: user.UserName,
235          privilege: user.RoleId,
236          status: user.Locked
237            ? 'Locked'
238            : user.Enabled
239              ? 'Enabled'
240              : 'Disabled',
241          actions: [
242            {
243              value: 'edit',
244              enabled: this.editEnable(user),
245              title: i18n.global.t('pageUserManagement.editUser'),
246            },
247            {
248              value: 'delete',
249              enabled:
250                user.UserName === this.$store.getters['global/username']
251                  ? false
252                  : true && user.UserName === 'root'
253                    ? false
254                    : true,
255              title: i18n.global.t('pageUserManagement.deleteUser'),
256            },
257          ],
258          ...user,
259        };
260      });
261    },
262    settings() {
263      return this.$store.getters['userManagement/accountSettings'];
264    },
265    passwordRequirements() {
266      return this.$store.getters['userManagement/accountPasswordRequirements'];
267    },
268  },
269  created() {
270    this.startLoader();
271    this.$store.dispatch('userManagement/getUsers').finally(() => {
272      this.endLoader();
273      this.isBusy = false;
274    });
275    this.$store.dispatch('userManagement/getAccountSettings');
276    this.$store.dispatch('userManagement/getAccountRoles');
277  },
278  methods: {
279    editEnable(user) {
280      if ('root' === this.$store.getters['global/username']) {
281        return true;
282      } else {
283        return user.UserName === 'root' ? false : true;
284      }
285    },
286    initModalUser(user) {
287      this.activeUser = user;
288      this.showUserModal = true;
289    },
290    initModalDelete(user) {
291      this.confirmDialog(
292        i18n.global.t('pageUserManagement.modal.deleteConfirmMessage', {
293          user: user.username,
294        }),
295        {
296          title: i18n.global.t('pageUserManagement.deleteUser'),
297          okTitle: i18n.global.t('pageUserManagement.deleteUser'),
298          cancelTitle: i18n.global.t('global.action.cancel'),
299          autoFocusButton: 'ok',
300        },
301      ).then((deleteConfirmed) => {
302        if (deleteConfirmed) {
303          this.deleteUser(user);
304        }
305      });
306    },
307    initModalSettings() {
308      this.setting = this.settings;
309      this.showSettingsModal = true;
310    },
311    saveUser({ isNewUser, userData }) {
312      this.startLoader();
313      if (isNewUser) {
314        this.$store
315          .dispatch('userManagement/createUser', userData)
316          .then((success) => this.successToast(success))
317          .catch(({ message }) => this.errorToast(message))
318          .finally(() => this.endLoader());
319      } else {
320        this.$store
321          .dispatch('userManagement/updateUser', userData)
322          .then((success) => this.successToast(success))
323          .catch(({ message }) => this.errorToast(message))
324          .finally(() => this.endLoader());
325      }
326    },
327    deleteUser({ username }) {
328      this.startLoader();
329      this.$store
330        .dispatch('userManagement/deleteUser', username)
331        .then((success) => this.successToast(success))
332        .catch(({ message }) => this.errorToast(message))
333        .finally(() => this.endLoader());
334    },
335    onBatchAction(action) {
336      const count = this.selectedRows.length;
337      switch (action) {
338        case 'delete':
339          this.confirmDialog(
340            i18n.global.t(
341              'pageUserManagement.modal.batchDeleteConfirmMessage',
342              count,
343            ),
344            {
345              title: i18n.global.t('pageUserManagement.deleteUser', count),
346              okTitle: i18n.global.t('pageUserManagement.deleteUser', count),
347              cancelTitle: i18n.global.t('global.action.cancel'),
348              autoFocusButton: 'ok',
349            },
350          ).then((deleteConfirmed) => {
351            if (deleteConfirmed) {
352              this.startLoader();
353              this.$store
354                .dispatch('userManagement/deleteUsers', this.selectedRows)
355                .then((messages) => {
356                  messages.forEach(({ type, message }) => {
357                    if (type === 'success') this.successToast(message);
358                    if (type === 'error') this.errorToast(message);
359                  });
360                })
361                .finally(() => this.endLoader());
362            }
363          });
364          break;
365        case 'enable':
366          this.startLoader();
367          this.$store
368            .dispatch('userManagement/enableUsers', this.selectedRows)
369            .then((messages) => {
370              messages.forEach(({ type, message }) => {
371                if (type === 'success') this.successToast(message);
372                if (type === 'error') this.errorToast(message);
373              });
374            })
375            .finally(() => this.endLoader());
376          break;
377        case 'disable':
378          this.confirmDialog(
379            i18n.global.t(
380              'pageUserManagement.modal.batchDisableConfirmMessage',
381              count,
382            ),
383            {
384              title: i18n.global.t('pageUserManagement.disableUser', count),
385              okTitle: i18n.global.t('pageUserManagement.disableUser', count),
386              cancelTitle: i18n.global.t('global.action.cancel'),
387              autoFocusButton: 'ok',
388            },
389          ).then((disableConfirmed) => {
390            if (disableConfirmed) {
391              this.startLoader();
392              this.$store
393                .dispatch('userManagement/disableUsers', this.selectedRows)
394                .then((messages) => {
395                  messages.forEach(({ type, message }) => {
396                    if (type === 'success') this.successToast(message);
397                    if (type === 'error') this.errorToast(message);
398                  });
399                })
400                .finally(() => this.endLoader());
401            }
402          });
403          break;
404      }
405    },
406    confirmDialog(message, options = {}) {
407      return this.$confirm({ message, ...options });
408    },
409    onTableRowAction(action, row) {
410      switch (action) {
411        case 'edit':
412          this.initModalUser(row);
413          break;
414        case 'delete':
415          this.initModalDelete(row);
416          break;
417        default:
418          break;
419      }
420    },
421    saveAccountSettings(settings) {
422      this.startLoader();
423      this.$store
424        .dispatch('userManagement/saveAccountSettings', settings)
425        .then((message) => this.successToast(message))
426        .catch(({ message }) => this.errorToast(message))
427        .finally(() => this.endLoader());
428    },
429  },
430};
431</script>
432
433<style lang="scss" scoped>
434.btn.collapsed {
435  svg {
436    transform: rotate(180deg);
437  }
438}
439</style>
440