Add new group entry on users list + fixes
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
This commit is contained in:
parent
a23d524098
commit
2b41b01bf2
12 changed files with 1517 additions and 298 deletions
|
@ -1377,21 +1377,6 @@ doesnotexist:-o-prefocus, .strengthify-wrapper {
|
|||
|
||||
/* USERS LIST -------------------------------------------------------------- */
|
||||
#body-settings {
|
||||
#app-navigation {
|
||||
/* Hack to override the javascript orderBy */
|
||||
#usergrouplist > li {
|
||||
order: 4;
|
||||
&#everyone {
|
||||
order:1;
|
||||
}
|
||||
&#admin {
|
||||
order:2;
|
||||
}
|
||||
&#disabled {
|
||||
order:3;
|
||||
}
|
||||
}
|
||||
}
|
||||
$grid-row-height: 46px;
|
||||
#app-content.user-list-grid {
|
||||
display: grid;
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1320
settings/package-lock.json
generated
1320
settings/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -13,6 +13,7 @@
|
|||
"dependencies": {
|
||||
"axios": "^0.18.0",
|
||||
"babel-polyfill": "^6.26.0",
|
||||
"nextcloud-vue": "^0.1.2",
|
||||
"v-tooltip": "^2.0.0-rc.33",
|
||||
"vue": "^2.5.17",
|
||||
"vue-click-outside": "^1.0.7",
|
||||
|
@ -33,6 +34,8 @@
|
|||
"babel-loader": "^8.0.2",
|
||||
"css-loader": "^1.0.0",
|
||||
"file-loader": "^1.1.11",
|
||||
"node-sass": "^4.9.3",
|
||||
"sass-loader": "^7.1.0",
|
||||
"vue-loader": "^15.4.2",
|
||||
"vue-template-compiler": "^2.5.17",
|
||||
"webpack": "^4.19.1",
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
<!--
|
||||
- @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>
|
||||
<div id="app-navigation" :class="{'icon-loading': menu.loading}">
|
||||
<div class="app-navigation-new" v-if="menu.new">
|
||||
<button type="button" :id="menu.new.id" :class="menu.new.icon" @click="menu.new.action">{{menu.new.text}}</button>
|
||||
</div>
|
||||
<ul :id="menu.id">
|
||||
<navigation-item v-for="item in menu.items" :item="item" :key="item.key" />
|
||||
</ul>
|
||||
<div id="app-settings" v-if="!!$slots['settings-content']">
|
||||
<div id="app-settings-header">
|
||||
<button class="settings-button"
|
||||
data-apps-slide-toggle="#app-settings-content"
|
||||
>{{t('settings', 'Settings')}}</button>
|
||||
</div>
|
||||
<div id="app-settings-content">
|
||||
<slot name="settings-content"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import navigationItem from './appNavigation/navigationItem';
|
||||
|
||||
export default {
|
||||
name: 'appNavigation',
|
||||
props: ['menu'],
|
||||
components: {
|
||||
navigationItem
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -1,155 +0,0 @@
|
|||
<!--
|
||||
- @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>
|
||||
|
||||
<!-- Is this a caption ? -->
|
||||
<li class="app-navigation-caption" v-if="item.caption">{{item.text}}</li>
|
||||
|
||||
<!-- Navigation item -->
|
||||
<nav-element v-else :id="item.id" v-bind="navElement(item)"
|
||||
:class="[{'icon-loading-small': item.loading, 'open': item.opened, 'collapsible': item.collapsible&&item.children&&item.children.length>0 }, item.classes]">
|
||||
|
||||
<!-- Bullet -->
|
||||
<div v-if="item.bullet" class="app-navigation-entry-bullet" :style="{ backgroundColor: item.bullet }"></div>
|
||||
|
||||
<!-- Main link -->
|
||||
<a :href="(item.href) ? item.href : '#' " @click="toggleCollapse" :class="item.icon">
|
||||
<img v-if="item.iconUrl" :alt="item.text" :src="item.iconUrl">
|
||||
{{item.text}}
|
||||
</a>
|
||||
|
||||
<!-- Popover, counter and button(s) -->
|
||||
<div v-if="item.utils" class="app-navigation-entry-utils">
|
||||
<ul>
|
||||
<!-- counter -->
|
||||
<li v-if="Number.isInteger(item.utils.counter)"
|
||||
class="app-navigation-entry-utils-counter">{{item.utils.counter}}</li>
|
||||
|
||||
<!-- first action if only one action -->
|
||||
<li v-if="item.utils.actions && item.utils.actions.length === 1"
|
||||
class="app-navigation-entry-utils-menu-button">
|
||||
<button @click="item.utils.actions[0].action" :class="item.utils.actions[0].icon" :title="item.utils.actions[0].text"></button>
|
||||
</li>
|
||||
|
||||
<!-- second action only two actions and no counter -->
|
||||
<li v-else-if="item.utils.actions && item.utils.actions.length === 2 && !Number.isInteger(item.utils.counter)"
|
||||
v-for="action in item.utils.actions" :key="action.action"
|
||||
class="app-navigation-entry-utils-menu-button">
|
||||
<button @click="action.action" :class="action.icon" :title="action.text"></button>
|
||||
</li>
|
||||
|
||||
<!-- menu if only at least one action and counter OR two actions and no counter-->
|
||||
<li v-else-if="item.utils.actions && item.utils.actions.length > 1 && (Number.isInteger(item.utils.counter) || item.utils.actions.length > 2)"
|
||||
class="app-navigation-entry-utils-menu-button">
|
||||
<button v-click-outside="hideMenu" @click="showMenu" ></button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- if more than 2 actions or more than 1 actions with counter -->
|
||||
<div v-if="item.utils && item.utils.actions && item.utils.actions.length > 1 && (Number.isInteger(item.utils.counter) || item.utils.actions.length > 2)"
|
||||
class="app-navigation-entry-menu" :class="{ 'open': openedMenu }">
|
||||
<popover-menu :menu="item.utils.actions"/>
|
||||
</div>
|
||||
|
||||
<!-- undo entry -->
|
||||
<div class="app-navigation-entry-deleted" v-if="item.undo">
|
||||
<div class="app-navigation-entry-deleted-description">{{item.undo.text}}</div>
|
||||
<button class="app-navigation-entry-deleted-button icon-history" :title="t('settings', 'Undo')"></button>
|
||||
</div>
|
||||
|
||||
<!-- edit entry -->
|
||||
<div class="app-navigation-entry-edit" v-if="item.edit">
|
||||
<form>
|
||||
<input type="text" v-model="item.text">
|
||||
<input type="submit" value="" class="icon-confirm">
|
||||
<input type="submit" value="" class="icon-close" @click.stop.prevent="cancelEdit">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- if the item has children, inject the component with proper data -->
|
||||
<ul v-if="item.children">
|
||||
<navigation-item v-for="(item, key) in item.children" :item="item" :key="key" />
|
||||
</ul>
|
||||
</nav-element>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import popoverMenu from '../popoverMenu';
|
||||
import ClickOutside from 'vue-click-outside';
|
||||
import Vue from 'vue';
|
||||
|
||||
export default {
|
||||
name: 'navigationItem',
|
||||
props: ['item'],
|
||||
components: {
|
||||
popoverMenu
|
||||
},
|
||||
directives: {
|
||||
ClickOutside
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
openedMenu: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
showMenu() {
|
||||
this.openedMenu = true;
|
||||
},
|
||||
hideMenu() {
|
||||
this.openedMenu = false;
|
||||
},
|
||||
toggleCollapse() {
|
||||
// if item.opened isn't set, Vue won't trigger view updates https://vuejs.org/v2/api/#Vue-set
|
||||
// ternary is here to detect the undefined state of item.opened
|
||||
Vue.set(this.item, 'opened', this.item.opened ? !this.item.opened : true);
|
||||
},
|
||||
cancelEdit() {
|
||||
// remove the editing class
|
||||
if (Array.isArray(this.item.classes))
|
||||
this.item.classes = this.item.classes.filter(
|
||||
item => item !== 'editing'
|
||||
);
|
||||
},
|
||||
// This is used to decide which outter element type to use
|
||||
// li or router-link
|
||||
navElement(item) {
|
||||
if (item.href) {
|
||||
return {
|
||||
is: 'li'
|
||||
};
|
||||
}
|
||||
return {
|
||||
is: 'router-link',
|
||||
tag: 'li',
|
||||
to: item.router,
|
||||
exact: true
|
||||
};
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// prevent click outside event with popupItem.
|
||||
this.popupItem = this.$el;
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -44,9 +44,9 @@
|
|||
</div>
|
||||
|
||||
<form class="row" id="new-user" v-show="showConfig.showNewUserForm"
|
||||
v-on:submit.prevent="createUser" :disabled="loading"
|
||||
v-on:submit.prevent="createUser" :disabled="loading.all"
|
||||
:class="{'sticky': scrolled && showConfig.showNewUserForm}">
|
||||
<div :class="loading?'icon-loading-small':'icon-add'"></div>
|
||||
<div :class="loading.all?'icon-loading-small':'icon-add'"></div>
|
||||
<div class="name">
|
||||
<input id="newusername" type="text" required v-model="newUser.id"
|
||||
:placeholder="t('settings', 'Username')" name="username"
|
||||
|
@ -74,12 +74,13 @@
|
|||
<div class="groups">
|
||||
<!-- hidden input trick for vanilla html5 form validation -->
|
||||
<input type="text" :value="newUser.groups" v-if="!settings.isAdmin"
|
||||
tabindex="-1" id="newgroups" :required="!settings.isAdmin" />
|
||||
<multiselect :options="canAddGroups" v-model="newUser.groups"
|
||||
:placeholder="t('settings', 'Add user in group')"
|
||||
tabindex="-1" id="newgroups" :required="!settings.isAdmin"
|
||||
:class="{'icon-loading-small': loading.groups}"/>
|
||||
<multiselect v-model="newUser.groups" :options="canAddGroups" :disabled="loading.groups||loading.all"
|
||||
tag-placeholder="create" :placeholder="t('settings', 'Add user in group')"
|
||||
label="name" track-by="id" class="multiselect-vue"
|
||||
:multiple="true" :close-on-select="false"
|
||||
:allowEmpty="settings.isAdmin">
|
||||
:multiple="true" :taggable="true" :close-on-select="false"
|
||||
@tag="createGroup">
|
||||
<!-- If user is not admin, he is a subadmin.
|
||||
Subadmins can't create users outside their groups
|
||||
Therefore, empty select is forbidden -->
|
||||
|
@ -154,7 +155,10 @@ export default {
|
|||
return {
|
||||
unlimitedQuota: unlimitedQuota,
|
||||
defaultQuota: defaultQuota,
|
||||
loading: false,
|
||||
loading: {
|
||||
all: false,
|
||||
groups: false
|
||||
},
|
||||
scrolled: false,
|
||||
searchQuery: '',
|
||||
newUser: {
|
||||
|
@ -318,10 +322,10 @@ export default {
|
|||
resetForm() {
|
||||
// revert form to original state
|
||||
Object.assign(this.newUser, this.$options.data.call(this).newUser);
|
||||
this.loading = false;
|
||||
this.loading.all = false;
|
||||
},
|
||||
createUser() {
|
||||
this.loading = true;
|
||||
this.loading.all = true;
|
||||
this.$store.dispatch('addUser', {
|
||||
userid: this.newUser.id,
|
||||
password: this.newUser.password,
|
||||
|
@ -332,7 +336,7 @@ export default {
|
|||
quota: this.newUser.quota.id,
|
||||
language: this.newUser.language.code,
|
||||
}).then(() => this.resetForm())
|
||||
.catch(() => this.loading = false);
|
||||
.catch(() => this.loading.all = false);
|
||||
},
|
||||
setNewUserDefaultGroup(value) {
|
||||
if (value && value.length > 0) {
|
||||
|
@ -345,6 +349,25 @@ export default {
|
|||
}
|
||||
// fallback, empty selected group
|
||||
this.newUser.groups = [];
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new group
|
||||
*
|
||||
* @param {string} groups Group id
|
||||
* @returns {Promise}
|
||||
*/
|
||||
createGroup(gid) {
|
||||
this.loading.groups = true;
|
||||
this.$store.dispatch('addGroup', gid)
|
||||
.then((group) => {
|
||||
this.newUser.groups.push(this.groups.find(group => group.id === gid))
|
||||
this.loading.groups = false;
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading.groups = false;
|
||||
});
|
||||
return this.$store.getters.getGroups[this.groups.length];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -296,7 +296,10 @@ const actions = {
|
|||
addGroup(context, gid) {
|
||||
return api.requireAdmin().then((response) => {
|
||||
return api.post(OC.linkToOCS(`cloud/groups`, 2), {groupid: gid})
|
||||
.then((response) => context.commit('addGroup', {gid: gid, displayName: gid}))
|
||||
.then((response) => {
|
||||
context.commit('addGroup', {gid: gid, displayName: gid})
|
||||
return {gid: gid, displayName: gid}
|
||||
})
|
||||
.catch((error) => {throw error;});
|
||||
}).catch((error) => {
|
||||
context.commit('API_FAILURE', { gid, error });
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
|
||||
|
||||
<script>
|
||||
import appNavigation from '../components/appNavigation';
|
||||
import { AppNavigation } from 'nextcloud-vue';
|
||||
import appList from '../components/appList';
|
||||
import Vue from 'vue';
|
||||
import VueLocalStorage from 'vue-localstorage'
|
||||
|
@ -42,7 +42,6 @@ import Multiselect from 'vue-multiselect';
|
|||
import api from '../store/api';
|
||||
import AppDetails from '../components/appDetails';
|
||||
|
||||
Vue.use(VueLocalStorage)
|
||||
Vue.use(VueLocalStorage)
|
||||
|
||||
export default {
|
||||
|
@ -59,7 +58,7 @@ export default {
|
|||
},
|
||||
components: {
|
||||
AppDetails,
|
||||
appNavigation,
|
||||
AppNavigation,
|
||||
appList,
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -57,21 +57,20 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import appNavigation from '../components/appNavigation';
|
||||
import { AppNavigation } from 'nextcloud-vue';
|
||||
import userList from '../components/userList';
|
||||
import Vue from 'vue';
|
||||
import VueLocalStorage from 'vue-localstorage'
|
||||
import Multiselect from 'vue-multiselect';
|
||||
import api from '../store/api';
|
||||
|
||||
Vue.use(VueLocalStorage)
|
||||
Vue.use(VueLocalStorage)
|
||||
|
||||
export default {
|
||||
name: 'Users',
|
||||
props: ['selectedGroup'],
|
||||
components: {
|
||||
appNavigation,
|
||||
AppNavigation,
|
||||
userList,
|
||||
Multiselect
|
||||
},
|
||||
|
@ -101,6 +100,8 @@ export default {
|
|||
// temporary value used for multiselect change
|
||||
selectedQuota: false,
|
||||
externalActions: [],
|
||||
showAddGroupEntry: false,
|
||||
loadingAddGroup: false,
|
||||
showConfig: {
|
||||
showStoragePath: false,
|
||||
showUserBackend: false,
|
||||
|
@ -198,6 +199,26 @@ export default {
|
|||
action: action
|
||||
});
|
||||
return this.externalActions;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new group
|
||||
*
|
||||
* @param {Object} event The form submit event
|
||||
* @returns {Promise}
|
||||
*/
|
||||
createGroup(event) {
|
||||
let gid = event.target[0].value;
|
||||
this.loadingAddGroup = true;
|
||||
this.$store.dispatch('addGroup', gid)
|
||||
.then(() => {
|
||||
this.showAddGroupEntry = false;
|
||||
this.loadingAddGroup = false;
|
||||
})
|
||||
.catch(() => {
|
||||
this.loadingAddGroup = false;
|
||||
});
|
||||
return this.$store.getters.getGroups[this.groups.length];
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -276,6 +297,7 @@ export default {
|
|||
// BUILD APP NAVIGATION MENU OBJECT
|
||||
menu() {
|
||||
// Data provided php side
|
||||
let self = this;
|
||||
let groups = this.$store.getters.getGroups;
|
||||
groups = Array.isArray(groups) ? groups : [];
|
||||
|
||||
|
@ -302,31 +324,19 @@ export default {
|
|||
|
||||
if (item.id !== 'admin' && item.id !== 'disabled' && this.settings.isAdmin) {
|
||||
// add delete button on real groups
|
||||
let self = this;
|
||||
item.utils.actions = [{
|
||||
icon: 'icon-delete',
|
||||
text: t('settings', 'Remove group'),
|
||||
action: function() {self.removeGroup(group.id)}
|
||||
action: function() {
|
||||
self.removeGroup(group.id)
|
||||
}
|
||||
}];
|
||||
};
|
||||
return item;
|
||||
});
|
||||
|
||||
// Adjust data
|
||||
let adminGroup = groups.find(group => group.id == 'admin');
|
||||
let disabledGroupIndex = groups.findIndex(group => group.id == 'disabled');
|
||||
let disabledGroup = groups[disabledGroupIndex];
|
||||
if (adminGroup && adminGroup.text) {
|
||||
adminGroup.text = t('settings', 'Admins'); // rename admin group
|
||||
adminGroup.icon = 'icon-user-admin'; // set icon
|
||||
}
|
||||
if (disabledGroup && disabledGroup.text) {
|
||||
disabledGroup.text = t('settings', 'Disabled users'); // rename disabled group
|
||||
disabledGroup.icon = 'icon-disabled-users'; // set icon
|
||||
if (!disabledGroup.utils.counter) {
|
||||
groups.splice(disabledGroupIndex, 1); // remove disabled if empty
|
||||
}
|
||||
}
|
||||
// Every item is added on top of the array, so we're going backward
|
||||
// Groups, separator, disabled, admin, everyone
|
||||
|
||||
// Add separator
|
||||
let realGroups = groups.find((group) => {return group.id !== 'disabled' && group.id !== 'admin'});
|
||||
|
@ -340,6 +350,26 @@ export default {
|
|||
groups.unshift(separator);
|
||||
}
|
||||
|
||||
// Adjust admin and disabled groups
|
||||
let adminGroup = groups.find(group => group.id == 'admin');
|
||||
let disabledGroup = groups.find(group => group.id == 'disabled');
|
||||
|
||||
// filter out admin and disabled
|
||||
groups = groups.filter(group => ['admin', 'disabled'].indexOf(group.id) === -1);
|
||||
|
||||
if (adminGroup && adminGroup.text) {
|
||||
adminGroup.text = t('settings', 'Admins'); // rename admin group
|
||||
adminGroup.icon = 'icon-user-admin'; // set icon
|
||||
groups.unshift(adminGroup); // add admin group if present
|
||||
}
|
||||
if (disabledGroup && disabledGroup.text) {
|
||||
disabledGroup.text = t('settings', 'Disabled users'); // rename disabled group
|
||||
disabledGroup.icon = 'icon-disabled-users'; // set icon
|
||||
if (disabledGroup.utils && disabledGroup.utils.counter > 0) {
|
||||
groups.unshift(disabledGroup); // add disabled if not empty
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Add everyone group
|
||||
let everyoneGroup = {
|
||||
|
@ -351,10 +381,35 @@ export default {
|
|||
};
|
||||
// users count
|
||||
if (this.userCount > 0) {
|
||||
everyoneGroup.utils = {counter: this.userCount};
|
||||
Vue.set(everyoneGroup, 'utils', {
|
||||
counter: this.userCount
|
||||
});
|
||||
}
|
||||
groups.unshift(everyoneGroup);
|
||||
|
||||
let addGroup = {
|
||||
id: 'addgroup',
|
||||
key: 'addgroup',
|
||||
icon: 'icon-add',
|
||||
text: t('settings', 'Add group'),
|
||||
classes: this.loadingAddGroup ? 'icon-loading-small' : ''
|
||||
};
|
||||
if (this.showAddGroupEntry) {
|
||||
Vue.set(addGroup, 'edit', {
|
||||
text: t('settings', 'Add group'),
|
||||
action: this.createGroup,
|
||||
reset: function() {
|
||||
self.showAddGroupEntry = false
|
||||
}
|
||||
});
|
||||
addGroup.classes = 'editing';
|
||||
} else {
|
||||
Vue.set(addGroup, 'action', function() {
|
||||
self.showAddGroupEntry = true
|
||||
})
|
||||
}
|
||||
groups.unshift(addGroup);
|
||||
|
||||
// Return
|
||||
return {
|
||||
id: 'usergrouplist',
|
||||
|
|
|
@ -13,14 +13,13 @@ module.exports = {
|
|||
{
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
'css-loader'
|
||||
'vue-style-loader', 'css-loader'
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
use: [
|
||||
'css-loader',
|
||||
'sass-loader'
|
||||
'vue-style-loader', 'css-loader', 'sass-loader'
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue