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
|
@ -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
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 New Issue