Rework new domain page, following new source creation method

This commit is contained in:
nemunaire 2020-07-19 04:35:20 +02:00
parent c4e8f5f807
commit f439ad605d
9 changed files with 373 additions and 283 deletions

View File

@ -48,9 +48,16 @@ func init() {
type SourceSettingsState struct {
happydns.Source
Name string `json:"name"`
State int32 `json:"state"`
Recall *int64 `json:"recall,omitempty"`
Name string `json:"_comment"`
State int32 `json:"state"`
Recall *int64 `json:"recall,omitempty"`
Redirect *string `json:"redirect,omitempty"`
}
type SourceSettingsResponse struct {
happydns.Source `json:"Source,omitempty"`
From *sources.CustomForm `json:"form,omitempty"`
Redirect *string `json:"redirect,omitempty"`
}
func getSourceSettingsState(cfg *config.Options, req *RequestResources, body io.Reader) Response {
@ -79,6 +86,7 @@ func getSourceSettingsState(cfg *config.Options, req *RequestResources, body io.
if uss.Recall != nil {
req.Session.GetValue(fmt.Sprintf("source-creation-%d", *uss.Recall), src)
req.Session.GetValue(fmt.Sprintf("source-creation-%d-name", *uss.Recall), &uss.Name)
req.Session.GetValue(fmt.Sprintf("source-creation-%d-next", *uss.Recall), &uss.Redirect)
}
var form *sources.CustomForm
@ -95,6 +103,9 @@ func getSourceSettingsState(cfg *config.Options, req *RequestResources, body io.
key, recallid := req.Session.FindNewKey("source-creation-")
req.Session.SetValue(key, src)
req.Session.SetValue(key+"-name", uss.Name)
if uss.Redirect != nil {
req.Session.SetValue(key+"-next", *uss.Redirect)
}
return recallid
})
}
@ -114,12 +125,17 @@ func getSourceSettingsState(cfg *config.Options, req *RequestResources, body io.
}
} else {
return APIResponse{
response: s,
response: SourceSettingsResponse{
Source: s,
Redirect: uss.Redirect,
},
}
}
}
return APIResponse{
response: form,
response: SourceSettingsResponse{
From: form,
},
}
}

View File

