aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--settings/css/settings.scss19
-rw-r--r--settings/src/components/appDetails.vue242
-rw-r--r--settings/src/components/appList.vue68
-rw-r--r--settings/src/components/appList/appItem.vue66
-rw-r--r--settings/src/components/appList/appScore.vue38
-rw-r--r--settings/src/store/api.js2
-rw-r--r--settings/src/store/apps.js81
-rw-r--r--settings/src/views/Apps.vue29
8 files changed, 347 insertions, 198 deletions
diff --git a/settings/css/settings.scss b/settings/css/settings.scss
index b200a4932cb..14e502551f7 100644
--- a/settings/css/settings.scss
+++ b/settings/css/settings.scss
@@ -756,6 +756,23 @@ span.version {
opacity: .5;
}
+.app-settings-content {
+ #searchresults {
+ display: none;
+ }
+}
+#apps-list.store {
+ .app-name {
+ display: block;
+ }
+}
+
+#app-sidebar #app-details-view {
+ .app-description p {
+ margin-bottom: 10px;
+ }
+}
+
@media (min-width: 1601px) {
#apps-list .section {
width: 22%;
@@ -946,8 +963,6 @@ span.version {
height: 64px;
opacity: .1;
}
- position: relative;
- height: 100%;
display: flex;
flex-wrap: wrap;
align-content: flex-start;
diff --git a/settings/src/components/appDetails.vue b/settings/src/components/appDetails.vue
index d007555dd5b..9192c68a868 100644
--- a/settings/src/components/appDetails.vue
+++ b/settings/src/components/appDetails.vue
@@ -21,167 +21,123 @@
-->
<template>
- <div id="app-sidebar">
+ <div id="app-details-view" style="padding: 20px;">
<h2>{{ app.name }}</h2>
+ <img :src="app.preview" width="100%" />
+ <app-score v-if="app.ratingNumThresholdReached" :score="app.score"></app-score>
+ <div class="app-author">
+ {{ author }}
+ {{ licence }}
+ </div>
+ <div class="actions">
+ <div class="warning hidden"></div>
+ <input v-if="app.update" class="update" type="button" :value="t('settings', 'Update to %s', app.update)" />
+ <input v-if="app.canUnInstall" class="uninstall" type="button" :value="t('settings', 'Remove')" />
+ <input v-if="app.active" class="enable" type="button" :value="t('settings','Disable')" v-on:click="disable(app.id)" />
+ <input v-if="!app.active && !app.needsDownload" class="enable" type="button" :value="t('settings','Enable')" v-on:click="enable(app.id)" :disabled="!app.canInstall" />
+ <input v-if="!app.active && app.needsDownload" class="enable needs-download" type="button" :value="t('settings', 'Enable')" :disabled="!app.canInstall"/>
+ </div>
+ <p class="documentation">
+ <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.user" :href="app.documentation.user" target="_blank" rel="noreferrer noopener">{{ t('settings', 'User documentation') }} ↗</a>
+ <a class="appslink" v-if="app.documentation.admin" :href="app.documentation.admin" target="_blank" rel="noreferrer noopener">{{ t('settings', 'Admin documentation') }} ↗</a>
+ <a class="appslink" v-if="app.documentation.developer" :href="app.documentation.developer" target="_blank" rel="noreferrer noopener">{{ t('settings', 'Developer documentation') }} ↗</a>
+ </p>
+
+ <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>
+
+ <div class="app-description" v-html="renderMarkdown"></div>
</div>
</template>
<script>
-import userRow from './userList/userRow';
import Multiselect from 'vue-multiselect';
-import InfiniteLoading from 'vue-infinite-loading';
-import Vue from 'vue';
-
+import AppScore from './appList/appScore';
export default {
- name: 'userList',
- props: ['users', 'showConfig', 'selectedGroup'],
+ name: 'appDetails',
+ props: ['app'],
components: {
- userRow,
Multiselect,
- InfiniteLoading
- },
- data() {
- let unlimitedQuota = {id:'none', label:t('settings', 'Unlimited')},
- defaultQuota = {id:'default', label:t('settings', 'Default quota')};
- return {
- unlimitedQuota: unlimitedQuota,
- defaultQuota: defaultQuota,
- loading: false,
- scrolled: false,
- newUser: {
- id:'',
- displayName:'',
- password:'',
- mailAddress:'',
- groups: [],
- subAdminsGroups: [],
- quota: defaultQuota,
- language: {code: 'en', name: t('settings', 'Default language')}
- }
- };
- },
- mounted() {
- if (!this.settings.canChangePassword) {
- OC.Notification.showTemporary(t('settings', 'Password change is disabled because the master key is disabled'));
- }
- /**
- * Init default language from server data. The use of this.settings
- * requires a computed variable,vwhich break the v-model binding of the form,
- * this is a much easier solution than getter and setter
- */
- Vue.set(this.newUser.language, 'code', this.settings.defaultLanguage);
+ AppScore
},
computed: {
- settings() {
- return this.$store.getters.getServerData;
- },
- filteredUsers() {
- if (this.selectedGroup === 'disabled') {
- let disabledUsers = this.users.filter(user => user.enabled !== true);
- if (disabledUsers.length===0 && this.$refs.infiniteLoading && this.$refs.infiniteLoading.isComplete) {
- // disabled group is empty, redirection to all users
- this.$router.push('users');
- this.$refs.infiniteLoading.$emit('$InfiniteLoading:reset');
- }
- return disabledUsers;
- }
- return this.users.filter(user => user.enabled === true);
- },
- groups() {
- // data provided php side + remove the disabled group
- return this.$store.getters.getGroups.filter(group => group.id !== 'disabled');
- },
- subAdminsGroups() {
- // data provided php side
- return this.$store.getters.getServerData.subadmingroups;
- },
- 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;
+ licence() {
+ return this.app.license + t('settings', '-licensed');
},
- minPasswordLength() {
- return this.$store.getters.getPasswordPolicyMinLength;
- },
- usersOffset() {
- return this.$store.getters.getUsersOffset;
- },
- usersLimit() {
- return this.$store.getters.getUsersLimit;
+ author() {
+ return t('settings', 'by') + ' ' + this.app.author;
},
+ renderMarkdown() {
+ // TODO: bundle marked as well
+ var renderer = new window.marked.Renderer();
+ renderer.link = function(href, title, text) {
+ try {
+ var prot = decodeURIComponent(unescape(href))
+ .replace(/[^\w:]/g, '')
+ .toLowerCase();
+ } catch (e) {
+ return '';
+ }
- /* LANGUAGES */
- languages() {
- return Array(
- {
- label: t('settings', 'Common languages'),
- languages: this.settings.languages.commonlanguages
- },
+ 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(
+ window.marked(this.app.description.trim(), {
+ renderer: renderer,
+ gfm: false,
+ highlight: false,
+ tables: false,
+ breaks: false,
+ pedantic: false,
+ sanitize: true,
+ smartLists: true,
+ smartypants: false
+ }),
{
- label: t('settings', 'All languages'),
- languages: this.settings.languages.languages
+ SAFE_FOR_JQUERY: true,
+ ALLOWED_TAGS: [
+ 'strong',
+ 'p',
+ 'a',
+ 'ul',
+ 'ol',
+ 'li',
+ 'em',
+ 'del',
+ 'blockquote'
+ ]
}
);
}
- },
- watch: {
- // watch url change and group select
- selectedGroup: function (val, old) {
- this.$store.commit('resetUsers');
- this.$refs.infiniteLoading.$emit('$InfiniteLoading:reset');
- }
- },
- methods: {
- onScroll(event) {
- this.scrolled = event.target.scrollTop>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 : ''})
- .then((response) => {response?$state.loaded():$state.complete()});
- },
-
- resetForm() {
- // revert form to original state
- Object.assign(this.newUser, this.$options.data.call(this).newUser);
- this.loading = false;
- },
- createUser() {
- this.loading = true;
- this.$store.dispatch('addUser', {
- userid: this.newUser.id,
- password: this.newUser.password,
- 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());
- }
}
}
</script>
diff --git a/settings/src/components/appList.vue b/settings/src/components/appList.vue
index b446b9c8a61..08809cf9b2b 100644
--- a/settings/src/components/appList.vue
+++ b/settings/src/components/appList.vue
@@ -21,19 +21,43 @@
-->
<template>
- <div id="app-content" :class="{ 'with-app-sidebar': app }">
- <div id="apps-list" class="installed">
- <div class="apps-header" v-if="category === 'app-bundles'">
- <div class="app-image"></div>
- <h2>Firmen-Paket <input class="enable" type="submit" data-bundleid="EnterpriseBundle" data-active="true" value="Alle aktivieren"></h2>
- <div class="app-version"></div>
- <div class="app-level"></div>
- <div class="app-groups"></div>
- <div class="actions">&nbsp;</div>
+ <div id="app-content" class="app-settings-content" :class="{ 'with-app-sidebar': app }">
+ <div id="apps-list" class="installed" v-if="useListView">
+ <app-item v-for="app in apps" :key="app.id" :app="app" :category="category" />
+ </div>
+
+ <div id="apps-list" class="installed" v-if="useBundleView">
+ <template v-for="app in apps">
+ <div class="apps-header" v-if="app.newCategory">
+ <div class="app-image"></div>
+ <h2>{{ app.categoryName }} <input class="enable" type="button" value="Alle aktivieren"></h2>
+ <div class="app-version"></div>
+ <div class="app-level"></div>
+ <div class="app-groups"></div>
+ <div class="actions">&nbsp;</div>
+ </div>
+ <app-item v-else :key="app.id" :app="app" :category="category"/>
+ </template>
+ </div>
+
+ <div id="apps-list" class="store" v-if="useAppStoreView">
+ <app-item v-for="app in apps" :key="app.id" :app="app" :category="category" :list-view="false" />
+ </div>
+
+ <div id="apps-list" class="installed" v-if="search !== ''">
+ <div>
+ <div></div>
+ <h2>{{ t('settings', 'Results from other categories') }}</h2>
</div>
+ <app-item v-for="app in searchApps" :key="app.id" :app="app" :category="category" :list-view="true" />
+ </div>
- <app-item v-for="app in apps" :key="app.id" :app="app" :category="category" />
+ <div id="apps-list-empty" class="emptycontent emptycontent-search" v-if="apps.length == 0 && loading">
+ <div id="app-list-empty-icon" class="icon-search"></div>
+ <h2>{{ t('settings', 'No apps found for your versoin')}}</h2>
</div>
+
+ <div id="searchresults"></div>
</div>
</template>
@@ -43,7 +67,7 @@ import Multiselect from 'vue-multiselect';
export default {
name: 'appList',
- props: ['category', 'app'],
+ props: ['category', 'app', 'search'],
components: {
Multiselect,
appItem
@@ -65,14 +89,28 @@ export default {
},
computed: {
apps() {
- return this.$store.getters.getApps;
+ return this.$store.getters.getApps
+ .filter(app => app.name.toLowerCase().search(this.search.toLowerCase()) !== -1)
+ },
+ searchApps() {
+ return this.$store.getters.getAllApps
+ .filter(app => app.name.toLowerCase().search(this.search.toLowerCase()) !== -1)
},
groups() {
console.log(this.$store.getters.getGroups);
- return this.$store.getters.getGroups;
- /*.filter(group => group.id !== 'disabled')
- .sort((a, b) => a.name.localeCompare(b.name));*/
+ return this.$store.getters.getGroups
+ .filter(group => group.id !== 'disabled')
+ .sort((a, b) => a.name.localeCompare(b.name));
},
+ 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');
+ }
},
methods: {
prefix(prefix, content) {
diff --git a/settings/src/components/appList/appItem.vue b/settings/src/components/appList/appItem.vue
index d72f69ff267..4b65aed0ca8 100644
--- a/settings/src/components/appList/appItem.vue
+++ b/settings/src/components/appList/appItem.vue
@@ -21,23 +21,26 @@
-->
<template>
- <div class="section" v-on:click="showAppDetails">
- <div class="app-image app-image-icon">
- <svg width="32" height="32" viewBox="0 0 32 32">
+ <div class="section">
+ <div class="app-image app-image-icon" v-on:click="showAppDetails">
+ <img :src="app.preview" v-if="!app.previewAsIcon" width="100%" />
+ <svg v-else width="32" height="32" viewBox="0 0 32 32" v-if="app.previewAsIcon">
<defs><filter id="invertIconApps-606"><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="url(#invertIconApps-606)" xlink:href="/core/img/places/default-app-icon.svg?v=13.0.2.1" class="app-icon"></image>
+ <image x="0" y="0" width="32" height="32" preserveAspectRatio="xMinYMin meet" filter="url(#invertIconApps-606)" :xlink:href="app.preview" class="app-icon"></image>
</svg>
</div>
- <div class="app-name">
+ <div class="app-name" v-on:click="showAppDetails">
{{ app.name }}
</div>
<div class="app-version">{{ app.version }}</div>
+ <div class="app-score" v-if="!listView"><app-score :score="app.score"></app-score> </div>
+ <div class="app-summary" v-if="!listView">{{ app.summary }}</div>
<div class="app-level">
<span class="official icon-checkmark" v-if="app.level === 200">{{ t('settings', 'Official') }}</span>
- <a :href="app.appstoreUrl" v-if="!app.internal">Im Store anzeigen ↗</a>
+ <a :href="appstoreUrl" v-if="!app.internal && listView">Im Store anzeigen ↗</a>
</div>
- <div class="app-groups">
+ <div class="app-groups" v-if="listView">
<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)">Auf Gruppen beschränken</label>
@@ -53,26 +56,31 @@
<div class="actions">
<div class="warning hidden"></div>
- <input v-if="app.update" class="update" type="button" :value="t('settings', 'Update to %s', app.update)" :data-appid="app.id" />
- <input v-if="app.canUnInstall" class="uninstall" type="button" :value="t('settings', 'Remove')" :data-appid="app.id" />
- <input v-if="app.active" class="enable" type="button" :data-appid="app.id" data-active="true" :value="t('settings','Disable')" v-on:click="disable(app.id)" />
- <input v-if="!app.active && !app.needsDownload" class="enable" type="button" :data-appid="app.id" :value="t('settings','Enable')" v-on:click="enable(app.id)" />
- <!--if !canInstall -> disable the enable button {{#unless canInstall}}disabled="disabled"{{/unless}} //-->
- <input v-if="!app.active && app.needsDownload" class="enable needs-download" type="button" :data-appid="app.id" :value="t('settings', 'Enable')"/>
- <!-- <php /* data-active="false" {{#unless canInstall}}disabled="disabled"{{/unless}} */ ?>//-->
-
+ <input v-if="app.update" class="update" type="button" :value="t('settings', 'Update to %s', app.update)" v-on:click="update(app.id)" />
+ <input v-if="app.canUnInstall" class="uninstall" type="button" :value="t('settings', 'Remove')" v-on:click="remove(app.id)" />
+ <input v-if="app.active" class="enable" type="button" :value="t('settings','Disable')" v-on:click="disable(app.id)" />
+ <input v-if="!app.active" class="enable" type="button" :value="enableButtonText" v-on:click="enable(app.id)" :disabled="!app.canInstall" />
</div>
</div>
</template>
<script>
import Multiselect from 'vue-multiselect';
+ import AppScore from './appScore';
export default {
name: 'appItem',
- props: ['app', 'category'],
+ props: {
+ app: {},
+ category: {},
+ listView: {
+ type: Boolean,
+ default: true,
+ }
+ },
components: {
Multiselect,
+ AppScore,
},
data() {
return {
@@ -87,15 +95,23 @@
}
},
computed: {
+ appstoreUrl() {
+ return `https://apps.nextcloud.com/apps/${this.app.id}`;
+ },
appGroups() {
return this.app.groups.map(group => {return {id: group, name: group}});
},
groups() {
- console.log(this.$store.getters.getGroups);
return this.$store.getters.getGroups
.filter(group => group.id !== 'disabled')
.sort((a, b) => a.name.localeCompare(b.name));
},
+ enableButtonText() {
+ if (this.app.needsDownload) {
+ return t('settings','Download and enable');
+ }
+ return t('settings','Enable');
+ }
},
watchers: {
@@ -145,15 +161,25 @@
this.$store.dispatch('enableApp', { appId: this.app.id, groups: currentGroups});
},
enable(appId) {
- this.$store.dispatch('enableApp', { appId: appId })
+ this.$store.dispatch('enableApp', { appId: appId, groups: [] })
.catch((error) => { OC.Notification.show(error)});
},
disable(appId) {
this.$store.dispatch('disableApp', { appId: appId })
.catch((error) => { OC.Notification.show(error)});
},
- remove() {},
- install() {},
+ remove(appId) {
+ this.$store.dispatch('uninstallApp', { appId: appId })
+ .catch((error) => { OC.Notification.show(error)});
+ },
+ install(appId) {
+ this.$store.dispatch('installApp', { appId: appId })
+ .catch((error) => { OC.Notification.show(error)});
+ },
+ update(appId) {
+ this.$store.dispatch('updateApp', { appId: appId })
+ .catch((error) => { OC.Notification.show(error)});
+ },
createUser() {
this.loading = true;
this.$store.dispatch('addUser', {
diff --git a/settings/src/components/appList/appScore.vue b/settings/src/components/appList/appScore.vue
index e69de29bb2d..8b099f5e3d9 100644
--- a/settings/src/components/appList/appScore.vue
+++ b/settings/src/components/appList/appScore.vue
@@ -0,0 +1,38 @@
+<!--
+ - @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" />
+</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> \ No newline at end of file
diff --git a/settings/src/store/api.js b/settings/src/store/api.js
index 059c9a11ef8..7501a7bb4cc 100644
--- a/settings/src/store/api.js
+++ b/settings/src/store/api.js
@@ -71,7 +71,7 @@ export default {
waitForpassword();
});
},
- get(url, data) {
+ get(url) {
return axios.get(sanitize(url), tokenHeaders)
.then((response) => Promise.resolve(response))
.catch((error) => Promise.reject(error));
diff --git a/settings/src/store/apps.js b/settings/src/store/apps.js
index d79540ad525..cc859c96845 100644
--- a/settings/src/store/apps.js
+++ b/settings/src/store/apps.js
@@ -25,11 +25,18 @@ import axios from 'axios/index';
const state = {
apps: [],
+ allApps: [],
categories: [],
updateCount: 0
};
const mutations = {
+
+ APPS_API_FAILURE(state, error) {
+ OC.Notification.showHtml(t('settings','An error occured during the request. Unable to proceed.')+'<br>'+error.error.response.data.data.message, {timeout: 7});
+ console.log(state, error);
+ },
+
initCategories(state, {categories, updateCount}) {
state.categories = categories;
state.updateCount = updateCount;
@@ -52,16 +59,36 @@ const mutations = {
state.apps = apps;
},
+ setAllApps(state, apps) {
+ state.allApps = apps;
+ },
+
enableApp(state, {appId, groups}) {
- state.apps.find(app => app.id === appId).active = true;
- state.apps.find(app => app.id === appId).groups = groups;
- console.log(state.apps.find(app => app.id === appId).groups);
+ let app = state.apps.find(app => app.id === appId);
+ app.active = true;
+ app.groups = groups;
},
disableApp(state, appId) {
+ let app = state.apps.find(app => app.id === appId);
+ app.active = false;
+ app.groups = [];
+ if (app.removable) {
+ app.canUnInstall = true;
+ }
+ },
+
+ uninstallApp(state, appId) {
state.apps.find(app => app.id === appId).active = false;
+ state.apps.find(app => app.id === appId).groups = [];
+ state.apps.find(app => app.id === appId).needsDownload = true;
+ state.apps.find(app => app.id === appId).canUnInstall = false;
+ state.apps.find(app => app.id === appId).canInstall = true;
},
+ resetApps(state) {
+ state.apps = [];
+ },
reset(state) {
state.apps = [];
state.categories = [];
@@ -74,7 +101,18 @@ const getters = {
return state.categories;
},
getApps(state) {
- return state.apps;
+ return state.apps.concat([]).sort(function (a, b) {
+ if (a.active !== b.active) {
+ return (a.active ? -1 : 1)
+ }
+ if (a.update !== b.update) {
+ return (a.update ? -1 : 1)
+ }
+ return OC.Util.naturalSortCompare(a.name, b.name);
+ });
+ },
+ getAllApps(state) {
+ return state.allApps;
},
getUpdateCount(state) {
return state.updateCount;
@@ -92,7 +130,7 @@ const actions = {
context.commit('enableApp', {appId: appId, groups: groups});
return true;
})
- .catch((error) => {throw error;})
+ .catch((error) => context.commit('APPS_API_FAILURE', { appId, error }))
}).catch((error) => context.commit('API_FAILURE', { appId, error }));
},
@@ -103,14 +141,28 @@ const actions = {
context.commit('disableApp', appId);
return true;
})
- .catch((error) => {throw error;})
+ .catch((error) => context.commit('APPS_API_FAILURE', { appId, error }))
}).catch((error) => context.commit('API_FAILURE', { appId, error }));
},
- installApp(appId) {
-
+ installApp(context, { appId }) {
+ return api.requireAdmin().then((response) => {
+ return api.get(OC.generateUrl(`settings/apps/enable/${appId}`))
+ .then((response) => {
+ context.commit('enableApp', appId);
+ return true;
+ })
+ .catch((error) => context.commit('APPS_API_FAILURE', { appId, error }))
+ }).catch((error) => context.commit('API_FAILURE', { appId, error }));
},
- uninstallApp(appId) {
-
+ uninstallApp(context, { appId }) {
+ return api.requireAdmin().then((response) => {
+ return api.get(OC.generateUrl(`settings/apps/uninstall/${appId}`))
+ .then((response) => {
+ context.commit('uninstallApp', appId);
+ return true;
+ })
+ .catch((error) => context.commit('APPS_API_FAILURE', { appId, error }))
+ }).catch((error) => context.commit('API_FAILURE', { appId, error }));
},
getApps(context, { category }) {
@@ -122,6 +174,15 @@ const actions = {
.catch((error) => context.commit('API_FAILURE', error))
},
+ getAllApps(context) {
+ return api.get(OC.generateUrl(`settings/apps/list`))
+ .then((response) => {
+ context.commit('setAllApps', response.data.apps);
+ return true;
+ })
+ .catch((error) => context.commit('API_FAILURE', error))
+ },
+
getCategories(context) {
return api.get(OC.generateUrl('settings/apps/categories'))
.then((response) => {
diff --git a/settings/src/views/Apps.vue b/settings/src/views/Apps.vue
index 17b54415979..7dbf9e887d8 100644
--- a/settings/src/views/Apps.vue
+++ b/settings/src/views/Apps.vue
@@ -23,12 +23,10 @@
<template>
<div id="app">
<app-navigation :menu="menu" />
- <app-list :category="category" :app="currentApp"></app-list>
+ <app-list :category="category" :app="currentApp" :search="search"></app-list>
<div id="app-sidebar" v-if="currentApp">
- {{ currentApp.name }}
- <div class="app-description">
- {{ currentApp.description }}
- </div>
+ {{ search }}
+ <app-details :app="currentApp"></app-details>
</div>
</div>
</template>
@@ -41,6 +39,7 @@ import Vue from 'vue';
import VueLocalStorage from 'vue-localstorage'
import Multiselect from 'vue-multiselect';
import api from '../store/api';
+import AppDetails from '../components/appDetails';
Vue.use(VueLocalStorage)
Vue.use(VueLocalStorage)
@@ -58,17 +57,33 @@ export default {
}
},
components: {
+ AppDetails,
appNavigation,
appList,
},
+ methods: {
+ setSearch(search) {
+ this.search = search;
+ }
+ },
beforeMount() {
this.$store.dispatch('getCategories');
- this.$store.dispatch('getApps', { category: this.category });
+ this.$store.dispatch('getApps', {category: this.category});
+ this.$store.dispatch('getAllApps');
this.$store.commit('setUpdateCount', this.$store.getters.getServerData.updateCount)
+ console.log(this.$store.getters.getServerData.updateCount);
+ },
+ mounted() {
+ // TODO: remove jQuery once we have a proper standardisation of the search
+ $('#searchbox').show();
+ let self = this;
+ $('#searchbox').change(function(e) {
+ self.setSearch($('#searchbox').val());
+ });
},
data() {
return {
-
+ search: ''
}
},
watch: {