mirror of
https://github.com/YunoHost/yunohost-admin.git
synced 2024-09-03 20:06:15 +02:00
Merge pull request #477 from YunoHost/enh-appv2
appv2: Reflect app manifest v2
This commit is contained in:
commit
8d150c2069
9 changed files with 71 additions and 261 deletions
|
@ -35,7 +35,7 @@ export default {
|
|||
<style lang="scss" scoped>
|
||||
.description-row {
|
||||
@include media-breakpoint-up(md) {
|
||||
margin: .5rem 0;
|
||||
margin: .25rem 0;
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
border-radius: 0.2rem;
|
||||
|
@ -54,6 +54,7 @@ export default {
|
|||
|
||||
.col {
|
||||
display: flex;
|
||||
align-self: start;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -166,7 +166,7 @@ export function formatYunoHostArgument (arg) {
|
|||
}
|
||||
},
|
||||
{
|
||||
types: ['select', 'user', 'domain', 'app'],
|
||||
types: ['select', 'user', 'domain', 'app', 'group'],
|
||||
name: 'SelectItem',
|
||||
props: ['id:name', 'choices'],
|
||||
callback: function () {
|
||||
|
|
|
@ -54,8 +54,6 @@
|
|||
"api_not_found": "Seems like the web-admin tried to query something that doesn't exist.",
|
||||
"api_not_responding": "The YunoHost API is not responding. Maybe 'yunohost-api' is down or got restarted?",
|
||||
"api_waiting": "Waiting for the server's response...",
|
||||
"app_actions": "Actions",
|
||||
"app_actions_label": "Perform actions",
|
||||
"app_choose_category": "Choose a category",
|
||||
"app_config_panel": "Config panel",
|
||||
"app_config_panel_label": "Configure this app",
|
||||
|
@ -69,18 +67,14 @@
|
|||
"app_install_parameters": "Install settings",
|
||||
"app_manage_label_and_tiles": "Manage label and tiles",
|
||||
"app_make_default": "Make default",
|
||||
"app_no_actions": "This application doesn't have any actions",
|
||||
"app_show_categories": "Show categories",
|
||||
"app_state_broken": "broken",
|
||||
"app_state_broken_explanation": "This application is currently broken and not installable according to YunoHost's automatic tests",
|
||||
"app_state_inprogress": "not yet working",
|
||||
"app_state_inprogress_explanation": "This maintainer of this app declared that this application is not ready yet for production use. BE CAREFUL!",
|
||||
"app_state_notworking": "not working",
|
||||
"app_state_notworking_explanation": "This maintainer of this app declared it as 'not working'. IT WILL BREAK YOUR SYSTEM!",
|
||||
"app_state_lowquality": "low quality",
|
||||
"app_state_lowquality_explanation": "This app may be functional, but may still contain issues, or is not fully integrated with YunoHost, or it does not respect the good practices.",
|
||||
"app_state_highquality": "high quality",
|
||||
"app_state_highquality_explanation": "This app is well-integrated with YunoHost since at least a year.",
|
||||
"app_state_working": "working",
|
||||
"app_state_working_explanation": "The maintainer of this app declared it as 'working'. It means that it should be functional (c.f. application level) but is not necessarily peer-reviewed, it may still contain issues or is not fully integrated with YunoHost.",
|
||||
"applications": "Applications",
|
||||
"archive_empty": "Empty archive",
|
||||
"backup": "Backup",
|
||||
|
@ -110,6 +104,7 @@
|
|||
"confirm_install_custom_app": "WARNING! Installing 3rd party applications may compromise the integrity and security of your system. You should probably NOT install it unless you know what you are doing. Are you willing to take that risk?",
|
||||
"confirm_install_domain_root": "Are you sure you want to install this application on '/'? You will not be able to install any other app on {domain}",
|
||||
"confirm_app_install": "Are you sure you want to install this application?",
|
||||
"confirm_install_app_broken": "WARNING! This application is broken according to YunoHost's automatic tests and it is likely to break your system! You should probably NOT install it unless you know what you are doing. Are you willing to take that risk?",
|
||||
"confirm_install_app_lowquality": "Warning: this application may work but is not well-integrated in YunoHost. Some features such as single sign-on and backup/restore might not be available.",
|
||||
"confirm_install_app_inprogress": "WARNING! This application is still experimental (if not explicitly not working) and it is likely to break your system! You should probably NOT install it unless you know what you are doing. Are you willing to take that risk?",
|
||||
"confirm_migrations_skip": "Skipping migrations is not recommended. Are you sure you want to do that?",
|
||||
|
@ -317,7 +312,8 @@
|
|||
"items_verbose_count": "There are {items}. | There is 1 {items}. | There are {items}.",
|
||||
"items_verbose_items_left": "There are {items} left. | There is 1 {items} left. | There are {items} left.",
|
||||
"label": "Label",
|
||||
"label_for_manifestname": "Label for {name} (name displayed in the user portal)",
|
||||
"label_for_manifestname": "Label for {name}",
|
||||
"label_for_manifestname_help": "This is the name displayed in the user portal. This can be changed later.",
|
||||
"last_ran": "Last time ran:",
|
||||
"license": "License",
|
||||
"local_archives": "Local archives",
|
||||
|
@ -329,6 +325,7 @@
|
|||
"manage_apps": "Manage apps",
|
||||
"manage_domains": "Manage domains",
|
||||
"manage_users": "Manage users",
|
||||
"manage_groups": "Manage groups",
|
||||
"migrations": "Migrations",
|
||||
"migrations_pending": "Pending migrations",
|
||||
"migrations_done": "Previous migrations",
|
||||
|
@ -414,7 +411,6 @@
|
|||
"change_url": "Change access URL of '{name}'",
|
||||
"install": "Install app '{name}'",
|
||||
"set_default": "Redirect '{domain}' domain root to '{name}'",
|
||||
"perform_action": "Perform action '{action}' of app '{name}'",
|
||||
"uninstall": "Uninstall app '{name}'",
|
||||
"update_config": "Update panel '{id}' of app '{name}' configuration"
|
||||
},
|
||||
|
@ -539,8 +535,6 @@
|
|||
"unignore": "Unignore",
|
||||
"uninstall": "Uninstall",
|
||||
"unknown": "Unknown",
|
||||
"unmaintained": "Unmaintained",
|
||||
"unmaintained_details": "This app has not been updated for quite a while and the previous maintainer has gone away or does not have time to maintain this app. Feel free to check the app repository to provide your help",
|
||||
"upnp": "UPnP",
|
||||
"upnp_disabled": "UPnP is disabled.",
|
||||
"upnp_enabled": "UPnP is enabled.",
|
||||
|
|
|
@ -210,16 +210,6 @@ const routes = [
|
|||
breadcrumb: ['app-list', 'app-info']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'app-actions',
|
||||
path: '/apps/:id/actions',
|
||||
component: () => import(/* webpackChunkName: "views/apps/actions" */ '@/views/app/AppActions'),
|
||||
props: true,
|
||||
meta: {
|
||||
args: { trad: 'app_actions' },
|
||||
breadcrumb: ['app-list', 'app-info', 'app-actions']
|
||||
}
|
||||
},
|
||||
{
|
||||
// no need for name here, only children are visited
|
||||
path: '/apps/:id/config-panel',
|
||||
|
|
|
@ -1,106 +0,0 @@
|
|||
<template>
|
||||
<view-base
|
||||
:queries="queries" @queries-response="onQueriesResponse"
|
||||
ref="view" skeleton="card-form-skeleton"
|
||||
>
|
||||
<template v-if="actions" #default>
|
||||
<b-alert variant="warning" class="mb-4">
|
||||
<icon iname="exclamation-triangle" /> {{ $t('experimental_warning') }}
|
||||
</b-alert>
|
||||
|
||||
<!-- ACTIONS FORMS -->
|
||||
<card-form
|
||||
v-for="(action, i) in actions" :key="i"
|
||||
:title="action.name" icon="wrench" title-tag="h4"
|
||||
:validation="$v.actions[i]" :id="action.id + '-form'" :server-error="action.serverError"
|
||||
@submit.prevent="performAction(action)" :submit-text="$t('perform')"
|
||||
>
|
||||
<form-field
|
||||
v-for="(field, fname) in action.fields" :key="fname" label-cols="0"
|
||||
v-bind="field" v-model="action.form[fname]" :validation="$v.actions[i][fname]"
|
||||
/>
|
||||
</card-form>
|
||||
</template>
|
||||
|
||||
<!-- In case of a custom url with no manifest found -->
|
||||
<b-alert v-else-if="actions === null" variant="warning">
|
||||
<icon iname="exclamation-triangle" /> {{ $t('app_no_actions') }}
|
||||
</b-alert>
|
||||
</view-base>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import api, { objectToParams } from '@/api'
|
||||
import { validationMixin } from 'vuelidate'
|
||||
|
||||
import { formatI18nField, formatYunoHostArguments, formatFormData } from '@/helpers/yunohostArguments'
|
||||
|
||||
export default {
|
||||
name: 'AppActions',
|
||||
|
||||
mixins: [validationMixin],
|
||||
|
||||
props: {
|
||||
id: { type: String, required: true }
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
queries: [
|
||||
['GET', `apps/${this.id}/actions`],
|
||||
['GET', { uri: 'domains' }],
|
||||
['GET', { uri: 'domains/main', storeKey: 'main_domain' }],
|
||||
['GET', { uri: 'users' }]
|
||||
],
|
||||
actions: undefined
|
||||
}
|
||||
},
|
||||
|
||||
validations () {
|
||||
const validations = {}
|
||||
for (const [i, action] of this.actions.entries()) {
|
||||
if (action.validations) {
|
||||
validations[i] = { form: action.validations }
|
||||
}
|
||||
}
|
||||
return { actions: validations }
|
||||
},
|
||||
|
||||
methods: {
|
||||
onQueriesResponse (data) {
|
||||
if (!data.actions) {
|
||||
this.actions = null
|
||||
return
|
||||
}
|
||||
|
||||
this.actions = data.actions.map(({ name, id, description, arguments: arguments_ }) => {
|
||||
const action = { name, id, serverError: '' }
|
||||
if (description) action.description = formatI18nField(description)
|
||||
if (arguments_ && arguments_.length) {
|
||||
const { form, fields, validations } = formatYunoHostArguments(arguments_)
|
||||
action.form = form
|
||||
action.fields = fields
|
||||
if (validations) action.validations = validations
|
||||
}
|
||||
return action
|
||||
})
|
||||
},
|
||||
|
||||
performAction (action) {
|
||||
// FIXME api expects at least one argument ?! (fake one given with { dontmindthis } )
|
||||
const args = objectToParams(action.form ? formatFormData(action.form) : { dontmindthis: undefined })
|
||||
|
||||
api.put(
|
||||
`apps/${this.id}/actions/${action.id}`,
|
||||
{ args },
|
||||
{ key: 'apps.perform_action', action: action.id, name: this.id }
|
||||
).then(() => {
|
||||
this.$refs.view.fetchQueries()
|
||||
}).catch(err => {
|
||||
if (err.name !== 'APIBadRequestError') throw err
|
||||
action.serverError = err.message
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -70,27 +70,28 @@
|
|||
<b-card-title class="d-flex mb-2">
|
||||
{{ app.manifest.name }}
|
||||
|
||||
<small v-if="app.state !== 'working'" class="d-flex align-items-center ml-2">
|
||||
<small v-if="app.state !== 'working' || app.high_quality" class="d-flex align-items-center ml-2">
|
||||
<b-badge
|
||||
v-if="app.state !== 'highquality'"
|
||||
:variant="(app.color === 'danger' && app.state === 'lowquality') ? 'warning' : app.color"
|
||||
v-if="app.state !== 'working'"
|
||||
:variant="app.color"
|
||||
v-b-popover.hover.bottom="$t(`app_state_${app.state}_explanation`)"
|
||||
>
|
||||
<!-- app.state can be 'lowquality' or 'inprogress' -->
|
||||
{{ $t('app_state_' + app.state) }}
|
||||
</b-badge>
|
||||
|
||||
<icon
|
||||
v-else iname="star" class="star"
|
||||
v-b-popover.hover.bottom="$t(`app_state_${app.state}_explanation`)"
|
||||
v-if="app.high_quality" iname="star" class="star"
|
||||
v-b-popover.hover.bottom="$t(`app_state_highquality_explanation`)"
|
||||
/>
|
||||
</small>
|
||||
</b-card-title>
|
||||
|
||||
<b-card-text>{{ app.manifest.description }}</b-card-text>
|
||||
|
||||
<b-card-text v-if="app.maintained === 'orphaned'" class="align-self-end mt-auto">
|
||||
<b-card-text v-if="!app.maintained" class="align-self-end mt-auto">
|
||||
<span class="alert-warning p-1" v-b-popover.hover.top="$t('orphaned_details')">
|
||||
<icon iname="warning" /> {{ $t(app.maintained) }}
|
||||
<icon iname="warning" /> {{ $t('orphaned') }}
|
||||
</span>
|
||||
</b-card-text>
|
||||
</b-card-body>
|
||||
|
@ -182,9 +183,9 @@ export default {
|
|||
|
||||
// Filtering options
|
||||
qualityOptions: [
|
||||
{ value: 'isHighQuality', text: this.$i18n.t('only_highquality_apps') },
|
||||
{ value: 'isDecentQuality', text: this.$i18n.t('only_decent_quality_apps') },
|
||||
{ value: 'isWorking', text: this.$i18n.t('only_working_apps') },
|
||||
{ value: 'high_quality', text: this.$i18n.t('only_highquality_apps') },
|
||||
{ value: 'decent_quality', text: this.$i18n.t('only_decent_quality_apps') },
|
||||
{ value: 'working', text: this.$i18n.t('only_working_apps') },
|
||||
{ value: 'all', text: this.$i18n.t('all_apps') }
|
||||
],
|
||||
categories: [
|
||||
|
@ -197,7 +198,7 @@ export default {
|
|||
search: '',
|
||||
category: null,
|
||||
subtag: 'all',
|
||||
quality: 'isDecentQuality',
|
||||
quality: 'decent_quality',
|
||||
|
||||
// Custom install form
|
||||
customInstall: {
|
||||
|
@ -264,51 +265,31 @@ export default {
|
|||
},
|
||||
|
||||
methods: {
|
||||
formatQuality (app) {
|
||||
const filters = {
|
||||
isHighQuality: false,
|
||||
isDecentQuality: false,
|
||||
isWorking: false,
|
||||
state: 'inprogress'
|
||||
}
|
||||
if (app.state === 'inprogress') return filters
|
||||
if (app.state === 'working' && app.level > 0) {
|
||||
filters.state = 'working'
|
||||
filters.isWorking = true
|
||||
}
|
||||
if (app.level <= 4 || app.level === '?') {
|
||||
filters.state = 'lowquality'
|
||||
return filters
|
||||
} else {
|
||||
filters.isDecentQuality = true
|
||||
}
|
||||
if (app.level >= 8) {
|
||||
filters.state = 'highquality'
|
||||
filters.isHighQuality = true
|
||||
}
|
||||
return filters
|
||||
},
|
||||
|
||||
formatColor (app) {
|
||||
if (app.isDecentQuality || app.isHighQuality) return 'success'
|
||||
if (app.isWorking) return 'warning'
|
||||
return 'danger'
|
||||
},
|
||||
|
||||
onQueriesResponse (data) {
|
||||
// APPS
|
||||
const apps = []
|
||||
for (const key in data.apps) {
|
||||
const app = data.apps[key]
|
||||
if (app.state === 'notworking') continue
|
||||
|
||||
Object.assign(app, this.formatQuality(app))
|
||||
app.isInstallable = !app.installed || app.manifest.multi_instance
|
||||
if (app.maintained !== 'request_adoption') {
|
||||
app.maintained = app.maintained ? 'maintained' : 'orphaned'
|
||||
app.isInstallable = !app.installed || app.manifest.integration.multi_instance
|
||||
app.working = app.state === 'working'
|
||||
app.decent_quality = app.working && app.level > 4
|
||||
app.high_quality = app.working && app.level >= 8
|
||||
app.color = 'danger'
|
||||
if (app.working && app.level <= 0) {
|
||||
app.state = 'broken'
|
||||
app.color = 'danger'
|
||||
} else if (app.working && app.level <= 4) {
|
||||
app.state = 'lowquality'
|
||||
app.color = 'warning'
|
||||
} else if (app.working) {
|
||||
app.color = 'success'
|
||||
}
|
||||
app.color = this.formatColor(app)
|
||||
app.searchValues = [app.id, app.state, app.manifest.name.toLowerCase(), app.manifest.description.toLowerCase()].join(' ')
|
||||
app.searchValues = [
|
||||
app.id,
|
||||
app.state,
|
||||
app.manifest.name,
|
||||
app.manifest.description,
|
||||
app.potential_alternative_to.join(' ')
|
||||
].join(' ').toLowerCase()
|
||||
apps.push(app)
|
||||
}
|
||||
this.apps = apps.sort((a, b) => a.id > b.id ? 1 : -1)
|
||||
|
@ -328,10 +309,8 @@ export default {
|
|||
|
||||
// INSTALL APP
|
||||
async onInstallClick (app) {
|
||||
if (!app.isDecentQuality) {
|
||||
// Ask for confirmation
|
||||
const state = app.color === 'danger' ? 'inprogress' : app.state
|
||||
const confirmed = await this.$askConfirmation(this.$i18n.t('confirm_install_app_' + state))
|
||||
if (!app.decent_quality) {
|
||||
const confirmed = await this.$askConfirmation(this.$i18n.t('confirm_install_app_' + app.state))
|
||||
if (!confirmed) return
|
||||
}
|
||||
this.$router.push({ name: 'app-install', params: { id: app.id } })
|
||||
|
|
|
@ -2,33 +2,22 @@
|
|||
<view-base :queries="queries" @queries-response="onQueriesResponse" ref="view">
|
||||
<!-- BASIC INFOS -->
|
||||
<card v-if="infos" :title="infos.label" icon="cube">
|
||||
<b-row
|
||||
v-for="(value, prop) in infos" :key="prop"
|
||||
no-gutters class="row-line"
|
||||
<description-row
|
||||
v-for="(value, key) in infos" :key="key"
|
||||
:term="$t(key)"
|
||||
>
|
||||
<b-col cols="auto" md="3">
|
||||
<strong>{{ $t(prop) }}</strong>
|
||||
</b-col>
|
||||
<b-col>
|
||||
<a v-if="prop === 'url'" :href="value" target="_blank">{{ value }}</a>
|
||||
<span v-else>{{ value }}</span>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row no-gutters class="row-line">
|
||||
<b-col cols="auto" md="3">
|
||||
<strong>{{ $t('app_info_access_desc') }}</strong>
|
||||
<span class="sep" />
|
||||
</b-col>
|
||||
<b-col>
|
||||
{{ allowedGroups.length > 0 ? allowedGroups.join(', ') + '.' : $t('nobody') }}
|
||||
<b-button
|
||||
size="sm" :to="{ name: 'group-list'}" variant="info"
|
||||
class="ml-2"
|
||||
>
|
||||
<icon iname="key-modern" /> {{ $t('groups_and_permissions_manage') }}
|
||||
</b-button>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<a v-if="key === 'url'" :href="value" target="_blank">{{ value }}</a>
|
||||
<template v-else>{{ value }}</template>
|
||||
</description-row>
|
||||
<description-row :term="$t('app_info_access_desc')">
|
||||
{{ allowedGroups.length > 0 ? allowedGroups.join(', ') + '.' : $t('nobody') }}
|
||||
<b-button
|
||||
size="sm" :to="{ name: 'group-list'}" variant="info"
|
||||
class="ml-2"
|
||||
>
|
||||
<icon iname="key-modern" /> {{ $t('groups_and_permissions_manage') }}
|
||||
</b-button>
|
||||
</description-row>
|
||||
</card>
|
||||
|
||||
<!-- OPERATIONS -->
|
||||
|
@ -145,19 +134,6 @@
|
|||
</b-form-group>
|
||||
</card>
|
||||
|
||||
<!-- EXPERIMENTAL (displayed if experimental feature has been enabled in web-admin options)-->
|
||||
<card v-if="experimental" :title="$t('experimental')" icon="flask">
|
||||
<!-- APP ACTIONS -->
|
||||
<b-form-group
|
||||
:label="$t('app_actions_label')" label-for="actions"
|
||||
label-cols-md="4" label-class="font-weight-bold"
|
||||
>
|
||||
<b-button id="actions" variant="warning" :to="{ name: 'app-actions', params: { id } }">
|
||||
<icon iname="flask" /> {{ $t('app_actions') }}
|
||||
</b-button>
|
||||
</b-form-group>
|
||||
</card>
|
||||
|
||||
<template #skeleton>
|
||||
<card-info-skeleton :item-count="8" />
|
||||
<card-form-skeleton />
|
||||
|
@ -195,7 +171,7 @@ export default {
|
|||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters(['domains', 'experimental']),
|
||||
...mapGetters(['domains']),
|
||||
|
||||
allowedGroups () {
|
||||
if (!this.app) return
|
||||
|
@ -243,7 +219,7 @@ export default {
|
|||
label: mainPermission.label,
|
||||
description: app.description,
|
||||
version: app.version,
|
||||
multi_instance: this.$i18n.t(app.manifest.multi_instance ? 'yes' : 'no'),
|
||||
multi_instance: this.$i18n.t(app.manifest.integration.multi_instance ? 'yes' : 'no'),
|
||||
install_time: readableDate(app.settings.install_time, true, true)
|
||||
}
|
||||
if (app.settings.domain && app.settings.path) {
|
||||
|
|
|
@ -3,18 +3,10 @@
|
|||
<template v-if="infos">
|
||||
<!-- BASIC INFOS -->
|
||||
<card :title="name" icon="download">
|
||||
<b-row
|
||||
<description-row
|
||||
v-for="(info, key) in infos" :key="key"
|
||||
no-gutters class="row-line"
|
||||
>
|
||||
<b-col cols="5" md="3" xl="3">
|
||||
<strong>{{ $t(key) }}</strong>
|
||||
<span class="sep" />
|
||||
</b-col>
|
||||
<b-col>
|
||||
<span>{{ info }}</span>
|
||||
</b-col>
|
||||
</b-row>
|
||||
:term="$t(key)" :details="info"
|
||||
/>
|
||||
</card>
|
||||
|
||||
<!-- INSTALL FORM -->
|
||||
|
@ -70,7 +62,6 @@ export default {
|
|||
],
|
||||
name: undefined,
|
||||
infos: undefined,
|
||||
formDisclaimer: null,
|
||||
form: undefined,
|
||||
fields: undefined,
|
||||
validations: null,
|
||||
|
@ -87,18 +78,20 @@ export default {
|
|||
onQueriesResponse (manifest) {
|
||||
this.name = manifest.name
|
||||
const infosKeys = ['id', 'description', 'license', 'version', 'multi_instance']
|
||||
manifest.license = manifest.upstream.license
|
||||
if (manifest.license === undefined || manifest.license === 'free') {
|
||||
infosKeys.splice(2, 1)
|
||||
}
|
||||
manifest.description = formatI18nField(manifest.description)
|
||||
manifest.multi_instance = this.$i18n.t(manifest.multi_instance ? 'yes' : 'no')
|
||||
manifest.multi_instance = this.$i18n.t(manifest.integration.multi_instance ? 'yes' : 'no')
|
||||
this.infos = Object.fromEntries(infosKeys.map(key => [key, manifest[key]]))
|
||||
|
||||
// FIXME yunohost should add the label field by default
|
||||
manifest.arguments.install.unshift({
|
||||
manifest.install.unshift({
|
||||
ask: this.$t('label_for_manifestname', { name: manifest.name }),
|
||||
default: manifest.name,
|
||||
name: 'label'
|
||||
name: 'label',
|
||||
help: this.$t('label_for_manifestname_help')
|
||||
})
|
||||
|
||||
const {
|
||||
|
@ -106,7 +99,7 @@ export default {
|
|||
fields,
|
||||
validations,
|
||||
errors
|
||||
} = formatYunoHostArguments(manifest.arguments.install)
|
||||
} = formatYunoHostArguments(manifest.install)
|
||||
|
||||
this.fields = fields
|
||||
this.form = form
|
||||
|
|
|
@ -68,25 +68,8 @@ export default {
|
|||
return
|
||||
}
|
||||
|
||||
const multiInstances = {}
|
||||
this.apps = apps.map(({ id, name, description, permissions, manifest }) => {
|
||||
// FIXME seems like some apps may no have a label (replace with id)
|
||||
const label = permissions[id + '.main'].label
|
||||
// Display the `id` of the instead of its `name` if multiple apps share the same name
|
||||
if (manifest.multi_instance) {
|
||||
if (!(name in multiInstances)) {
|
||||
multiInstances[name] = []
|
||||
}
|
||||
const labels = multiInstances[name]
|
||||
if (labels.includes(label)) {
|
||||
name = id
|
||||
}
|
||||
labels.push(label)
|
||||
}
|
||||
if (label === name) {
|
||||
name = null
|
||||
}
|
||||
return { id, name, description, label }
|
||||
this.apps = apps.map(({ id, name, description, manifest }) => {
|
||||
return { id, name: manifest.name, label: name, description }
|
||||
}).sort((prev, app) => {
|
||||
return prev.label > app.label ? 1 : -1
|
||||
})
|
||||
|
|
Loading…
Add table
Reference in a new issue