diff options
author | Christopher Ng <chrng8@gmail.com> | 2023-06-16 18:03:35 -0700 |
---|---|---|
committer | Pytal <24800714+Pytal@users.noreply.github.com> | 2023-06-21 11:12:40 -0700 |
commit | 84ff000767aaf52a0a176cb28bde373cc7f24ca1 (patch) | |
tree | 2208411a5285269aaf6fc40e7fedf3bf17c4532a /apps/settings/src/components/UserList.vue | |
parent | 3a557a88ce01a9c3694d1a72ca42bd89975aa66f (diff) | |
download | nextcloud-server-84ff000767aaf52a0a176cb28bde373cc7f24ca1.tar.gz nextcloud-server-84ff000767aaf52a0a176cb28bde373cc7f24ca1.zip |
enh(a11y): New user modal
Signed-off-by: Christopher Ng <chrng8@gmail.com>
Diffstat (limited to 'apps/settings/src/components/UserList.vue')
-rw-r--r-- | apps/settings/src/components/UserList.vue | 332 |
1 files changed, 24 insertions, 308 deletions
diff --git a/apps/settings/src/components/UserList.vue b/apps/settings/src/components/UserList.vue index 9a97aff085f..2f3da92ca02 100644 --- a/apps/settings/src/components/UserList.vue +++ b/apps/settings/src/components/UserList.vue @@ -26,150 +26,12 @@ :aria-label="t('settings', 'User\'s table')" class="user-list-grid" @scroll.passive="onScroll"> - <NcModal v-if="showConfig.showNewUserForm" size="small" @close="closeModal"> - <form id="new-user" - :disabled="loading.all" - class="modal__content" - @submit.prevent="createUser"> - <h2>{{ t('settings','New user') }}</h2> - <input id="newusername" - ref="newusername" - v-model="newUser.id" - :disabled="settings.newUserGenerateUserID" - :placeholder="settings.newUserGenerateUserID - ? t('settings', 'Will be autogenerated') - : t('settings', 'Username')" - autocapitalize="none" - autocomplete="off" - autocorrect="off" - class="modal__item" - name="username" - pattern="[a-zA-Z0-9 _\.@\-']+" - required - type="text"> - <input id="newdisplayname" - v-model="newUser.displayName" - :placeholder="t('settings', 'Display name')" - autocapitalize="none" - autocomplete="off" - autocorrect="off" - class="modal__item" - name="displayname" - type="text"> - <input id="newuserpassword" - ref="newuserpassword" - v-model="newUser.password" - :minlength="minPasswordLength" - :maxlength="469" - :placeholder="t('settings', 'Password')" - :required="newUser.mailAddress===''" - autocapitalize="none" - autocomplete="new-password" - autocorrect="off" - class="modal__item" - name="password" - type="password"> - <input id="newemail" - v-model="newUser.mailAddress" - :placeholder="t('settings', 'Email')" - :required="newUser.password==='' || settings.newUserRequireEmail" - autocapitalize="none" - autocomplete="off" - autocorrect="off" - class="modal__item" - name="email" - type="email"> - <div class="groups modal__item"> - <!-- hidden input trick for vanilla html5 form validation --> - <input v-if="!settings.isAdmin" - id="newgroups" - :class="{'icon-loading-small': loading.groups}" - :required="!settings.isAdmin" - :value="newUser.groups" - tabindex="-1" - type="text"> - <NcMultiselect v-model="newUser.groups" - :close-on-select="false" - :disabled="loading.groups||loading.all" - :multiple="true" - :options="canAddGroups" - :placeholder="t('settings', 'Add user to group')" - :tag-width="60" - :taggable="true" - class="multiselect-vue" - label="name" - tag-placeholder="create" - track-by="id" - @tag="createGroup"> - <!-- If user is not admin, he is a subadmin. - Subadmins can't create users outside their groups - Therefore, empty select is forbidden --> - <span slot="noResult">{{ t('settings', 'No results') }}</span> - </NcMultiselect> - </div> - <div v-if="subAdminsGroups.length>0 && settings.isAdmin" - class="subadmins modal__item"> - <NcMultiselect v-model="newUser.subAdminsGroups" - :close-on-select="false" - :multiple="true" - :options="subAdminsGroups" - :placeholder="t('settings', 'Set user as admin for')" - :tag-width="60" - class="multiselect-vue" - label="name" - track-by="id"> - <span slot="noResult">{{ t('settings', 'No results') }}</span> - </NcMultiselect> - </div> - <div class="quota modal__item"> - <NcMultiselect v-model="newUser.quota" - :allow-empty="false" - :options="quotaOptions" - :placeholder="t('settings', 'Select user quota')" - :taggable="true" - class="multiselect-vue" - label="label" - track-by="id" - @tag="validateQuota" /> - </div> - <div v-if="showConfig.showLanguages" class="languages modal__item"> - <NcMultiselect v-model="newUser.language" - :allow-empty="false" - :options="languages" - :placeholder="t('settings', 'Default language')" - class="multiselect-vue" - group-label="label" - group-values="languages" - label="name" - track-by="code" /> - </div> - <div v-if="showConfig.showStoragePath" class="storageLocation" /> - <div v-if="showConfig.showUserBackend" class="userBackend" /> - <div v-if="showConfig.showLastLogin" class="lastLogin" /> - <div :class="{'icon-loading-small': loading.manager}" class="modal__item managers"> - <NcMultiselect ref="manager" - v-model="newUser.manager" - :close-on-select="true" - :user-select="true" - :options="possibleManagers" - :placeholder="t('settings', 'Select user manager')" - class="multiselect-vue" - label="displayname" - track-by="id" - @search-change="searchUserManager"> - <span slot="noResult">{{ t('settings', 'No results') }}</span> - </NcMultiselect> - </div> - <div class="user-actions"> - <NcButton id="newsubmit" - type="primary" - native-type="submit" - value=""> - {{ t('settings', 'Add a new user') }} - </NcButton> - </div> - </form> - </NcModal> + <NewUserModal v-if="showConfig.showNewUserForm" + :loading="loading" + :new-user="newUser" + :show-config="showConfig" + @reset="resetForm" + @close="showConfig.showNewUserForm = false" /> <div id="grid-header" :class="{'sticky': scrolled && !showConfig.showNewUserForm}" class="row"> @@ -225,7 +87,7 @@ <div class="userActions" /> </div> - <user-row v-for="user in filteredUsers" + <UserRow v-for="user in filteredUsers" :key="user.id" :external-actions="externalActions" :groups="groups" @@ -256,23 +118,24 @@ </template> <script> -import { subscribe, unsubscribe } from '@nextcloud/event-bus' -import InfiniteLoading from 'vue-infinite-loading' import Vue from 'vue' -import NcModal from '@nextcloud/vue/dist/Components/NcModal.js' -import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' -import NcMultiselect from '@nextcloud/vue/dist/Components/NcMultiselect.js' +import InfiniteLoading from 'vue-infinite-loading' + +import { subscribe, unsubscribe } from '@nextcloud/event-bus' -import userRow from './UserList/UserRow.vue' +import UserRow from './Users/UserRow.vue' +import NewUserModal from './Users/NewUserModal.vue' const unlimitedQuota = { id: 'none', label: t('settings', 'Unlimited'), } + const defaultQuota = { id: 'default', label: t('settings', 'Default quota'), } + const newUser = { id: '', displayName: '', @@ -290,13 +153,13 @@ const newUser = { export default { name: 'UserList', + components: { - NcModal, - userRow, - NcMultiselect, InfiniteLoading, - NcButton, + NewUserModal, + UserRow, }, + props: { users: { type: Array, @@ -315,20 +178,19 @@ export default { default: () => [], }, }, + data() { return { - unlimitedQuota, - defaultQuota, loading: { all: false, groups: false, }, scrolled: false, - possibleManagers: [], searchQuery: '', newUser: Object.assign({}, newUser), } }, + computed: { settings() { return this.$store.getters.getServerData @@ -352,16 +214,6 @@ export default { .filter(group => group.id !== 'disabled') .sort((a, b) => a.name.localeCompare(b.name)) }, - canAddGroups() { - // disabled if no permission to add new users to group - return this.groups.map(group => { - // clone object because we don't want - // to edit the original groups - group = Object.assign({}, group) - group.$isDisabled = group.canAdd === false - return group - }) - }, subAdminsGroups() { // data provided php side return this.$store.getters.getSubadminGroups @@ -374,14 +226,11 @@ export default { }), []) // add default presets if (this.settings.allowUnlimitedQuota) { - quotaPreset.unshift(this.unlimitedQuota) + quotaPreset.unshift(unlimitedQuota) } - quotaPreset.unshift(this.defaultQuota) + quotaPreset.unshift(defaultQuota) return quotaPreset }, - minPasswordLength() { - return this.$store.getters.getPasswordPolicyMinLength - }, usersOffset() { return this.$store.getters.getUsersOffset }, @@ -435,10 +284,6 @@ export default { }, }, - async beforeMount() { - await this.searchUserManager() - }, - mounted() { if (!this.settings.canChangePassword) { OC.Notification.showTemporary(t('settings', 'Password change is disabled because the master key is disabled')) @@ -466,38 +311,10 @@ export default { }, methods: { - async searchUserManager(query) { - await this.$store.dispatch('searchUsers', { offset: 0, limit: 10, search: query }).then(response => { - const users = response?.data ? Object.values(response?.data.ocs.data.users) : [] - if (users.length > 0) { - this.possibleManagers = users - } - }) - }, onScroll(event) { this.scrolled = event.target.scrollTo > 0 }, - /** - * Validate quota string to make sure it's a valid human file size - * - * @param {string} quota Quota in readable format '5 GB' - * @return {object} - */ - validateQuota(quota) { - // only used for new presets sent through @Tag - const validQuota = OC.Util.computerFileSize(quota) - if (validQuota !== null && validQuota >= 0) { - // unify format output - quota = OC.Util.humanFileSize(OC.Util.computerFileSize(quota)) - this.newUser.quota = { id: quota, label: quota } - return this.newUser.quota - } - // Default is unlimited - this.newUser.quota = this.quotaOptions[0] - return this.quotaOptions[0] - }, - infiniteHandler($state) { this.$store.dispatch('getUsers', { offset: this.usersOffset, @@ -521,6 +338,7 @@ export default { this.$store.commit('resetUsers') this.$refs.infiniteLoading.stateChanger.reset() }, + resetSearch() { this.search({ query: '' }) }, @@ -546,38 +364,7 @@ export default { this.loading.all = false }, - createUser() { - this.loading.all = true - this.$store.dispatch('addUser', { - userid: this.newUser.id, - password: this.newUser.password, - displayName: this.newUser.displayName, - email: this.newUser.mailAddress, - groups: this.newUser.groups.map(group => group.id), - subadmin: this.newUser.subAdminsGroups.map(group => group.id), - quota: this.newUser.quota.id, - language: this.newUser.language.code, - manager: this.newUser.manager.id, - }) - .then(() => { - this.resetForm() - this.$refs.newusername.focus() - this.closeModal() - }) - .catch((error) => { - this.loading.all = false - if (error.response && error.response.data && error.response.data.ocs && error.response.data.ocs.meta) { - const statuscode = error.response.data.ocs.meta.statuscode - if (statuscode === 102) { - // wrong username - this.$refs.newusername.focus() - } else if (statuscode === 107) { - // wrong password - this.$refs.newuserpassword.focus() - } - } - }) - }, + setNewUserDefaultGroup(value) { if (value && value.length > 0) { // setting new user default group to the current selected one @@ -592,25 +379,6 @@ export default { }, /** - * Create a new group - * - * @param {string} gid Group id - * @return {Promise} - */ - createGroup(gid) { - this.loading.groups = true - this.$store.dispatch('addGroup', gid) - .then((group) => { - this.newUser.groups.push(this.groups.find(group => group.id === gid)) - this.loading.groups = false - }) - .catch(() => { - this.loading.groups = false - }) - return this.$store.getters.getGroups[this.groups.length] - }, - - /** * If the selected group is the disabled group but the count is 0 * redirect to the all users page. * we only check for 0 because we don't have the count on ldap @@ -625,58 +393,6 @@ export default { this.$refs.infiniteLoading.stateChanger.reset() } }, - closeModal() { - // eslint-disable-next-line vue/no-mutating-props - this.showConfig.showNewUserForm = false - }, }, } </script> -<style lang="scss" scoped> - .modal-wrapper { - margin: 2vh 0; - align-items: flex-start; - } - .modal__content { - display: flex; - padding: 20px; - flex-direction: column; - align-items: center; - text-align: center; - } - .modal__item { - margin-bottom: 16px; - width: 100%; - } - .modal__item:not(:focus):not(:active) { - border-color: var(--color-border-dark); - } - .modal__item::v-deep .multiselect { - width: 100%; - } - .user-actions { - margin-top: 20px; - } - .modal__content::v-deep .multiselect__single { - text-align: left; - box-sizing: border-box; - } - .modal__content::v-deep .multiselect__content-wrapper { - box-sizing: border-box; - } - .row::v-deep .multiselect__single { - z-index: auto !important; - } - - /* fake input for groups validation */ - input#newgroups { - position: absolute; - opacity: 0; - /* The "hidden" input is behind the Multiselect, so in general it does - * not receives clicks. However, with Firefox, after the validation - * fails, it will receive the first click done on it, so its width needs - * to be set to 0 to prevent that ("pointer-events: none" does not - * prevent it). */ - width: 0; - } -</style> |