Merge pull request #9565 from nextcloud/feature/noid/app-settings
Migrate apps management to Vue.jspull/9768/head
commit
b49f8e43bd
@ -1,44 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2016, ownCloud, Inc.
|
||||
*
|
||||
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||
* @author Kamil Domanski <kdomanski@kdemail.net>
|
||||
* @author Lukas Reschke <lukas@statuscode.ch>
|
||||
*
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
\OC_JSON::checkAdminUser();
|
||||
\OC_JSON::callCheck();
|
||||
|
||||
$lastConfirm = (int) \OC::$server->getSession()->get('last-password-confirm');
|
||||
if ($lastConfirm < (time() - 30 * 60 + 15)) { // allow 15 seconds delay
|
||||
$l = \OC::$server->getL10N('core');
|
||||
OC_JSON::error(array( 'data' => array( 'message' => $l->t('Password confirmation is required'))));
|
||||
exit();
|
||||
}
|
||||
|
||||
if (!array_key_exists('appid', $_POST)) {
|
||||
OC_JSON::error();
|
||||
exit;
|
||||
}
|
||||
|
||||
$appIds = (array)$_POST['appid'];
|
||||
foreach($appIds as $appId) {
|
||||
$appId = OC_App::cleanAppId($appId);
|
||||
\OC::$server->getAppManager()->disableApp($appId);
|
||||
}
|
||||
OC_JSON::success();
|
||||
@ -1,61 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2016, ownCloud, Inc.
|
||||
*
|
||||
* @author Bart Visscher <bartv@thisnet.nl>
|
||||
* @author Christopher Schäpers <kondou@ts.unde.re>
|
||||
* @author Kamil Domanski <kdomanski@kdemail.net>
|
||||
* @author Lukas Reschke <lukas@statuscode.ch>
|
||||
* @author Robin Appelman <robin@icewind.nl>
|
||||
* @author Thomas Müller <thomas.mueller@tmit.eu>
|
||||
*
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
use OCP\ILogger;
|
||||
|
||||
OC_JSON::checkAdminUser();
|
||||
\OC_JSON::callCheck();
|
||||
|
||||
$lastConfirm = (int) \OC::$server->getSession()->get('last-password-confirm');
|
||||
if ($lastConfirm < (time() - 30 * 60 + 15)) { // allow 15 seconds delay
|
||||
$l = \OC::$server->getL10N('core');
|
||||
OC_JSON::error(array( 'data' => array( 'message' => $l->t('Password confirmation is required'))));
|
||||
exit();
|
||||
}
|
||||
|
||||
$groups = isset($_POST['groups']) ? (array)$_POST['groups'] : [];
|
||||
$appIds = isset($_POST['appIds']) ? (array)$_POST['appIds'] : [];
|
||||
|
||||
try {
|
||||
$updateRequired = false;
|
||||
foreach($appIds as $appId) {
|
||||
$app = new OC_App();
|
||||
$appId = OC_App::cleanAppId($appId);
|
||||
$app->enable($appId, $groups);
|
||||
if(\OC_App::shouldUpgrade($appId)) {
|
||||
$updateRequired = true;
|
||||
}
|
||||
}
|
||||
|
||||
OC_JSON::success(['data' => ['update_required' => $updateRequired]]);
|
||||
} catch (Exception $e) {
|
||||
\OC::$server->getLogger()->logException($e, [
|
||||
'level' => ILogger::DEBUG,
|
||||
'app' => 'core',
|
||||
]);
|
||||
OC_JSON::error(array("data" => array("message" => $e->getMessage()) ));
|
||||
}
|
||||
@ -1,55 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2016, ownCloud, Inc.
|
||||
*
|
||||
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||
* @author Lukas Reschke <lukas@statuscode.ch>
|
||||
* @author Robin Appelman <robin@icewind.nl>
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
\OC_JSON::checkAdminUser();
|
||||
\OC_JSON::callCheck();
|
||||
|
||||
$lastConfirm = (int) \OC::$server->getSession()->get('last-password-confirm');
|
||||
if ($lastConfirm < (time() - 30 * 60 + 15)) { // allow 15 seconds delay
|
||||
$l = \OC::$server->getL10N('core');
|
||||
OC_JSON::error(array( 'data' => array( 'message' => $l->t('Password confirmation is required'))));
|
||||
exit();
|
||||
}
|
||||
|
||||
if (!array_key_exists('appid', $_POST)) {
|
||||
OC_JSON::error();
|
||||
exit;
|
||||
}
|
||||
|
||||
$appId = (string)$_POST['appid'];
|
||||
$appId = OC_App::cleanAppId($appId);
|
||||
|
||||
// FIXME: move to controller
|
||||
/** @var \OC\Installer $installer */
|
||||
$installer = \OC::$server->query(\OC\Installer::class);
|
||||
$result = $installer->removeApp($app);
|
||||
if($result !== false) {
|
||||
// FIXME: Clear the cache - move that into some sane helper method
|
||||
\OC::$server->getMemCacheFactory()->createDistributed('settings')->remove('listApps-0');
|
||||
\OC::$server->getMemCacheFactory()->createDistributed('settings')->remove('listApps-1');
|
||||
OC_JSON::success(array('data' => array('appid' => $appId)));
|
||||
} else {
|
||||
$l = \OC::$server->getL10N('settings');
|
||||
OC_JSON::error(array('data' => array( 'message' => $l->t("Couldn't remove app.") )));
|
||||
}
|
||||
@ -1,58 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2016, ownCloud, Inc.
|
||||
*
|
||||
* @author Christopher Schäpers <kondou@ts.unde.re>
|
||||
* @author Frank Karlitschek <frank@karlitschek.de>
|
||||
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||
* @author Lukas Reschke <lukas@statuscode.ch>
|
||||
* @author Robin Appelman <robin@icewind.nl>
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
* @author Thomas Müller <thomas.mueller@tmit.eu>
|
||||
*
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
\OC_JSON::checkAdminUser();
|
||||
\OC_JSON::callCheck();
|
||||
|
||||
if (!array_key_exists('appid', $_POST)) {
|
||||
\OC_JSON::error(array(
|
||||
'message' => 'No AppId given!'
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
$appId = (string)$_POST['appid'];
|
||||
$appId = OC_App::cleanAppId($appId);
|
||||
|
||||
$config = \OC::$server->getConfig();
|
||||
$config->setSystemValue('maintenance', true);
|
||||
try {
|
||||
$installer = \OC::$server->query(\OC\Installer::class);
|
||||
$result = $installer->updateAppstoreApp($appId);
|
||||
$config->setSystemValue('maintenance', false);
|
||||
} catch(Exception $ex) {
|
||||
$config->setSystemValue('maintenance', false);
|
||||
OC_JSON::error(array('data' => array( 'message' => $ex->getMessage() )));
|
||||
return;
|
||||
}
|
||||
|
||||
if($result !== false) {
|
||||
OC_JSON::success(array('data' => array('appid' => $appId)));
|
||||
} else {
|
||||
$l = \OC::$server->getL10N('settings');
|
||||
OC_JSON::error(array('data' => array( 'message' => $l->t("Couldn't update app.") )));
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,224 @@
|
||||
<!--
|
||||
- @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;">
|
||||
<a class="close icon-close" href="#" v-on:click="hideAppDetails"><span class="hidden-visually">Close</span></a>
|
||||
<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 === 200 || hasRating">
|
||||
<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" type="button" :value="t('settings', 'Update to {version}', {version: app.update})" :disabled="installing || loading(app.id)"/>
|
||||
<input v-if="app.canUnInstall" class="uninstall" type="button" :value="t('settings', 'Remove')" :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" class="enable" type="button" :value="enableButtonText" v-on:click="enable(app.id)" v-tooltip.auto="enableButtonTooltip" :disabled="!app.canInstall || 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)">Auf Gruppen beschränken</label>
|
||||
<input type="hidden" class="group_select" title="Alle" 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">
|
||||
<span slot="noResult">{{t('settings', 'No results')}}</span>
|
||||
</multiselect>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<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 Multiselect from 'vue-multiselect';
|
||||
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;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
hideAppDetails() {
|
||||
this.$router.push({
|
||||
name: 'apps-category',
|
||||
params: {category: this.category}
|
||||
});
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
appstoreUrl() {
|
||||
return `https://apps.nextcloud.com/apps/${this.app.id}`;
|
||||
},
|
||||
licence() {
|
||||
if (this.app.licence)
|
||||
return ('' + this.app.licence).toUpperCase() + t('settings', '-licensed');
|
||||
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() {
|
||||
// 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 '';
|
||||
}
|
||||
|
||||
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
|
||||
}),
|
||||
{
|
||||
SAFE_FOR_JQUERY: true,
|
||||
ALLOWED_TAGS: [
|
||||
'strong',
|
||||
'p',
|
||||
'a',
|
||||
'ul',
|
||||
'ol',
|
||||
'li',
|
||||
'em',
|
||||
'del',
|
||||
'blockquote'
|
||||
]
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,181 @@
|
||||
<!--
|
||||
- @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="{installed: (useBundleView || useListView), store: useAppStoreView}">
|
||||
<template v-if="useListView">
|
||||
<app-item v-for="app in apps" :key="app.id" :app="app" :category="category" />
|
||||
</template>
|
||||
<template v-for="bundle in bundles" v-if="useBundleView && bundleApps(bundle.id).length > 0">
|
||||
<div class="apps-header">
|
||||
<div class="app-image"></div>
|
||||
<h2>{{ bundle.name }} <input type="button" :value="bundleToggleText(bundle.id)" v-on:click="toggleBundle(bundle.id)"></h2>
|
||||
<div class="app-version"></div>
|
||||
<div class="app-level"></div>
|
||||
<div class="app-groups"></div>
|
||||
<div class="actions"> </div>
|
||||
</div>
|
||||
<app-item v-for="app in bundleApps(bundle.id)" :key="bundle.id + app.id" :app="app" :category="category"/>
|
||||
</template>
|
||||
<template v-if="useAppStoreView">
|
||||
<app-item v-for="app in apps" :key="app.id" :app="app" :category="category" :list-view="false" />
|
||||
</template>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="apps-list-search" class="installed">
|
||||
<template v-if="search !== '' && searchApps.length > 0">
|
||||
<div class="section">
|
||||
<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" />
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div id="apps-list-empty" class="emptycontent emptycontent-search" v-if="!loading && searchApps.length === 0 && apps.length === 0">
|
||||
<div id="app-list-empty-icon" class="icon-settings-dark"></div>
|
||||
<h2>{{ t('settings', 'No apps found for your versoin')}}</h2>
|
||||
</div>
|
||||
|
||||
<div id="searchresults"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import appItem from './appList/appItem';
|
||||
import Multiselect from 'vue-multiselect';
|
||||
import prefix from './prefixMixin';
|
||||
|
||||
export default {
|
||||
name: 'appList',
|
||||
mixins: [prefix],
|
||||
props: ['category', 'app', 'search'],
|
||||
components: {
|
||||
Multiselect,
|
||||
appItem
|
||||
},
|
||||
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) {
|
||||
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);
|
||||
});
|
||||
|
||||
if (this.category === 'installed') {
|
||||
return apps.filter(app => app.installed);
|
||||
}
|
||||
if (this.category === 'enabled') {
|
||||
return apps.filter(app => app.active);
|
||||
}
|
||||
if (this.category === 'disabled') {
|
||||
return apps.filter(app => !app.active);
|
||||
}
|
||||
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;
|
||||
},
|
||||
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.log(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>
|
||||
@ -0,0 +1,118 @@
|
||||
<!--
|
||||
- @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="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="!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" type="button" :value="t('settings', 'Update to {update}', {update: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" class="enable" type="button" :value="enableButtonText" v-on:click="enable(app.id)" v-tooltip.auto="enableButtonTooltip" :disabled="!app.canInstall || installing || loading(app.id)" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Multiselect from 'vue-multiselect';
|
||||
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: {
|
||||
Multiselect,
|
||||
AppScore,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isSelected: false,
|
||||
scrolled: false,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.isSelected = (this.app.id === this.$route.params.id);
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
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>
|
||||
@ -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" 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>
|
||||
@ -0,0 +1,117 @@
|
||||
<!--
|
||||
- @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');
|
||||
},
|
||||
enableButtonTooltip() {
|
||||
if (this.app.needsDownload) {
|
||||
return t('settings','The app will be downloaded from the app store');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
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});
|
||||
},
|
||||
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>
|
||||
@ -0,0 +1,32 @@
|
||||
<!--
|
||||
- @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>
|
||||
@ -0,0 +1,293 @@
|
||||
/*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
import api from './api';
|
||||
import axios from 'axios/index';
|
||||
import Vue from 'vue';
|
||||
|
||||
const state = {
|
||||
apps: [],
|
||||
categories: [],
|
||||
updateCount: 0,
|
||||
loading: {},
|
||||
loadingList: false,
|
||||
};
|
||||
|
||||
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;
|
||||
},
|
||||
|
||||
setUpdateCount(state, updateCount) {
|
||||
state.updateCount = updateCount;
|
||||
},
|
||||
|
||||
addCategory(state, category) {
|
||||
state.categories.push(category);
|
||||
},
|
||||
|
||||
appendCategories(state, categoriesArray) {
|
||||
// convert obj to array
|
||||
state.categories = categoriesArray;
|
||||
},
|
||||
|
||||
setAllApps(state, apps) {
|
||||
state.apps = apps;
|
||||
},
|
||||
|
||||
setError(state, {appId, error}) {
|
||||
let app = state.apps.find(app => app.id === appId);
|
||||
app.error = error;
|
||||
},
|
||||
|
||||
clearError(state, {appId, error}) {
|
||||
let app = state.apps.find(app => app.id === appId);
|
||||
app.error = null;
|
||||
},
|
||||
|
||||
enableApp(state, {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;
|
||||
},
|
||||
|
||||
updateApp(state, appId) {
|
||||
let app = state.apps.find(app => app.id === appId);
|
||||
let version = app.update;
|
||||
app.update = null;
|
||||
app.version = version;
|
||||
state.updateCount--;
|
||||
|
||||
},
|
||||
|
||||
resetApps(state) {
|
||||
state.apps = [];
|
||||
},
|
||||
reset(state) {
|
||||
state.apps = [];
|
||||
state.categories = [];
|
||||
state.updateCount = 0;
|
||||
},
|
||||
startLoading(state, id) {
|
||||
if (Array.isArray(id)) {
|
||||
id.forEach((_id) => {
|
||||
Vue.set(state.loading, _id, true);
|
||||
})
|
||||
} else {
|
||||
Vue.set(state.loading, id, true);
|
||||
}
|
||||
},
|
||||
stopLoading(state, id) {
|
||||
if (Array.isArray(id)) {
|
||||
id.forEach((_id) => {
|
||||
Vue.set(state.loading, _id, false);
|
||||
})
|
||||
} else {
|
||||
Vue.set(state.loading, id, false);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const getters = {
|
||||
loading(state) {
|
||||
return function(id) {
|
||||
return state.loading[id];
|
||||
}
|
||||
},
|
||||
getCategories(state) {
|
||||
return state.categories;
|
||||
},
|
||||
getAllApps(state) {
|
||||
return state.apps;
|
||||
},
|
||||
getUpdateCount(state) {
|
||||
return state.updateCount;
|
||||
}
|
||||
};
|
||||
|
||||
const actions = {
|
||||
|
||||
enableApp(context, { appId, groups }) {
|
||||
let apps;
|
||||
if (Array.isArray(appId)) {
|
||||
apps = appId;
|
||||
} else {
|
||||
apps = [appId];
|
||||
}
|
||||
return api.requireAdmin().then((response) => {
|
||||
context.commit('startLoading', apps);
|
||||
context.commit('startLoading', 'install');
|
||||
return api.post(OC.generateUrl(`settings/apps/enable`), {appIds: apps, groups: groups})
|
||||
.then((response) => {
|
||||
context.commit('stopLoading', apps);
|
||||
context.commit('stopLoading', 'install');
|
||||
apps.forEach(_appId => {
|
||||
context.commit('enableApp', {appId: _appId, groups: groups});
|
||||
});
|
||||
|
||||
// check for server health
|
||||
return api.get(OC.generateUrl('apps/files'))
|
||||
.then(() => {
|
||||
if (response.data.update_required) {
|
||||
OC.dialogs.info(
|
||||
t(
|
||||
'settings',
|
||||
'The app has been enabled but needs to be updated. You will be redirected to the update page in 5 seconds.'
|
||||
),
|
||||
t('settings','App update'),
|
||||
function () {
|
||||
window.location.reload();
|
||||
},
|
||||
true
|
||||
);
|
||||
setTimeout(function() {
|
||||
location.reload();
|
||||
}, 5000);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
if (!Array.isArray(appId)) {
|
||||
context.commit('setError', {
|
||||
appId: apps,
|
||||
error: t('settings', 'Error: This app can not be enabled because it makes the server unstable')
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
context.commit('setError', {appId: apps, error: t('settings', 'Error while enabling app')});
|
||||
context.commit('stopLoading', apps);
|
||||
context.commit('stopLoading', 'install');
|
||||
context.commit('APPS_API_FAILURE', { appId, error })
|
||||
})
|
||||
}).catch((error) => context.commit('API_FAILURE', { appId, error }));
|
||||
},
|
||||
disableApp(context, { appId }) {
|
||||
let apps;
|
||||
if (Array.isArray(appId)) {
|
||||
apps = appId;
|
||||
} else {
|
||||
apps = [appId];
|
||||
}
|
||||
return api.requireAdmin().then((response) => {
|
||||
context.commit('startLoading', apps);
|
||||
return api.post(OC.generateUrl(`settings/apps/disable`), {appIds: apps})
|
||||
.then((response) => {
|
||||
context.commit('stopLoading', apps);
|
||||
apps.forEach(_appId => {
|
||||
context.commit('disableApp', _appId);
|
||||
});
|
||||
return true;
|
||||
})
|
||||
.catch((error) => {
|
||||
context.commit('stopLoading', apps);
|
||||
context.commit('APPS_API_FAILURE', { appId, error })
|
||||
})
|
||||
}).catch((error) => context.commit('API_FAILURE', { appId, error }));
|
||||
},
|
||||
uninstallApp(context, { appId }) {
|
||||
return api.requireAdmin().then((response) => {
|
||||
context.commit('startLoading', appId);
|
||||
return api.get(OC.generateUrl(`settings/apps/uninstall/${appId}`))
|
||||
.then((response) => {
|
||||
context.commit('stopLoading', appId);
|
||||
context.commit('uninstallApp', appId);
|
||||
return true;
|
||||
})
|
||||
.catch((error) => {
|
||||
context.commit('stopLoading', appId);
|
||||
context.commit('APPS_API_FAILURE', { appId, error })
|
||||
})
|
||||
}).catch((error) => context.commit('API_FAILURE', { appId, error }));
|
||||
},
|
||||
|
||||
updateApp(context, { appId }) {
|
||||
return api.requireAdmin().then((response) => {
|
||||
context.commit('startLoading', appId);
|
||||
context.commit('startLoading', 'install');
|
||||
return api.get(OC.generateUrl(`settings/apps/update/${appId}`))
|
||||
.then((response) => {
|
||||
context.commit('stopLoading', 'install');
|
||||
context.commit('stopLoading', appId);
|
||||
context.commit('updateApp', appId);
|
||||
return true;
|
||||
})
|
||||
.catch((error) => {
|
||||
context.commit('stopLoading', appId);
|
||||
context.commit('stopLoading', 'install');
|
||||
context.commit('APPS_API_FAILURE', { appId, error })
|
||||
})
|
||||
}).catch((error) => context.commit('API_FAILURE', { appId, error }));
|
||||
},
|
||||
|
||||
getAllApps(context) {
|
||||
context.commit('startLoading', 'list');
|
||||
return api.get(OC.generateUrl(`settings/apps/list`))
|
||||
.then((response) => {
|
||||
context.commit('setAllApps', response.data.apps);
|
||||
context.commit('stopLoading', 'list');
|
||||
return true;
|
||||
})
|
||||
.catch((error) => context.commit('API_FAILURE', error))
|
||||
},
|
||||
|
||||
getCategories(context) {
|
||||
context.commit('startLoading', 'categories');
|
||||
return api.get(OC.generateUrl('settings/apps/categories'))
|
||||
.then((response) => {
|
||||
if (response.data.length > 0) {
|
||||
context.commit('appendCategories', response.data);
|
||||
context.commit('stopLoading', 'categories');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.catch((error) => context.commit('API_FAILURE', error));
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
export default { state, mutations, getters, actions };
|
||||
@ -0,0 +1,215 @@
|
||||
<!--
|
||||
- @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">
|
||||
<app-navigation :menu="menu" />
|
||||
<div id="app-content" class="app-settings-content" :class="{ 'with-app-sidebar': currentApp, 'icon-loading': loadingList }">
|
||||
<app-list :category="category" :app="currentApp" :search="search"></app-list>
|
||||
<div id="app-sidebar" v-if="id && currentApp">
|
||||
<app-details :category="category" :app="currentApp"></app-details>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import appNavigation from '../components/appNavigation';
|
||||
import appList from '../components/appList';
|
||||
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)
|
||||
|
||||
export default {
|
||||
name: 'Apps',
|
||||
props: {
|
||||
category: {
|
||||
type: String,
|
||||
default: 'installed',
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
}
|
||||
},
|
||||
components: {
|
||||
AppDetails,
|
||||
appNavigation,
|
||||
appList,
|
||||
},
|
||||
methods: {
|
||||
setSearch(search) {
|
||||
this.search = search;
|
||||
}
|
||||
},
|
||||
beforeMount() {
|
||||
this.$store.dispatch('getCategories');
|
||||
this.$store.dispatch('getAllApps');
|
||||
this.$store.dispatch('getGroups', {offset: 0, limit: -1});
|
||||
this.$store.commit('setUpdateCount', 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: {
|
||||
category: function (val, old) {
|
||||
this.setSearch('');
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
loading() {
|
||||
return this.$store.getters.loading('categories');
|
||||
},
|
||||
loadingList() {
|
||||
return this.$store.getters.loading('list');
|
||||
},
|
||||
currentApp() {
|
||||
return this.apps.find(app => app.id === this.id );
|
||||
},
|
||||
categories() {
|
||||
return this.$store.getters.getCategories;
|
||||
},
|
||||
apps() {
|
||||
return this.$store.getters.getAllApps;
|
||||
},
|
||||
updateCount() {
|
||||
return this.$store.getters.getUpdateCount;
|
||||
},
|
||||
settings() {
|
||||
return this.$store.getters.getServerData;
|
||||
},
|
||||
|
||||
// BUILD APP NAVIGATION MENU OBJECT
|
||||
menu() {
|
||||
// Data provided php side
|
||||
let categories = this.$store.getters.getCategories;
|
||||
categories = Array.isArray(categories) ? categories : [];
|
||||
|
||||
// Map groups
|
||||
categories = categories.map(category => {
|
||||
let item = {};
|
||||
item.id = 'app-category-' + category.ident;
|
||||
item.icon = 'icon-category-' + category.ident;
|
||||
item.classes = []; // empty classes, active will be set later
|
||||
item.router = { // router link to
|
||||
name: 'apps-category',
|
||||
params: {category: category.ident}
|
||||
};
|
||||
item.text = category.displayName;
|
||||
|
||||
return item;
|
||||
});
|
||||
|
||||
|
||||
// Add everyone group
|
||||
let defaultCategories = [
|
||||
{
|
||||
id: 'app-category-your-apps',
|
||||
classes: [],
|
||||
router: {name: 'apps'},
|
||||
icon: 'icon-category-installed',
|
||||
text: t('settings', 'Your apps'),
|
||||
},
|
||||
{
|
||||
id: 'app-category-enabled',
|
||||
classes: [],
|
||||
icon: 'icon-category-enabled',
|
||||
router: {name: 'apps-category', params: {category: 'enabled'}},
|
||||
text: t('settings', 'Active apps'),
|
||||
}, {
|
||||
id: 'app-category-disabled',
|
||||
classes: [],
|
||||
icon: 'icon-category-disabled',
|
||||
router: {name: 'apps-category', params: {category: 'disabled'}},
|
||||
text: t('settings', 'Disabled apps'),
|
||||
}
|
||||
];
|
||||
|
||||
if (!this.settings.appstoreEnabled) {
|
||||
return {
|
||||
id: 'appscategories',
|
||||
items: defaultCategories,
|
||||
}
|
||||
}
|
||||
|
||||
if (this.$store.getters.getUpdateCount > 0) {
|
||||
defaultCategories.push({
|
||||
id: 'app-category-updates',
|
||||
classes: [],
|
||||
icon: 'icon-download',
|
||||
router: {name: 'apps-category', params: {category: 'updates'}},
|
||||
text: t('settings', 'Updates'),
|
||||
utils: {counter: this.$store.getters.getUpdateCount}
|
||||
});
|
||||
}
|
||||
|
||||
defaultCategories.push({
|
||||
id: 'app-category-app-bundles',
|
||||
classes: [],
|
||||
icon: 'icon-category-app-bundles',
|
||||
router: {name: 'apps-category', params: {category: 'app-bundles'}},
|
||||
text: t('settings', 'App bundles'),
|
||||
});
|
||||
|
||||
categories = defaultCategories.concat(categories);
|
||||
|
||||
// Set current group as active
|
||||
let activeGroup = categories.findIndex(group => group.id === 'app-category-' + this.category);
|
||||
if (activeGroup >= 0) {
|
||||
categories[activeGroup].classes.push('active');
|
||||
} else {
|
||||
categories[0].classes.push('active');
|
||||
}
|
||||
|
||||
categories.push({
|
||||
id: 'app-developer-docs',
|
||||
classes: [],
|
||||
href: this.settings.developerDocumentation,
|
||||
text: t('settings', 'Developer documentation') + ' ↗',
|
||||
});
|
||||
|
||||
// Return
|
||||
return {
|
||||
id: 'appscategories',
|
||||
items: categories,
|
||||
loading: this.loading
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -1,213 +0,0 @@
|
||||
<?php
|
||||
style('settings', 'settings');
|
||||
vendor_script(
|
||||
'core',
|
||||
[
|
||||
'marked/marked.min',
|
||||
]
|
||||
);
|
||||
script(
|
||||
'settings',
|
||||
[
|
||||
'settings',
|
||||
'apps',
|
||||
]
|
||||
);
|
||||
/** @var array $_ */
|
||||
/** @var \OCP\IURLGenerator $urlGenerator */
|
||||
$urlGenerator = $_['urlGenerator'];
|
||||
?>
|
||||
<script id="categories-template" type="text/x-handlebars-template">
|
||||
{{#each this}}
|
||||
<li id="app-category-{{ident}}" data-category-id="{{ident}}" tabindex="0">
|
||||
<a href="#" class="icon-category-{{ident}}">{{displayName}}</a>
|
||||
<div class="app-navigation-entry-utils">
|
||||
<ul>
|
||||
<li class="app-navigation-entry-utils-counter">{{ counter }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
{{/each}}
|
||||
|
||||
<?php if($_['appstoreEnabled']): ?>
|
||||
<li>
|
||||
<a class="app-external icon-info" target="_blank" rel="noreferrer noopener" href="<?php p($urlGenerator->linkToDocs('developer-manual')); ?>"><?php p($l->t('Developer documentation'));?> ↗</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
</script>
|
||||
|
||||
<script id="app-template-installed" type="text/x-handlebars">
|
||||
{{#if newCategory}}
|
||||
<div class="apps-header">
|
||||
<div class="app-image"></div>
|
||||
<h2>{{categoryName}} <input class="enable" type="submit" data-bundleid="{{bundleId}}" data-active="true" value="<?php p($l->t('Enable all'));?>"/></h2>
|
||||
<div class="app-version"></div>
|
||||
<div class="app-level"></div>
|
||||
<div class="app-groups"></div>
|
||||
<div class="actions"> </div>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="section" id="app-{{id}}">
|
||||
<div class="app-image app-image-icon"></div>
|
||||
<div class="app-name">
|
||||
{{#if detailpage}}
|
||||
<a href="{{detailpage}}" target="_blank" rel="noreferrer noopener">{{name}}</a>
|
||||
{{else}}
|
||||
{{name}}
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="app-version">{{version}}</div>
|
||||
<div class="app-level">
|
||||
{{{level}}}{{#unless internal}}<a href="https://apps.nextcloud.com/apps/{{id}}" target="_blank"><?php p($l->t('View in store'));?> ↗</a>{{/unless}}
|
||||
</div>
|
||||
|
||||
<div class="app-groups">
|
||||
{{#if active}}
|
||||
<div class="groups-enable">
|
||||
<input type="checkbox" class="groups-enable__checkbox checkbox" id="groups_enable-{{id}}"/>
|
||||
<label for="groups_enable-{{id}}"><?php p($l->t('Limit to groups')); ?></label>
|
||||
<input type="hidden" class="group_select" title="<?php p($l->t('All')); ?>">
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<div class="warning hidden"></div>
|
||||
<input class="update hidden" type="submit" value="<?php p($l->t('Update to %s', array('{{update}}'))); ?>" data-appid="{{id}}" />
|
||||
{{#if canUnInstall}}
|
||||
<input class="uninstall" type="submit" value="<?php p($l->t('Remove')); ?>" data-appid="{{id}}" />
|
||||
{{/if}}
|
||||
{{#if active}}
|
||||
<input class="enable" type="submit" data-appid="{{id}}" data-active="true" value="<?php p($l->t("Disable"));?>"/>
|
||||
{{else}}
|
||||
<input class="enable{{#if needsDownload}} needs-download{{/if}}" type="submit" data-appid="{{id}}" data-active="false" {{#unless canInstall}}disabled="disabled"{{/unless}} value="<?php p($l->t("Enable"));?>"/>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script id="app-template" type="text/x-handlebars">
|
||||
<div class="section" id="app-{{id}}">
|
||||
{{#if preview}}
|
||||
<div class="app-image{{#if previewAsIcon}} app-image-icon{{/if}} icon-loading">
|
||||
</div>
|
||||
{{/if}}
|
||||
<h2 class="app-name">
|
||||
{{#if detailpage}}
|
||||
<a href="{{detailpage}}" target="_blank" rel="noreferrer noopener">{{name}}</a>
|
||||
{{else}}
|
||||
{{name}}
|
||||
{{/if}}
|
||||
</h2>
|
||||
<div class="app-level">
|
||||
{{{level}}}
|
||||
</div>
|
||||
{{#if ratingNumThresholdReached }}
|
||||
<div class="app-score">{{{score}}}</div>
|
||||
{{/if}}
|
||||
<div class="app-detailpage"></div>
|
||||
|
||||
<div class="app-description-container hidden">
|
||||
<div class="app-version">{{version}}</div>
|
||||
{{#if profilepage}}<a href="{{profilepage}}" target="_blank" rel="noreferrer noopener">{{/if}}
|
||||
<div class="app-author"><?php p($l->t('by %s', ['{{author}}']));?>
|
||||
{{#if licence}}
|
||||
(<?php p($l->t('%s-licensed', ['{{licence}}'])); ?>)
|
||||
{{/if}}
|
||||
</div>
|
||||
{{#if profilepage}}</a>{{/if}}
|
||||
<div class="app-description">{{{description}}}</div>
|
||||
<!--<div class="app-changed">{{changed}}</div>-->
|
||||
{{#if documentation}}
|
||||
<p class="documentation">
|
||||
<?php p($l->t("Documentation:"));?>
|
||||
{{#if documentation.user}}
|
||||
<span class="userDocumentation">
|
||||
<a id="userDocumentation" class="appslink" href="{{documentation.user}}" target="_blank" rel="noreferrer noopener"><?php p($l->t('User documentation'));?> ↗</a>
|
||||
</span>
|
||||
{{/if}}
|
||||
|
||||
{{#if documentation.admin}}
|
||||
<span class="adminDocumentation">
|
||||
<a id="adminDocumentation" class="appslink" href="{{documentation.admin}}" target="_blank" rel="noreferrer noopener"><?php p($l->t('Admin documentation'));?> ↗</a>
|
||||
</span>
|
||||
{{/if}}
|
||||
|
||||
{{#if documentation.developer}}
|
||||
<span class="developerDocumentation">
|
||||
<a id="developerDocumentation" class="appslink" href="{{documentation.developer}}" target="_blank" rel="noreferrer noopener"><?php p($l->t('Developer documentation'));?> ↗</a>
|
||||
</span>
|
||||
{{/if}}
|
||||
</p>
|
||||
{{/if}}
|
||||
|
||||
{{#if website}}
|
||||
<a id="userDocumentation" class="appslink" href="{{website}}" target="_blank" rel="noreferrer noopener"><?php p($l->t('Visit website'));?> ↗</a>
|
||||
{{/if}}
|
||||
|
||||
{{#if bugs}}
|
||||
<a id="adminDocumentation" class="appslink" href="{{bugs}}" target="_blank" rel="noreferrer noopener"><?php p($l->t('Report a bug'));?> ↗</a>
|
||||
{{/if}}
|
||||
</div><!-- end app-description-container -->
|
||||
<div class="app-description-toggle-show" role="link"><?php p($l->t("Show description …"));?></div>
|
||||
<div class="app-description-toggle-hide hidden" role="link"><?php p($l->t("Hide description …"));?></div>
|
||||
|
||||
<div class="app-dependencies update hidden">
|
||||
<p><?php p($l->t('This app has an update available.')); ?></p>
|
||||
</div>
|
||||
|
||||
{{#if missingMinOwnCloudVersion}}
|
||||
<div class="app-dependencies">
|
||||
<p><?php p($l->t('This app has no minimum Nextcloud version assigned. This will be an error in the future.')); ?></p>
|
||||
</div>
|
||||
{{else}}
|
||||
{{#if missingMaxOwnCloudVersion}}
|
||||
<div class="app-dependencies">
|
||||
<p><?php p($l->t('This app has no maximum Nextcloud version assigned. This will be an error in the future.')); ?></p>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#unless canInstall}}
|
||||
<div class="app-dependencies">
|
||||
<p><?php p($l->t('This app cannot be installed because the following dependencies are not fulfilled:')); ?></p>
|
||||
<ul class="missing-dependencies">
|
||||
{{#each missingDependencies}}
|
||||
<li>{{this}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
{{/unless}}
|
||||
|
||||
<input class="update hidden" type="submit" value="<?php p($l->t('Update to %s', array('{{update}}'))); ?>" data-appid="{{id}}" />
|
||||
{{#if active}}
|
||||
<input class="enable" type="submit" data-appid="{{id}}" data-active="true" value="<?php p($l->t("Disable"));?>"/>
|
||||
<div class="groups-enable">
|
||||
<input type="checkbox" class="groups-enable__checkbox checkbox" id="groups_enable-{{id}}"/>
|
||||
<label for="groups_enable-{{id}}"><?php p($l->t('Enable only for specific groups')); ?></label>
|
||||
</div>
|
||||
<input type="hidden" class="group_select" title="<?php p($l->t('All')); ?>" style="width: 200px">
|
||||
{{else}}
|
||||
<input class="enable{{#if needsDownload}} needs-download{{/if}}" type="submit" data-appid="{{id}}" data-active="false" {{#unless canInstall}}disabled="disabled"{{/unless}} value="<?php p($l->t("Enable"));?>"/>
|
||||
{{/if}}
|
||||
{{#if canUnInstall}}
|
||||
<input class="uninstall" type="submit" value="<?php p($l->t('Remove')); ?>" data-appid="{{id}}" />
|
||||
{{/if}}
|
||||
|
||||
<div class="warning hidden"></div>
|
||||
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<div id="app-navigation" class="icon-loading" data-category="<?php p($_['category']);?>">
|
||||
<ul id="apps-categories">
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
<div id="app-content" class="icon-loading">
|
||||
<div id="apps-list"></div>
|
||||
<div id="apps-list-empty" class="hidden emptycontent emptycontent-search">
|
||||
<div id="app-list-empty-icon" class="icon-search"></div>
|
||||
<h2><?php p($l->t('No apps found for your version')) ?></h2>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,266 +0,0 @@
|
||||
/**
|
||||
* ownCloud
|
||||
*
|
||||
* @author Vincent Petry
|
||||
* @copyright 2015 Vincent Petry <pvince81@owncloud.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This library 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 library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
describe('OC.Settings.Apps tests', function() {
|
||||
var Apps;
|
||||
|
||||
beforeEach(function() {
|
||||
var $el = $('<div id="apps-list"></div>' +
|
||||
'<div id="apps-list-empty" class="hidden"></div>' +
|
||||
'<div id="app-template">' +
|
||||
// dummy template for testing
|
||||
'<div id="app-{{id}}" data-id="{{id}}" class="section">{{name}}</div>' +
|
||||
'</div>'
|
||||
);
|
||||
$('#testArea').append($el);
|
||||
|
||||
Apps = OC.Settings.Apps;
|
||||
});
|
||||
afterEach(function() {
|
||||
Apps.State.apps = null;
|
||||
Apps.State.currentCategory = null;
|
||||
});
|
||||
|
||||
describe('Filtering apps', function() {
|
||||
var oldApps;
|
||||
|
||||
function loadApps(appList) {
|
||||
Apps.State.apps = appList;
|
||||
|
||||
_.each(appList, function(appSpec) {
|
||||
Apps.renderApp(appSpec);
|
||||
});
|
||||
}
|
||||
|
||||
function getResultsFromDom() {
|
||||
var results = [];
|
||||
$('#apps-list .section:not(.hidden)').each(function() {
|
||||
results.push($(this).attr('data-id'));
|
||||
});
|
||||
return results;
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
loadApps([
|
||||
{id: 'appone', name: 'App One', description: 'The first app', author: 'author1', level: 200},
|
||||
{id: 'apptwo', name: 'App Two', description: 'The second app', author: 'author2', level: 100},
|
||||
{id: 'appthree', name: 'App Three', description: 'Third app', author: 'author3', level: 0},
|
||||
{id: 'somestuff', name: 'Some Stuff', description: 'whatever', author: 'author4', level: 0}
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns no results when query does not match anything', function() {
|
||||
expect(getResultsFromDom().length).toEqual(4);
|
||||
expect($('#apps-list:not(.hidden)').length).toEqual(1);
|
||||
expect($('#apps-list-empty:not(.hidden)').length).toEqual(0);
|
||||
|
||||
Apps.filter('absurdity');
|
||||
expect(getResultsFromDom().length).toEqual(0);
|
||||
expect($('#apps-list:not(.hidden)').length).toEqual(0);
|
||||
expect($('#apps-list-empty:not(.hidden)').length).toEqual(1);
|
||||
|
||||
Apps.filter('');
|
||||
expect(getResultsFromDom().length).toEqual(4);
|
||||
expect($('#apps-list:not(.hidden)').length).toEqual(1);
|
||||
expect($('#apps-list-empty:not(.hidden)').length).toEqual(0);
|
||||
expect(getResultsFromDom().length).toEqual(4);
|
||||
});
|
||||
it('returns relevant results when query matches name', function() {
|
||||
expect($('#apps-list:not(.hidden)').length).toEqual(1);
|
||||
expect($('#apps-list-empty:not(.hidden)').length).toEqual(0);
|
||||
|
||||
var results;
|
||||
Apps.filter('app');
|
||||
results = getResultsFromDom();
|
||||
expect(results.length).toEqual(3);
|
||||
expect(results[0]).toEqual('appone');
|
||||
expect(results[1]).toEqual('apptwo');
|
||||
expect(results[2]).toEqual('appthree');
|
||||
|
||||
expect($('#apps-list:not(.hidden)').length).toEqual(1);
|
||||
expect($('#apps-list-empty:not(.hidden)').length).toEqual(0);
|
||||
});
|
||||
it('returns relevant result when query matches name', function() {
|
||||
var results;
|
||||
Apps.filter('TWO');
|
||||
results = getResultsFromDom();
|
||||
expect(results.length).toEqual(1);
|
||||
expect(results[0]).toEqual('apptwo');
|
||||
});
|
||||
it('returns relevant result when query matches description', function() {
|
||||
var results;
|
||||
Apps.filter('ever');
|
||||
results = getResultsFromDom();
|
||||
expect(results.length).toEqual(1);
|
||||
expect(results[0]).toEqual('somestuff');
|
||||
});
|
||||
it('returns relevant results when query matches author name', function() {
|
||||
var results;
|
||||
Apps.filter('author');
|
||||
results = getResultsFromDom();
|
||||
expect(results.length).toEqual(4);
|
||||
expect(results[0]).toEqual('appone');
|
||||
expect(results[1]).toEqual('apptwo');
|
||||
expect(results[2]).toEqual('appthree');
|
||||
expect(results[3]).toEqual('somestuff');
|
||||
});
|
||||
it('returns relevant result when query matches author name', function() {
|
||||
var results;
|
||||
Apps.filter('thor3');
|
||||
results = getResultsFromDom();
|
||||
expect(results.length).toEqual(1);
|
||||
expect(results[0]).toEqual('appthree');
|
||||
});
|
||||
it('returns relevant result when query matches level name', function() {
|
||||
var results;
|
||||
Apps.filter('Offic');
|
||||
results = getResultsFromDom();
|
||||
expect(results.length).toEqual(1);
|
||||
expect(results[0]).toEqual('appone');
|
||||
});
|
||||
it('returns relevant result when query matches level name', function() {
|
||||
var results;
|
||||
Apps.filter('Appro');
|
||||
results = getResultsFromDom();
|
||||
expect(results.length).toEqual(1);
|
||||
expect(results[0]).toEqual('apptwo');
|
||||
});
|
||||
it('returns relevant result when query matches level name', function() {
|
||||
var results;
|
||||
Apps.filter('Exper');
|
||||
results = getResultsFromDom();
|
||||
expect(results.length).toEqual(2);
|
||||
expect(results[0]).toEqual('appthree');
|
||||
expect(results[1]).toEqual('somestuff');
|
||||
});
|
||||
});
|
||||
|
||||
describe('loading categories', function() {
|
||||
var suite = this;
|
||||
|
||||
beforeEach( function(){
|
||||
suite.server = sinon.fakeServer.create();
|
||||
});
|
||||
|
||||
afterEach( function(){
|
||||
suite.server.restore();
|
||||
});
|
||||
|
||||
function getResultsFromDom() {
|
||||
var results = [];
|
||||
$('#apps-list .section:not(.hidden)').each(function() {
|
||||
results.push($(this).attr('data-id'));
|
||||
});
|
||||
return results;
|
||||
}
|
||||
|
||||
it('does not sort applications using the level', function() {
|
||||
Apps.loadCategory('TestId');
|
||||
|
||||
suite.server.requests[0].respond(
|
||||
200,
|
||||
{
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
JSON.stringify({
|
||||
apps: [
|
||||
{
|
||||
id: 'foo',
|
||||
name: 'Foo app',
|
||||
description: 'Hello',
|
||||
level: 0,
|
||||
author: 'foo'
|
||||
},
|
||||
{
|
||||
id: 'alpha',
|
||||
name: 'Alpha app',
|
||||
description: 'Hello',
|
||||
level: 300,
|
||||
author: ['alpha', 'beta']
|
||||
},
|
||||
{
|
||||
id: 'nolevel',
|
||||
name: 'No level',
|
||||
description: 'Hello',
|
||||
author: 'bar'
|
||||
},
|
||||
{
|
||||
id: 'zork',
|
||||
name: 'Some famous adventure game',
|
||||
description: 'Hello',
|
||||
level: 200,
|
||||
author: 'baz'
|
||||
|
||||
},
|
||||
{
|
||||
id: 'delta',
|
||||
name: 'Mathematical symbol',
|
||||
description: 'Hello',
|
||||
level: 200,
|
||||
author: 'foobar'
|
||||
}
|
||||
]
|
||||
})
|
||||
);
|
||||
|
||||
var results = getResultsFromDom();
|
||||
expect(results.length).toEqual(5);
|
||||
expect(results).toEqual(['alpha', 'foo', 'delta', 'nolevel', 'zork']);
|
||||
expect(OC.Settings.Apps.State.apps).toEqual({
|
||||
'foo': {
|
||||
id: 'foo',
|
||||
name: 'Foo app',
|
||||
description: 'Hello',
|
||||
level: 0,
|
||||
author: 'foo'
|
||||
},
|
||||
'alpha': {
|
||||
id: 'alpha',
|
||||
name: 'Alpha app',
|
||||
description: 'Hello',
|
||||
level: 300,
|
||||
author: ['alpha', 'beta']
|
||||
},
|
||||
'nolevel': {
|
||||
id: 'nolevel',
|
||||
name: 'No level',
|
||||
description: 'Hello',
|
||||
author: 'bar'
|
||||
},
|
||||
'zork': {
|
||||
id: 'zork',
|
||||
name: 'Some famous adventure game',
|
||||
description: 'Hello',
|
||||
level: 200,
|
||||
author: 'baz',
|
||||
},
|
||||
'delta': {
|
||||
id: 'delta',
|
||||
name: 'Mathematical symbol',
|
||||
description: 'Hello',
|
||||
level: 200,
|
||||
author: 'foobar'
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
Loading…
Reference in New Issue