@ -0,0 +1,119 @@
<!--
Copyright or © or Copr. happyDNS (2020)
contact@happydns.org
This software is a computer program whose purpose is to provide a modern
interface to interact with DNS systems.
This software is governed by the CeCILL license under French law and abiding
by the rules of distribution of free software. You can use, modify and/or
redistribute the software under the terms of the CeCILL license as
circulated by CEA, CNRS and INRIA at the following URL
"http://www.cecill.info".
As a counterpart to the access to the source code and rights to copy, modify
and redistribute granted by the license, users are provided only with a
limited warranty and the software's author, the holder of the economic
rights, and the successive licensors have only limited liability.
In this respect, the user's attention is drawn to the risks associated with
loading, using, modifying and/or developing or reproducing the software by
the user in light of its specific status of free software, that may mean
that it is complicated to manipulate, and that also therefore means that it
is reserved for developers and experienced professionals having in-depth
computer knowledge. Users are therefore encouraged to load and test the
software's suitability as regards their requirements in conditions enabling
the security of their systems and/or data to be ensured and, more generally,
to use and operate it in the same conditions as regards security.
The fact that you are presently reading this means that you have had
knowledge of the CeCILL license and that you accept its terms.
-->
<template>
<b-modal id="modal-add-source" scrollable size="lg" title="New Source Form" :ok-title="state >= 0 ? 'OK' : 'Next >'" :ok-disabled="!mySource" @ok="handleModalSourceSubmit">
<template v-if="state >= 0 && form" v-slot:modal-footer>
<h-source-state-buttons :form="form" @previousState="handleModalSourcePrevious" @nextState="handleModalSourceSubmit" />
</template>
<div v-if="state < 0">
<p>
First, you need to select the provider hosting your domain:
</p>
<h-new-source-selector v-model="mySource" />
</div>
<div v-else-if="mySource">
<h-source-state v-model="settings" class="mt-2 mb-2" :form="form" :source-name="sourceSpecs[mySource].name" :state="state" @submit="handleModalSourceSubmit" />
</div>
</b-modal>
</template>
<script>
import SourceState from '@/mixins/sourceState'
export default {
name: 'HModalAddSource',
components: {
hNewSourceSelector: () => import('@/components/hNewSourceSelector'),
hSourceState: () => import('@/components/hSourceState'),
hSourceStateButtons: () => import('@/components/hSourceStateButtons')
},
mixins: [SourceState],
methods: {
handleModalSourcePrevious () {
if (this.form.previousButtonState <= 0) {
this.state = this.form.previousButtonState
this.form = null
} else {
this.loadState(this.form.previousButtonState)
}
},
handleModalSourceSubmit (bvModalEvt) {
if (bvModalEvt) {
bvModalEvt.preventDefault()
}
if (this.form) {
if (this.form.nextButtonState !== undefined) {
if (this.form.nextButtonState === -1) {
this.state = this.form.nextButtonState
this.form = null
} else {
this.loadState(
this.form.nextButtonState,
null,
(_, newSource) => {
if (newSource) {
this.hide()
this.$emit('done', newSource)
}
}
)
}
} else if (this.form.nextButtonLink !== undefined) {
window.location = this.form.nextButtonLink
}
} else {
this.loadState(0)
}
},
hide () {
this.$bvModal.hide('modal-add-source')
},
show () {
this.mySource = ''
this.resetSettings()
this.settings.redirect = window.location.pathname
this.state = -1
this.$bvModal.show('modal-add-source')
}
}
}
</script>

View File

@ -32,15 +32,18 @@
-->
<template>
<div v-if="!isLoading" class="d-flex flex-row justify-content-around flex-wrap align-self-center">
<div v-for="(src, index) in sources" :key="index" type="button" class="p-3 source" @click="$emit('sourceSelected', src, index)">
<img :src="'/api/source_specs/' + index + '.png'" :alt="src.name">
{{ src.name }}<br>
<p class="text-muted" style="position: absolute;font-size: 80%;margin-top: 10.5em;width: 20%">
{{ src.description }}
</p>
</div>
</div>
<b-list-group>
<b-list-group-item v-if="isLoading" class="d-flex justify-content-center align-items-center">
<b-spinner variant="primary" label="Spinning" class="mr-3" /> Retrieving usable sources...
</b-list-group-item>
<b-list-group-item v-for="(src, idx) in sources" :key="idx" :active="srcSelected === idx" button @click="selectSource(idx)">
<div class="d-inline-block text-center" style="width: 50px;">
<img :src="'/api/source_specs/' + idx + '.png'" :alt="src.name" style="max-width: 100%; max-height: 2.5em; margin: -.6em .4em -.6em -.6em">
</div>
<strong>{{ src.name }}</strong> &ndash;
<small class="text-muted" :title="src.description">{{ src.description }}</small>
</b-list-group-item>
</b-list-group>
</template>
<script>
@ -49,9 +52,17 @@ import axios from 'axios'
export default {
name: 'HNewSourceSelector',
props: {
value: {
type: String,
default: null
}
},
data: function () {
return {
sources: null
sources: null,
srcSelected: null
}
},
@ -65,27 +76,16 @@ export default {
axios
.get('/api/source_specs')
.then(response => (this.sources = response.data))
},
methods: {
selectSource (idx) {
if (this.value !== null) {
this.srcSelected = idx
this.$emit('input', this.srcSelected)
}
this.$emit('sourceSelected', idx)
}
}
}
</script>
<style>
.source {
box-shadow: 2px 2px black;
border: 1px solid black;
display: flex;
flex-direction: column;
align-items: center;
margin: 2.5% 0;
width: 30%;
max-width: 200px;
height: 150px;
text-align: center;
vertical-align: middle;
}
.source img {
max-width: 100%;
max-height: 90%;
padding: 2%;
}
</style>

