diff options
author | Christopher Ng <chrng8@gmail.com> | 2025-03-25 14:31:44 -0700 |
---|---|---|
committer | Christopher Ng <chrng8@gmail.com> | 2025-03-28 14:12:52 -0700 |
commit | ace13ca64ff202d9d726ee8c9cf2086c9a7415cc (patch) | |
tree | 1ce73682a4d3f806ba91c95c6d98d41bdb47b785 | |
parent | 8a3a38815faf1591d9be9601da17c246b57a431c (diff) | |
download | nextcloud-server-ace13ca64ff202d9d726ee8c9cf2086c9a7415cc.tar.gz nextcloud-server-ace13ca64ff202d9d726ee8c9cf2086c9a7415cc.zip |
fix(settings): Allow searching for groups in user row
Signed-off-by: Christopher Ng <chrng8@gmail.com>
-rw-r--r-- | apps/settings/src/components/UserList.vue | 7 | ||||
-rw-r--r-- | apps/settings/src/components/Users/UserRow.vue | 81 | ||||
-rw-r--r-- | apps/settings/src/mixins/UserRowMixin.js | 43 | ||||
-rw-r--r-- | apps/settings/src/store/users.js | 18 |
4 files changed, 89 insertions, 60 deletions
diff --git a/apps/settings/src/components/UserList.vue b/apps/settings/src/components/UserList.vue index 00c7ec547cb..80ba7923e5d 100644 --- a/apps/settings/src/components/UserList.vue +++ b/apps/settings/src/components/UserList.vue @@ -34,8 +34,6 @@ users, settings, hasObfuscated, - groups, - subAdminsGroups, quotaOptions, languages, externalActions, @@ -179,11 +177,6 @@ export default { .sort((a, b) => a.name.localeCompare(b.name)) }, - subAdminsGroups() { - // data provided php side - return this.$store.getters.getSubadminGroups - }, - quotaOptions() { // convert the preset array into objects const quotaPreset = this.settings.quotaPreset.reduce((acc, cur) => acc.concat({ diff --git a/apps/settings/src/components/Users/UserRow.vue b/apps/settings/src/components/Users/UserRow.vue index 485a139e285..02badf79736 100644 --- a/apps/settings/src/components/Users/UserRow.vue +++ b/apps/settings/src/components/Users/UserRow.vue @@ -117,6 +117,8 @@ label="name" :no-wrap="true" :create-option="(value) => ({ name: value, isCreating: true })" + @open="loadGroupDetails" + @search="searchGroups" @option:created="createGroup" @option:selected="options => addUserGroup(options.at(-1))" @option:deselected="removeUserGroup" /> @@ -127,10 +129,10 @@ </span> </td> - <td v-if="subAdminsGroups.length > 0 && (settings.isAdmin || settings.isDelegatedAdmin)" + <td v-if="userSubAdminGroups.length > 0 && (settings.isAdmin || settings.isDelegatedAdmin)" data-cy-user-list-cell-subadmins class="row__cell row__cell--large row__cell--multiline"> - <template v-if="editing && (settings.isAdmin || settings.isDelegatedAdmin) && subAdminsGroups.length > 0"> + <template v-if="editing && (settings.isAdmin || settings.isDelegatedAdmin) && userSubAdminGroups.length > 0"> <label class="hidden-visually" :for="'subadmins' + uniqueId"> {{ t('settings', 'Set account as admin for') }} @@ -145,15 +147,17 @@ :append-to-body="false" :multiple="true" :no-wrap="true" - :options="subAdminsGroups" + :options="availableSubAdminGroups" :placeholder="t('settings', 'Set account as admin for')" - :value="userSubAdminsGroups" + :value="userSubAdminGroups" + @open="loadGroupDetails" + @search="searchGroups" @option:deselected="removeUserSubAdmin" @option:selected="options => addUserSubAdmin(options.at(-1))" /> </template> <span v-else-if="!isObfuscated" - :title="userSubAdminsGroupsLabels?.length > 40 ? userSubAdminsGroupsLabels : null"> - {{ userSubAdminsGroupsLabels }} + :title="userSubAdminGroupsLabels?.length > 40 ? userSubAdminGroupsLabels : null"> + {{ userSubAdminGroupsLabels }} </span> </td> @@ -296,6 +300,8 @@ import UserRowActions from './UserRowActions.vue' import UserRowMixin from '../../mixins/UserRowMixin.js' import { isObfuscated, unlimitedQuota } from '../../utils/userUtils.ts' +import { formatGroup } from '../../utils/groups.ts' +import logger from '../../logger.ts' export default { name: 'UserRow', @@ -330,14 +336,6 @@ export default { type: Boolean, required: true, }, - groups: { - type: Array, - default: () => [], - }, - subAdminsGroups: { - type: Array, - required: true, - }, quotaOptions: { type: Array, required: true, @@ -381,6 +379,8 @@ export default { editedDisplayName: this.user.displayname, editedPassword: '', editedMail: this.user.email ?? '', + // Cancelable promise for search groups request + promise: null, } }, @@ -412,13 +412,13 @@ export default { userGroupsLabels() { return this.userGroups - .map(group => group.name) + .map(group => group.name ?? group.id) .join(', ') }, - userSubAdminsGroupsLabels() { - return this.userSubAdminsGroups - .map(group => group.name) + userSubAdminGroupsLabels() { + return this.userSubAdminGroups + .map(group => group.name ?? group.id) .join(', ') }, @@ -554,6 +554,46 @@ export default { this.loadingPossibleManagers = false }, + async loadGroupDetails() { + this.loading.groups = true + try { + const { data } = await this.$store.dispatch('getUserGroups', { + userId: this.user.id, + }) + const groups = data.ocs?.data?.groups + if (!groups) { + logger.error(t('settings', 'Failed to load groups with details')) + return + } + this.availableGroups = this.availableGroups.map(availableGroup => groups.find(group => group.id === availableGroup.id) ?? availableGroup) + } catch (error) { + logger.error(t('settings', 'Failed to load groups with details'), { error }) + } + this.loading.groups = false + }, + + async searchGroups(query, toggleLoading) { + if (this.promise) { + this.promise.cancel() + } + this.loading.groups = true + toggleLoading(true) + try { + this.promise = await searchGroups({ + search: query, + offset: 0, + limit: 25, + }) + const groups = (await this.promise).data.ocs?.data?.groups ?? [] + this.availableGroups = groups.map(formatGroup) + } catch (error) { + logger.error(t('settings', 'Failed to search groups'), { error }) + } + this.promise = null + this.loading.groups = false + toggleLoading(false) + }, + async searchUserManager(query) { await this.$store.dispatch('searchUsers', { offset: 0, limit: 10, search: query }).then(response => { const users = response?.data ? this.filterManagers(Object.values(response?.data.ocs.data.users)) : [] @@ -703,6 +743,7 @@ export default { this.loading = { groups: true, subadmins: true } try { await this.$store.dispatch('addGroup', gid) + this.availableGroups.push({ id: gid, name: gid }) const userid = this.user.id await this.$store.dispatch('addUserGroup', { userid, gid }) } catch (error) { @@ -732,6 +773,7 @@ export default { } try { await this.$store.dispatch('addUserGroup', { userid, gid }) + this.userGroups.push({ id: group.id, name: group.id }) } catch (error) { console.error(error) } finally { @@ -756,6 +798,7 @@ export default { userid, gid, }) + this.userGroups = this.userGroups.filter(group => group.id !== gid) this.loading.groups = false // remove user from current list if current list is the removed group if (this.$route.params.selectedGroup === gid) { @@ -780,6 +823,7 @@ export default { userid, gid, }) + this.userSubAdminGroups.push({ id: group.id, name: group.id }) this.loading.subadmins = false } catch (error) { console.error(error) @@ -801,6 +845,7 @@ export default { userid, gid, }) + this.userSubAdminGroups = this.userSubAdminGroups.filter(group => group.id !== gid) } catch (error) { console.error(error) } finally { diff --git a/apps/settings/src/mixins/UserRowMixin.js b/apps/settings/src/mixins/UserRowMixin.js index 984607ef3d3..2cb69ff2e96 100644 --- a/apps/settings/src/mixins/UserRowMixin.js +++ b/apps/settings/src/mixins/UserRowMixin.js @@ -16,14 +16,6 @@ export default { type: Object, default: () => ({}), }, - groups: { - type: Array, - default: () => [], - }, - subAdminsGroups: { - type: Array, - default: () => [], - }, quotaOptions: { type: Array, default: () => [], @@ -49,38 +41,19 @@ export default { formattedFullTime, } }, + data() { + return { + availableGroups: this.user.groups.map(id => ({ id, name: id })), + availableSubAdminGroups: this.user.subadmin.map(id => ({ id, name: id })), + userGroups: this.user.groups.map(id => ({ id, name: id })), + userSubAdminGroups: this.user.subadmin.map(id => ({ id, name: id })), + } + }, computed: { showConfig() { return this.$store.getters.getShowConfig }, - /* GROUPS MANAGEMENT */ - userGroups() { - const userGroups = this.groups.filter(group => this.user.groups.includes(group.id)) - return userGroups - }, - userSubAdminsGroups() { - const userSubAdminsGroups = this.subAdminsGroups.filter(group => this.user.subadmin.includes(group.id)) - return userSubAdminsGroups - }, - availableGroups() { - return this.groups.map((group) => { - // clone object because we don't want - // to edit the original groups - const groupClone = Object.assign({}, group) - - // two settings here: - // 1. user NOT in group but no permission to add - // 2. user is in group but no permission to remove - groupClone.$isDisabled - = (group.canAdd === false - && !this.user.groups.includes(group.id)) - || (group.canRemove === false - && this.user.groups.includes(group.id)) - return groupClone - }) - }, - /* QUOTA MANAGEMENT */ usedSpace() { const quotaUsed = this.user.quota.used > 0 ? this.user.quota.used : 0 diff --git a/apps/settings/src/store/users.js b/apps/settings/src/store/users.js index 2a3a2d272d9..0914db6471b 100644 --- a/apps/settings/src/store/users.js +++ b/apps/settings/src/store/users.js @@ -465,6 +465,24 @@ const actions = { }, /** + * Get user groups + * + * @param {object} context store context + * @param {object} options destructuring object + * @param {number} options.userId User id + * @return {Promise} + */ + async getUserGroups(context, { userId }) { + const url = generateOcsUrl('cloud/users/{userId}/groups/details', { userId }) + try { + const response = await api.get(url) + return response + } catch (error) { + context.commit('API_FAILURE', error) + } + }, + + /** * Get all users with full details * * @param {object} context store context |