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>
|
<style lang="scss" scoped>
|
||||||
.description-row {
|
.description-row {
|
||||||
@include media-breakpoint-up(md) {
|
@include media-breakpoint-up(md) {
|
||||||
margin: .5rem 0;
|
margin: .25rem 0;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: rgba(0, 0, 0, 0.05);
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
border-radius: 0.2rem;
|
border-radius: 0.2rem;
|
||||||
|
@ -54,6 +54,7 @@ export default {
|
||||||
|
|
||||||
.col {
|
.col {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-self: start;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -166,7 +166,7 @@ export function formatYunoHostArgument (arg) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
types: ['select', 'user', 'domain', 'app'],
|
types: ['select', 'user', 'domain', 'app', 'group'],
|
||||||
name: 'SelectItem',
|
name: 'SelectItem',
|
||||||
props: ['id:name', 'choices'],
|
props: ['id:name', 'choices'],
|
||||||
callback: function () {
|
callback: function () {
|
||||||
|
|
|
@ -54,8 +54,6 @@
|
||||||
"api_not_found": "Seems like the web-admin tried to query something that doesn't exist.",
|
"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_not_responding": "The YunoHost API is not responding. Maybe 'yunohost-api' is down or got restarted?",
|
||||||
"api_waiting": "Waiting for the server's response...",
|
"api_waiting": "Waiting for the server's response...",
|
||||||
"app_actions": "Actions",
|
|
||||||
"app_actions_label": "Perform actions",
|
|
||||||
"app_choose_category": "Choose a category",
|
"app_choose_category": "Choose a category",
|
||||||
"app_config_panel": "Config panel",
|
"app_config_panel": "Config panel",
|
||||||
"app_config_panel_label": "Configure this app",
|
"app_config_panel_label": "Configure this app",
|
||||||
|
@ -69,18 +67,14 @@
|
||||||
"app_install_parameters": "Install settings",
|
"app_install_parameters": "Install settings",
|
||||||
"app_manage_label_and_tiles": "Manage label and tiles",
|
"app_manage_label_and_tiles": "Manage label and tiles",
|
||||||
"app_make_default": "Make default",
|
"app_make_default": "Make default",
|
||||||
"app_no_actions": "This application doesn't have any actions",
|
|
||||||
"app_show_categories": "Show categories",
|
"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": "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_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": "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_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_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",
|
"applications": "Applications",
|
||||||
"archive_empty": "Empty archive",
|
"archive_empty": "Empty archive",
|
||||||
"backup": "Backup",
|
"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_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_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_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_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_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?",
|
"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_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.",
|
"items_verbose_items_left": "There are {items} left. | There is 1 {items} left. | There are {items} left.",
|
||||||
"label": "Label",
|
"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:",
|
"last_ran": "Last time ran:",
|
||||||
"license": "License",
|
"license": "License",
|
||||||
"local_archives": "Local archives",
|
"local_archives": "Local archives",
|
||||||
|
@ -329,6 +325,7 @@
|
||||||
"manage_apps": "Manage apps",
|
"manage_apps": "Manage apps",
|
||||||
"manage_domains": "Manage domains",
|
"manage_domains": "Manage domains",
|
||||||
"manage_users": "Manage users",
|
"manage_users": "Manage users",
|
||||||
|
"manage_groups": "Manage groups",
|
||||||
"migrations": "Migrations",
|
"migrations": "Migrations",
|
||||||
"migrations_pending": "Pending migrations",
|
"migrations_pending": "Pending migrations",
|
||||||
"migrations_done": "Previous migrations",
|
"migrations_done": "Previous migrations",
|
||||||
|
@ -414,7 +411,6 @@
|
||||||
"change_url": "Change access URL of '{name}'",
|
"change_url": "Change access URL of '{name}'",
|
||||||
"install": "Install app '{name}'",
|
"install": "Install app '{name}'",
|
||||||
"set_default": "Redirect '{domain}' domain root to '{name}'",
|
"set_default": "Redirect '{domain}' domain root to '{name}'",
|
||||||
"perform_action": "Perform action '{action}' of app '{name}'",
|
|
||||||
"uninstall": "Uninstall app '{name}'",
|
"uninstall": "Uninstall app '{name}'",
|
||||||
"update_config": "Update panel '{id}' of app '{name}' configuration"
|
"update_config": "Update panel '{id}' of app '{name}' configuration"
|
||||||
},
|
},
|
||||||
|
@ -539,8 +535,6 @@
|
||||||
"unignore": "Unignore",
|
"unignore": "Unignore",
|
||||||
"uninstall": "Uninstall",
|
"uninstall": "Uninstall",
|
||||||
"unknown": "Unknown",
|
"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": "UPnP",
|
||||||
"upnp_disabled": "UPnP is disabled.",
|
"upnp_disabled": "UPnP is disabled.",
|
||||||
"upnp_enabled": "UPnP is enabled.",
|
"upnp_enabled": "UPnP is enabled.",
|
||||||
|
|
|
@ -210,16 +210,6 @@ const routes = [
|
||||||
breadcrumb: ['app-list', 'app-info']
|
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
|
// no need for name here, only children are visited
|
||||||
path: '/apps/:id/config-panel',
|
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">
|
<b-card-title class="d-flex mb-2">
|
||||||
{{ app.manifest.name }}
|
{{ 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
|
<b-badge
|
||||||
v-if="app.state !== 'highquality'"
|
v-if="app.state !== 'working'"
|
||||||
:variant="(app.color === 'danger' && app.state === 'lowquality') ? 'warning' : app.color"
|
:variant="app.color"
|
||||||
v-b-popover.hover.bottom="$t(`app_state_${app.state}_explanation`)"
|
v-b-popover.hover.bottom="$t(`app_state_${app.state}_explanation`)"
|
||||||
>
|
>
|
||||||
|
<!-- app.state can be 'lowquality' or 'inprogress' -->
|
||||||
{{ $t('app_state_' + app.state) }}
|
{{ $t('app_state_' + app.state) }}
|
||||||
</b-badge>
|
</b-badge>
|
||||||
|
|
||||||
<icon
|
<icon
|
||||||
v-else iname="star" class="star"
|
v-if="app.high_quality" iname="star" class="star"
|
||||||
v-b-popover.hover.bottom="$t(`app_state_${app.state}_explanation`)"
|
v-b-popover.hover.bottom="$t(`app_state_highquality_explanation`)"
|
||||||
/>
|
/>
|
||||||
</small>
|
</small>
|
||||||
</b-card-title>
|
</b-card-title>
|
||||||
|
|
||||||
<b-card-text>{{ app.manifest.description }}</b-card-text>
|
<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')">
|
<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>
|
</span>
|
||||||
</b-card-text>
|
</b-card-text>
|
||||||
</b-card-body>
|
</b-card-body>
|
||||||
|
@ -182,9 +183,9 @@ export default {
|
||||||
|
|
||||||
// Filtering options
|
// Filtering options
|
||||||
qualityOptions: [
|
qualityOptions: [
|
||||||
{ value: 'isHighQuality', text: this.$i18n.t('only_highquality_apps') },
|
{ value: 'high_quality', text: this.$i18n.t('only_highquality_apps') },
|
||||||
{ value: 'isDecentQuality', text: this.$i18n.t('only_decent_quality_apps') },
|
{ value: 'decent_quality', text: this.$i18n.t('only_decent_quality_apps') },
|
||||||
{ value: 'isWorking', text: this.$i18n.t('only_working_apps') },
|
{ value: 'working', text: this.$i18n.t('only_working_apps') },
|
||||||
{ value: 'all', text: this.$i18n.t('all_apps') }
|
{ value: 'all', text: this.$i18n.t('all_apps') }
|
||||||
],
|
],
|
||||||
categories: [
|
categories: [
|
||||||
|
@ -197,7 +198,7 @@ export default {
|
||||||
search: '',
|
search: '',
|
||||||
category: null,
|
category: null,
|
||||||
subtag: 'all',
|
subtag: 'all',
|
||||||
quality: 'isDecentQuality',
|
quality: 'decent_quality',
|
||||||
|
|
||||||
// Custom install form
|
// Custom install form
|
||||||
customInstall: {
|
customInstall: {
|
||||||
|
@ -264,51 +265,31 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
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) {
|
onQueriesResponse (data) {
|
||||||
// APPS
|
|
||||||
const apps = []
|
const apps = []
|
||||||
for (const key in data.apps) {
|
for (const key in data.apps) {
|
||||||
const app = data.apps[key]
|
const app = data.apps[key]
|
||||||
if (app.state === 'notworking') continue
|
app.isInstallable = !app.installed || app.manifest.integration.multi_instance
|
||||||
|
app.working = app.state === 'working'
|
||||||
Object.assign(app, this.formatQuality(app))
|
app.decent_quality = app.working && app.level > 4
|
||||||
app.isInstallable = !app.installed || app.manifest.multi_instance
|
app.high_quality = app.working && app.level >= 8
|
||||||
if (app.maintained !== 'request_adoption') {
|
app.color = 'danger'
|
||||||
app.maintained = app.maintained ? 'maintained' : 'orphaned'
|
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.searchValues = [app.id, app.state, app.manifest.name.toLowerCase(), app.manifest.description.toLowerCase()].join(' ')
|
app.id,
|
||||||
|
app.state,
|
||||||
|
app.manifest.name,
|
||||||
|
app.manifest.description,
|
||||||
|
app.potential_alternative_to.join(' ')
|
||||||
|
].join(' ').toLowerCase()
|
||||||
apps.push(app)
|
apps.push(app)
|
||||||
}
|
}
|
||||||
this.apps = apps.sort((a, b) => a.id > b.id ? 1 : -1)
|
this.apps = apps.sort((a, b) => a.id > b.id ? 1 : -1)
|
||||||
|
@ -328,10 +309,8 @@ export default {
|
||||||
|
|
||||||
// INSTALL APP
|
// INSTALL APP
|
||||||
async onInstallClick (app) {
|
async onInstallClick (app) {
|
||||||
if (!app.isDecentQuality) {
|
if (!app.decent_quality) {
|
||||||
// Ask for confirmation
|
const confirmed = await this.$askConfirmation(this.$i18n.t('confirm_install_app_' + app.state))
|
||||||
const state = app.color === 'danger' ? 'inprogress' : app.state
|
|
||||||
const confirmed = await this.$askConfirmation(this.$i18n.t('confirm_install_app_' + state))
|
|
||||||
if (!confirmed) return
|
if (!confirmed) return
|
||||||
}
|
}
|
||||||
this.$router.push({ name: 'app-install', params: { id: app.id } })
|
this.$router.push({ name: 'app-install', params: { id: app.id } })
|
||||||
|
|
|
@ -2,33 +2,22 @@
|
||||||
<view-base :queries="queries" @queries-response="onQueriesResponse" ref="view">
|
<view-base :queries="queries" @queries-response="onQueriesResponse" ref="view">
|
||||||
<!-- BASIC INFOS -->
|
<!-- BASIC INFOS -->
|
||||||
<card v-if="infos" :title="infos.label" icon="cube">
|
<card v-if="infos" :title="infos.label" icon="cube">
|
||||||
<b-row
|
<description-row
|
||||||
v-for="(value, prop) in infos" :key="prop"
|
v-for="(value, key) in infos" :key="key"
|
||||||
no-gutters class="row-line"
|
:term="$t(key)"
|
||||||
>
|
>
|
||||||
<b-col cols="auto" md="3">
|
<a v-if="key === 'url'" :href="value" target="_blank">{{ value }}</a>
|
||||||
<strong>{{ $t(prop) }}</strong>
|
<template v-else>{{ value }}</template>
|
||||||
</b-col>
|
</description-row>
|
||||||
<b-col>
|
<description-row :term="$t('app_info_access_desc')">
|
||||||
<a v-if="prop === 'url'" :href="value" target="_blank">{{ value }}</a>
|
{{ allowedGroups.length > 0 ? allowedGroups.join(', ') + '.' : $t('nobody') }}
|
||||||
<span v-else>{{ value }}</span>
|
<b-button
|
||||||
</b-col>
|
size="sm" :to="{ name: 'group-list'}" variant="info"
|
||||||
</b-row>
|
class="ml-2"
|
||||||
<b-row no-gutters class="row-line">
|
>
|
||||||
<b-col cols="auto" md="3">
|
<icon iname="key-modern" /> {{ $t('groups_and_permissions_manage') }}
|
||||||
<strong>{{ $t('app_info_access_desc') }}</strong>
|
</b-button>
|
||||||
<span class="sep" />
|
</description-row>
|
||||||
</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>
|
|
||||||
</card>
|
</card>
|
||||||
|
|
||||||
<!-- OPERATIONS -->
|
<!-- OPERATIONS -->
|
||||||
|
@ -145,19 +134,6 @@
|
||||||
</b-form-group>
|
</b-form-group>
|
||||||
</card>
|
</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>
|
<template #skeleton>
|
||||||
<card-info-skeleton :item-count="8" />
|
<card-info-skeleton :item-count="8" />
|
||||||
<card-form-skeleton />
|
<card-form-skeleton />
|
||||||
|
@ -195,7 +171,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(['domains', 'experimental']),
|
...mapGetters(['domains']),
|
||||||
|
|
||||||
allowedGroups () {
|
allowedGroups () {
|
||||||
if (!this.app) return
|
if (!this.app) return
|
||||||
|
@ -243,7 +219,7 @@ export default {
|
||||||
label: mainPermission.label,
|
label: mainPermission.label,
|
||||||
description: app.description,
|
description: app.description,
|
||||||
version: app.version,
|
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)
|
install_time: readableDate(app.settings.install_time, true, true)
|
||||||
}
|
}
|
||||||
if (app.settings.domain && app.settings.path) {
|
if (app.settings.domain && app.settings.path) {
|
||||||
|
|
|
@ -3,18 +3,10 @@
|
||||||
<template v-if="infos">
|
<template v-if="infos">
|
||||||
<!-- BASIC INFOS -->
|
<!-- BASIC INFOS -->
|
||||||
<card :title="name" icon="download">
|
<card :title="name" icon="download">
|
||||||
<b-row
|
<description-row
|
||||||
v-for="(info, key) in infos" :key="key"
|
v-for="(info, key) in infos" :key="key"
|
||||||
no-gutters class="row-line"
|
:term="$t(key)" :details="info"
|
||||||
>
|
/>
|
||||||
<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>
|
|
||||||
</card>
|
</card>
|
||||||
|
|
||||||
<!-- INSTALL FORM -->
|
<!-- INSTALL FORM -->
|
||||||
|
@ -70,7 +62,6 @@ export default {
|
||||||
],
|
],
|
||||||
name: undefined,
|
name: undefined,
|
||||||
infos: undefined,
|
infos: undefined,
|
||||||
formDisclaimer: null,
|
|
||||||
form: undefined,
|
form: undefined,
|
||||||
fields: undefined,
|
fields: undefined,
|
||||||
validations: null,
|
validations: null,
|
||||||
|
@ -87,18 +78,20 @@ export default {
|
||||||
onQueriesResponse (manifest) {
|
onQueriesResponse (manifest) {
|
||||||
this.name = manifest.name
|
this.name = manifest.name
|
||||||
const infosKeys = ['id', 'description', 'license', 'version', 'multi_instance']
|
const infosKeys = ['id', 'description', 'license', 'version', 'multi_instance']
|
||||||
|
manifest.license = manifest.upstream.license
|
||||||
if (manifest.license === undefined || manifest.license === 'free') {
|
if (manifest.license === undefined || manifest.license === 'free') {
|
||||||
infosKeys.splice(2, 1)
|
infosKeys.splice(2, 1)
|
||||||
}
|
}
|
||||||
manifest.description = formatI18nField(manifest.description)
|
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]]))
|
this.infos = Object.fromEntries(infosKeys.map(key => [key, manifest[key]]))
|
||||||
|
|
||||||
// FIXME yunohost should add the label field by default
|
// 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 }),
|
ask: this.$t('label_for_manifestname', { name: manifest.name }),
|
||||||
default: manifest.name,
|
default: manifest.name,
|
||||||
name: 'label'
|
name: 'label',
|
||||||
|
help: this.$t('label_for_manifestname_help')
|
||||||
})
|
})
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -106,7 +99,7 @@ export default {
|
||||||
fields,
|
fields,
|
||||||
validations,
|
validations,
|
||||||
errors
|
errors
|
||||||
} = formatYunoHostArguments(manifest.arguments.install)
|
} = formatYunoHostArguments(manifest.install)
|
||||||
|
|
||||||
this.fields = fields
|
this.fields = fields
|
||||||
this.form = form
|
this.form = form
|
||||||
|
|
|
@ -68,25 +68,8 @@ export default {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const multiInstances = {}
|
this.apps = apps.map(({ id, name, description, manifest }) => {
|
||||||
this.apps = apps.map(({ id, name, description, permissions, manifest }) => {
|
return { id, name: manifest.name, label: name, description }
|
||||||
// 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 }
|
|
||||||
}).sort((prev, app) => {
|
}).sort((prev, app) => {
|
||||||
return prev.label > app.label ? 1 : -1
|
return prev.label > app.label ? 1 : -1
|
||||||
})
|
})
|
||||||
|
|
Loading…
Add table
Reference in a new issue