View File

@ -42,7 +42,7 @@
<b-list-group-item v-for="(source, index) in sources" :key="index" button class="d-flex justify-content-between align-items-center" @click="selectSource(source)">
<div>
<div class="d-inline-block text-center" style="width: 50px;">
<img :src="'/api/source_specs/' + source._srctype + '.png'" :alt="sources_specs[source._srctype].name" :title="sources_specs[source._srctype].name" style="max-width: 100%; max-height: 2.5em; margin: -.6em .4em -.6em -.6em">
<img v-if="sources_specs" :src="'/api/source_specs/' + source._srctype + '.png'" :alt="sources_specs[source._srctype].name" :title="sources_specs[source._srctype].name" style="max-width: 100%; max-height: 2.5em; margin: -.6em .4em -.6em -.6em">
</div>
<span v-if="source._comment">{{ source._comment }}</span>
<em v-else>No name</em>
@ -104,19 +104,10 @@ export default {
},
mounted () {
setTimeout(() => {
axios
.get('/api/domains')
.then(response => { this.domains = response.data })
axios
.get('/api/sources')
.then(response => {
this.sources = response.data
if (this.sources.length === 0 && this.execNewIfEmpty) {
this.$emit('newSource')
}
})
}, 100)
axios
.get('/api/domains')
.then(response => { this.domains = response.data })
this.updateSources()
axios
.get('/api/source_specs')
.then(response => { this.sources_specs = response.data })
@ -125,6 +116,17 @@ export default {
methods: {
selectSource (source) {
this.$emit('sourceSelected', source)
},
updateSources () {
axios
.get('/api/sources')
.then(response => {
this.sources = response.data
if (this.sources.length === 0 && this.emitNewIfEmpty) {
this.$emit('newSource')
}
})
}
}
}

View File

@ -0,0 +1,130 @@
// Copyright or © or Copr. happyDNS (2020)
//
// contact@happydns.org
//
// This software is a computer program whose purpose is to provide a modern
// interface to interact with DNS systems.
//
// This software is governed by the CeCILL license under French law and abiding
// by the rules of distribution of free software. You can use, modify and/or
// redistribute the software under the terms of the CeCILL license as
// circulated by CEA, CNRS and INRIA at the following URL
// "http://www.cecill.info".
//
// As a counterpart to the access to the source code and rights to copy, modify
// and redistribute granted by the license, users are provided only with a
// limited warranty and the software's author, the holder of the economic
// rights, and the successive licensors have only limited liability.
//
// In this respect, the user's attention is drawn to the risks associated with
// loading, using, modifying and/or developing or reproducing the software by
// the user in light of its specific status of free software, that may mean
// that it is complicated to manipulate, and that also therefore means that it
// is reserved for developers and experienced professionals having in-depth
// computer knowledge. Users are therefore encouraged to load and test the
// software's suitability as regards their requirements in conditions enabling
// the security of their systems and/or data to be ensured and, more generally,
// to use and operate it in the same conditions as regards security.
//
// The fact that you are presently reading this means that you have had
// knowledge of the CeCILL license and that you accept its terms.
import SourceSettingsApi from '@/services/SourceSettingsApi'
import SourceSpecsApi from '@/services/SourceSpecsApi'
export default {
data () {
return {
form: null,
mySource: null,
settings: null,
state: 0,
sourceSpecs: null
}
},
computed: {
isLoading () {
return this.form == null || this.sourceSpecs == null
}
},
mounted () {
this.resetSettings()
this.updateSourceSettingsForm()
},
methods: {
loadState (toState, recallid, cbSuccess, cbFail) {
SourceSettingsApi.getSourceSettings(this.mySource, toState, this.settings, recallid)
.then(
response => {
if (response.data.form) {
this.form = response.data.form
if (cbSuccess) {
cbSuccess(toState)
} else {
this.state = toState
}
} else if (response.data.Source) {
this.$root.$bvToast.toast(
'Done', {
title: (response.data.Source._comment ? response.data.Source._comment : 'Your new source') + ' has been added.',
autoHideDelay: 5000,
variant: 'success',
toaster: 'b-toaster-content-right'
}
)
if (response.data.redirect && window.location.pathname !== response.data.redirect) {
this.$router.push(response.data.redirect)
} else if (cbSuccess) {
cbSuccess(toState, response.data.Source)
} else {
this.state = toState
}
}
},
error => {
this.$root.$bvToast.toast(
error.response.data.errmsg, {
title: 'Something went wrong during source configuration validation',
autoHideDelay: 5000,
variant: 'danger',
toaster: 'b-toaster-content-right'
}
)
if (cbFail) {
cbFail(error.response.data)
}
})
},
resetSettings () {
this.settings = {
Source: {},
_comment: '',
redirect: null
}
},
updateSourceSettingsForm () {
SourceSpecsApi.getSourceSpecs()
.then(
response => (this.sourceSpecs = response.data),
error => {
this.$root.$bvToast.toast(
error.response.data.errmsg, {
title: 'Something went wrong during source configuration',
autoHideDelay: 5000,
variant: 'danger',
toaster: 'b-toaster-content-right'
}
)
})
if (this.mySource && this.state >= 0) {
this.loadState(this.state, this.$route.query.recall)
}
}
}
}

View File

@ -32,20 +32,17 @@
import Api from '@/services/Api'
export default {
getSourceSettings (source, state, settings, srcName, recallid) {
getSourceSettings (source, state, settings, recallid) {
if (!state) {
state = 0
}
var data = { state: state }
if (settings) {
data.Source = settings
}
if (srcName) {
data.name = srcName
if (!settings) {
settings = {}
}
settings.state = state
if (recallid) {
data.recall = parseInt(recallid)
settings.recall = parseInt(recallid)
}
return Api().post('/api/source_settings/' + source, data)
return Api().post('/api/source_settings/' + source, settings)
}
}

View File

@ -39,69 +39,22 @@
</button>
Select the source where lives your domain <span class="text-monospace">{{ $route.params.domain }}</span>
</h1>
<hr style="margin-bottom:0">
<b-row v-if="step === 0" class="mb-5">
<b-col>
<h3>
Your existing sources
</h3>
<div v-if="validating" class="d-flex justify-content-center align-items-center">
<b-spinner variant="primary" label="Spinning" class="mr-3" /> Validating domain &hellip;
</div>
<h-user-source-selector :sources="sources" @sourceSelected="selectExistingSource" />
</b-col>
<b-col lg="6">
<h3>Use a new source</h3>
<b-row v-else>
<b-col offset-md="2" md="8">
<source-list ref="sourceList" emit-new-if-empty @newSource="newSource" @sourceSelected="selectExistingSource" />
<h-new-source-selector @sourceSelected="selectNewSource" />
<p class="text-center mt-3">
Can't find the source here? <a href="#" @click.prevent="newSource">Add it now!</a>
</p>
</b-col>
</b-row>
<div v-if="step & 1">
<b-row>
<b-col lg="4" md="5" class="bg-light">
<div class="text-center mb-3 mt-2">
<img :src="'/api/source_specs/' + source_specs_selected + '.png'" :alt="sources[source_specs_selected].name" style="max-width: 100%; max-height: 10em">
</div>
<h3>
{{ sources[source_specs_selected].name }}
</h3>
<p class="text-muted text-justify">
{{ sources[source_specs_selected].description }}
</p>
</b-col>
<b-col lg="8" md="7">
<form v-if="!isLoading" class="mt-2 mb-5" @submit.stop.prevent="submitNewSource">
<h-resource-value-simple-input
id="src-name"
v-model="new_source_name"
edit
:index="0"
label="Name your source"
description="Give an explicit name in order to easily find this service."
:placeholder="sources[source_specs_selected].name + ' 1'"
required
/>
<h-fields v-model="source_specs_values" edit :fields="source_specs.fields" />
<div class="ml-3 mr-3">
<b-button type="button" variant="secondary" @click="step=step&(~1)">
&lt; Use another source
</b-button>
<b-button class="float-right" type="submit" variant="primary">
Add this source &gt;
</b-button>
</div>
</form>
</b-col>
</b-row>
</div>
<div v-if="step & 2 && isLoading" class="d-flex justify-content-center align-items-center">
<b-spinner variant="primary" label="Spinning" class="mr-3" /> Validating source...
</div>
<h-modal-add-source ref="addSrcModal" @done="doneAdd" />
</b-container>
</template>
@ -111,70 +64,27 @@ import axios from 'axios'
export default {
components: {
hFields: () => import('@/components/hFields'),
hNewSourceSelector: () => import('@/components/hNewSourceSelector'),
hResourceValueSimpleInput: () => import('@/components/hResourceValueSimpleInput'),
hUserSourceSelector: () => import('@/components/hUserSourceSelector')
hModalAddSource: () => import('@/components/hModalAddSource'),
sourceList: () => import('@/components/sourceList')
},
data: function () {
return {
new_source_name: '',
sources: null,
source_specs: null,
source_specs_selected: null,
source_specs_values: {},
step: 0
validating: false
}
},
computed: {
isLoading () {
if (this.step === 0) {
return this.sources == null
} else if (this.step & 1) {
return this.source_specs_selected == null || this.source_specs == null
} else if (this.step & 2) {
return true
} else {
return false
}
}
},
mounted () {
axios
.get('/api/source_specs')
.then(response => (this.sources = response.data))
},
methods: {
doneAdd () {
this.$refs.sourceList.updateSources()
},
selectNewSource (_, sourceSpec) {
this.step |= 1
this.source_specs_selected = sourceSpec
axios
.get('/api/source_specs/' + encodeURIComponent(sourceSpec))
.then(
response => {
this.source_specs = response.data
},
error => {
this.step &= ~1
this.$bvToast.toast(
error.response.data.errmsg, {
title: 'An error occurs when creating the source!',
autoHideDelay: 5000,
variant: 'danger',
toaster: 'b-toaster-content-right'
}
)
}
)
newSource () {
this.$refs.addSrcModal.show()
},
selectExistingSource (source) {
this.step |= 2
this.validating = true
axios
.post('/api/domains', {
@ -188,43 +98,16 @@ export default {
title: 'New domain attached to happyDNS!',
autoHideDelay: 5000,
variant: 'success',
href: 'domains/' + encodeURIComponent(response.data.domain),
toaster: 'b-toaster-content-right'
}
)
this.$router.push('/')
this.$router.push('/domains/' + encodeURIComponent(response.data.domain))
},
(error) => {
this.step &= ~2
this.validating = false
this.$bvToast.toast(
error.response.data.errmsg, {
title: 'An error occurs when creating the source!',
autoHideDelay: 5000,
variant: 'danger',
toaster: 'b-toaster-content-right'
}
)
}
)
},
submitNewSource () {
var mySource = {
_srctype: this.source_specs_selected,
_comment: this.new_source_name,
Source: this.source_specs_values
}
axios
.post('/api/sources', mySource)
.then(
(response) => {
this.selectExistingSource(response.data)
},
(error) => {
this.$bvToast.toast(
error.response.data.errmsg, {
title: 'An error occurs when creating the source!',
title: 'An error occurs when adding the domain!',
autoHideDelay: 5000,
variant: 'danger',
toaster: 'b-toaster-content-right'
@ -233,7 +116,6 @@ export default {
}
)
}
}
}
</script>

View File

@ -32,7 +32,11 @@
-->
<template>
<h-new-source-selector @sourceSelected="selectNewSource" />
<b-row class="my-3">
<b-col offset-md="2" md="8">
<h-new-source-selector @sourceSelected="selectNewSource" />
</b-col>
</b-row>
</template>
<script>
@ -43,7 +47,7 @@ export default {
},
methods: {
selectNewSource (src, index) {
selectNewSource (index, src) {
this.$router.push('/sources/new/' + encodeURIComponent(index) + '/0')
}
}

View File

@ -64,8 +64,7 @@
</template>
<script>
import SourceSettingsApi from '@/services/SourceSettingsApi'
import SourceSpecsApi from '@/services/SourceSpecsApi'
import SourceState from '@/mixins/sourceState'
export default {
@ -74,108 +73,49 @@ export default {
hSourceStateButtons: () => import('@/components/hSourceStateButtons')
},
data: function () {
return {
form: null,
settings: {
Source: {},
_comment: ''
},
sourceSpecs: null
}
},
mixins: [SourceState],
computed: {
isLoading () {
return this.form == null || this.sourceSpecs == null
}
},
mounted () {
this.updateSourceSettingsForm()
created () {
this.mySource = this.$route.params.provider
this.state = parseInt(this.$route.params.state)
},
methods: {
loadState (toState, settings, recallid) {
var mySource = this.$route.params.provider
var source = settings ? settings.Source : null
var srcName = settings ? settings._comment : null
SourceSettingsApi.getSourceSettings(mySource, toState, source, srcName, recallid)
.then(
response => {
if (response.data.fields !== undefined) {
if (settings) {
this.$router.push('/sources/new/' + encodeURIComponent(mySource) + '/' + toState)
}
this.form = response.data
} else {
this.$root.$bvToast.toast(
'Done', {
title: response.data._comment ? response.data._comment : 'Your new source' + ' has been added.',
autoHideDelay: 5000,
variant: 'success',
toaster: 'b-toaster-content-right'
}
)
this.$router.push('/sources/' + encodeURIComponent(response.data._id))
}
},
error => {
this.$root.$bvToast.toast(
error.response.data.errmsg, {
title: 'Something went wrong during source configuration validation',
autoHideDelay: 5000,
variant: 'danger',
toaster: 'b-toaster-content-right'
}
)
if (!settings) {
this.$router.push('/sources/new')
}
})
},
previousState () {
if (this.form.previousButtonState !== undefined) {
if (this.form.previousButtonState === -1) {
this.$router.push('/sources/new/')
} else {
this.loadState(this.form.previousButtonState, this.settings)
this.loadState(
this.form.previousButtonState,
null,
this.reactOnSuccess,
() => {
this.$router.push('/sources/new')
}
)
}
}
},
reactOnSuccess (toState, newSource) {
if (newSource) {
this.$router.push('/sources/' + encodeURIComponent(newSource._id))
} else {
this.$router.push('/sources/new/' + encodeURIComponent(this.mySource) + '/' + toState)
}
},
submitSettings () {
if (this.form.nextButtonState !== undefined) {
if (this.form.nextButtonState === -1) {
this.$router.push('/sources/new/')
} else {
this.loadState(this.form.nextButtonState, this.settings)
this.loadState(this.form.nextButtonState, null, this.reactOnSuccess)
}
} else if (this.form.nextButtonLink !== undefined) {
window.location = this.form.nextButtonLink
}
},
updateSourceSettingsForm () {
var state = parseInt(this.$route.params.state)
SourceSpecsApi.getSourceSpecs()
.then(
response => (this.sourceSpecs = response.data),
error => {
this.$root.$bvToast.toast(
error.response.data.errmsg, {
title: 'Something went wrong during source configuration',
autoHideDelay: 5000,
variant: 'danger',
toaster: 'b-toaster-content-right'
}
)
this.$router.push('/sources/new/')
})
this.loadState(state, null, this.$route.query.recall)
}
}
}