summaryrefslogtreecommitdiffstats
path: root/apps/settings/src
diff options
context:
space:
mode:
authorChristoph Wurst <christoph@winzerhof-wurst.at>2023-05-02 08:59:46 +0200
committerChristoph Wurst <christoph@winzerhof-wurst.at>2023-05-12 13:56:48 +0200
commit1381c4c157f3174917c994038ab74074a42a2aa8 (patch)
treefe70c5181a99330f4f3292ca217b386a2c6ff354 /apps/settings/src
parent1399c88ee178d9fd60f3e9356f1d8c498c6c97e1 (diff)
downloadnextcloud-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.vue34
-rw-r--r--apps/settings/src/components/UserList/UserRow.vue58
-rw-r--r--apps/settings/src/components/UserList/UserRowSimple.vue4
-rw-r--r--apps/settings/src/store/users.js44
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'
&& (