summaryrefslogtreecommitdiffstats
path: root/settings/src
diff options
context:
space:
mode:
authorJohn Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>2018-09-12 17:20:39 +0200
committerJohn Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>2018-09-28 15:37:37 +0200
commit2b41b01bf2c2c1346d5368a6686a175a4e694f15 (patch)
treed7a74acb632953e35524629036adfee31c22cd48 /settings/src
parenta23d5240987392f4eb140eefbb81882ef6ae6a5a (diff)
downloadnextcloud-server-2b41b01bf2c2c1346d5368a6686a175a4e694f15.tar.gz
nextcloud-server-2b41b01bf2c2c1346d5368a6686a175a4e694f15.zip
Add new group entry on users list + fixes
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
Diffstat (limited to 'settings/src')
-rw-r--r--settings/src/components/appNavigation.vue54
-rw-r--r--settings/src/components/appNavigation/navigationItem.vue155
-rw-r--r--settings/src/components/userList.vue47
-rw-r--r--settings/src/store/users.js5
-rw-r--r--settings/src/views/Apps.vue5
-rw-r--r--settings/src/views/Users.vue97
6 files changed, 117 insertions, 246 deletions
diff --git a/settings/src/components/appNavigation.vue b/settings/src/components/appNavigation.vue
deleted file mode 100644
index 52a70786a9a..00000000000
--- a/settings/src/components/appNavigation.vue
+++ /dev/null
@@ -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>
diff --git a/settings/src/components/appNavigation/navigationItem.vue b/settings/src/components/appNavigation/navigationItem.vue
deleted file mode 100644
index 4464c0abd0a..00000000000
--- a/settings/src/components/appNavigation/navigationItem.vue
+++ /dev/null
@@ -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>
diff --git a/settings/src/components/userList.vue b/settings/src/components/userList.vue
index 69f459b3a6c..521ad7cc392 100644
--- a/settings/src/components/userList.vue
+++ b/settings/src/components/userList.vue
@@ -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')"
- label="name" track-by="id" class="multiselect-vue"
- :multiple="true" :close-on-select="false"
- :allowEmpty="settings.isAdmin">
+ 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" :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];
}
}
}
diff --git a/settings/src/store/users.js b/settings/src/store/users.js
index f6e7f9ff650..ba2611b3eec 100644
--- a/settings/src/store/users.js
+++ b/settings/src/store/users.js
@@ -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 });
diff --git a/settings/src/views/Apps.vue b/settings/src/views/Apps.vue
index f6553160c95..790b5db73b8 100644
--- a/settings/src/views/Apps.vue
+++ b/settings/src/views/Apps.vue
@@ -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'
@@ -43,7 +43,6 @@ import api from '../store/api';
import AppDetails from '../components/appDetails';
Vue.use(VueLocalStorage)
-Vue.use(VueLocalStorage)
export default {
name: 'Apps',
@@ -59,7 +58,7 @@ export default {
},
components: {
AppDetails,
- appNavigation,
+ AppNavigation,
appList,
},
methods: {
diff --git a/settings/src/views/Users.vue b/settings/src/views/Users.vue
index 3b624d32aff..2bc68b90657 100644
--- a/settings/src/views/Users.vue
+++ b/settings/src/views/Users.vue
@@ -57,7 +57,7 @@
</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'
@@ -65,13 +65,12 @@ 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',