1<template>
2  <b-modal id="modal-user" ref="modal" @hidden="resetForm">
3    <template #modal-title>
4      <template v-if="newUser">
5        {{ $t('pageUserManagement.addUser') }}
6      </template>
7      <template v-else>
8        {{ $t('pageUserManagement.editUser') }}
9      </template>
10    </template>
11    <b-form id="form-user" novalidate @submit.prevent="handleSubmit">
12      <b-container>
13        <!-- Manual unlock form control -->
14        <b-row v-if="!newUser && manualUnlockPolicy && user.Locked">
15          <b-col sm="9">
16            <alert :show="true" variant="warning" small>
17              <template v-if="!v$.form.manualUnlock.$dirty">
18                {{ $t('pageUserManagement.modal.accountLocked') }}
19              </template>
20              <template v-else>
21                {{ $t('pageUserManagement.modal.clickSaveToUnlockAccount') }}
22              </template>
23            </alert>
24          </b-col>
25          <b-col sm="3">
26            <input
27              v-model="form.manualUnlock"
28              data-test-id="userManagement-input-manualUnlock"
29              type="hidden"
30            />
31            <b-button
32              variant="primary"
33              :disabled="v$.form.manualUnlock.$dirty"
34              data-test-id="userManagement-button-manualUnlock"
35              @click="v$.form.manualUnlock.$touch()"
36            >
37              {{ $t('pageUserManagement.modal.unlock') }}
38            </b-button>
39          </b-col>
40        </b-row>
41        <b-row>
42          <b-col>
43            <b-form-group :label="$t('pageUserManagement.modal.accountStatus')">
44              <b-form-radio
45                v-model="form.status"
46                name="user-status"
47                :value="true"
48                data-test-id="userManagement-radioButton-statusEnabled"
49                @input="v$.form.status.$touch()"
50              >
51                {{ $t('global.status.enabled') }}
52              </b-form-radio>
53              <b-form-radio
54                v-model="form.status"
55                name="user-status"
56                data-test-id="userManagement-radioButton-statusDisabled"
57                :value="false"
58                :disabled="!newUser && originalUsername === disabled"
59                @input="v$.form.status.$touch()"
60              >
61                {{ $t('global.status.disabled') }}
62              </b-form-radio>
63            </b-form-group>
64            <b-form-group
65              :label="$t('pageUserManagement.modal.username')"
66              label-for="username"
67            >
68              <b-form-text id="username-help-block">
69                {{ $t('pageUserManagement.modal.cannotStartWithANumber') }}
70                <br />
71                {{
72                  $t(
73                    'pageUserManagement.modal.noSpecialCharactersExceptUnderscore',
74                  )
75                }}
76              </b-form-text>
77              <b-form-input
78                id="username"
79                v-model="form.username"
80                type="text"
81                aria-describedby="username-help-block"
82                data-test-id="userManagement-input-username"
83                :state="getValidationState(v$.form.username)"
84                :disabled="!newUser && originalUsername === disabled"
85                @input="v$.form.username.$touch()"
86              />
87              <b-form-invalid-feedback role="alert">
88                <template v-if="v$.form.username.required.$invalid">
89                  {{ $t('global.form.fieldRequired') }}
90                </template>
91                <template v-else-if="v$.form.username.maxLength.$invalid">
92                  {{
93                    $t('global.form.lengthMustBeBetween', { min: 1, max: 16 })
94                  }}
95                </template>
96                <template v-else-if="v$.form.username.pattern.$invalid">
97                  {{ $t('global.form.invalidFormat') }}
98                </template>
99              </b-form-invalid-feedback>
100            </b-form-group>
101            <b-form-group
102              :label="$t('pageUserManagement.modal.privilege')"
103              label-for="privilege"
104            >
105              <b-form-select
106                id="privilege"
107                v-model="form.privilege"
108                :options="privilegeTypes"
109                data-test-id="userManagement-select-privilege"
110                :state="getValidationState(v$.form.privilege)"
111                :disabled="!newUser && originalUsername === 'root'"
112                @input="v$.form.privilege.$touch()"
113              >
114                <template #first>
115                  <b-form-select-option :value="null" disabled>
116                    {{ $t('global.form.selectAnOption') }}
117                  </b-form-select-option>
118                </template>
119              </b-form-select>
120              <b-form-invalid-feedback role="alert">
121                <template v-if="v$.form.privilege.required.$invalid">
122                  {{ $t('global.form.fieldRequired') }}
123                </template>
124              </b-form-invalid-feedback>
125            </b-form-group>
126          </b-col>
127          <b-col>
128            <b-form-group
129              :label="$t('pageUserManagement.modal.userPassword')"
130              label-for="password"
131            >
132              <b-form-text id="password-help-block">
133                {{
134                  $t('pageUserManagement.modal.passwordMustBeBetween', {
135                    min: passwordRequirements.minLength,
136                    max: passwordRequirements.maxLength,
137                  })
138                }}
139              </b-form-text>
140              <input-password-toggle>
141                <b-form-input
142                  id="password"
143                  v-model="form.password"
144                  type="password"
145                  data-test-id="userManagement-input-password"
146                  aria-describedby="password-help-block"
147                  :state="getValidationState(v$.form.password)"
148                  class="form-control-with-button"
149                  @input="v$.form.password.$touch()"
150                />
151                <b-form-invalid-feedback role="alert">
152                  <template v-if="v$.form.password.required.$invalid">
153                    {{ $t('global.form.fieldRequired') }}
154                  </template>
155                  <template
156                    v-if="
157                      v$.form.password.minLength.$invalid ||
158                      v$.form.password.maxLength.$invalid
159                    "
160                  >
161                    {{
162                      $t('pageUserManagement.modal.passwordMustBeBetween', {
163                        min: passwordRequirements.minLength,
164                        max: passwordRequirements.maxLength,
165                      })
166                    }}
167                  </template>
168                </b-form-invalid-feedback>
169              </input-password-toggle>
170            </b-form-group>
171            <b-form-group
172              :label="$t('pageUserManagement.modal.confirmUserPassword')"
173              label-for="password-confirmation"
174            >
175              <input-password-toggle>
176                <b-form-input
177                  id="password-confirmation"
178                  v-model="form.passwordConfirmation"
179                  data-test-id="userManagement-input-passwordConfirmation"
180                  type="password"
181                  :state="getValidationState(v$.form.passwordConfirmation)"
182                  class="form-control-with-button"
183                  @input="v$.form.passwordConfirmation.$touch()"
184                />
185                <b-form-invalid-feedback role="alert">
186                  <template
187                    v-if="v$.form.passwordConfirmation.required.$invalid"
188                  >
189                    {{ $t('global.form.fieldRequired') }}
190                  </template>
191                  <template
192                    v-else-if="
193                      v$.form.passwordConfirmation.sameAsPassword.$invalid
194                    "
195                  >
196                    {{ $t('pageUserManagement.modal.passwordsDoNotMatch') }}
197                  </template>
198                </b-form-invalid-feedback>
199              </input-password-toggle>
200            </b-form-group>
201          </b-col>
202        </b-row>
203      </b-container>
204    </b-form>
205    <template #modal-footer="{ cancel }">
206      <b-button
207        variant="secondary"
208        data-test-id="userManagement-button-cancel"
209        @click="cancel()"
210      >
211        {{ $t('global.action.cancel') }}
212      </b-button>
213      <b-button
214        form="form-user"
215        data-test-id="userManagement-button-submit"
216        type="submit"
217        variant="primary"
218        @click="onOk"
219      >
220        <template v-if="newUser">
221          {{ $t('pageUserManagement.addUser') }}
222        </template>
223        <template v-else>
224          {{ $t('global.action.save') }}
225        </template>
226      </b-button>
227    </template>
228  </b-modal>
229</template>
230
231<script>
232import {
233  required,
234  maxLength,
235  minLength,
236  requiredIf,
237} from '@vuelidate/validators';
238import { helpers, sameAs } from 'vuelidate/lib/validators';
239import VuelidateMixin from '@/components/Mixins/VuelidateMixin.js';
240import { useVuelidate } from '@vuelidate/core';
241
242import InputPasswordToggle from '@/components/Global/InputPasswordToggle';
243import Alert from '@/components/Global/Alert';
244import { useI18n } from 'vue-i18n';
245
246export default {
247  components: { Alert, InputPasswordToggle },
248  mixins: [VuelidateMixin],
249  props: {
250    user: {
251      type: Object,
252      default: null,
253    },
254    passwordRequirements: {
255      type: Object,
256      required: true,
257    },
258  },
259  setup() {
260    return {
261      v$: useVuelidate(),
262    };
263  },
264  data() {
265    return {
266      $t: useI18n().t,
267      originalUsername: '',
268      form: {
269        status: true,
270        username: '',
271        privilege: null,
272        password: '',
273        passwordConfirmation: '',
274        manualUnlock: false,
275      },
276      disabled: this.$store.getters['global/username'],
277    };
278  },
279  computed: {
280    newUser() {
281      return this.user ? false : true;
282    },
283    accountSettings() {
284      return this.$store.getters['userManagement/accountSettings'];
285    },
286    manualUnlockPolicy() {
287      return !this.accountSettings.accountLockoutDuration;
288    },
289    privilegeTypes() {
290      return this.$store.getters['userManagement/accountRoles'];
291    },
292  },
293  watch: {
294    user: function (value) {
295      if (value === null) return;
296      this.originalUsername = value.username;
297      this.form.username = value.username;
298      this.form.status = value.Enabled;
299      this.form.privilege = value.privilege;
300    },
301  },
302  validations() {
303    return {
304      form: {
305        status: {
306          required,
307        },
308        username: {
309          required,
310          maxLength: maxLength(16),
311          pattern: helpers.regex('pattern', /^([a-zA-Z_][a-zA-Z0-9_]*)/),
312        },
313        privilege: {
314          required,
315        },
316        password: {
317          required: requiredIf(function () {
318            return this.requirePassword();
319          }),
320          minLength: minLength(this.passwordRequirements.minLength),
321          maxLength: maxLength(this.passwordRequirements.maxLength),
322        },
323        passwordConfirmation: {
324          required: requiredIf(function () {
325            return this.requirePassword();
326          }),
327          sameAsPassword: sameAs('password'),
328        },
329        manualUnlock: {},
330      },
331    };
332  },
333  methods: {
334    handleSubmit() {
335      let userData = {};
336
337      if (this.newUser) {
338        this.v$.$touch();
339        if (this.v$.$invalid) return;
340        userData.username = this.form.username;
341        userData.status = this.form.status;
342        userData.privilege = this.form.privilege;
343        userData.password = this.form.password;
344      } else {
345        if (this.v$.$invalid) return;
346        userData.originalUsername = this.originalUsername;
347        if (this.v$.form.status.$dirty) {
348          userData.status = this.form.status;
349        }
350        if (this.v$.form.username.$dirty) {
351          userData.username = this.form.username;
352        }
353        if (this.v$.form.privilege.$dirty) {
354          userData.privilege = this.form.privilege;
355        }
356        if (this.v$.form.password.$dirty) {
357          userData.password = this.form.password;
358        }
359        if (this.v$.form.manualUnlock.$dirty) {
360          // If form manualUnlock control $dirty then
361          // set user Locked property to false
362          userData.locked = false;
363        }
364        if (Object.entries(userData).length === 1) {
365          this.closeModal();
366          return;
367        }
368      }
369
370      this.$emit('ok', { isNewUser: this.newUser, userData });
371      this.closeModal();
372    },
373    closeModal() {
374      this.$nextTick(() => {
375        this.$refs.modal.hide();
376      });
377    },
378    resetForm() {
379      this.form.originalUsername = '';
380      this.form.status = true;
381      this.form.username = '';
382      this.form.privilege = null;
383      this.form.password = '';
384      this.form.passwordConfirmation = '';
385      this.v$.$reset();
386      this.$emit('hidden');
387    },
388    requirePassword() {
389      if (this.newUser) return true;
390      if (this.v$.form.password.$dirty) return true;
391      if (this.v$.form.passwordConfirmation.$dirty) return true;
392      return false;
393    },
394    onOk(bvModalEvt) {
395      // prevent modal close
396      bvModalEvt.preventDefault();
397      this.handleSubmit();
398    },
399  },
400};
401</script>
402