aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristopher Ng <chrng8@gmail.com>2025-03-25 14:31:44 -0700
committerChristopher Ng <chrng8@gmail.com>2025-03-28 14:12:52 -0700
commitace13ca64ff202d9d726ee8c9cf2086c9a7415cc (patch)
tree1ce73682a4d3f806ba91c95c6d98d41bdb47b785
parent8a3a38815faf1591d9be9601da17c246b57a431c (diff)
downloadnextcloud-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.vue7
-rw-r--r--apps/settings/src/components/Users/UserRow.vue81
-rw-r--r--apps/settings/src/mixins/UserRowMixin.js43
-rw-r--r--apps/settings/src/store/users.js18
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