update DomainInfo page with moar data and config panel

This commit is contained in:
axolotle 2022-02-01 22:17:32 +01:00
parent b7bc950719
commit dcb9534e77
4 changed files with 242 additions and 48 deletions

View file

@ -144,6 +144,14 @@
"disabled": "Disabled", "disabled": "Disabled",
"dns": "DNS", "dns": "DNS",
"domain": { "domain": {
"cert": {
"types": {
"self-signed": "Self-signed",
"lets-encrypt": "Let's Encrypt",
"other-unknown": "Other/Unknown"
},
"valid_for": "valid for {days}"
},
"config": { "config": {
"edit": "Edit domain configuration", "edit": "Edit domain configuration",
"title": "Domain configuration" "title": "Domain configuration"
@ -153,6 +161,19 @@
"auto_config_ignored": "ignored, won't be changed by YunoHost unless you check the overwrite option", "auto_config_ignored": "ignored, won't be changed by YunoHost unless you check the overwrite option",
"auto_config_ok": "Automatic configuration seems to be OK!", "auto_config_ok": "Automatic configuration seems to be OK!",
"auto_config_zone": "Current DNS zone", "auto_config_zone": "Current DNS zone",
"semi_auto_status": {
"activable": "Activable",
"activated": "Activated",
"has_changes": "Has changes",
"unavailable": "Unavailable"
},
"methods": {
"auto": "Automatic",
"handled_in_parent": "Handled in parent domain",
"manual": "Manual",
"none": "None",
"semi_auto": "Semi-automatic"
},
"edit": "Edit DNS configuration", "edit": "Edit DNS configuration",
"info": "The automatic DNS records configuration is an experimental feature. <br>Consider saving your current DNS zone from your DNS registrar's interface before pushing records from here.", "info": "The automatic DNS records configuration is an experimental feature. <br>Consider saving your current DNS zone from your DNS registrar's interface before pushing records from here.",
"manual_config": "Suggested DNS records for manual configuration", "manual_config": "Suggested DNS records for manual configuration",
@ -161,7 +182,20 @@
"push_force_confirm": "Are you sure you want to force push all suggested dns records? Be aware that it may overwrite manually or important default records set by you or your registrar.", "push_force_confirm": "Are you sure you want to force push all suggested dns records? Be aware that it may overwrite manually or important default records set by you or your registrar.",
"push_force_warning": "It looks like some DNS records that YunoHost would have set are already in the registrar configuration. You can use the overwrite option if you know what you are doing." "push_force_warning": "It looks like some DNS records that YunoHost would have set are already in the registrar configuration. You can use the overwrite option if you know what you are doing."
}, },
"explain": {
"main_domain": "The main domain is the domain from which users can connect to the portal (via \"{domain}/yunohost/sso\").<br>Therefore, it is not possible to delete it.<br>If you want to delete \"{domain}\", you will first have to choose or add another domain and set it as the main domain."
},
"info": {
"apps_on_domain": "Apps installed on domain",
"certificate_authority": "SSL Certification authority",
"dns_config_method": "DNS config method",
"dns_semi_auto_config_feature": "Semi-auto DNS configuration feature",
"domain_type": "Domain type",
"registrar": "Registrar"
},
"types": { "types": {
"parent_domain": "Parent domain",
"subdomain": "Subdomain",
"main_domain": "Main domain" "main_domain": "Main domain"
}, },
"toggle_subdomains": "Toggle subdomains" "toggle_subdomains": "Toggle subdomains"
@ -554,8 +588,10 @@
"browse": "Browse", "browse": "Browse",
"collapse": "Collapse", "collapse": "Collapse",
"default": "Default", "default": "Default",
"link": "Link",
"none": "None", "none": "None",
"separator": ", " "separator": ", ",
"valid": "Valid"
}, },
"wrong_password_or_username": "Wrong password or username", "wrong_password_or_username": "Wrong password or username",
"yes": "Yes", "yes": "Yes",

View file

