add custom app install card and update with install routes

This commit is contained in:
Axolotle 2020-10-03 16:00:00 +02:00
parent ad25c7373e
commit 3fde9d84bb
5 changed files with 157 additions and 36 deletions

View file

@ -20,6 +20,7 @@
"app_install_custom_no_manifest": "No manifest.json file", "app_install_custom_no_manifest": "No manifest.json file",
"app_make_default": "Make default", "app_make_default": "Make default",
"app_no_actions": "This application doesn't have any actions", "app_no_actions": "This application doesn't have any actions",
"app_not_found": "Unable to find an app matching your criteria.",
"app_show_categories": "Show categories", "app_show_categories": "Show categories",
"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!",
@ -64,8 +65,9 @@
"confirm_firewall_close": "Are you sure you want to close port {port} (protocol: {protocol}, connection: {connection})", "confirm_firewall_close": "Are you sure you want to close port {port} (protocol: {protocol}, connection: {connection})",
"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": "You will not be able to install any other app on %s. Continue?", "confirm_install_domain_root": "You will not be able to install any other app on %s. Continue?",
"confirm_install_app_warning": "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_app_install": "Are you sure you want to install this application?",
"confirm_install_app_danger": "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_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?", "confirm_migrations_skip": "Skipping migrations is not recommended. Are you sure you want to do that?",
"confirm_postinstall": "You are about to launch the post-installation process on the domain {domain}. It may take a few minutes, *do not interrupt the operation*.", "confirm_postinstall": "You are about to launch the post-installation process on the domain {domain}. It may take a few minutes, *do not interrupt the operation*.",
"confirm_restore": "Are you sure you want to restore {name}?", "confirm_restore": "Are you sure you want to restore {name}?",
@ -135,7 +137,8 @@
"username_syntax": "Invalid username: Must be lower-case alphanumeric and underscore characters only", "username_syntax": "Invalid username: Must be lower-case alphanumeric and underscore characters only",
"domain_syntax": "Invalid domain name: Must be lower-case alphanumeric, dot and dash characters only", "domain_syntax": "Invalid domain name: Must be lower-case alphanumeric, dot and dash characters only",
"dynDomain_syntax": "Invalid domain name: Must be lower-case alphanumeric and dash characters only", "dynDomain_syntax": "Invalid domain name: Must be lower-case alphanumeric and dash characters only",
"firewall_port_already": "Port {port} is already {state} (protocol: {protocol}; connection: {connection})" "firewall_port_already": "Port {port} is already {state} (protocol: {protocol}; connection: {connection})",
"not_github_link": "Url must be a valid Github link to a repository"
}, },
"form_input_example": "Example: %s", "form_input_example": "Example: %s",
"from_to": "from {0} to {1}", "from_to": "from {0} to {1}",
@ -179,7 +182,7 @@
"inactive": "Inactive", "inactive": "Inactive",
"infos": "Info", "infos": "Info",
"install": "Install", "install": "Install",
"install_name": "Install %s", "install_name": "Install {id}",
"install_time": "Install time", "install_time": "Install time",
"installation_complete": "Installation complete", "installation_complete": "Installation complete",
"installed": "Installed", "installed": "Installed",

View file

@ -178,13 +178,13 @@ const routes = [
} }
}, },
{ {
name: 'app-catalog-home', name: 'app-catalog',
path: '/apps/catalog', path: '/apps/catalog',
component: () => import(/* webpackChunkName: "views/apps-catalog" */ '@/views/app/AppCatalog'), component: () => import(/* webpackChunkName: "views/apps-catalog" */ '@/views/app/AppCatalog'),
meta: { meta: {
breadcrumb: [ breadcrumb: [
{ name: 'app-list', trad: 'applications' }, { name: 'app-list', trad: 'applications' },
{ name: 'app-catalog-home', trad: 'catalog' } { name: 'app-catalog', trad: 'catalog' }
] ]
} }
}, },

View file

