diff options
author | Christoph Wurst <christoph@winzerhof-wurst.at> | 2023-05-02 08:59:46 +0200 |
---|---|---|
committer | Christoph Wurst <christoph@winzerhof-wurst.at> | 2023-05-12 13:56:48 +0200 |
commit | 1381c4c157f3174917c994038ab74074a42a2aa8 (patch) | |
tree | fe70c5181a99330f4f3292ca217b386a2c6ff354 /apps/settings/src | |
parent | 1399c88ee178d9fd60f3e9356f1d8c498c6c97e1 (diff) | |
download | nextcloud-server-1381c4c157f3174917c994038ab74074a42a2aa8.tar.gz nextcloud-server-1381c4c157f3174917c994038ab74074a42a2aa8.zip |
feat(users): Store and load a user's manager
Co-Authored-By: hamza221 <hamzamahjoubi221@gmail.com>
Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
Diffstat (limited to 'apps/settings/src')
-rw-r--r-- | apps/settings/src/components/UserList.vue | 34 | ||||
-rw-r--r-- | apps/settings/src/components/UserList/UserRow.vue | 58 | ||||
-rw-r--r-- | apps/settings/src/components/UserList/UserRowSimple.vue | 4 | ||||
-rw-r--r-- | apps/settings/src/store/users.js | 44 |
4 files changed, 134 insertions, 6 deletions
diff --git a/apps/settings/src/components/UserList.vue b/apps/settings/src/components/UserList.vue index d2ab07dae50..235e9078243 100644 --- a/apps/settings/src/components/UserList.vue +++ b/apps/settings/src/components/UserList.vue @@ -146,6 +146,20 @@ <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" + @search-change="searchUserManager" + label="displayname" + track-by="id"> + <span slot="noResult">{{ t('settings', 'No results') }}</span> + </NcMultiselect> + </div> <div class="user-actions"> <NcButton id="newsubmit" type="primary" @@ -208,7 +222,9 @@ class="headerLastLogin lastLogin"> {{ t('settings', 'Last login') }} </th> - + <th id="headerManager" class="manager"> + {{ t('settings', 'Manager') }} + </th> <th class="userActions hidden-visually"> {{ t('settings', 'User actions') }} </th> @@ -224,6 +240,7 @@ :show-config="showConfig" :sub-admins-groups="subAdminsGroups" :user="user" + :users="users" :is-dark-theme="isDarkTheme" /> <InfiniteLoading ref="infiniteLoading" @infinite="infiniteHandler"> @@ -268,6 +285,7 @@ const newUser = { password: '', mailAddress: '', groups: [], + manager: '', subAdminsGroups: [], quota: defaultQuota, language: { @@ -312,6 +330,7 @@ export default { groups: false, }, scrolled: false, + possibleManagers: [], searchQuery: '', newUser: Object.assign({}, newUser), } @@ -422,6 +441,10 @@ 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')) @@ -449,6 +472,14 @@ 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 }, @@ -532,6 +563,7 @@ export default { 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() diff --git a/apps/settings/src/components/UserList/UserRow.vue b/apps/settings/src/components/UserList/UserRow.vue index 4007c551901..ce414210e99 100644 --- a/apps/settings/src/components/UserList/UserRow.vue +++ b/apps/settings/src/components/UserList/UserRow.vue @@ -217,6 +217,22 @@ track-by="code" @input="setUserLanguage" /> </td> + <td :class="{'icon-loading-small': loading.manager}" class="managers"> + <NcMultiselect ref="manager" + v-model="currentManager" + :close-on-select="true" + :user-select="true" + :options="possibleManagers" + :placeholder="t('settings', 'Select manager')" + class="multiselect-vue" + label="displayname" + track-by="id" + @search-change="searchUserManager" + @remove="updateUserManager" + @select="updateUserManager"> + <span slot="noResult">{{ t('settings', 'No results') }}</span> + </NcMultiselect> + </td> <!-- don't show this on edit mode --> <td v-if="showConfig.showStoragePath || showConfig.showUserBackend" @@ -275,6 +291,10 @@ export default { }, mixins: [UserRowMixin], props: { + users: { + type: Array, + required: true, + }, user: { type: Object, required: true, @@ -317,6 +337,8 @@ export default { rand: parseInt(Math.random() * 1000), openedMenu: false, feedbackMessage: '', + possibleManagers: [], + currentManager: '', editing: false, loading: { all: false, @@ -330,10 +352,12 @@ export default { disable: false, languages: false, wipe: false, + manager: false, }, } }, computed: { + /* USER POPOVERMENU ACTIONS */ userActions() { const actions = [ @@ -363,6 +387,12 @@ export default { return actions.concat(this.externalActions) }, }, + async beforeMount() { + await this.searchUserManager() + if (this.user.manager) { + await this.initManager(this.user.manager) + } + }, methods: { /* MENU HANDLING */ @@ -399,6 +429,34 @@ export default { ) }, + filterManagers(managers) { + return managers.filter((manager) => manager.id !== this.user.id) + }, + async initManager(userId) { + await this.$store.dispatch('getUser', userId).then(response => { + this.currentManager = response?.data.ocs.data + }) + }, + 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)) : [] + if (users.length > 0) { + this.possibleManagers = users + } + }) + }, + + updateUserManager(manager) { + this.loading.manager = true + this.$store.dispatch('setUserData', { + userid: this.user.id, + key: 'manager', + value: this.currentManager ? this.currentManager.id : '', + }).then(() => { + this.loading.manager = false + }) + }, + deleteUser() { const userid = this.user.id OC.dialogs.confirmDestructive( diff --git a/apps/settings/src/components/UserList/UserRowSimple.vue b/apps/settings/src/components/UserList/UserRowSimple.vue index ed63d25d93c..6da45f804c2 100644 --- a/apps/settings/src/components/UserList/UserRowSimple.vue +++ b/apps/settings/src/components/UserList/UserRowSimple.vue @@ -55,7 +55,9 @@ <td v-if="showConfig.showLastLogin" :title="userLastLoginTooltip" class="lastLogin"> {{ userLastLogin }} </td> - + <td class="managers"> + {{ user.manager }} + </td> <td class="userActions"> <div v-if="canEdit && !loading.all" class="toggleUserActions"> <NcActions> diff --git a/apps/settings/src/store/users.js b/apps/settings/src/store/users.js index f1941aa6704..ab8105ecb51 100644 --- a/apps/settings/src/store/users.js +++ b/apps/settings/src/store/users.js @@ -254,6 +254,41 @@ let searchRequestCancelSource = null const actions = { /** + * search users + * + * @param {object} context store context + * @param {object} options destructuring object + * @param {number} options.offset List offset to request + * @param {number} options.limit List number to return from offset + * @param {string} options.search Search amongst users + * @return {Promise} + */ + searchUsers(context, { offset, limit, search }) { + search = typeof search === 'string' ? search : '' + + return api.get(generateOcsUrl('cloud/users/details?offset={offset}&limit={limit}&search={search}', { offset, limit, search })).catch((error) => { + if (!axios.isCancel(error)) { + context.commit('API_FAILURE', error) + } + }) + }, + + /** + * Get user details + * + * @param {object} context store context + * @param {string} userId user id + * @return {Promise} + */ + getUser(context, userId) { + return api.get(generateOcsUrl(`cloud/users/${userId}`)).catch((error) => { + if (!axios.isCancel(error)) { + context.commit('API_FAILURE', error) + } + }) + }, + + /** * Get all users with full details * * @param {object} context store context @@ -548,11 +583,12 @@ const actions = { * @param {string} options.subadmin User subadmin groups * @param {string} options.quota User email * @param {string} options.language User language + * @param {string} options.manager User manager * @return {Promise} */ - addUser({ commit, dispatch }, { userid, password, displayName, email, groups, subadmin, quota, language }) { + addUser({ commit, dispatch }, { userid, password, displayName, email, groups, subadmin, quota, language, manager }) { return api.requireAdmin().then((response) => { - return api.post(generateOcsUrl('cloud/users'), { userid, password, displayName, email, groups, subadmin, quota, language }) + return api.post(generateOcsUrl('cloud/users'), { userid, password, displayName, email, groups, subadmin, quota, language, manager }) .then((response) => dispatch('addUserData', userid || response.data.ocs.data.id)) .catch((error) => { throw error }) }).catch((error) => { @@ -605,8 +641,8 @@ const actions = { * @return {Promise} */ setUserData(context, { userid, key, value }) { - const allowedEmpty = ['email', 'displayname'] - if (['email', 'language', 'quota', 'displayname', 'password'].indexOf(key) !== -1) { + const allowedEmpty = ['email', 'displayname', 'manager'] + if (['email', 'language', 'quota', 'displayname', 'password', 'manager'].indexOf(key) !== -1) { // We allow empty email or displayname if (typeof value === 'string' && ( |