Remove users management settings cleaning leftovers (#17360)
Remove users management settings cleaning leftovers
This commit is contained in:
commit
ecfa6f7e0c
|
@ -227,7 +227,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import userRow from './userList/UserRow'
|
||||
import userRow from './UserList/UserRow'
|
||||
import { Multiselect } from 'nextcloud-vue'
|
||||
import InfiniteLoading from 'vue-infinite-loading'
|
||||
import Vue from 'vue'
|
||||
|
|
|
@ -1,237 +0,0 @@
|
|||
<!--
|
||||
- @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
|
||||
-
|
||||
- @author Julius Härtl <jus@bitgrid.net>
|
||||
-
|
||||
- @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-details-view" style="padding: 20px;">
|
||||
<h2>
|
||||
<div v-if="!app.preview" class="icon-settings-dark"></div>
|
||||
<svg v-if="app.previewAsIcon && app.preview" width="32" height="32" viewBox="0 0 32 32">
|
||||
<defs><filter :id="filterId"><feColorMatrix in="SourceGraphic" type="matrix" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"></feColorMatrix></filter></defs>
|
||||
<image x="0" y="0" width="32" height="32" preserveAspectRatio="xMinYMin meet" :filter="filterUrl" :xlink:href="app.preview" class="app-icon"></image>
|
||||
</svg>
|
||||
{{ app.name }}</h2>
|
||||
<img v-if="app.screenshot" :src="app.screenshot" width="100%" />
|
||||
<div class="app-level" v-if="app.level === 300 || app.level === 200 || hasRating">
|
||||
<span class="supported icon-checkmark-color" v-if="app.level === 300"
|
||||
v-tooltip.auto="t('settings', 'This app is supported via your current Nextcloud subscription.')">
|
||||
{{ t('settings', 'Supported') }}</span>
|
||||
<span class="official icon-checkmark" v-if="app.level === 200"
|
||||
v-tooltip.auto="t('settings', 'Official apps are developed by and within the community. They offer central functionality and are ready for production use.')">
|
||||
{{ t('settings', 'Official') }}</span>
|
||||
<app-score v-if="hasRating" :score="app.appstoreData.ratingOverall"></app-score>
|
||||
</div>
|
||||
|
||||
<div class="app-author" v-if="author">
|
||||
{{ t('settings', 'by') }}
|
||||
<span v-for="(a, index) in author">
|
||||
<a v-if="a['@attributes'] && a['@attributes']['homepage']" :href="a['@attributes']['homepage']">{{ a['@value'] }}</a><span v-else-if="a['@value']">{{ a['@value'] }}</span><span v-else>{{ a }}</span><span v-if="index+1 < author.length">, </span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="app-licence" v-if="licence">{{ licence }}</div>
|
||||
<div class="actions">
|
||||
<div class="actions-buttons">
|
||||
<input v-if="app.update" class="update primary" type="button" :value="t('settings', 'Update to {version}', {version: app.update})" v-on:click="update(app.id)" :disabled="installing || loading(app.id)"/>
|
||||
<input v-if="app.canUnInstall" class="uninstall" type="button" :value="t('settings', 'Remove')" v-on:click="remove(app.id)" :disabled="installing || loading(app.id)"/>
|
||||
<input v-if="app.active" class="enable" type="button" :value="t('settings','Disable')" v-on:click="disable(app.id)" :disabled="installing || loading(app.id)" />
|
||||
<input v-if="!app.active && (app.canInstall || app.isCompatible)" class="enable primary" type="button" :value="enableButtonText" v-on:click="enable(app.id)" v-tooltip.auto="enableButtonTooltip" :disabled="!app.canInstall || installing || loading(app.id)" />
|
||||
<input v-else-if="!app.active" class="enable force" type="button" :value="forceEnableButtonText" v-on:click="forceEnable(app.id)" v-tooltip.auto="forceEnableButtonTooltip" :disabled="installing || loading(app.id)" />
|
||||
</div>
|
||||
<div class="app-groups">
|
||||
<div class="groups-enable" v-if="app.active && canLimitToGroups(app)">
|
||||
<input type="checkbox" :value="app.id" v-model="groupCheckedAppsData" v-on:change="setGroupLimit" class="groups-enable__checkbox checkbox" :id="prefix('groups_enable', app.id)">
|
||||
<label :for="prefix('groups_enable', app.id)">{{ t('settings', 'Limit to groups') }}</label>
|
||||
<input type="hidden" class="group_select" :title="t('settings', 'All')" value="">
|
||||
<multiselect v-if="isLimitedToGroups(app)" :options="groups" :value="appGroups" @select="addGroupLimitation" @remove="removeGroupLimitation" :options-limit="5"
|
||||
:placeholder="t('settings', 'Limit app usage to groups')"
|
||||
label="name" track-by="id" class="multiselect-vue"
|
||||
:multiple="true" :close-on-select="false"
|
||||
:tag-width="60" @search-change="asyncFindGroup">
|
||||
<span slot="noResult">{{t('settings', 'No results')}}</span>
|
||||
</multiselect>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="app-dependencies">
|
||||
<li v-if="app.missingMinOwnCloudVersion">{{ t('settings', 'This app has no minimum Nextcloud version assigned. This will be an error in the future.') }}</li>
|
||||
<li v-if="app.missingMaxOwnCloudVersion">{{ t('settings', 'This app has no maximum Nextcloud version assigned. This will be an error in the future.') }}</li>
|
||||
<li v-if="!app.canInstall">
|
||||
{{ t('settings', 'This app cannot be installed because the following dependencies are not fulfilled:') }}
|
||||
<ul class="missing-dependencies">
|
||||
<li v-for="dep in app.missingDependencies">{{ dep }}</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p class="documentation">
|
||||
<a class="appslink" :href="appstoreUrl" v-if="!app.internal" target="_blank" rel="noreferrer noopener">{{ t('settings', 'View in store')}} ↗</a>
|
||||
|
||||
<a class="appslink" v-if="app.website" :href="app.website" target="_blank" rel="noreferrer noopener">{{ t('settings', 'Visit website') }} ↗</a>
|
||||
<a class="appslink" v-if="app.bugs" :href="app.bugs" target="_blank" rel="noreferrer noopener">{{ t('settings', 'Report a bug') }} ↗</a>
|
||||
|
||||
<a class="appslink" v-if="app.documentation && app.documentation.user" :href="app.documentation.user" target="_blank" rel="noreferrer noopener">{{ t('settings', 'User documentation') }} ↗</a>
|
||||
<a class="appslink" v-if="app.documentation && app.documentation.admin" :href="app.documentation.admin" target="_blank" rel="noreferrer noopener">{{ t('settings', 'Admin documentation') }} ↗</a>
|
||||
<a class="appslink" v-if="app.documentation && app.documentation.developer" :href="app.documentation.developer" target="_blank" rel="noreferrer noopener">{{ t('settings', 'Developer documentation') }} ↗</a>
|
||||
</p>
|
||||
|
||||
<div class="app-description" v-html="renderMarkdown"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Multiselect } from 'nextcloud-vue';
|
||||
import marked from 'marked';
|
||||
import dompurify from 'dompurify'
|
||||
|
||||
import AppScore from './appList/appScore';
|
||||
import AppManagement from './appManagement';
|
||||
import prefix from './prefixMixin';
|
||||
import SvgFilterMixin from './svgFilterMixin';
|
||||
|
||||
export default {
|
||||
mixins: [AppManagement, prefix, SvgFilterMixin],
|
||||
name: 'appDetails',
|
||||
props: ['category', 'app'],
|
||||
components: {
|
||||
Multiselect,
|
||||
AppScore
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
groupCheckedAppsData: false,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.app.groups.length > 0) {
|
||||
this.groupCheckedAppsData = true;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
appstoreUrl() {
|
||||
return `https://apps.nextcloud.com/apps/${this.app.id}`;
|
||||
},
|
||||
licence() {
|
||||
if (this.app.licence) {
|
||||
return t('settings', '{license}-licensed', { license: ('' + this.app.licence).toUpperCase() } );
|
||||
}
|
||||
return null;
|
||||
},
|
||||
hasRating() {
|
||||
return this.app.appstoreData && this.app.appstoreData.ratingNumOverall > 5;
|
||||
},
|
||||
author() {
|
||||
if (typeof this.app.author === 'string') {
|
||||
return [
|
||||
{
|
||||
'@value': this.app.author
|
||||
}
|
||||
]
|
||||
}
|
||||
if (this.app.author['@value']) {
|
||||
return [this.app.author];
|
||||
}
|
||||
return this.app.author;
|
||||
},
|
||||
appGroups() {
|
||||
return this.app.groups.map(group => {return {id: group, name: group}});
|
||||
},
|
||||
groups() {
|
||||
return this.$store.getters.getGroups
|
||||
.filter(group => group.id !== 'disabled')
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
},
|
||||
renderMarkdown() {
|
||||
var renderer = new marked.Renderer();
|
||||
renderer.link = function(href, title, text) {
|
||||
try {
|
||||
var prot = decodeURIComponent(unescape(href))
|
||||
.replace(/[^\w:]/g, '')
|
||||
.toLowerCase();
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (prot.indexOf('http:') !== 0 && prot.indexOf('https:') !== 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var out = '<a href="' + href + '" rel="noreferrer noopener"';
|
||||
if (title) {
|
||||
out += ' title="' + title + '"';
|
||||
}
|
||||
out += '>' + text + '</a>';
|
||||
return out;
|
||||
};
|
||||
renderer.image = function(href, title, text) {
|
||||
if (text) {
|
||||
return text;
|
||||
}
|
||||
return title;
|
||||
};
|
||||
renderer.blockquote = function(quote) {
|
||||
return quote;
|
||||
};
|
||||
return dompurify.sanitize(
|
||||
marked(this.app.description.trim(), {
|
||||
renderer: renderer,
|
||||
gfm: false,
|
||||
highlight: false,
|
||||
tables: false,
|
||||
breaks: false,
|
||||
pedantic: false,
|
||||
sanitize: true,
|
||||
smartLists: true,
|
||||
smartypants: false
|
||||
}),
|
||||
{
|
||||
SAFE_FOR_JQUERY: true,
|
||||
ALLOWED_TAGS: [
|
||||
'strong',
|
||||
'p',
|
||||
'a',
|
||||
'ul',
|
||||
'ol',
|
||||
'li',
|
||||
'em',
|
||||
'del',
|
||||
'blockquote'
|
||||
]
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.force {
|
||||
background: var(--color-main-background);
|
||||
border-color: var(--color-error);
|
||||
color: var(--color-error);
|
||||
}
|
||||
.force:hover,
|
||||
.force:active {
|
||||
background: var(--color-error);
|
||||
border-color: var(--color-error) !important;
|
||||
color: var(--color-main-background);
|
||||
}
|
||||
</style>
|
|
@ -1,201 +0,0 @@
|
|||
<!--
|
||||
- @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
|
||||
-
|
||||
- @author Julius Härtl <jus@bitgrid.net>
|
||||
-
|
||||
- @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-content-inner">
|
||||
<div id="apps-list" class="apps-list" :class="{installed: (useBundleView || useListView), store: useAppStoreView}">
|
||||
<template v-if="useListView">
|
||||
<transition-group name="app-list" tag="div" class="apps-list-container">
|
||||
<AppItem v-for="app in apps"
|
||||
:key="app.id"
|
||||
:app="app"
|
||||
:category="category" />
|
||||
</transition-group>
|
||||
</template>
|
||||
<transition-group v-if="useBundleView"
|
||||
name="app-list"
|
||||
tag="div"
|
||||
class="apps-list-container">
|
||||
<template v-for="bundle in bundles">
|
||||
<div :key="bundle.id" class="apps-header">
|
||||
<div class="app-image" />
|
||||
<h2>{{ bundle.name }} <input type="button" :value="bundleToggleText(bundle.id)" @click="toggleBundle(bundle.id)"></h2>
|
||||
<div class="app-version" />
|
||||
<div class="app-level" />
|
||||
<div class="app-groups" />
|
||||
<div class="actions">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<AppItem v-for="app in bundleApps(bundle.id)"
|
||||
:key="bundle.id + app.id"
|
||||
:app="app"
|
||||
:category="category" />
|
||||
</template>
|
||||
</transition-group>
|
||||
<template v-if="useAppStoreView">
|
||||
<AppItem v-for="app in apps"
|
||||
:key="app.id"
|
||||
:app="app"
|
||||
:category="category"
|
||||
:list-view="false" />
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div id="apps-list-search" class="apps-list installed">
|
||||
<div class="apps-list-container">
|
||||
<template v-if="search !== '' && searchApps.length > 0">
|
||||
<div class="section">
|
||||
<div />
|
||||
<td colspan="5">
|
||||
<h2>{{ t('settings', 'Results from other categories') }}</h2>
|
||||
</td>
|
||||
</div>
|
||||
<AppItem v-for="app in searchApps"
|
||||
:key="app.id"
|
||||
:app="app"
|
||||
:category="category"
|
||||
:list-view="true" />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="search !== '' && !loading && searchApps.length === 0 && apps.length === 0" id="apps-list-empty" class="emptycontent emptycontent-search">
|
||||
<div id="app-list-empty-icon" class="icon-settings-dark" />
|
||||
<h2>{{ t('settings', 'No apps found for your version') }}</h2>
|
||||
</div>
|
||||
|
||||
<div id="searchresults" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AppItem from './AppList/AppItem'
|
||||
import PrefixMixin from './PrefixMixin'
|
||||
|
||||
export default {
|
||||
name: 'AppList',
|
||||
components: {
|
||||
AppItem
|
||||
},
|
||||
mixins: [PrefixMixin],
|
||||
props: ['category', 'app', 'search'],
|
||||
computed: {
|
||||
loading() {
|
||||
return this.$store.getters.loading('list')
|
||||
},
|
||||
apps() {
|
||||
let apps = this.$store.getters.getAllApps
|
||||
.filter(app => app.name.toLowerCase().search(this.search.toLowerCase()) !== -1)
|
||||
.sort(function(a, b) {
|
||||
const sortStringA = '' + (a.active ? 0 : 1) + (a.update ? 0 : 1) + a.name
|
||||
const sortStringB = '' + (b.active ? 0 : 1) + (b.update ? 0 : 1) + b.name
|
||||
return OC.Util.naturalSortCompare(sortStringA, sortStringB)
|
||||
})
|
||||
|
||||
if (this.category === 'installed') {
|
||||
return apps.filter(app => app.installed)
|
||||
}
|
||||
if (this.category === 'enabled') {
|
||||
return apps.filter(app => app.active && app.installed)
|
||||
}
|
||||
if (this.category === 'disabled') {
|
||||
return apps.filter(app => !app.active && app.installed)
|
||||
}
|
||||
if (this.category === 'app-bundles') {
|
||||
return apps.filter(app => app.bundles)
|
||||
}
|
||||
if (this.category === 'updates') {
|
||||
return apps.filter(app => app.update)
|
||||
}
|
||||
// filter app store categories
|
||||
return apps.filter(app => {
|
||||
return app.appstore && app.category !== undefined
|
||||
&& (app.category === this.category || app.category.indexOf(this.category) > -1)
|
||||
})
|
||||
},
|
||||
bundles() {
|
||||
return this.$store.getters.getServerData.bundles.filter(bundle => this.bundleApps(bundle.id).length > 0)
|
||||
},
|
||||
bundleApps() {
|
||||
return function(bundle) {
|
||||
return this.$store.getters.getAllApps
|
||||
.filter(app => app.bundleId === bundle)
|
||||
}
|
||||
},
|
||||
searchApps() {
|
||||
if (this.search === '') {
|
||||
return []
|
||||
}
|
||||
return this.$store.getters.getAllApps
|
||||
.filter(app => {
|
||||
if (app.name.toLowerCase().search(this.search.toLowerCase()) !== -1) {
|
||||
return (!this.apps.find(_app => _app.id === app.id))
|
||||
}
|
||||
return false
|
||||
})
|
||||
},
|
||||
useAppStoreView() {
|
||||
return !this.useListView && !this.useBundleView
|
||||
},
|
||||
useListView() {
|
||||
return (this.category === 'installed' || this.category === 'enabled' || this.category === 'disabled' || this.category === 'updates')
|
||||
},
|
||||
useBundleView() {
|
||||
return (this.category === 'app-bundles')
|
||||
},
|
||||
allBundlesEnabled() {
|
||||
let self = this
|
||||
return function(id) {
|
||||
return self.bundleApps(id).filter(app => !app.active).length === 0
|
||||
}
|
||||
},
|
||||
bundleToggleText() {
|
||||
let self = this
|
||||
return function(id) {
|
||||
if (self.allBundlesEnabled(id)) {
|
||||
return t('settings', 'Disable all')
|
||||
}
|
||||
return t('settings', 'Enable all')
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleBundle(id) {
|
||||
if (this.allBundlesEnabled(id)) {
|
||||
return this.disableBundle(id)
|
||||
}
|
||||
return this.enableBundle(id)
|
||||
},
|
||||
enableBundle(id) {
|
||||
let apps = this.bundleApps(id).map(app => app.id)
|
||||
this.$store.dispatch('enableApp', { appId: apps, groups: [] })
|
||||
.catch((error) => { console.error(error); OC.Notification.show(error) })
|
||||
},
|
||||
disableBundle(id) {
|
||||
let apps = this.bundleApps(id).map(app => app.id)
|
||||
this.$store.dispatch('disableApp', { appId: apps, groups: [] })
|
||||
.catch((error) => { OC.Notification.show(error) })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,136 +0,0 @@
|
|||
<!--
|
||||
- @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
|
||||
-
|
||||
- @author Julius Härtl <jus@bitgrid.net>
|
||||
-
|
||||
- @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 class="section" v-bind:class="{ selected: isSelected }" v-on:click="showAppDetails">
|
||||
<div class="app-image app-image-icon" v-on:click="showAppDetails">
|
||||
<div v-if="(listView && !app.preview) || (!listView && !app.screenshot)" class="icon-settings-dark"></div>
|
||||
|
||||
<svg v-if="listView && app.preview" width="32" height="32" viewBox="0 0 32 32">
|
||||
<defs><filter :id="filterId"><feColorMatrix in="SourceGraphic" type="matrix" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"></feColorMatrix></filter></defs>
|
||||
<image x="0" y="0" width="32" height="32" preserveAspectRatio="xMinYMin meet" :filter="filterUrl" :xlink:href="app.preview" class="app-icon"></image>
|
||||
</svg>
|
||||
|
||||
<img v-if="!listView && app.screenshot" :src="app.screenshot" width="100%" />
|
||||
</div>
|
||||
<div class="app-name" v-on:click="showAppDetails">
|
||||
{{ app.name }}
|
||||
</div>
|
||||
<div class="app-summary" v-if="!listView">{{ app.summary }}</div>
|
||||
<div class="app-version" v-if="listView">
|
||||
<span v-if="app.version">{{ app.version }}</span>
|
||||
<span v-else-if="app.appstoreData.releases[0].version">{{ app.appstoreData.releases[0].version }}</span>
|
||||
</div>
|
||||
|
||||
<div class="app-level">
|
||||
<span class="supported icon-checkmark-color" v-if="app.level === 300"
|
||||
v-tooltip.auto="t('settings', 'This app is supported via your current Nextcloud subscription.')">
|
||||
{{ t('settings', 'Supported') }}</span>
|
||||
<span class="official icon-checkmark" v-if="app.level === 200"
|
||||
v-tooltip.auto="t('settings', 'Official apps are developed by and within the community. They offer central functionality and are ready for production use.')">
|
||||
{{ t('settings', 'Official') }}</span>
|
||||
<app-score v-if="hasRating && !listView" :score="app.score"></app-score>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<div class="warning" v-if="app.error">{{ app.error }}</div>
|
||||
<div class="icon icon-loading-small" v-if="loading(app.id)"></div>
|
||||
<input v-if="app.update" class="update primary" type="button" :value="t('settings', 'Update to {update}', {update:app.update})" v-on:click.stop="update(app.id)" :disabled="installing || loading(app.id)" />
|
||||
<input v-if="app.canUnInstall" class="uninstall" type="button" :value="t('settings', 'Remove')" v-on:click.stop="remove(app.id)" :disabled="installing || loading(app.id)" />
|
||||
<input v-if="app.active" class="enable" type="button" :value="t('settings','Disable')" v-on:click.stop="disable(app.id)" :disabled="installing || loading(app.id)" />
|
||||
<input v-if="!app.active && (app.canInstall || app.isCompatible)" class="enable" type="button" :value="enableButtonText" v-on:click.stop="enable(app.id)" v-tooltip.auto="enableButtonTooltip" :disabled="!app.canInstall || installing || loading(app.id)" />
|
||||
<input v-else-if="!app.active" class="enable force" type="button" :value="forceEnableButtonText" v-on:click.stop="forceEnable(app.id)" v-tooltip.auto="forceEnableButtonTooltip" :disabled="installing || loading(app.id)" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AppScore from './appScore';
|
||||
import AppManagement from '../appManagement';
|
||||
import SvgFilterMixin from '../svgFilterMixin';
|
||||
|
||||
export default {
|
||||
name: 'appItem',
|
||||
mixins: [AppManagement, SvgFilterMixin],
|
||||
props: {
|
||||
app: {},
|
||||
category: {},
|
||||
listView: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'$route.params.id': function (id) {
|
||||
this.isSelected = (this.app.id === id);
|
||||
}
|
||||
},
|
||||
components: {
|
||||
AppScore,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isSelected: false,
|
||||
scrolled: false,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.isSelected = (this.app.id === this.$route.params.id);
|
||||
},
|
||||
computed: {
|
||||
hasRating() {
|
||||
return this.app.appstoreData && this.app.appstoreData.ratingNumOverall > 5;
|
||||
},
|
||||
},
|
||||
watchers: {
|
||||
|
||||
},
|
||||
methods: {
|
||||
showAppDetails(event) {
|
||||
if (event.currentTarget.tagName === 'INPUT' || event.currentTarget.tagName === 'A') {
|
||||
return;
|
||||
}
|
||||
this.$router.push({
|
||||
name: 'apps-details',
|
||||
params: {category: this.category, id: this.app.id}
|
||||
});
|
||||
},
|
||||
prefix(prefix, content) {
|
||||
return prefix + '_' + content;
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.force {
|
||||
background: var(--color-main-background);
|
||||
border-color: var(--color-error);
|
||||
color: var(--color-error);
|
||||
}
|
||||
.force:hover,
|
||||
.force:active {
|
||||
background: var(--color-error);
|
||||
border-color: var(--color-error) !important;
|
||||
color: var(--color-main-background);
|
||||
}
|
||||
</style>
|
|
@ -1,38 +0,0 @@
|
|||
<!--
|
||||
- @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
|
||||
-
|
||||
- @author Julius Härtl <jus@bitgrid.net>
|
||||
-
|
||||
- @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>
|
||||
<img :src="scoreImage" class="app-score-image">
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'AppScore',
|
||||
props: ['score'],
|
||||
computed: {
|
||||
scoreImage() {
|
||||
let score = Math.round(this.score * 10)
|
||||
let imageName = 'rating/s' + score + '.svg'
|
||||
return OC.imagePath('core', imageName)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,138 +0,0 @@
|
|||
<!--
|
||||
- @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
|
||||
-
|
||||
- @author Julius Härtl <jus@bitgrid.net>
|
||||
-
|
||||
- @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/>.
|
||||
-
|
||||
-->
|
||||
|
||||
<script>
|
||||
export default {
|
||||
mounted() {
|
||||
if (this.app.groups.length > 0) {
|
||||
this.groupCheckedAppsData = true;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
appGroups() {
|
||||
return this.app.groups.map(group => {return {id: group, name: group}});
|
||||
},
|
||||
loading() {
|
||||
let self = this;
|
||||
return function(id) {
|
||||
return self.$store.getters.loading(id);
|
||||
}
|
||||
},
|
||||
installing() {
|
||||
return this.$store.getters.loading('install');
|
||||
},
|
||||
enableButtonText() {
|
||||
if (this.app.needsDownload) {
|
||||
return t('settings', 'Download and enable');
|
||||
}
|
||||
return t('settings', 'Enable');
|
||||
},
|
||||
forceEnableButtonText() {
|
||||
if (this.app.needsDownload) {
|
||||
return t('settings', 'Enable untested app');
|
||||
}
|
||||
return t('settings', 'Enable untested app');
|
||||
},
|
||||
enableButtonTooltip() {
|
||||
if (this.app.needsDownload) {
|
||||
return t('settings','The app will be downloaded from the app store');
|
||||
}
|
||||
return false;
|
||||
},
|
||||
forceEnableButtonTooltip() {
|
||||
const base = t('settings', 'This app is not marked as compatible with your Nextcloud version. If you continue you will still be able to install the app. Note that the app might not work as expected.');
|
||||
if (this.app.needsDownload) {
|
||||
return base + ' ' + t('settings','The app will be downloaded from the app store');
|
||||
}
|
||||
return base;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
asyncFindGroup(query) {
|
||||
return this.$store.dispatch('getGroups', {search: query, limit: 5, offset: 0});
|
||||
},
|
||||
isLimitedToGroups(app) {
|
||||
if (this.app.groups.length || this.groupCheckedAppsData) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
setGroupLimit: function() {
|
||||
if (!this.groupCheckedAppsData) {
|
||||
this.$store.dispatch('enableApp', {appId: this.app.id, groups: []});
|
||||
}
|
||||
},
|
||||
canLimitToGroups(app) {
|
||||
if (app.types && app.types.includes('filesystem')
|
||||
|| app.types.includes('prelogin')
|
||||
|| app.types.includes('authentication')
|
||||
|| app.types.includes('logging')
|
||||
|| app.types.includes('prevent_group_restriction')) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
addGroupLimitation(group) {
|
||||
let groups = this.app.groups.concat([]).concat([group.id]);
|
||||
this.$store.dispatch('enableApp', { appId: this.app.id, groups: groups});
|
||||
},
|
||||
removeGroupLimitation(group) {
|
||||
let currentGroups = this.app.groups.concat([]);
|
||||
let index = currentGroups.indexOf(group.id);
|
||||
if (index > -1) {
|
||||
currentGroups.splice(index, 1);
|
||||
}
|
||||
this.$store.dispatch('enableApp', { appId: this.app.id, groups: currentGroups});
|
||||
},
|
||||
forceEnable(appId) {
|
||||
this.$store.dispatch('forceEnableApp', { appId: appId, groups: [] })
|
||||
.then((response) => { OC.Settings.Apps.rebuildNavigation(); })
|
||||
.catch((error) => { OC.Notification.show(error)});
|
||||
},
|
||||
enable(appId) {
|
||||
this.$store.dispatch('enableApp', { appId: appId, groups: [] })
|
||||
.then((response) => { OC.Settings.Apps.rebuildNavigation(); })
|
||||
.catch((error) => { OC.Notification.show(error)});
|
||||
},
|
||||
disable(appId) {
|
||||
this.$store.dispatch('disableApp', { appId: appId })
|
||||
.then((response) => { OC.Settings.Apps.rebuildNavigation(); })
|
||||
.catch((error) => { OC.Notification.show(error)});
|
||||
},
|
||||
remove(appId) {
|
||||
this.$store.dispatch('uninstallApp', { appId: appId })
|
||||
.then((response) => { OC.Settings.Apps.rebuildNavigation(); })
|
||||
.catch((error) => { OC.Notification.show(error)});
|
||||
},
|
||||
install(appId) {
|
||||
this.$store.dispatch('enableApp', { appId: appId })
|
||||
.then((response) => { OC.Settings.Apps.rebuildNavigation(); })
|
||||
.catch((error) => { OC.Notification.show(error)});
|
||||
},
|
||||
update(appId) {
|
||||
this.$store.dispatch('updateApp', { appId: appId })
|
||||
.then((response) => { OC.Settings.Apps.rebuildNavigation(); })
|
||||
.catch((error) => { OC.Notification.show(error)});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,32 +0,0 @@
|
|||
<!--
|
||||
- @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
|
||||
-
|
||||
- @author Julius Härtl <jus@bitgrid.net>
|
||||
-
|
||||
- @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/>.
|
||||
-
|
||||
-->
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'PrefixMixin',
|
||||
methods: {
|
||||
prefix(prefix, content) {
|
||||
return prefix + '_' + content
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,40 +0,0 @@
|
|||
<!--
|
||||
- @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
|
||||
-
|
||||
- @author Julius Härtl <jus@bitgrid.net>
|
||||
-
|
||||
- @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/>.
|
||||
-
|
||||
-->
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SvgFilterMixin',
|
||||
data() {
|
||||
return {
|
||||
filterId: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filterUrl() {
|
||||
return `url(#${this.filterId})`
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.filterId = 'invertIconApps' + Math.floor((Math.random() * 100)) + new Date().getSeconds() + new Date().getMilliseconds()
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,429 +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-content" class="user-list-grid" v-on:scroll.passive="onScroll">
|
||||
<div class="row" id="grid-header" :class="{'sticky': scrolled && !showConfig.showNewUserForm}">
|
||||
<div id="headerAvatar" class="avatar"></div>
|
||||
<div id="headerName" class="name">{{ t('settings', 'Username') }}</div>
|
||||
<div id="headerDisplayName" class="displayName">{{ t('settings', 'Display name') }}</div>
|
||||
<div id="headerPassword" class="password">{{ t('settings', 'Password') }}</div>
|
||||
<div id="headerAddress" class="mailAddress">{{ t('settings', 'Email') }}</div>
|
||||
<div id="headerGroups" class="groups">{{ t('settings', 'Groups') }}</div>
|
||||
<div id="headerSubAdmins" class="subadmins"
|
||||
v-if="subAdminsGroups.length>0 && settings.isAdmin">{{ t('settings', 'Group admin for') }}</div>
|
||||
<div id="headerQuota" class="quota">{{ t('settings', 'Quota') }}</div>
|
||||
<div id="headerLanguages" class="languages"
|
||||
v-if="showConfig.showLanguages">{{ t('settings', 'Language') }}</div>
|
||||
<div class="headerStorageLocation storageLocation"
|
||||
v-if="showConfig.showStoragePath">{{ t('settings', 'Storage location') }}</div>
|
||||
<div class="headerUserBackend userBackend"
|
||||
v-if="showConfig.showUserBackend">{{ t('settings', 'User backend') }}</div>
|
||||
<div class="headerLastLogin lastLogin"
|
||||
v-if="showConfig.showLastLogin">{{ t('settings', 'Last login') }}</div>
|
||||
<div class="userActions"></div>
|
||||
</div>
|
||||
|
||||
<form class="row" id="new-user" v-show="showConfig.showNewUserForm"
|
||||
v-on:submit.prevent="createUser" :disabled="loading.all"
|
||||
:class="{'sticky': scrolled && showConfig.showNewUserForm}">
|
||||
<div :class="loading.all?'icon-loading-small':'icon-add'"></div>
|
||||
<div class="name">
|
||||
<input id="newusername" type="text" required v-model="newUser.id"
|
||||
:placeholder="this.settings.newUserGenerateUserID
|
||||
? t('settings', 'Will be autogenerated')
|
||||
: t('settings', 'Username')"
|
||||
name="username" autocomplete="off" autocapitalize="none"
|
||||
autocorrect="off" ref="newusername" pattern="[a-zA-Z0-9 _\.@\-']+"
|
||||
:disabled="this.settings.newUserGenerateUserID">
|
||||
</div>
|
||||
<div class="displayName">
|
||||
<input id="newdisplayname" type="text" v-model="newUser.displayName"
|
||||
:placeholder="t('settings', 'Display name')" name="displayname"
|
||||
autocomplete="off" autocapitalize="none" autocorrect="off">
|
||||
</div>
|
||||
<div class="password">
|
||||
<input id="newuserpassword" type="password" v-model="newUser.password"
|
||||
:required="newUser.mailAddress===''" ref="newuserpassword"
|
||||
:placeholder="t('settings', 'Password')" name="password"
|
||||
autocomplete="new-password" autocapitalize="none" autocorrect="off"
|
||||
:minlength="minPasswordLength">
|
||||
</div>
|
||||
<div class="mailAddress">
|
||||
<input id="newemail" type="email" v-model="newUser.mailAddress"
|
||||
:required="newUser.password==='' || this.settings.newUserRequireEmail"
|
||||
:placeholder="t('settings', 'Email')" name="email"
|
||||
autocomplete="off" autocapitalize="none" autocorrect="off">
|
||||
</div>
|
||||
<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"
|
||||
: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-width="60" @tag="createGroup">
|
||||
<!-- If user is not admin, he is a subadmin.
|
||||
Subadmins can't create users outside their groups
|
||||
Therefore, empty select is forbidden -->
|
||||
<span slot="noResult">{{t('settings', 'No results')}}</span>
|
||||
</multiselect>
|
||||
</div>
|
||||
<div class="subadmins" v-if="subAdminsGroups.length>0 && settings.isAdmin">
|
||||
<multiselect :options="subAdminsGroups" v-model="newUser.subAdminsGroups"
|
||||
:placeholder="t('settings', 'Set user as admin for')"
|
||||
label="name" track-by="id" class="multiselect-vue"
|
||||
:multiple="true" :close-on-select="false" :tag-width="60">
|
||||
<span slot="noResult">{{t('settings', 'No results')}}</span>
|
||||
</multiselect>
|
||||
</div>
|
||||
<div class="quota">
|
||||
<multiselect :options="quotaOptions" v-model="newUser.quota"
|
||||
:placeholder="t('settings', 'Select user quota')"
|
||||
label="label" track-by="id" class="multiselect-vue"
|
||||
:allowEmpty="false" :taggable="true"
|
||||
@tag="validateQuota" >
|
||||
</multiselect>
|
||||
</div>
|
||||
<div class="languages" v-if="showConfig.showLanguages">
|
||||
<multiselect :options="languages" v-model="newUser.language"
|
||||
:placeholder="t('settings', 'Default language')"
|
||||
label="name" track-by="code" class="multiselect-vue"
|
||||
:allowEmpty="false" group-values="languages" group-label="label">
|
||||
</multiselect>
|
||||
</div>
|
||||
<div class="storageLocation" v-if="showConfig.showStoragePath"></div>
|
||||
<div class="userBackend" v-if="showConfig.showUserBackend"></div>
|
||||
<div class="lastLogin" v-if="showConfig.showLastLogin"></div>
|
||||
<div class="userActions">
|
||||
<input type="submit" id="newsubmit" class="button primary icon-checkmark-white has-tooltip"
|
||||
value="" :title="t('settings', 'Add a new user')">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<user-row v-for="(user, key) in filteredUsers" :user="user" :key="key" :settings="settings" :showConfig="showConfig"
|
||||
:groups="groups" :subAdminsGroups="subAdminsGroups" :quotaOptions="quotaOptions" :languages="languages"
|
||||
:externalActions="externalActions" />
|
||||
<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"></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>
|
||||
|
||||
<script>
|
||||
import userRow from './userList/userRow';
|
||||
import { Multiselect } from 'nextcloud-vue'
|
||||
import InfiniteLoading from 'vue-infinite-loading';
|
||||
import Vue from 'vue';
|
||||
|
||||
const unlimitedQuota = {
|
||||
id: 'none',
|
||||
label: t('settings', 'Unlimited')
|
||||
}
|
||||
const defaultQuota = {
|
||||
id: 'default',
|
||||
label: t('settings', 'Default quota')
|
||||
}
|
||||
const newUser = {
|
||||
id: '',
|
||||
displayName: '',
|
||||
password: '',
|
||||
mailAddress: '',
|
||||
groups: [],
|
||||
subAdminsGroups: [],
|
||||
quota: defaultQuota,
|
||||
language: {
|
||||
code: 'en',
|
||||
name: t('settings', 'Default language')
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'userList',
|
||||
props: ['users', 'showConfig', 'selectedGroup', 'externalActions'],
|
||||
components: {
|
||||
userRow,
|
||||
Multiselect,
|
||||
InfiniteLoading
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
unlimitedQuota,
|
||||
defaultQuota,
|
||||
loading: {
|
||||
all: false,
|
||||
groups: false
|
||||
},
|
||||
scrolled: false,
|
||||
searchQuery: '',
|
||||
newUser: Object.assign({}, newUser)
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
if (!this.settings.canChangePassword) {
|
||||
OC.Notification.showTemporary(t('settings', 'Password change is disabled because the master key is disabled'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset and init new user form
|
||||
*/
|
||||
this.resetForm()
|
||||
|
||||
/**
|
||||
* Register search
|
||||
*/
|
||||
this.userSearch = new OCA.Search(this.search, this.resetSearch);
|
||||
},
|
||||
computed: {
|
||||
settings() {
|
||||
return this.$store.getters.getServerData;
|
||||
},
|
||||
filteredUsers() {
|
||||
if (this.selectedGroup === 'disabled') {
|
||||
let disabledUsers = this.users.filter(user => user.enabled === false);
|
||||
if (disabledUsers.length === 0 && this.$refs.infiniteLoading && this.$refs.infiniteLoading.isComplete) {
|
||||
// disabled group is empty, redirection to all users
|
||||
this.$router.push({ name: 'users' });
|
||||
this.$refs.infiniteLoading.stateChanger.reset()
|
||||
}
|
||||
return disabledUsers;
|
||||
}
|
||||
if (!this.settings.isAdmin) {
|
||||
// we don't want subadmins to edit themselves
|
||||
return this.users.filter(user => user.enabled !== false && user.id !== OC.getCurrentUser().uid);
|
||||
}
|
||||
return this.users.filter(user => user.enabled !== false);
|
||||
},
|
||||
groups() {
|
||||
// data provided php side + remove the disabled group
|
||||
return this.$store.getters.getGroups
|
||||
.filter(group => group.id !== 'disabled')
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
},
|
||||
canAddGroups() {
|
||||
// disabled if no permission to add new users to group
|
||||
return this.groups.map(group => {
|
||||
// clone object because we don't want
|
||||
// to edit the original groups
|
||||
group = Object.assign({}, group);
|
||||
group.$isDisabled = group.canAdd === false;
|
||||
return group;
|
||||
});
|
||||
},
|
||||
subAdminsGroups() {
|
||||
// data provided php side
|
||||
return this.$store.getters.getSubadminGroups;
|
||||
},
|
||||
quotaOptions() {
|
||||
// convert the preset array into objects
|
||||
let quotaPreset = this.settings.quotaPreset.reduce((acc, cur) => acc.concat({id: cur, label: cur}), []);
|
||||
// add default presets
|
||||
quotaPreset.unshift(this.unlimitedQuota);
|
||||
quotaPreset.unshift(this.defaultQuota);
|
||||
return quotaPreset;
|
||||
},
|
||||
minPasswordLength() {
|
||||
return this.$store.getters.getPasswordPolicyMinLength;
|
||||
},
|
||||
usersOffset() {
|
||||
return this.$store.getters.getUsersOffset;
|
||||
},
|
||||
usersLimit() {
|
||||
return this.$store.getters.getUsersLimit;
|
||||
},
|
||||
usersCount() {
|
||||
return this.users.length
|
||||
},
|
||||
|
||||
/* LANGUAGES */
|
||||
languages() {
|
||||
return Array(
|
||||
{
|
||||
label: t('settings', 'Common languages'),
|
||||
languages: this.settings.languages.commonlanguages
|
||||
},
|
||||
{
|
||||
label: t('settings', 'All languages'),
|
||||
languages: this.settings.languages.languages
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// watch url change and group select
|
||||
selectedGroup: function (val, old) {
|
||||
this.$store.commit('resetUsers');
|
||||
this.$refs.infiniteLoading.stateChanger.reset()
|
||||
this.setNewUserDefaultGroup(val);
|
||||
},
|
||||
|
||||
// make sure the infiniteLoading state is changed if we manually
|
||||
// add/remove data from the store
|
||||
usersCount: function(val, old) {
|
||||
// deleting the last user, reset the list
|
||||
if (val === 0 && old === 1) {
|
||||
this.$refs.infiniteLoading.stateChanger.reset()
|
||||
// adding the first user, warn the infiniteLoader that
|
||||
// the list is not empty anymore (we don't fetch the newly
|
||||
// added user as we already have all the info we need)
|
||||
} else if (val === 1 && old === 0) {
|
||||
this.$refs.infiniteLoading.stateChanger.loaded()
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onScroll(event) {
|
||||
this.scrolled = event.target.scrollTo > 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Validate quota string to make sure it's a valid human file size
|
||||
*
|
||||
* @param {string} quota Quota in readable format '5 GB'
|
||||
* @returns {Object}
|
||||
*/
|
||||
validateQuota(quota) {
|
||||
// only used for new presets sent through @Tag
|
||||
let validQuota = OC.Util.computerFileSize(quota);
|
||||
if (validQuota !== null && validQuota >= 0) {
|
||||
// unify format output
|
||||
quota = OC.Util.humanFileSize(OC.Util.computerFileSize(quota));
|
||||
return this.newUser.quota = {id: quota, label: quota};
|
||||
}
|
||||
// Default is unlimited
|
||||
return this.newUser.quota = this.quotaOptions[0];
|
||||
},
|
||||
|
||||
infiniteHandler($state) {
|
||||
this.$store.dispatch('getUsers', {
|
||||
offset: this.usersOffset,
|
||||
limit: this.usersLimit,
|
||||
group: this.selectedGroup !== 'disabled' ? this.selectedGroup : '',
|
||||
search: this.searchQuery
|
||||
})
|
||||
.then((response) => { response ? $state.loaded() : $state.complete() });
|
||||
},
|
||||
|
||||
/* SEARCH */
|
||||
search(query) {
|
||||
this.searchQuery = query;
|
||||
this.$store.commit('resetUsers');
|
||||
this.$refs.infiniteLoading.stateChanger.reset()
|
||||
},
|
||||
resetSearch() {
|
||||
this.search('');
|
||||
},
|
||||
|
||||
resetForm() {
|
||||
// revert form to original state
|
||||
this.newUser = Object.assign({}, newUser);
|
||||
|
||||
/**
|
||||
* Init default language from server data. The use of this.settings
|
||||
* requires a computed variable, which break the v-model binding of the form,
|
||||
* this is a much easier solution than getter and setter on a computed var
|
||||
*/
|
||||
if (this.settings.defaultLanguage) {
|
||||
Vue.set(this.newUser.language, 'code', this.settings.defaultLanguage);
|
||||
}
|
||||
|
||||
/**
|
||||
* In case the user directly loaded the user list within a group
|
||||
* the watch won't be triggered. We need to initialize it.
|
||||
*/
|
||||
this.setNewUserDefaultGroup(this.selectedGroup);
|
||||
|
||||
this.loading.all = false;
|
||||
},
|
||||
createUser() {
|
||||
this.loading.all = true;
|
||||
this.$store.dispatch('addUser', {
|
||||
userid: this.newUser.id,
|
||||
password: this.newUser.password,
|
||||
displayName: this.newUser.displayName,
|
||||
email: this.newUser.mailAddress,
|
||||
groups: this.newUser.groups.map(group => group.id),
|
||||
subadmin: this.newUser.subAdminsGroups.map(group => group.id),
|
||||
quota: this.newUser.quota.id,
|
||||
language: this.newUser.language.code,
|
||||
})
|
||||
.then(() => {
|
||||
this.resetForm()
|
||||
this.$refs.newusername.focus();
|
||||
})
|
||||
.catch((error) => {
|
||||
this.loading.all = false;
|
||||
if (error.response && error.response.data && error.response.data.ocs && error.response.data.ocs.meta) {
|
||||
const statuscode = error.response.data.ocs.meta.statuscode
|
||||
if (statuscode === 102) {
|
||||
// wrong username
|
||||
this.$refs.newusername.focus();
|
||||
} else if (statuscode === 107) {
|
||||
// wrong password
|
||||
this.$refs.newuserpassword.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
setNewUserDefaultGroup(value) {
|
||||
if (value && value.length > 0) {
|
||||
// setting new user default group to the current selected one
|
||||
let currentGroup = this.groups.find(group => group.id === value);
|
||||
if (currentGroup) {
|
||||
this.newUser.groups = [currentGroup];
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
Loading…
Reference in New Issue