@ -68,7 +68,10 @@ body {
display: block; display: block;
} }
.tooltip { top: 0; }
// Descriptive list (<b-row /> elems with <b-col> inside) // Descriptive list (<b-row /> elems with <b-col> inside)
// FIXME REMOVE when every infos switch to `DescriptionRow`
.row-line { .row-line {
@include media-breakpoint-up(md) { @include media-breakpoint-up(md) {
&:hover { &:hover {

View file

@ -22,6 +22,7 @@ export default {
state: () => ({ state: () => ({
main_domain: undefined, main_domain: undefined,
domains: undefined, // Array domains: undefined, // Array
domains_details: {},
users: undefined, // basic user data: Object {username: {data}} users: undefined, // basic user data: Object {username: {data}}
users_details: {}, // precise user data: Object {username: {data}} users_details: {}, // precise user data: Object {username: {data}}
groups: undefined, groups: undefined,
@ -33,6 +34,22 @@ export default {
state.domains = domains state.domains = domains
}, },
'SET_DOMAINS_DETAILS' (state, [name, { domains }]) {
Vue.set(state.domains_details, name, domains[name])
},
'UPDATE_DOMAINS_DETAILS' (state, payload) {
// FIXME use a common function to execute the same code ?
this.commit('SET_DOMAINS_DETAILS', payload)
},
'DEL_DOMAINS_DETAILS' (state, [name]) {
Vue.delete(state.domains_details, name)
if (state.domains) {
Vue.delete(state.domains, name)
}
},
'ADD_DOMAINS' (state, [{ domain }]) { 'ADD_DOMAINS' (state, [{ domain }]) {
state.domains.push(domain) state.domains.push(domain)
}, },

View file

@ -1,83 +1,221 @@
<template> <template>
<view-base :queries="queries" skeleton="card-list-skeleton"> <view-base :queries="queries" @queries-response="onQueriesResponse" skeleton="card-list-skeleton">
<card :title="name" icon="globe"> <!-- INFO CARD -->
<!-- VISIT --> <card v-if="domain" :title="name" icon="globe">
<p>{{ $t('domain_visit_url', { url: 'https://' + name }) }}</p> <template #header-buttons>
<b-button variant="success" :href="'https://' + name" target="_blank"> <!-- DEFAULT DOMAIN -->
<icon iname="external-link" /> {{ $t('domain_visit') }} <b-button v-if="!isMainDomain" @click="setAsDefaultDomain" variant="info">
</b-button> <icon iname="star" /> {{ $t('set_default') }}
<hr> </b-button>
<!-- DEFAULT DOMAIN --> <!-- DELETE DOMAIN -->
<p>{{ $t('domain_default_desc') }}</p> <b-button @click="deleteDomain" :disabled="isMainDomain" variant="danger">
<p v-if="isMainDomain" class="alert alert-info"> <icon iname="trash-o" /> {{ $t('delete') }}
<icon iname="star" /> {{ $t('domain_default_longdesc') }} </b-button>
</p> </template>
<b-button v-else variant="info" @click="setAsDefaultDomain">
<icon iname="star" /> {{ $t('set_default') }}
</b-button>
<hr>
<!-- DOMAIN CONFIG --> <!-- DOMAIN LINK -->
<p>{{ $t('domain.config.edit') }}</p> <description-row :term="$t('words.link')">
<b-button variant="warning" :to="{ name: 'domain-config', param: { name } }"> <b-link :href="'https://' + name" target="_blank">
<icon iname="cog" /> {{ $t('domain.config.title') }} https://{{ name }}
</b-button> </b-link>
<hr> </description-row>
<!-- DNS CONFIG --> <!-- DOMAIN TYPE -->
<p>{{ $t('domain.dns.edit') }}</p> <description-row
<b-button variant="warning" :to="{ name: 'domain-dns', param: { name } }"> :term="$t('domain.info.domain_type')"
<icon iname="globe" /> {{ $t('domain_dns_config') }} >
</b-button> <span v-if="isMainDomain">
<hr> <icon iname="star" />
<explain-what
id="explain-main-domain"
:title="$t('domain.types.main_domain')"
:content="$t('domain.explain.main_domain', { domain: name })"
>{{ $t('domain.types.main_domain') }}</explain-what>
,&nbsp;
</span>
{{ $t('domain.types.' + (parentName ? 'subdomain' : 'parent_domain' )) }}
</description-row>
<!-- DELETE --> <!-- DOMAIN CERT AUTHORITY -->
<p>{{ $t('domain_delete_longdesc') }}</p> <description-row :term="$t('domain.info.certificate_authority')">
<p <icon :iname="cert.status.icon" :variant="cert.status.variant" class="mr-1" />
v-if="isMainDomain" class="alert alert-info" {{ $t('domain.cert.types.' + cert.authority) }}
v-html="$t('domain_delete_forbidden_desc', { domain: name })" ({{ $t('domain.cert.valid_for', { days: $tc('day_validity', cert.validity) }) }})
/> </description-row>
<b-button v-else variant="danger" @click="deleteDomain">
<icon iname="trash-o" /> {{ $t('delete') }} <!-- DOMAIN DNS METHOD -->
</b-button> <description-row v-if="dns" :term="$t('domain.info.dns_config_method')">
{{ $t('domain.dns.methods.' + dns.method) }}
<template v-if="dns.method === 'semi_auto'">
(<icon :iname="semiAuto.icon" :variant="semiAuto.variant" class="mr-1" />
{{ $t('domain.dns.semi_auto_status.' + dns.semi_auto_status) }})
</template>
<template v-else-if="dns.method === 'handled_in_parent'">
<b-button
variant="outline-dark" size="xs" class="py-0 ml-1"
:to="{ name: 'domain-info', params: { name: parentName }}"
>
{{ parentName }}
</b-button>
</template>
</description-row>
<!-- DOMAIN SEMI-AUTO OPTIONAL FEATURE STATUS -->
<description-row
v-if="!['semi_auto', 'auto', 'handled_in_parent', 'none'].includes(dns.method)"
:term="$t('domain.info.dns_semi_auto_config_feature')"
>
<icon :iname="semiAuto.icon" :variant="semiAuto.variant" class="mr-1" />
{{ $t('domain.dns.semi_auto_status.' + dns.semi_auto_status) }}
</description-row>
<!-- DOMAIN REGISTRAR -->
<description-row v-if="dns.registrar" :term="$t('domain.info.registrar')" :details="dns.registrar" />
<!-- DOMAIN APPS -->
<description-row :term="$t('domain.info.apps_on_domain')">
<b-button-group
v-for="app in domain.apps" :key="app.id"
size="sm" class="mr-2"
>
<b-button class="py-0 font-weight-bold" variant="outline-dark" :to="{ name: 'app-info', params: { id: app.id }}">
{{ app.name }}
</b-button>
<b-button
variant="outline-dark" class="py-0 px-1"
:href="'https://' + name + app.path" target="_blank"
>
<span class="sr-only">{{ $t('app.visit_app') }}</span>
<icon iname="external-link" />
</b-button>
</b-button-group>
{{ domain.apps ? '' : $t('words.none') }}
</description-row>
</card> </card>
<config-panels v-if="config.panels" v-bind="config" @submit="applyConfig" />
</view-base> </view-base>
</template> </template>
<script> <script>
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import api from '@/api' import api, { objectToParams } from '@/api'
import {
formatFormData,
formatYunoHostConfigPanels
} from '@/helpers/yunohostArguments'
import ConfigPanels from '@/components/ConfigPanels'
export default { export default {
name: 'DomainInfo', name: 'DomainInfo',
props: { components: {
name: { ConfigPanels
type: String,
required: true
}
}, },
data: () => { props: {
name: { type: String, required: true }
},
data () {
return { return {
queries: [ queries: [
['GET', { uri: 'domains/main', storeKey: 'main_domain' }] ['GET', { uri: 'domains', storeKey: 'domains' }],
] ['GET', { uri: 'domains/main', storeKey: 'main_domain' }],
['GET', { uri: 'domains', storeKey: 'domains_details', param: this.name }],
['GET', `domains/${this.name}/config?full`]
],
config: {}
} }
}, },
computed: { computed: {
...mapGetters(['mainDomain']), ...mapGetters(['mainDomain']),
domain () {
return this.$store.getters.domain(this.name)
},
parentName () {
return this.$store.getters.highestDomainParentName(this.name)
},
certStatus () {
const cert = this.domain.certificate
const trad = (cert.validity < 15 ? 'renew.' : '') + cert.authority
if (cert.validity <= 0) {
return { trad: 'invalid', icon: 'times', variant: 'danger' }
} else if (cert.authority === 'other-unknown') {
return cert.validity < 15
? { trad, icon: 'exclamation', variant: 'danger' }
: { trad, icon: 'check', variant: 'success' }
} else if (cert.authority === 'lets-encrypt') {
return { trad, icon: 'thumbs-up', variant: 'success' }
}
return { trad, icon: 'exclamation', variant: 'warning' }
},
cert () {
return {
...this.domain.certificate,
status: this.certStatus
}
},
dns () {
return this.domain.dns
},
isMainDomain () { isMainDomain () {
if (!this.mainDomain) return if (!this.mainDomain) return
return this.name === this.mainDomain return this.name === this.mainDomain
},
semiAuto () {
const status = this.dns.semi_auto_status
if (!status) return
if (status === 'unavailable') return { icon: 'times', variant: 'danger' }
if (status === 'activable') return { icon: 'check', variant: 'info' }
if (status === 'activated') return { icon: 'check', variant: 'success' }
// FIXME mutate status on push --dry_run (misconfigured + has_diff)
if (status === 'has_changes') return { icon: 'wrench', variant: 'warning' }
if (status === 'misconfigured') return { icon: 'times', variant: 'danger' }
return undefined
} }
}, },
methods: { methods: {
onQueriesResponse (domains, mainDomain, domain, config) {
this.config = formatYunoHostConfigPanels(config)
},
async applyConfig (id_) {
const formatedData = await formatFormData(
this.config.forms[id_],
{ removeEmpty: false, removeNull: true, multipart: false }
)
api.put(
`domains/${this.name}/config`,
{ key: id_, args: objectToParams(formatedData) },
{ key: 'domains.update_config', name: this.name }
).then(() => {
this.$refs.view.fetchQueries({ triggerLoading: true })
}).catch(err => {
if (err.name !== 'APIBadRequestError') throw err
const panel = this.config.panels.find(({ id }) => id_ === id)
if (err.data.name) {
this.config.errors[id_][err.data.name].message = err.message
} else this.$set(panel, 'serverError', err.message)
})
},
async deleteDomain () { async deleteDomain () {
const confirmed = await this.$askConfirmation(this.$i18n.t('confirm_delete', { name: this.name })) const confirmed = await this.$askConfirmation(this.$i18n.t('confirm_delete', { name: this.name }))
if (!confirmed) return if (!confirmed) return