Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>tags/v14.0.0beta1
@@ -636,8 +636,9 @@ input { | |||
opacity: 1 !important; | |||
} | |||
} | |||
&.multiselect--disabled { | |||
background-color: nc-darken($color-main-background, 8%); | |||
&.multiselect--disabled, | |||
&.multiselect--disabled .multiselect__single { | |||
background-color: nc-darken($color-main-background, 8%) !important; | |||
} | |||
.multiselect__tags { | |||
display: flex; | |||
@@ -683,7 +684,7 @@ input { | |||
.multiselect__single { | |||
padding: 8px 10px; | |||
flex: 0 0 100%; | |||
z-index: 5; | |||
z-index: 1; /* above input */ | |||
background-color: $color-main-background; | |||
cursor: pointer; | |||
} |
@@ -1289,6 +1289,9 @@ doesnotexist:-o-prefocus, .strengthify-wrapper { | |||
grid-template-columns: 44px; | |||
grid-auto-columns: min-content; | |||
border-top: $color-border 1px solid; | |||
&.disabled { | |||
opacity: .5; | |||
} | |||
.name, | |||
.displayName, | |||
.password { | |||
@@ -1363,6 +1366,7 @@ doesnotexist:-o-prefocus, .strengthify-wrapper { | |||
left: 2px; | |||
bottom: 2px; | |||
height: 3px; | |||
z-index: 5; /* above multiselect */ | |||
} | |||
} | |||
.icon-confirm { | |||
@@ -1381,6 +1385,9 @@ doesnotexist:-o-prefocus, .strengthify-wrapper { | |||
img { | |||
display: block; | |||
} | |||
&.icon-loading > img { | |||
display: none; | |||
} | |||
} | |||
.toggleUserActions { | |||
position: relative; | |||
@@ -1419,6 +1426,7 @@ doesnotexist:-o-prefocus, .strengthify-wrapper { | |||
display: flex; | |||
align-items: center; | |||
justify-content: center; | |||
grid-row-start: span 4; | |||
} | |||
.users-list-end { | |||
opacity: .5; |
@@ -1,10 +1,10 @@ | |||
<template> | |||
<li> | |||
<a @click="dispatchToStore" v-if="item.href" :href="(item.href) ? item.href : '#' "> | |||
<a @click="item.action" v-if="item.href" :href="(item.href) ? item.href : '#' "> | |||
<span :class="item.icon"></span> | |||
<span>{{item.text}}</span> | |||
</a> | |||
<button @click="dispatchToStore(item.action)" v-else> | |||
<button @click="item.action" v-else> | |||
<span :class="item.icon"></span> | |||
<span>{{item.text}}</span> | |||
</button> | |||
@@ -13,11 +13,6 @@ | |||
<script> | |||
export default { | |||
props: ['item'], | |||
methods: { | |||
dispatchToStore () { | |||
this.$store.dispatch(this.item.action, this.item.data); | |||
} | |||
} | |||
props: ['item'] | |||
} | |||
</script> |
@@ -82,11 +82,17 @@ | |||
</div> | |||
</form> | |||
<user-row v-for="(user, key) in users" :user="user" :key="key" :settings="settings" :showConfig="showConfig" | |||
<user-row v-for="(user, key) in filteredUsers" :user="user" :key="key" :settings="settings" :showConfig="showConfig" | |||
:groups="groups" :subAdminsGroups="subAdminsGroups" :quotaOptions="quotaOptions" /> | |||
<infinite-loading @infinite="infiniteHandler"> | |||
<span slot="spinner"><div class="users-icon-loading"></div></span> | |||
<span slot="no-more"><div class="users-list-end">— {{t('settings', 'no more results')}} —</div></span> | |||
<infinite-loading @infinite="infiniteHandler" ref="infiniteLoading"> | |||
<div slot="spinner"><div class="users-icon-loading icon-loading"></div></div> | |||
<div slot="no-more"><div class="users-list-end">— {{t('settings', 'no more results')}} —</div></div> | |||
<div slot="no-results"> | |||
<div id="emptycontent"> | |||
<div class="icon-contacts-dark"></div> | |||
<h2>{{t('settings', 'No users in here')}}</h2> | |||
</div> | |||
</div> | |||
</infinite-loading> | |||
</div> | |||
</template> | |||
@@ -132,6 +138,12 @@ export default { | |||
settings() { | |||
return this.$store.getters.getServerData; | |||
}, | |||
filteredUsers() { | |||
if (this.route.hash === '#group_disabled') { | |||
return this.users.filter(user => user.enabled !== true); | |||
} | |||
return this.users.filter(user => user.enabled === true); | |||
}, | |||
groups() { | |||
// data provided php side + remove the disabled group | |||
return this.$store.getters.getGroups.filter(group => group.id !== '_disabled'); | |||
@@ -156,7 +168,30 @@ export default { | |||
}, | |||
usersLimit() { | |||
return this.$store.getters.getUsersLimit; | |||
}, | |||
}, | |||
route() { | |||
return this.$store.getters.getRoute; | |||
}, | |||
// get selected hash | |||
selectedGroup() { | |||
let hash = this.route.hash; | |||
if (typeof hash === 'string' && hash.length > 0) { | |||
// we have a valid hash: groupXXXX | |||
// group_XXXX are reserved groups | |||
let split = hash.split('group'); | |||
if (split.length === 2 && split[1].charAt(0) !== '_') { | |||
return hash.split('group')[1]; | |||
} | |||
} | |||
return ''; | |||
} | |||
}, | |||
watch: { | |||
// watch url change and group select | |||
selectedGroup: function (val, old) { | |||
this.$store.commit('resetUsers'); | |||
this.$refs.infiniteLoading.$emit('$InfiniteLoading:reset'); | |||
} | |||
}, | |||
methods: { | |||
onScroll(event) { | |||
@@ -182,7 +217,7 @@ export default { | |||
}, | |||
infiniteHandler($state) { | |||
this.$store.dispatch('getUsers', {offset:this.usersOffset, limit:this.usersLimit}) | |||
this.$store.dispatch('getUsers', {offset:this.usersOffset, limit:this.usersLimit, group:this.selectedGroup}) | |||
.then((response) => {response?$state.loaded():$state.complete()}); | |||
}, | |||
@@ -197,8 +232,10 @@ export default { | |||
userid: this.newUser.id, | |||
password: this.newUser.password, | |||
email: this.newUser.mailAddress, | |||
groups: this.newUser.groups.map(group => group.id) | |||
}).then(() =>this.resetForm()); | |||
groups: this.newUser.groups.map(group => group.id), | |||
subadmin: this.newUser.subAdminsGroups.map(group => group.id), | |||
quota: this.newUser.quota.id | |||
}).then(() => this.resetForm()); | |||
} | |||
} | |||
} |
@@ -1,6 +1,6 @@ | |||
<template> | |||
<div class="row"> | |||
<div class="avatar"><img alt="" width="32" height="32" :src="generateAvatar(user.id, 32)" :srcset="generateAvatar(user.id, 64)+' 2x, '+generateAvatar(user.id, 128)+' 4x'"></div> | |||
<div class="row" :class="{'disabled': loading.delete || loading.disable}"> | |||
<div class="avatar" :class="{'icon-loading': 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'"></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" | |||
@@ -59,7 +59,7 @@ | |||
{{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'"> | |||
<div class="toggleUserActions" v-if="OC.currentUser !== user.id && user.id !== 'admin' && !loading.all"> | |||
<div class="icon-more" v-click-outside="hideMenu" @click="showMenu"></div> | |||
<div class="popovermenu" :class="{ 'open': openedMenu }"> | |||
<popover-menu :menu="userActions" /> | |||
@@ -100,7 +100,9 @@ export default { | |||
mailAddress: false, | |||
groups: false, | |||
subadmins: false, | |||
quota: false | |||
quota: false, | |||
delete: false, | |||
disable: false | |||
} | |||
} | |||
}, | |||
@@ -110,13 +112,11 @@ export default { | |||
return [{ | |||
icon: 'icon-delete', | |||
text: t('settings','Delete user'), | |||
action: 'deleteUser', | |||
data: this.user.id | |||
action: this.deleteUser | |||
},{ | |||
'icon': this.user.enabled ? 'icon-close' : 'icon-add', | |||
'text': this.user.enabled ? t('settings','Disable user') : t('settings','Enable user'), | |||
'action': 'enableDisableUser', | |||
data: {userid: this.user.id, enabled: !this.user.enabled} | |||
'action': this.enableDisableUser | |||
}] | |||
}, | |||
@@ -199,6 +199,29 @@ export default { | |||
return '+'+count; | |||
}, | |||
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 | |||
* |
@@ -33,7 +33,7 @@ export default { | |||
.catch((error) => Promise.reject(error)); | |||
}, | |||
patch(url, data) { | |||
return axios.patch(sanitize(url), { data: data, headers: tokenHeaders.headers }) | |||
return axios.patch(sanitize(url), data, tokenHeaders) | |||
.then((response) => Promise.resolve(response)) | |||
.catch((error) => Promise.reject(error)); | |||
}, |
@@ -1,17 +1,23 @@ | |||
import Vue from 'vue' | |||
import Vuex from 'vuex' | |||
import users from './users' | |||
import settings from './settings' | |||
import Vue from 'vue'; | |||
import Vuex from 'vuex'; | |||
import users from './users'; | |||
import settings from './settings'; | |||
Vue.use(Vuex) | |||
const debug = process.env.NODE_ENV !== 'production' | |||
const debug = process.env.NODE_ENV !== 'production'; | |||
const mutations = { | |||
API_FAILURE(state, error) { | |||
console.log(state, error); | |||
} | |||
} | |||
}; | |||
const getters = { | |||
getRoute(state) { | |||
return state.route; | |||
} | |||
}; | |||
export default new Vuex.Store({ | |||
modules: { | |||
@@ -20,5 +26,6 @@ export default new Vuex.Store({ | |||
}, | |||
strict: debug, | |||
mutations | |||
}) | |||
mutations, | |||
getters | |||
}); |
@@ -19,6 +19,7 @@ const state = { | |||
minPasswordLength: 0, | |||
usersOffset: 0, | |||
usersLimit: 25, | |||
userCount: 0 | |||
}; | |||
const mutations = { | |||
@@ -31,9 +32,10 @@ const mutations = { | |||
setPasswordPolicyMinLength(state, length) { | |||
state.minPasswordLength = length!=='' ? length : 0; | |||
}, | |||
initGroups(state, {groups, orderBy}) { | |||
initGroups(state, {groups, orderBy, userCount}) { | |||
state.groups = groups; | |||
state.orderBy = orderBy; | |||
state.userCount = userCount; | |||
state.groups = orderGroups(state.groups, state.orderBy); | |||
}, | |||
addGroup(state, groupid) { | |||
@@ -87,7 +89,10 @@ const mutations = { | |||
}, | |||
enableDisableUser(state, { userid, enabled }) { | |||
state.users.find(user => user.id == userid).enabled = enabled; | |||
// increment or not | |||
state.groups.find(group => group.id == '_disabled').usercount += enabled ? -1 : 1; | |||
state.userCount += enabled ? 1 : -1; | |||
console.log(enabled); | |||
}, | |||
setUserData(state, { userid, key, value }) { | |||
if (key === 'quota') { | |||
@@ -97,6 +102,14 @@ const mutations = { | |||
state.users.find(user => user.id == userid)[key] = value; | |||
} | |||
}, | |||
/** | |||
* Reset users list | |||
*/ | |||
resetUsers(state) { | |||
state.users = []; | |||
state.usersOffset = 0; | |||
} | |||
}; | |||
const getters = { | |||
@@ -114,10 +127,14 @@ const getters = { | |||
}, | |||
getUsersLimit(state) { | |||
return state.usersLimit; | |||
}, | |||
getUserCount(state) { | |||
return state.userCount; | |||
} | |||
}; | |||
const actions = { | |||
/** | |||
* Get all users with full details | |||
* | |||
@@ -125,10 +142,25 @@ const actions = { | |||
* @param {Object} options | |||
* @param {int} options.offset List offset to request | |||
* @param {int} options.limit List number to return from offset | |||
* @param {string} options.search Search amongst users | |||
* @param {string} options.group Get users from group | |||
* @returns {Promise} | |||
*/ | |||
getUsers(context, { offset, limit, search }) { | |||
getUsers(context, { offset, limit, search, group }) { | |||
search = typeof search === 'string' ? search : ''; | |||
group = typeof group === 'string' ? group : ''; | |||
if (group !== '') { | |||
return api.get(OC.linkToOCS(`cloud/groups/${group}/users/details?offset=${offset}&limit=${limit}&search=${search}`, 2)) | |||
.then((response) => { | |||
if (Object.keys(response.data.ocs.data.users).length > 0) { | |||
context.commit('appendUsers', response.data.ocs.data.users); | |||
return true; | |||
} | |||
return false; | |||
}) | |||
.catch((error) => context.commit('API_FAILURE', error)); | |||
} | |||
return api.get(OC.linkToOCS(`cloud/users/details?offset=${offset}&limit=${limit}&search=${search}`, 2)) | |||
.then((response) => { | |||
if (Object.keys(response.data.ocs.data.users).length > 0) { | |||
@@ -200,7 +232,7 @@ const actions = { | |||
}, | |||
/** | |||
* Add group | |||
* Remove group | |||
* | |||
* @param {Object} context | |||
* @param {string} gid Group id | |||
@@ -289,7 +321,7 @@ const actions = { | |||
* @param {string} userid User id | |||
* @returns {Promise} | |||
*/ | |||
deleteUser(context, userid) { | |||
deleteUser(context, { userid }) { | |||
return api.requireAdmin().then((response) => { | |||
return api.delete(OC.linkToOCS(`cloud/users/${userid}`, 2)) | |||
.then((response) => context.commit('deleteUser', userid)) | |||
@@ -305,12 +337,19 @@ const actions = { | |||
* @param {string} options.userid User id | |||
* @param {string} options.password User password | |||
* @param {string} options.email User email | |||
* @param {string} options.groups User groups | |||
* @param {string} options.subadmin User subadmin groups | |||
* @param {string} options.quota User email | |||
* @returns {Promise} | |||
*/ | |||
addUser({context, dispatch}, {userid, password, email, groups}) { | |||
addUser({context, dispatch}, { userid, password, email, groups, subadmin, quota }) { | |||
console.log(subadmin, quota); | |||
return api.requireAdmin().then((response) => { | |||
return api.post(OC.linkToOCS(`cloud/users`, 2), {userid, password, email, groups}) | |||
.then((response) => dispatch('addUserData', userid)) | |||
return api.post(OC.linkToOCS(`cloud/users`, 2), { userid, password, email, groups, subadmin, quota }) | |||
.then((response) => { | |||
//let quotaDis = dispatch('setUserData', { userid, key: 'quota', value:quota }); | |||
//let subadminDis = dispatch('addUserSubAdmin', userid); | |||
}) | |||
.catch((error) => context.commit('API_FAILURE', { userid, error })); | |||
}); | |||
}, |
@@ -39,7 +39,8 @@ export default { | |||
beforeMount() { | |||
this.$store.commit('initGroups', { | |||
groups: this.$store.getters.getServerData.groups, | |||
orderBy: this.$store.getters.getServerData.sortGroups | |||
orderBy: this.$store.getters.getServerData.sortGroups, | |||
userCount: this.$store.getters.getServerData.userCount | |||
}); | |||
this.$store.dispatch('getPasswordPolicyMinLength'); | |||
}, | |||
@@ -66,6 +67,9 @@ export default { | |||
} | |||
}, | |||
computed: { | |||
route() { | |||
return this.$store.getters.getRoute; | |||
}, | |||
users() { | |||
return this.$store.getters.getUsers; | |||
}, | |||
@@ -96,6 +100,9 @@ export default { | |||
this.setLocalStorage('showStoragePath', status); | |||
} | |||
}, | |||
userCount() { | |||
return this.$store.getters.getUserCount; | |||
}, | |||
menu() { | |||
let self = this; | |||
// Data provided php side | |||
@@ -114,24 +121,34 @@ export default { | |||
}); | |||
// Adjust data | |||
if (groups[0].id === 'admin') { | |||
groups[0].text = t('settings', 'Admins');} // rename admin group | |||
if (groups[1].id === '_disabled') { | |||
groups[1].text = t('settings', 'Disabled users'); // rename disabled group | |||
if (groups[1].utils.counter === 0) { | |||
groups.splice(1, 1); // remove disabled if empty | |||
let adminGroup = groups.find(group => group.id == 'admin'); | |||
let disabledGroup = groups.find(group => group.id == '_disabled'); | |||
if (adminGroup.text) { | |||
adminGroup.text = t('settings', 'Admins');} // rename admin group | |||
if (disabledGroup.text) { | |||
disabledGroup.text = t('settings', 'Disabled users'); // rename disabled group | |||
if (disabledGroup.utils.counter === 0) { | |||
groups.splice(groups.findIndex(group => group.id == '_disabled'), 1); // remove disabled if empty | |||
} | |||
} | |||
// Add everyone group | |||
groups.unshift({ | |||
id: '_everyone', | |||
classes: ['active'], | |||
classes: [], | |||
href:'#group_everyone', | |||
text: t('settings', 'Everyone'), | |||
utils: {counter: this.users.length} | |||
utils: {counter: this.userCount} | |||
}); | |||
// Set current group as active | |||
let activeGroup = groups.findIndex(group => group.href === this.route.hash); | |||
if (activeGroup >= 0) { | |||
groups[activeGroup].classes.push('active'); | |||
} else { | |||
groups[0].classes.push('active'); | |||
} | |||
// Return | |||
return { | |||
id: 'usergrouplist', |
@@ -113,16 +113,22 @@ $disabledUsersGroup = [ | |||
$allGroups = array_merge_recursive($adminGroup, $groups); | |||
/* QUOTAS PRESETS */ | |||
$quotaPreset=$config->getAppValue('files', 'quota_preset', '1 GB, 5 GB, 10 GB'); | |||
$quotaPreset=explode(',', $quotaPreset); | |||
foreach($quotaPreset as &$preset) { | |||
$preset=trim($preset); | |||
$quotaPreset = $config->getAppValue('files', 'quota_preset', '1 GB, 5 GB, 10 GB'); | |||
$quotaPreset = explode(',', $quotaPreset); | |||
foreach ($quotaPreset as &$preset) { | |||
$preset = trim($preset); | |||
} | |||
$quotaPreset=array_diff($quotaPreset, array('default', 'none')); | |||
$defaultQuota=$config->getAppValue('files', 'default_quota', 'none'); | |||
$quotaPreset = array_diff($quotaPreset, array('default', 'none')); | |||
$defaultQuota = $config->getAppValue('files', 'default_quota', 'none'); | |||
\OC::$server->getEventDispatcher()->dispatch('OC\Settings\Users::loadAdditionalScripts'); | |||
/* TOTAL USERS COUNT */ | |||
function addition($v, $w) { | |||
return $v+$w; | |||
} | |||
$userCount = array_reduce($userManager->countUsers(), 'addition', 0); | |||
/* FINAL DATA */ | |||
$serverData = array(); | |||
// groups | |||
@@ -132,7 +138,7 @@ $serverData['subadmingroups'] = $groups; | |||
$serverData['subadmins'] = $subAdmins; | |||
$serverData['sortGroups'] = $sortGroupsBy; | |||
$serverData['quotaPreset'] = $quotaPreset; | |||
$serverData['userCount'] = $userManager->countUsers(); | |||
$serverData['userCount'] = $userCount-$disabledUsers; | |||
// Settings | |||
$serverData['defaultQuota'] = $defaultQuota; | |||
$serverData['canChangePassword'] = $canChangePassword; |