diff --git a/app/src/components/globals/DescriptionRow.vue b/app/src/components/globals/DescriptionRow.vue
index c6056892..878d00c0 100644
--- a/app/src/components/globals/DescriptionRow.vue
+++ b/app/src/components/globals/DescriptionRow.vue
@@ -35,7 +35,7 @@ export default {
diff --git a/app/src/helpers/yunohostArguments.js b/app/src/helpers/yunohostArguments.js
index 0ba1d683..c0e52613 100644
--- a/app/src/helpers/yunohostArguments.js
+++ b/app/src/helpers/yunohostArguments.js
@@ -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 () {
diff --git a/app/src/i18n/locales/en.json b/app/src/i18n/locales/en.json
index 79efb90b..2091446e 100644
--- a/app/src/i18n/locales/en.json
+++ b/app/src/i18n/locales/en.json
@@ -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.",
diff --git a/app/src/router/routes.js b/app/src/router/routes.js
index 0cddd8f7..7d815851 100644
--- a/app/src/router/routes.js
+++ b/app/src/router/routes.js
@@ -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',
diff --git a/app/src/views/app/AppActions.vue b/app/src/views/app/AppActions.vue
deleted file mode 100644
index 6a92dded..00000000
--- a/app/src/views/app/AppActions.vue
+++ /dev/null
@@ -1,106 +0,0 @@
-
-
-
-
- {{ $t('experimental_warning') }}
-
-
-
-
-
-
-
-
-
-
- {{ $t('app_no_actions') }}
-
-
-
-
-
diff --git a/app/src/views/app/AppCatalog.vue b/app/src/views/app/AppCatalog.vue
index f03a59f1..66bdc39b 100644
--- a/app/src/views/app/AppCatalog.vue
+++ b/app/src/views/app/AppCatalog.vue
@@ -70,27 +70,28 @@
{{ app.manifest.name }}
-
+
+
{{ $t('app_state_' + app.state) }}
{{ app.manifest.description }}
-
+
- {{ $t(app.maintained) }}
+ {{ $t('orphaned') }}
@@ -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 } })
diff --git a/app/src/views/app/AppInfo.vue b/app/src/views/app/AppInfo.vue
index e86cbbc0..19727b7b 100644
--- a/app/src/views/app/AppInfo.vue
+++ b/app/src/views/app/AppInfo.vue
@@ -2,33 +2,22 @@
-
-
- {{ $t(prop) }}
-
-
- {{ value }}
- {{ value }}
-
-
-
-
- {{ $t('app_info_access_desc') }}
-
-
-
- {{ allowedGroups.length > 0 ? allowedGroups.join(', ') + '.' : $t('nobody') }}
-
- {{ $t('groups_and_permissions_manage') }}
-
-
-
+ {{ value }}
+ {{ value }}
+
+
+ {{ allowedGroups.length > 0 ? allowedGroups.join(', ') + '.' : $t('nobody') }}
+
+ {{ $t('groups_and_permissions_manage') }}
+
+
@@ -145,19 +134,6 @@
-
-
-
-
-
- {{ $t('app_actions') }}
-
-
-
-
@@ -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) {
diff --git a/app/src/views/app/AppInstall.vue b/app/src/views/app/AppInstall.vue
index 6cfa3fa0..8af0d9c0 100644
--- a/app/src/views/app/AppInstall.vue
+++ b/app/src/views/app/AppInstall.vue
@@ -3,18 +3,10 @@
-
-
- {{ $t(key) }}
-
-
-
- {{ info }}
-
-
+ :term="$t(key)" :details="info"
+ />
@@ -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
diff --git a/app/src/views/app/AppList.vue b/app/src/views/app/AppList.vue
index 27e1931b..f156b240 100644
--- a/app/src/views/app/AppList.vue
+++ b/app/src/views/app/AppList.vue
@@ -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
})