source: Implement actions on importable after migration domains

This commit is contained in:
nemunaire 2021-01-14 03:03:10 +01:00
parent 518775341e
commit 735d68e7af
7 changed files with 177 additions and 6 deletions

View File

@ -36,6 +36,7 @@ import (
"fmt"
"io"
"io/ioutil"
"net/http"
"strconv"
"git.happydns.org/happydns/config"
@ -54,6 +55,9 @@ func init() {
router.GET("/api/sources/:sid/domains", apiAuthHandler(sourceHandler(getDomainsHostedBySource)))
router.GET("/api/sources/:sid/domains_with_actions", apiAuthHandler(sourceHandler(getDomainsWithActionsHostedBySource)))
router.POST("/api/sources/:sid/domains_with_actions", apiAuthHandler(sourceHandler(doDomainsWithActionsHostedBySource)))
router.GET("/api/sources/:sid/available_resource_types", apiAuthHandler(sourceHandler(getAvailableResourceTypes)))
}
@ -227,6 +231,58 @@ func getDomainsHostedBySource(_ *config.Options, req *RequestResources, body io.
}
}
func getDomainsWithActionsHostedBySource(_ *config.Options, req *RequestResources, body io.Reader) Response {
sr, ok := req.Source.Source.(sources.ListDomainsWithActionsSource)
if !ok {
return APIErrorResponse{
err: fmt.Errorf("Source doesn't support domain listing."),
}
}
if domains, err := sr.ListDomainsWithActions(); err != nil {
return APIErrorResponse{
err: err,
}
} else {
return APIResponse{
response: domains,
}
}
}
func doDomainsWithActionsHostedBySource(_ *config.Options, req *RequestResources, body io.Reader) Response {
sr, ok := req.Source.Source.(sources.ListDomainsWithActionsSource)
if !ok {
return APIErrorResponse{
err: fmt.Errorf("Source doesn't support domain listing."),
}
}
var us sources.ImportableDomain
err := json.NewDecoder(body).Decode(&us)
if err != nil {
return APIErrorResponse{
err: err,
}
}
if len(us.FQDN) == 0 {
return APIErrorResponse{
err: fmt.Errorf("Domain to act on not filled"),
}
}
success, err := sr.ActionOnListedDomain(us.FQDN, us.BtnAction)
status := http.StatusBadRequest
if success {
status = http.StatusOK
}
return APIErrorResponse{
err: err,
status: status,
}
}
func getAvailableResourceTypes(_ *config.Options, req *RequestResources, body io.Reader) Response {
lrt, ok := req.Source.Source.(sources.LimitedResourceTypesSource)
if !ok {

View File

@ -48,6 +48,14 @@ export default {
return Api().get('/api/sources/' + encodeURIComponent(srcId) + '/domains')
},
listSourceDomainsWithActions (srcId) {
return Api().get('/api/sources/' + encodeURIComponent(srcId) + '/domains_with_actions')
},
doSourceDomainsAction (srcId, fqdn, action) {
return Api().post('/api/sources/' + encodeURIComponent(srcId) + '/domains_with_actions', { fqdn, action })
},
deleteSource (source) {
return Api().delete('/api/sources/' + encodeURIComponent(source._id))
}

View File

@ -34,12 +34,22 @@
<template>
<h-zone-list :domains="listImportableDomains" loading-str="wait.asking-domains" :parent-is-loading="isLoading">
<template #badges="{ domain }">
<b-badge v-if="haveDomain(domain)" class="ml-1" variant="success">
<b-badge v-if="domain.state" class="ml-1" :variant="domain.state">
<b-icon v-if="domain.state === 'success'" icon="check" />
<b-icon v-else-if="domain.state === 'info'" icon="exclamation-circle" />
<b-icon v-else-if="domain.state === 'warning'" icon="exclamation-triangle" />
<b-icon v-else-if="domain.state === 'danger'" icon="exclamation-octagon" />
{{ domain.message }}
</b-badge>
<b-badge v-else-if="haveDomain(domain)" class="ml-1" variant="success">
<b-icon icon="check" /> {{ $t('service.already') }}
</b-badge>
<b-button v-else type="button" class="ml-1" variant="primary" size="sm" @click="importDomain(domain)">
{{ $t('domains.add-now') }}
</b-button>
<b-button v-if="domain.btn" type="button" class="ml-1" :variant="domain.state" size="sm" @click="doDomainAction(domain)">
{{ $t(domain.btn) }}
</b-button>
</template>
<template v-if="noDomainsList" #no-domain>
<b-list-group-item class="text-center">
@ -79,6 +89,10 @@ export default {
type: Boolean,
default: false
},
showDomainsWithActions: {
type: Boolean,
default: false
},
source: {
type: Object,
required: true
@ -87,6 +101,7 @@ export default {
data: function () {
return {
domainsWithActions: null,
importableDomains: null
}
},
@ -101,7 +116,7 @@ export default {
return []
}
var ret = this.importableDomains
let ret = this.importableDomains
if (!this.showAlreadyImported) {
ret = ret.filter(
@ -109,7 +124,15 @@ export default {
)
}
return ret.map(d => ({ domain: d, id_source: this.source._id }))
ret = ret.map(d => ({ domain: d, id_source: this.source._id }))
if (this.domainsWithActions && this.showDomainsWithActions) {
this.domainsWithActions.forEach((dwa) => {
ret.push({ domain: dwa.fqdn, state: dwa.state, message: dwa.message, btn: dwa.btn, action: dwa.action, id_source: this.source._id })
})
}
return ret
},
noDomainsList () {
@ -144,20 +167,48 @@ export default {
},
methods: {
doDomainAction (domain) {
SourcesApi.doSourceDomainsAction(this.source._id, domain.domain, domain.action)
.then(
response => {
this.$store.dispatch('domains/getAllMyDomains')
if (response.data.errmsg) {
this.$root.$bvToast.toast(
response.data.errmsg, {
title: this.$t('domains.attached-new'),
autoHideDelay: 5000,
variant: 'success',
toaster: 'b-toaster-content-right'
}
)
}
},
error => {
this.$root.$bvToast.toast(
error.response.data.errmsg, {
title: this.$t('errors.domain-attach'),
autoHideDelay: 5000,
variant: 'danger',
toaster: 'b-toaster-content-right'
}
)
})
},
haveDomain (domain) {
return this.domains_getAll.find(i => i.domain === domain.domain)
},
importAllDomains () {
this.listImportableDomains.forEach((domain) => {
if (!this.haveDomain(domain)) {
if (!domain.state && !this.haveDomain(domain)) {
this.importDomain(domain)
}
})
},
importDomain (domain) {
var vm = this
const vm = this
this.addDomainToSource(this.source, domain.domain, null, function (data) {
vm.$store.dispatch('domains/getAllMyDomains')
})
@ -189,6 +240,24 @@ export default {
)
this.$router.replace('/sources/' + encodeURIComponent(this.mySource._id))
})
if (!this.showDomainsWithActions || this.sourceSpecs_getAll[this.source._srctype].capabilities.indexOf('ListDomainsWithActions') === -1) {
return
}
SourcesApi.listSourceDomainsWithActions(this.source._id)
.then(
response => (this.domainsWithActions = response.data),
error => {
this.$root.$bvToast.toast(
error.response.data.errmsg, {
title: this.$t('errors.domain-access'),
autoHideDelay: 5000,
variant: 'danger',
toaster: 'b-toaster-content-right'
}
)
})
}
}
}

View File

@ -51,6 +51,7 @@
"domains": {
"kind": "domain",
"actions": {
"do-migration": "Migrate now",
"reimport": "Re-import",
"view": "View my zone",
"propagate": "Publish my changes",

View File

@ -45,6 +45,9 @@ import {
BIconChevronLeft,
BIconChevronRight,
BIconChevronUp,
BIconExclamationCircle,
BIconExclamationOctagon,
BIconExclamationTriangle,
BIconGridFill,
BIconLink,
BIconLink45deg,
@ -126,6 +129,9 @@ Vue.component('BIconChevronDown', BIconChevronDown)
Vue.component('BIconChevronLeft', BIconChevronLeft)
Vue.component('BIconChevronRight', BIconChevronRight)
Vue.component('BIconChevronUp', BIconChevronUp)
Vue.component('BIconExclamationCircle', BIconExclamationCircle)
Vue.component('BIconExclamationOctagon', BIconExclamationOctagon)
Vue.component('BIconExclamationTriangle', BIconExclamationTriangle)
Vue.component('BIconGridFill', BIconGridFill)
Vue.component('BIconLink', BIconLink)
Vue.component('BIconLink45deg', BIconLink45deg)

View File

@ -56,7 +56,7 @@
</b-button>
</div>
</template>
<h-source-list-domains ref="newDomains" :source="filteredSource" @no-domains-list-change="noDomainsList = $event" />
<h-source-list-domains ref="newDomains" :source="filteredSource" show-domains-with-actions @no-domains-list-change="noDomainsList = $event" />
</b-card>
<h-list-group-input-new-domain v-if="$refs.zlist && !$refs.zlist.isLoading && (!filteredSource || noDomainsList)" autofocus class="mt-2" :my-source="filteredSource" />
</b-col>

View File

@ -51,12 +51,39 @@ type SourceInfos struct {
Capabilities []string `json:"capabilities,omitempty"`
}
// ImportableDomain is a structure dedicated to handle actions associated with special domains.
type ImportableDomain struct {
// FQDN is the domain FQDN.
FQDN string `json:"fqdn"`
// State represents the importable state of the domaine between: danger, warning, info, success.
State string `json:"state,omitempty"`
// Msg is the message to display to the user.
Msg string `json:"message,omitempty"`
// Btn is the label to display on the button (no message = no button).
Btn string `json:"btn,omitempty"`
// BtnAction is the name of the event raised by clicking on the button.
BtnAction string `json:"action,omitempty"`
}
// ListDomainsSource are functions to declare when we can retrives a list of managable domains from the given Source.
type ListDomainsSource interface {
// ListDomains retrieves the list of avaiable domains inside the Source.
ListDomains() ([]string, error)
}
// ListDomainsWithActionsSource are functions to declare when we can retrives a list of domains from the given Source, but not directly importable, it requires or can handle actions.
type ListDomainsWithActionsSource interface {
// ListDomains retrieves the list of avaiable domains inside the Source.
ListDomainsWithActions() ([]ImportableDomain, error)
// ActionOnListedDomain calls the action designated on the button for the given domain.
ActionOnListedDomain(string, string) (bool, error)
}
type LimitedResourceTypesSource interface {
ListAvailableTypes() []uint16
}
@ -69,6 +96,10 @@ func GetSourceCapabilities(src happydns.Source) (caps []string) {
caps = append(caps, "ListDomains")
}
if _, ok := src.(ListDomainsWithActionsSource); ok {
caps = append(caps, "ListDomainsWithActions")
}
if _, ok := src.(forms.CustomSettingsForm); ok {
caps = append(caps, "CustomSettingsForm")
}