123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488 |
- <!--
- - @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
- -
- - @author John Molakvoæ <skjnldsv@protonmail.com>
- -
- - @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 class="row" v-if="Object.keys(user).length ===1">
- <div class="avatar" :class="{'icon-loading-small': loading.delete || loading.disable}">
- <img alt="" width="32" height="32" :src="generateAvatar(user.id, 32)"
- :srcset="generateAvatar(user.id, 64)+' 2x, '+generateAvatar(user.id, 128)+' 4x'"
- v-if="!loading.delete && !loading.disable">
- </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 -->
- <div class="row" v-else :class="{'disabled': loading.delete || loading.disable}">
- <div class="avatar" :class="{'icon-loading-small': loading.delete || loading.disable}">
- <img alt="" width="32" height="32" :src="generateAvatar(user.id, 32)"
- :srcset="generateAvatar(user.id, 64)+' 2x, '+generateAvatar(user.id, 128)+' 4x'"
- v-if="!loading.delete && !loading.disable">
- </div>
- <div class="name">{{user.id}}</div>
- <form class="displayName" :class="{'icon-loading-small': loading.displayName}" v-on:submit.prevent="updateDisplayName">
- <input :id="'displayName'+user.id+rand" type="text"
- :disabled="loading.displayName||loading.all"
- :value="user.displayname" ref="displayName"
- autocomplete="new-password" autocorrect="off" autocapitalize="off" spellcheck="false" />
- <input type="submit" class="icon-confirm" value="" />
- </form>
- <form class="password" v-if="settings.canChangePassword" :class="{'icon-loading-small': loading.password}"
- v-on:submit.prevent="updatePassword">
- <input :id="'password'+user.id+rand" type="password" required
- :disabled="loading.password||loading.all" :minlength="minPasswordLength"
- value="" :placeholder="t('settings', 'New password')" ref="password"
- autocomplete="new-password" autocorrect="off" autocapitalize="off" spellcheck="false" />
- <input type="submit" class="icon-confirm" value="" />
- </form>
- <div v-else></div>
- <form class="mailAddress" :class="{'icon-loading-small': loading.mailAddress}" v-on:submit.prevent="updateEmail">
- <input :id="'mailAddress'+user.id+rand" type="email"
- :disabled="loading.mailAddress||loading.all"
- :value="user.email" ref="mailAddress"
- autocomplete="new-password" autocorrect="off" autocapitalize="off" spellcheck="false" />
- <input type="submit" class="icon-confirm" value="" />
- </form>
- <div class="groups" :class="{'icon-loading-small': loading.groups}">
- <multiselect :value="userGroups" :options="groups" :disabled="loading.groups||loading.all"
- tag-placeholder="create" :placeholder="t('settings', 'Add user in group')"
- label="name" track-by="id" class="multiselect-vue" :limit="2"
- :multiple="true" :taggable="settings.isAdmin" :closeOnSelect="false"
- @tag="createGroup" @select="addUserGroup" @remove="removeUserGroup">
- <span slot="limit" class="multiselect__limit" v-tooltip.auto="formatGroupsTitle(userGroups)">+{{userGroups.length-2}}</span>
- <span slot="noResult">{{t('settings', 'No results')}}</span>
- </multiselect>
- </div>
- <div class="subadmins" v-if="subAdminsGroups.length>0 && settings.isAdmin" :class="{'icon-loading-small': loading.subadmins}">
- <multiselect :value="userSubAdminsGroups" :options="subAdminsGroups" :disabled="loading.subadmins||loading.all"
- :placeholder="t('settings', 'Set user as admin for')"
- label="name" track-by="id" class="multiselect-vue" :limit="2"
- :multiple="true" :closeOnSelect="false"
- @select="addUserSubAdmin" @remove="removeUserSubAdmin">
- <span slot="limit" class="multiselect__limit" v-tooltip.auto="formatGroupsTitle(userSubAdminsGroups)">+{{userSubAdminsGroups.length-2}}</span>
- <span slot="noResult">{{t('settings', 'No results')}}</span>
- </multiselect>
- </div>
- <div class="quota" :class="{'icon-loading-small': loading.quota}">
- <multiselect :value="userQuota" :options="quotaOptions" :disabled="loading.quota||loading.all"
- tag-placeholder="create" :placeholder="t('settings', 'Select user quota')"
- label="label" track-by="id" class="multiselect-vue"
- :allowEmpty="false" :taggable="true"
- @tag="validateQuota" @input="setUserQuota">
- </multiselect>
- <progress class="quota-user-progress" :class="{'warn':usedQuota>80}" :value="usedQuota" max="100"></progress>
- </div>
- <div class="languages" :class="{'icon-loading-small': loading.languages}"
- v-if="showConfig.showLanguages">
- <multiselect :value="userLanguage" :options="languages" :disabled="loading.languages||loading.all"
- :placeholder="t('settings', 'No language set')"
- label="name" track-by="code" class="multiselect-vue"
- :allowEmpty="false" group-values="languages" group-label="label"
- @input="setUserLanguage">
- </multiselect>
- </div>
- <div class="storageLocation" v-if="showConfig.showStoragePath">{{user.storageLocation}}</div>
- <div class="userBackend" v-if="showConfig.showUserBackend">{{user.backend}}</div>
- <div class="lastLogin" v-if="showConfig.showLastLogin" v-tooltip.auto="user.lastLogin>0 ? OC.Util.formatDate(user.lastLogin) : ''">
- {{user.lastLogin>0 ? OC.Util.relativeModifiedDate(user.lastLogin) : t('settings','Never')}}
- </div>
- <div class="userActions">
- <div class="toggleUserActions" v-if="OC.currentUser !== user.id && user.id !== 'admin' && !loading.all">
- <div class="icon-more" v-click-outside="hideMenu" @click="toggleMenu"></div>
- <div class="popovermenu" :class="{ 'open': openedMenu }">
- <popover-menu :menu="userActions" />
- </div>
- </div>
- </div>
- </div>
- </template>
-
- <script>
- import popoverMenu from '../popoverMenu';
- import ClickOutside from 'vue-click-outside';
- import Multiselect from 'vue-multiselect';
- import Vue from 'vue'
- import VTooltip from 'v-tooltip'
-
- Vue.use(VTooltip)
-
- export default {
- name: 'userRow',
- props: ['user', 'settings', 'groups', 'subAdminsGroups', 'quotaOptions', 'showConfig', 'languages'],
- components: {
- popoverMenu,
- Multiselect
- },
- directives: {
- ClickOutside
- },
- mounted() {
- // required if popup needs to stay opened after menu click
- // since we only have disable/delete actions, let's close it directly
- // this.popupItem = this.$el;
- },
- data() {
- return {
- rand: parseInt(Math.random() * 1000),
- openedMenu: false,
- loading: {
- all: false,
- displayName: false,
- password: false,
- mailAddress: false,
- groups: false,
- subadmins: false,
- quota: false,
- delete: false,
- disable: false,
- languages: false
- }
- }
- },
- computed: {
- /* USER POPOVERMENU ACTIONS */
- userActions() {
- return [{
- icon: 'icon-delete',
- text: t('settings','Delete user'),
- action: this.deleteUser
- },{
- icon: this.user.enabled ? 'icon-close' : 'icon-add',
- text: this.user.enabled ? t('settings','Disable user') : t('settings','Enable user'),
- action: this.enableDisableUser
- }]
- },
-
- /* GROUPS MANAGEMENT */
- userGroups() {
- let userGroups = this.groups.filter(group => this.user.groups.includes(group.id));
- return userGroups;
- },
- userSubAdminsGroups() {
- let userSubAdminsGroups = this.subAdminsGroups.filter(group => this.user.subadmin.includes(group.id));
- return userSubAdminsGroups;
- },
-
- /* QUOTA MANAGEMENT */
- usedQuota() {
- let quota = this.user.quota.quota;
- if (quota > 0) {
- quota = Math.min(100, Math.round(this.user.quota.used / quota * 100));
- } else {
- var usedInGB = this.user.quota.used / (10 * Math.pow(2, 30));
- //asymptotic curve approaching 50% at 10GB to visualize used stace with infinite quota
- quota = 95 * (1 - (1 / (usedInGB + 1)));
- }
- return isNaN(quota) ? 0 : quota;
- },
- // Mapping saved values to objects
- userQuota() {
- if (this.user.quota.quota >= 0) {
- // if value is valid, let's map the quotaOptions or return custom quota
- let humanQuota = OC.Util.humanFileSize(this.user.quota.quota);
- let userQuota = this.quotaOptions.find(quota => quota.id === humanQuota);
- return userQuota ? userQuota : {id:humanQuota, label:humanQuota};
- } else if (this.user.quota.quota === 'default') {
- // default quota is replaced by the proper value on load
- return this.quotaOptions[0];
- }
- return this.quotaOptions[1]; // unlimited
- },
-
- /* PASSWORD POLICY? */
- minPasswordLength() {
- return this.$store.getters.getPasswordPolicyMinLength;
- },
-
- /* LANGUAGE */
- userLanguage() {
- let availableLanguages = this.languages[0].languages.concat(this.languages[1].languages);
- let userLang = availableLanguages.find(lang => lang.code === this.user.language);
- if (typeof userLang !== 'object' && this.user.language !== '') {
- return {
- code: this.user.language,
- name: this.user.language
- }
- } else if(this.user.language === '') {
- return false;
- }
- return userLang;
- }
- },
- methods: {
- /* MENU HANDLING */
- toggleMenu() {
- this.openedMenu = !this.openedMenu;
- },
- hideMenu() {
- this.openedMenu = false;
- },
-
- /**
- * Generate avatar url
- *
- * @param {string} user The user name
- * @param {int} size Size integer, default 32
- * @returns {string}
- */
- generateAvatar(user, size=32) {
- return OC.generateUrl(
- '/avatar/{user}/{size}?v={version}',
- {
- user: user,
- size: size,
- version: oc_userconfig.avatar.version
- }
- );
- },
-
- /**
- * Format array of groups objects to a string for the popup
- *
- * @param {array} groups The groups
- * @returns {string}
- */
- formatGroupsTitle(groups) {
- let names = groups.map(group => group.name);
- return names.slice(2,).join(', ');
- },
-
- deleteUser() {
- this.loading.delete = true;
- this.loading.all = true;
- let userid = this.user.id;
- return this.$store.dispatch('deleteUser', {userid})
- .then(() => {
- this.loading.delete = false
- this.loading.all = false
- });
- },
-
- enableDisableUser() {
- this.loading.delete = true;
- this.loading.all = true;
- let userid = this.user.id;
- let 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
- * @returns {Promise}
- */
- updateDisplayName() {
- let 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
- * @returns {Promise}
- */
- updatePassword() {
- let 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
- * @returns {Promise}
- */
- updateEmail() {
- let 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} groups Group id
- * @returns {Promise}
- */
- createGroup(gid) {
- this.loading = {groups:true, subadmins:true}
- this.$store.dispatch('addGroup', gid)
- .then(() => {
- this.loading = {groups:false, subadmins:false};
- let userid = this.user.id;
- this.$store.dispatch('addUserGroup', {userid, gid});
- })
- .catch(() => {
- this.loading = {groups:false, subadmins:false};
- });
- return this.$store.getters.getGroups[this.groups.length];
- },
-
- /**
- * Add user to group
- *
- * @param {object} group Group object
- * @returns {Promise}
- */
- addUserGroup(group) {
- this.loading.groups = true;
- let userid = this.user.id;
- let gid = group.id;
- return this.$store.dispatch('addUserGroup', {userid, gid})
- .then(() => this.loading.groups = false);
- },
-
- /**
- * Remove user from group
- *
- * @param {object} group Group object
- * @returns {Promise}
- */
- removeUserGroup(group) {
- this.loading.groups = true;
- let userid = this.user.id;
- let gid = group.id;
- return this.$store.dispatch('removeUserGroup', {userid, gid})
- .then(() => {
- 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
- * @returns {Promise}
- */
- addUserSubAdmin(group) {
- this.loading.subadmins = true;
- let userid = this.user.id;
- let gid = group.id;
- return this.$store.dispatch('addUserSubAdmin', {userid, gid})
- .then(() => this.loading.subadmins = false);
- },
-
- /**
- * Remove user from group
- *
- * @param {object} group Group object
- * @returns {Promise}
- */
- removeUserSubAdmin(group) {
- this.loading.subadmins = true;
- let userid = this.user.id;
- let gid = group.id;
- return this.$store.dispatch('removeUserSubAdmin', {userid, gid})
- .then(() => 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'}
- * @returns {string}
- */
- setUserQuota(quota = 'none') {
- this.loading.quota = true;
- // ensure we only send the preset id
- quota = quota.id ? quota.id : quota;
- this.$store.dispatch('setUserData', {
- userid: this.user.id,
- key: 'quota',
- value: quota
- }).then(() => 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'
- * @returns {Promise|boolean}
- */
- validateQuota(quota) {
- // only used for new presets sent through @Tag
- let 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'}
- * @returns {Object}
- */
- setUserLanguage(lang) {
- this.loading.languages = true;
- // ensure we only send the preset id
- this.$store.dispatch('setUserData', {
- userid: this.user.id,
- key: 'language',
- value: lang.code
- }).then(() => this.loading.languages = false);
- return lang;
- }
- }
- }
- </script>
|