diff options
Diffstat (limited to 'apps/settings/src/components/UserList')
-rw-r--r-- | apps/settings/src/components/UserList/UserRow.vue | 688 | ||||
-rw-r--r-- | apps/settings/src/components/UserList/UserRowSimple.vue | 199 |
2 files changed, 0 insertions, 887 deletions
diff --git a/apps/settings/src/components/UserList/UserRow.vue b/apps/settings/src/components/UserList/UserRow.vue deleted file mode 100644 index de0a09f2221..00000000000 --- a/apps/settings/src/components/UserList/UserRow.vue +++ /dev/null @@ -1,688 +0,0 @@ -<!-- - - @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com> - - @copyright Copyright (c) 2019 Gary Kim <gary@garykim.dev> - - - - @author John Molakvoæ <skjnldsv@protonmail.com> - - @author Gary Kim <gary@garykim.dev> - - - - @license GNU AGPL version 3 or any later version - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU Affero General Public License as - - published by the Free Software Foundation, either version 3 of the - - License, or (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU Affero General Public License for more details. - - - - You should have received a copy of the GNU Affero General Public License - - along with this program. If not, see <http://www.gnu.org/licenses/>. - - - --> - -<template> - <!-- Obfuscated user: Logged in user does not have permissions to see all of the data --> - <div v-if="Object.keys(user).length ===1" :data-id="user.id" class="row"> - <div :class="{'icon-loading-small': loading.delete || loading.disable || loading.wipe}" - class="avatar"> - <img v-if="!loading.delete && !loading.disable && !loading.wipe" - :src="generateAvatar(user.id, 32)" - :srcset="generateAvatar(user.id, 64)+' 2x, '+generateAvatar(user.id, 128)+' 4x'" - alt="" - height="32" - width="32"> - </div> - <div class="name"> - {{ user.id }} - </div> - <div class="obfuscated"> - {{ t('settings','You do not have permissions to see the details of this user') }} - </div> - </div> - - <!-- User full data --> - <UserRowSimple v-else-if="!editing" - :editing.sync="editing" - :feedback-message="feedbackMessage" - :groups="groups" - :languages="languages" - :loading="loading" - :opened-menu.sync="openedMenu" - :settings="settings" - :show-config="showConfig" - :sub-admins-groups="subAdminsGroups" - :user-actions="userActions" - :user="user" - :class="{'row--menu-opened': openedMenu}" /> - <div v-else - :class="{ - 'disabled': loading.delete || loading.disable, - 'row--menu-opened': openedMenu - }" - :data-id="user.id" - class="row row--editable"> - <div :class="{'icon-loading-small': loading.delete || loading.disable || loading.wipe}" - class="avatar"> - <img v-if="!loading.delete && !loading.disable && !loading.wipe" - :src="generateAvatar(user.id, 32)" - :srcset="generateAvatar(user.id, 64)+' 2x, '+generateAvatar(user.id, 128)+' 4x'" - alt="" - height="32" - width="32"> - </div> - <!-- dirty hack to ellipsis on two lines --> - <div v-if="user.backendCapabilities.setDisplayName" class="displayName"> - <form :class="{'icon-loading-small': loading.displayName}" - class="displayName" - @submit.prevent="updateDisplayName"> - <input :id="'displayName'+user.id+rand" - ref="displayName" - :disabled="loading.displayName||loading.all" - :value="user.displayname" - autocapitalize="off" - autocomplete="off" - autocorrect="off" - spellcheck="false" - type="text"> - <input class="icon-confirm" - type="submit" - value=""> - </form> - </div> - <div v-else class="name"> - {{ user.id }} - <div class="displayName subtitle"> - <div v-tooltip="user.displayname.length > 20 ? user.displayname : ''" class="cellText"> - {{ user.displayname }} - </div> - </div> - </div> - <form v-if="settings.canChangePassword && user.backendCapabilities.setPassword" - :class="{'icon-loading-small': loading.password}" - class="password" - @submit.prevent="updatePassword"> - <input :id="'password'+user.id+rand" - ref="password" - :disabled="loading.password || loading.all" - :minlength="minPasswordLength" - :placeholder="t('settings', 'Add new password')" - autocapitalize="off" - autocomplete="new-password" - autocorrect="off" - required - spellcheck="false" - type="password" - value=""> - <input class="icon-confirm" type="submit" value=""> - </form> - <div v-else /> - <form :class="{'icon-loading-small': loading.mailAddress}" - class="mailAddress" - @submit.prevent="updateEmail"> - <input :id="'mailAddress'+user.id+rand" - ref="mailAddress" - :disabled="loading.mailAddress||loading.all" - :placeholder="t('settings', 'Add new email address')" - :value="user.email" - autocapitalize="off" - autocomplete="new-password" - autocorrect="off" - spellcheck="false" - type="email"> - <input class="icon-confirm" type="submit" value=""> - </form> - <div :class="{'icon-loading-small': loading.groups}" class="groups"> - <Multiselect :close-on-select="false" - :disabled="loading.groups||loading.all" - :limit="2" - :multiple="true" - :options="availableGroups" - :placeholder="t('settings', 'Add user to group')" - :tag-width="60" - :taggable="settings.isAdmin" - :value="userGroups" - class="multiselect-vue" - label="name" - tag-placeholder="create" - track-by="id" - @remove="removeUserGroup" - @select="addUserGroup" - @tag="createGroup"> - <span slot="noResult">{{ t('settings', 'No results') }}</span> - </Multiselect> - </div> - <div v-if="subAdminsGroups.length>0 && settings.isAdmin" - :class="{'icon-loading-small': loading.subadmins}" - class="subadmins"> - <Multiselect :close-on-select="false" - :disabled="loading.subadmins||loading.all" - :limit="2" - :multiple="true" - :options="subAdminsGroups" - :placeholder="t('settings', 'Set user as admin for')" - :tag-width="60" - :value="userSubAdminsGroups" - class="multiselect-vue" - label="name" - track-by="id" - @remove="removeUserSubAdmin" - @select="addUserSubAdmin"> - <span slot="noResult">{{ t('settings', 'No results') }}</span> - </Multiselect> - </div> - <div v-tooltip.auto="usedSpace" - :class="{'icon-loading-small': loading.quota}" - class="quota"> - <Multiselect :allow-empty="false" - :disabled="loading.quota||loading.all" - :options="quotaOptions" - :placeholder="t('settings', 'Select user quota')" - :taggable="true" - :value="userQuota" - class="multiselect-vue" - label="label" - tag-placeholder="create" - track-by="id" - @input="setUserQuota" - @tag="validateQuota" /> - </div> - <div v-if="showConfig.showLanguages" - :class="{'icon-loading-small': loading.languages}" - class="languages"> - <Multiselect :allow-empty="false" - :disabled="loading.languages||loading.all" - :options="languages" - :placeholder="t('settings', 'No language set')" - :value="userLanguage" - class="multiselect-vue" - group-label="label" - group-values="languages" - label="name" - track-by="code" - @input="setUserLanguage" /> - </div> - - <!-- don't show this on edit mode --> - <div v-if="showConfig.showStoragePath || showConfig.showUserBackend" - class="storageLocation" /> - <div v-if="showConfig.showLastLogin" /> - - <div class="userActions"> - <div v-if="!loading.all" - class="toggleUserActions"> - <Actions> - <ActionButton icon="icon-checkmark" - @click="editing = false"> - {{ t('settings', 'Done') }} - </ActionButton> - </Actions> - <div v-click-outside="hideMenu" class="userPopoverMenuWrapper"> - <button class="icon-more" - @click.prevent="toggleMenu" /> - <div :class="{ 'open': openedMenu }" class="popovermenu"> - <PopoverMenu :menu="userActions" /> - </div> - </div> - </div> - <div :style="{opacity: feedbackMessage !== '' ? 1 : 0}" - class="feedback"> - <div class="icon-checkmark" /> - {{ feedbackMessage }} - </div> - </div> - </div> -</template> - -<script> -import ClickOutside from 'vue-click-outside' -import Vue from 'vue' -import VTooltip from 'v-tooltip' -import { - PopoverMenu, - Multiselect, - Actions, - ActionButton, -} from '@nextcloud/vue' -import UserRowSimple from './UserRowSimple' -import UserRowMixin from '../../mixins/UserRowMixin' - -Vue.use(VTooltip) - -export default { - name: 'UserRow', - components: { - UserRowSimple, - PopoverMenu, - Actions, - ActionButton, - Multiselect, - }, - directives: { - ClickOutside, - }, - mixins: [UserRowMixin], - props: { - user: { - type: Object, - required: true, - }, - settings: { - type: Object, - default: () => ({}), - }, - groups: { - type: Array, - default: () => [], - }, - subAdminsGroups: { - type: Array, - default: () => [], - }, - quotaOptions: { - type: Array, - default: () => [], - }, - showConfig: { - type: Object, - default: () => ({}), - }, - languages: { - type: Array, - required: true, - }, - externalActions: { - type: Array, - default: () => [], - }, - }, - data() { - return { - rand: parseInt(Math.random() * 1000), - openedMenu: false, - feedbackMessage: '', - editing: false, - loading: { - all: false, - displayName: false, - password: false, - mailAddress: false, - groups: false, - subadmins: false, - quota: false, - delete: false, - disable: false, - languages: false, - wipe: false, - }, - } - }, - computed: { - /* USER POPOVERMENU ACTIONS */ - userActions() { - const actions = [ - { - icon: 'icon-delete', - text: t('settings', 'Delete user'), - action: this.deleteUser, - }, - { - icon: 'icon-delete', - text: t('settings', 'Wipe all devices'), - action: this.wipeUserDevices, - }, - { - icon: this.user.enabled ? 'icon-close' : 'icon-add', - text: this.user.enabled ? t('settings', 'Disable user') : t('settings', 'Enable user'), - action: this.enableDisableUser, - }, - ] - if (this.user.email !== null && this.user.email !== '') { - actions.push({ - icon: 'icon-mail', - text: t('settings', 'Resend welcome email'), - action: this.sendWelcomeMail, - }) - } - return actions.concat(this.externalActions) - }, - }, - - methods: { - /* MENU HANDLING */ - toggleMenu() { - this.openedMenu = !this.openedMenu - }, - hideMenu() { - this.openedMenu = false - }, - - wipeUserDevices() { - const userid = this.user.id - OC.dialogs.confirmDestructive( - t('settings', 'In case of lost device or exiting the organization, this can remotely wipe the Nextcloud data from all devices associated with {userid}. Only works if the devices are connected to the internet.', { userid }), - t('settings', 'Remote wipe of devices'), - { - type: OC.dialogs.YES_NO_BUTTONS, - confirm: t('settings', 'Wipe {userid}\'s devices', { userid }), - confirmClasses: 'error', - cancel: t('settings', 'Cancel'), - }, - (result) => { - if (result) { - this.loading.wipe = true - this.loading.all = true - this.$store.dispatch('wipeUserDevices', userid) - .then(() => { - this.loading.wipe = false - this.loading.all = false - }) - } - }, - true - ) - }, - - deleteUser() { - const userid = this.user.id - OC.dialogs.confirmDestructive( - t('settings', 'Fully delete {userid}\'s account including all their personal files, app data, etc.', { userid }), - t('settings', 'Account deletion'), - { - type: OC.dialogs.YES_NO_BUTTONS, - confirm: t('settings', 'Delete {userid}\'s account', { userid }), - confirmClasses: 'error', - cancel: t('settings', 'Cancel'), - }, - (result) => { - if (result) { - this.loading.delete = true - this.loading.all = true - return this.$store.dispatch('deleteUser', userid) - .then(() => { - this.loading.delete = false - this.loading.all = false - }) - } - }, - true - ) - }, - - enableDisableUser() { - this.loading.delete = true - this.loading.all = true - const userid = this.user.id - const enabled = !this.user.enabled - return this.$store.dispatch('enableDisableUser', { - userid, - enabled, - }) - .then(() => { - this.loading.delete = false - this.loading.all = false - }) - }, - - /** - * Set user displayName - * - * @param {string} displayName The display name - */ - updateDisplayName() { - const displayName = this.$refs.displayName.value - this.loading.displayName = true - this.$store.dispatch('setUserData', { - userid: this.user.id, - key: 'displayname', - value: displayName, - }).then(() => { - this.loading.displayName = false - this.$refs.displayName.value = displayName - }) - }, - - /** - * Set user password - * - * @param {string} password The email adress - */ - updatePassword() { - const password = this.$refs.password.value - this.loading.password = true - this.$store.dispatch('setUserData', { - userid: this.user.id, - key: 'password', - value: password, - }).then(() => { - this.loading.password = false - this.$refs.password.value = '' // empty & show placeholder - }) - }, - - /** - * Set user mailAddress - * - * @param {string} mailAddress The email adress - */ - updateEmail() { - const mailAddress = this.$refs.mailAddress.value - this.loading.mailAddress = true - this.$store.dispatch('setUserData', { - userid: this.user.id, - key: 'email', - value: mailAddress, - }).then(() => { - this.loading.mailAddress = false - this.$refs.mailAddress.value = mailAddress - }) - }, - - /** - * Create a new group and add user to it - * - * @param {string} gid Group id - */ - async createGroup(gid) { - this.loading = { groups: true, subadmins: true } - try { - await this.$store.dispatch('addGroup', gid) - const userid = this.user.id - await this.$store.dispatch('addUserGroup', { userid, gid }) - } catch (error) { - console.error(error) - } finally { - this.loading = { groups: false, subadmins: false } - } - return this.$store.getters.getGroups[this.groups.length] - }, - - /** - * Add user to group - * - * @param {object} group Group object - */ - async addUserGroup(group) { - if (group.canAdd === false) { - return false - } - this.loading.groups = true - const userid = this.user.id - const gid = group.id - try { - await this.$store.dispatch('addUserGroup', { userid, gid }) - } catch (error) { - console.error(error) - } finally { - this.loading.groups = false - } - }, - - /** - * Remove user from group - * - * @param {object} group Group object - */ - async removeUserGroup(group) { - if (group.canRemove === false) { - return false - } - - this.loading.groups = true - const userid = this.user.id - const gid = group.id - - try { - await this.$store.dispatch('removeUserGroup', { - userid, - gid, - }) - this.loading.groups = false - // remove user from current list if current list is the removed group - if (this.$route.params.selectedGroup === gid) { - this.$store.commit('deleteUser', userid) - } - } catch { - this.loading.groups = false - } - }, - - /** - * Add user to group - * - * @param {object} group Group object - */ - async addUserSubAdmin(group) { - this.loading.subadmins = true - const userid = this.user.id - const gid = group.id - - try { - await this.$store.dispatch('addUserSubAdmin', { - userid, - gid, - }) - this.loading.subadmins = false - } catch (error) { - console.error(error) - } - }, - - /** - * Remove user from group - * - * @param {object} group Group object - */ - async removeUserSubAdmin(group) { - this.loading.subadmins = true - const userid = this.user.id - const gid = group.id - - try { - await this.$store.dispatch('removeUserSubAdmin', { - userid, - gid, - }) - } catch (error) { - console.error(error) - } finally { - this.loading.subadmins = false - } - }, - - /** - * Dispatch quota set request - * - * @param {string | object} quota Quota in readable format '5 GB' or Object {id: '5 GB', label: '5GB'} - * @return {string} - */ - async setUserQuota(quota = 'none') { - this.loading.quota = true - // ensure we only send the preset id - quota = quota.id ? quota.id : quota - - try { - await this.$store.dispatch('setUserData', { - userid: this.user.id, - key: 'quota', - value: quota, - }) - } catch (error) { - console.error(error) - } finally { - this.loading.quota = false - } - return quota - }, - - /** - * Validate quota string to make sure it's a valid human file size - * - * @param {string} quota Quota in readable format '5 GB' - * @return {Promise|boolean} - */ - validateQuota(quota) { - // only used for new presets sent through @Tag - const validQuota = OC.Util.computerFileSize(quota) - if (validQuota !== null && validQuota >= 0) { - // unify format output - return this.setUserQuota(OC.Util.humanFileSize(OC.Util.computerFileSize(quota))) - } - // if no valid do not change - return false - }, - - /** - * Dispatch language set request - * - * @param {object} lang language object {code:'en', name:'English'} - * @return {object} - */ - async setUserLanguage(lang) { - this.loading.languages = true - // ensure we only send the preset id - try { - await this.$store.dispatch('setUserData', { - userid: this.user.id, - key: 'language', - value: lang.code, - }) - } catch (error) { - console.error(error) - } finally { - this.loading.languages = false - } - return lang - }, - - /** - * Dispatch new welcome mail request - */ - sendWelcomeMail() { - this.loading.all = true - this.$store.dispatch('sendWelcomeMail', this.user.id) - .then(success => { - if (success) { - // Show feedback to indicate the success - this.feedbackMessage = t('setting', 'Welcome mail sent!') - setTimeout(() => { - this.feedbackMessage = '' - }, 2000) - } - this.loading.all = false - }) - }, - - }, -} -</script> -<style scoped lang="scss"> - // Force menu to be above other rows - .row--menu-opened { - z-index: 1 !important; - } - .row::v-deep .multiselect__single { - z-index: auto !important; - } -</style> diff --git a/apps/settings/src/components/UserList/UserRowSimple.vue b/apps/settings/src/components/UserList/UserRowSimple.vue deleted file mode 100644 index 50ed78a0bc7..00000000000 --- a/apps/settings/src/components/UserList/UserRowSimple.vue +++ /dev/null @@ -1,199 +0,0 @@ -<template> - <div class="row" - :class="{'disabled': loading.delete || loading.disable}" - :data-id="user.id"> - <div class="avatar" :class="{'icon-loading-small': loading.delete || loading.disable || loading.wipe}"> - <img v-if="!loading.delete && !loading.disable && !loading.wipe" - alt="" - width="32" - height="32" - :src="generateAvatar(user.id, 32)" - :srcset="generateAvatar(user.id, 64)+' 2x, '+generateAvatar(user.id, 128)+' 4x'"> - </div> - <!-- dirty hack to ellipsis on two lines --> - <div class="name"> - {{ user.id }} - <div class="displayName subtitle"> - <div v-tooltip="user.displayname.length > 20 ? user.displayname : ''" class="cellText"> - {{ user.displayname }} - </div> - </div> - </div> - <div /> - <div class="mailAddress"> - <div v-tooltip="user.email !== null && user.email.length > 20 ? user.email : ''" class="cellText"> - {{ user.email }} - </div> - </div> - <div class="groups"> - {{ userGroupsLabels }} - </div> - <div v-if="subAdminsGroups.length > 0 && settings.isAdmin" class="subAdminsGroups"> - {{ userSubAdminsGroupsLabels }} - </div> - <div class="userQuota"> - <div class="quota"> - {{ userQuota }} ({{ usedSpace }}) - <progress class="quota-user-progress" - :class="{'warn': usedQuota > 80}" - :value="usedQuota" - max="100" /> - </div> - </div> - <div v-if="showConfig.showLanguages" class="languages"> - {{ userLanguage.name }} - </div> - <div v-if="showConfig.showUserBackend || showConfig.showStoragePath" class="userBackend"> - <div v-if="showConfig.showUserBackend" class="userBackend"> - {{ user.backend }} - </div> - <div v-if="showConfig.showStoragePath" v-tooltip="user.storageLocation" class="storageLocation subtitle"> - {{ user.storageLocation }} - </div> - </div> - <div v-if="showConfig.showLastLogin" v-tooltip.auto="userLastLoginTooltip" class="lastLogin"> - {{ userLastLogin }} - </div> - - <div class="userActions"> - <div v-if="canEdit && !loading.all" class="toggleUserActions"> - <Actions> - <ActionButton icon="icon-rename" @click="toggleEdit"> - {{ t('settings', 'Edit User') }} - </ActionButton> - </Actions> - <div class="userPopoverMenuWrapper"> - <button v-click-outside="hideMenu" - class="icon-more" - :aria-label="t('settings', 'Toggle user actions menu')" - @click.prevent="toggleMenu" /> - <div class="popovermenu" :class="{ 'open': openedMenu }" :aria-expanded="openedMenu"> - <PopoverMenu :menu="userActions" /> - </div> - </div> - </div> - <div class="feedback" :style="{opacity: feedbackMessage !== '' ? 1 : 0}"> - <div class="icon-checkmark" /> - {{ feedbackMessage }} - </div> - </div> - </div> -</template> - -<script> -import PopoverMenu from '@nextcloud/vue/dist/Components/PopoverMenu' -import Actions from '@nextcloud/vue/dist/Components/Actions' -import ActionButton from '@nextcloud/vue/dist/Components/ActionButton' -import ClickOutside from 'vue-click-outside' -import { getCurrentUser } from '@nextcloud/auth' -import UserRowMixin from '../../mixins/UserRowMixin' -export default { - name: 'UserRowSimple', - components: { - PopoverMenu, - ActionButton, - Actions, - }, - directives: { - ClickOutside, - }, - mixins: [UserRowMixin], - props: { - user: { - type: Object, - required: true, - }, - loading: { - type: Object, - required: true, - }, - showConfig: { - type: Object, - required: true, - }, - userActions: { - type: Array, - required: true, - }, - openedMenu: { - type: Boolean, - required: true, - }, - feedbackMessage: { - type: String, - required: true, - }, - subAdminsGroups: { - type: Array, - required: true, - }, - settings: { - type: Object, - required: true, - }, - }, - computed: { - userGroupsLabels() { - return this.userGroups - .map(group => group.name) - .join(', ') - }, - userSubAdminsGroupsLabels() { - return this.userSubAdminsGroups - .map(group => group.name) - .join(', ') - }, - usedSpace() { - if (this.user.quota.used) { - return t('settings', '{size} used', { size: OC.Util.humanFileSize(this.user.quota.used) }) - } - return t('settings', '{size} used', { size: OC.Util.humanFileSize(0) }) - }, - canEdit() { - return getCurrentUser().uid !== this.user.id || this.settings.isAdmin - }, - userQuota() { - let quota = this.user.quota.quota - - if (quota === 'default') { - quota = this.settings.defaultQuota - if (quota !== 'none') { - // convert to numeric value to match what the server would usually return - quota = OC.Util.computerFileSize(quota) - } - } - - // when the default quota is unlimited, the server returns -3 here, map it to "none" - if (quota === 'none' || quota === -3) { - return t('settings', 'Unlimited') - } else if (quota >= 0) { - return OC.Util.humanFileSize(quota) - } - return OC.Util.humanFileSize(0) - }, - }, - methods: { - toggleMenu() { - this.$emit('update:openedMenu', !this.openedMenu) - }, - hideMenu() { - this.$emit('update:openedMenu', false) - }, - toggleEdit() { - this.$emit('update:editing', true) - }, - }, -} -</script> - -<style lang="scss"> - .cellText { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - .icon-more { - background-color: var(--color-main-background); - border: 0; - } -</style> |