@ -18,6 +18,11 @@
$alert-padding-x: 1rem; $alert-padding-x: 1rem;
$color-purple: #9932cc;
$theme-colors: (
"best": $color-purple
);
/* /*

View file

@ -20,10 +20,10 @@
</b-input-group-prepend> </b-input-group-prepend>
<b-form-input <b-form-input
id="search-input" :placeholder="$t('search_for_apps')" id="search-input" :placeholder="$t('search_for_apps')"
v-model="search" @input="onSearchInput" v-model="search" @input="setCategory"
/> />
<b-input-group-append> <b-input-group-append>
<b-select v-model="quality" :options="qualityOptions" /> <b-select v-model="quality" :options="qualityOptions" @change="setCategory" />
</b-input-group-append> </b-input-group-append>
</b-input-group> </b-input-group>
@ -43,7 +43,7 @@
</b-card-group> </b-card-group>
<!-- APPS CARDS --> <!-- APPS CARDS -->
<b-card-group v-else deck> <b-card-group v-else-if="filteredApps.length > 0" deck>
<b-card no-body v-for="app in filteredApps" :key="app.id"> <b-card no-body v-for="app in filteredApps" :key="app.id">
<b-card-body class="d-flex flex-column"> <b-card-body class="d-flex flex-column">
<b-card-title class="d-flex"> <b-card-title class="d-flex">
@ -77,7 +77,7 @@
<icon iname="book" /> {{ $t('readme') }} <icon iname="book" /> {{ $t('readme') }}
</b-button> </b-button>
<b-button v-if="app.isInstallable" :variant="app.color"> <b-button v-if="app.isInstallable" :variant="app.color" @click="onAppInstallClick(app)">
<icon iname="plus" /> {{ $t('install') }} <icon v-if="app.color === 'danger'" class="ml-1" iname="warning" /> <icon iname="plus" /> {{ $t('install') }} <icon v-if="app.color === 'danger'" class="ml-1" iname="warning" />
</b-button> </b-button>
<b-button v-else :variant="app.color" disabled> <b-button v-else :variant="app.color" disabled>
@ -86,20 +86,81 @@
</b-button-group> </b-button-group>
</b-card> </b-card>
</b-card-group> </b-card-group>
<!-- NO APPS -->
<b-alert
v-else
variant="warning" show class="mt-4"
>
<icon iname="exclamation-triangle" /> {{ $t('app_not_found') }}
</b-alert>
<!-- INSTALL CUSTOM APP -->
<b-card class="basic-form mt-5">
<template v-slot:header>
<h2><icon iname="download" /> {{ $t('custom_app_install') }}</h2>
</template>
<b-form id="custom-app-form" @submit.prevent="onSubmit">
<b-alert variant="warning" show>
<icon iname="exclamation-triangle" /> {{ $t('confirm_install_custom_app') }}
</b-alert>
<!-- URL -->
<input-helper
id="url" :label="$t('url')"
v-model="form.url" placeholder="https://github.com/USER/REPOSITORY"
:state="form.isValid" :error="form.error" @input="validateUrl"
>
<template v-slot:description>
<icon iname="github" /> {{ $t('custom_app_url_only_github') }}
</template>
</input-helper>
</b-form>
<template v-slot:footer>
<b-button
variant="success"
:disabled="form.url === '' || form.isValid === false"
v-b-modal.custom-app-install-modal
>
{{ $t('install') }}
</b-button>
</template>
</b-card>
<!-- CONFIRM APP INSTALL MODAL -->
<b-modal
id="app-install-modal" centered ref="app-install-modal"
:ok-title="$t('install')" :title="$t('confirm_app_install')"
:header-bg-variant="selectedApp.color"
:header-text-variant="selectedApp.color === 'danger' ? 'light' : 'dark'"
@ok="goToAppInstallForm"
>
{{ $t('confirm_install_app_' + selectedApp.state) }}
</b-modal>
<!-- CONFIRM CUSTOM APP INSTALL MODAL -->
<b-modal
id="custom-app-install-modal" centered
:ok-title="$t('install')" :title="$t('confirm_app_install')"
header-bg-variant="danger" header-text-variant="light"
@ok="goToCustomAppInstallForm"
>
{{ $t('confirm_install_custom_app') }}
</b-modal>
</div> </div>
</template> </template>
<script> <script>
import api from '@/helpers/api' import api from '@/helpers/api'
import InputHelper from '@/components/InputHelper'
export default { export default {
name: 'AppCatalog', name: 'AppCatalog',
data () { data () {
return { return {
category: null,
search: '',
quality: 'all',
searchAppsKeys: ['id', 'state', 'manifest.name'], searchAppsKeys: ['id', 'state', 'manifest.name'],
qualityOptions: [ qualityOptions: [
{ value: 'isHighQuality', text: this.$i18n.t('only_highquality_apps') }, { value: 'isHighQuality', text: this.$i18n.t('only_highquality_apps') },
@ -107,12 +168,26 @@ export default {
{ value: 'isWorking', text: this.$i18n.t('only_working_apps') }, { value: 'isWorking', text: this.$i18n.t('only_working_apps') },
{ value: 'all', text: this.$i18n.t('all_apps') } { value: 'all', text: this.$i18n.t('all_apps') }
], ],
// computed/filled from api data // Computed/filled from api data
categories: [ categories: [
{ text: this.$i18n.t('app_choose_category'), value: null }, { text: this.$i18n.t('app_choose_category'), value: null },
{ text: this.$i18n.t('all_apps'), value: 'all', icon: 'search' } { text: this.$i18n.t('all_apps'), value: 'all', icon: 'search' }
], ],
apps: undefined apps: undefined,
// Set by user inputs
category: null,
search: '',
quality: 'all',
selectedApp: {
// Set some basic values to avoid modal errors
state: 'lowquality',
color: 'warning'
},
form: {
url: '',
isValid: null,
error: this.$i18n.t('form_errors.not_github_link')
}
} }
}, },
@ -199,16 +274,47 @@ export default {
return 'danger' return 'danger'
}, },
onSearchInput () { setCategory () {
// allow search without selecting a category // allow search without selecting a category
if (this.category === null) { if (this.category === null) {
this.category = 'all' this.category = 'all'
} }
},
// INSTALL APP METHODS
onAppInstallClick (app) {
this.selectedApp = app
if (!app.isDecentQuality) {
// Ask for confirmation
this.$refs['app-install-modal'].show()
} else {
this.goToAppInstallForm()
}
},
goToAppInstallForm () {
this.$router.push({ name: 'app-install', params: { id: this.selectedApp.id } })
},
// INSTALL CUSTOM APP METHODS
validateUrl () {
const match = this.form.url.match(/^https:\/\/github.com\/[a-zA-Z0-9-_.]+\/[a-zA-Z0-9-_.]+[/]?$/)
this.form.isValid = match ? null : false
},
goToCustomAppInstallForm () {
this.$router.push({ name: 'app-install-custom', params: { id: this.form.url } })
} }
}, },
created () { created () {
this.fetchData() this.fetchData()
},
components: {
InputHelper
} }
} }
</script> </script>
@ -222,11 +328,10 @@ select {
border-top-left-radius: 0; border-top-left-radius: 0;
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
} }
.card-deck {
.card { .card {
margin-top: 2rem; margin-top: 2rem;
flex-basis: 100%; flex-basis: 100%;
min-height: 12rem;
@include media-breakpoint-up(md) { @include media-breakpoint-up(md) {
flex-basis: 50%; flex-basis: 50%;
max-width: calc(50% - 30px); max-width: calc(50% - 30px);
@ -235,16 +340,24 @@ select {
flex-basis: 33%; flex-basis: 33%;
max-width: calc(33.3% - 30px); max-width: calc(33.3% - 30px);
} }
}
} .app-card {
.category-card { min-height: 12rem;
}
.category-card {
@include media-breakpoint-up(sm) {
min-height: 10rem; min-height: 10rem;
}
border: 0; border: 0;
.btn { .btn {
padding: 1rem;
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
}
} }
.btn-group { .btn-group {

View file

@ -12,7 +12,7 @@
/> />
</b-input-group> </b-input-group>
<div class="buttons"> <div class="buttons">
<b-button variant="success" :to="{ name: 'app-catalog-home' }"> <b-button variant="success" :to="{ name: 'app-catalog' }">
<icon iname="plus" /> {{ $t('install') }} <icon iname="plus" /> {{ $t('install') }}
</b-button> </b-button>
</div> </div>