2025-04-01 19:04:10 +02:00
< script setup >
2025-04-02 12:43:59 +02:00
import { ref , onMounted , useTemplateRef } from 'vue' ;
2025-05-27 13:55:36 +02:00
import { Spinner , Button , SingleSelect , FormGroup , TextInput , Notification } from 'pankow' ;
import { copyToClipboard } from 'pankow/utils' ;
2025-04-01 19:04:10 +02:00
import { redirectIfNeeded } from '../utils.js' ;
2025-05-03 09:51:32 +02:00
import DomainsModel from '../models/DomainsModel.js' ;
2025-04-02 12:43:59 +02:00
import ProvisionModel from '../models/ProvisionModel.js' ;
2025-04-01 19:04:10 +02:00
import DomainProviderForm from '../components/DomainProviderForm.vue' ;
2025-04-02 12:43:59 +02:00
const provisionModel = ProvisionModel . create ( ) ;
const ipProviders = [
{ name : 'Disabled' , value : 'noop' } ,
{ name : 'Public IP' , value : 'generic' } ,
{ name : 'Static IP Address' , value : 'fixed' } ,
{ name : 'Network Interface' , value : 'network-interface' }
] ;
2025-04-01 19:04:10 +02:00
const formError = ref ( { } ) ;
const busy = ref ( false ) ;
const ready = ref ( false ) ;
2025-05-28 14:51:12 +02:00
const waitingForDnsSetup = ref ( false ) ;
2025-04-01 19:04:10 +02:00
const progressMessage = ref ( '' ) ;
const taskMinutesActive = ref ( 0 ) ;
const domain = ref ( '' ) ;
2025-04-02 12:43:59 +02:00
const instanceId = ref ( '' ) ;
const setupToken = ref ( '' ) ;
2025-04-01 19:04:10 +02:00
const zoneName = ref ( '' ) ;
const provider = ref ( '' ) ;
2025-05-03 09:51:32 +02:00
const dnsConfig = ref ( DomainsModel . createEmptyConfig ( ) ) ;
2025-04-02 12:43:59 +02:00
const tlsProvider = ref ( 'letsencrypt-prod-wildcard' ) ;
const showAdvanced = ref ( false ) ;
const ipv4Provider = ref ( 'generic' ) ;
const ipv4Address = ref ( '' ) ;
const ipv4Interface = ref ( '' ) ;
const ipv6Provider = ref ( 'generic' ) ;
const ipv6Address = ref ( '' ) ;
const ipv6Interface = ref ( '' ) ;
const form = useTemplateRef ( 'form' ) ;
const isFormValid = ref ( false ) ;
function checkValidity ( ) {
if ( ! provider . value ) return false ;
isFormValid . value = form . value . checkValidity ( ) ;
}
2025-04-01 19:04:10 +02:00
2025-04-02 12:43:59 +02:00
async function waitForDnsSetup ( ) {
waitingForDnsSetup . value = true ;
formError . value = { } ;
const [ error , result ] = await provisionModel . status ( ) ;
if ( error ) {
setTimeout ( waitForDnsSetup , 5000 ) ;
return console . error ( error ) ;
}
if ( ! result . setup . active ) {
if ( ! result . adminFqdn || result . setup . errorMessage ) { // setup reset or errored. start over
formError . value . dnsWait = result . setup . errorMessage ;
waitingForDnsSetup . value = false ;
} else { // proceed to activation
window . location . href = 'https://' + result . adminFqdn + '/activation.html' + ( window . location . search ) ;
}
return ;
}
progressMessage . value = result . setup . message ;
taskMinutesActive . value = ( new Date ( ) - new Date ( result . setup . startTime ) ) / 60000 ;
setTimeout ( waitForDnsSetup , 5000 ) ;
}
2025-04-01 19:04:10 +02:00
async function onSubmit ( ) {
2025-04-02 12:43:59 +02:00
if ( ! isFormValid . value ) return ;
2025-04-01 19:04:10 +02:00
busy . value = true ;
formError . value = { } ;
2025-04-02 12:43:59 +02:00
const data = {
domainConfig : {
domain : domain . value ,
zoneName : zoneName . value ,
provider : provider . value ,
2025-05-03 09:51:32 +02:00
config : DomainsModel . filterConfigForProvider ( provider . value , dnsConfig . value ) ,
2025-04-02 12:43:59 +02:00
tlsConfig : {
2025-04-02 15:31:43 +02:00
provider : tlsProvider . value . replace ( '-wildcard' , '' ) ,
wildcard : tlsProvider . value . indexOf ( '-wildcard' ) !== - 1 ? true : false ,
2025-04-02 12:43:59 +02:00
} ,
} ,
ipv4Config : {
provider : ipv4Provider . value ,
ip : ipv4Provider . value === 'fixed' ? ipv4Address . value : '' ,
ifname : ipv4Provider . value === 'network-interface' ? ipv4Interface . value : '' ,
} ,
ipv6Config : {
provider : ipv6Provider . value ,
ip : ipv6Provider . value === 'fixed' ? ipv6Address . value : '' ,
ifname : ipv6Provider . value === 'network-interface' ? ipv6Interface . value : '' ,
} ,
providerToken : instanceId . value ,
setupToken : setupToken . value ,
} ;
const [ error ] = await provisionModel . setup ( data ) ;
if ( error ) {
if ( error . status === 422 ) {
if ( provider . value === 'ami' ) {
2025-04-02 15:45:57 +02:00
formError . value . ami = error . body . message ;
2025-04-02 12:43:59 +02:00
} else {
2025-04-02 15:45:57 +02:00
formError . value . setup = error . body . message ;
2025-04-02 12:43:59 +02:00
}
} else {
2025-04-02 15:45:57 +02:00
formError . value . generic = error . body ? error . body . message : 'Internal error' ;
2025-04-02 12:43:59 +02:00
}
busy . value = false ;
return ;
}
waitForDnsSetup ( ) ;
2025-04-01 19:04:10 +02:00
}
2025-05-27 13:55:36 +02:00
function onCopyToClipboard ( value ) {
copyToClipboard ( value ) ;
window . pankow . notify ( 'copied to clipboard' ) ;
}
2025-04-01 19:04:10 +02:00
onMounted ( async ( ) => {
2025-04-02 12:43:59 +02:00
const search = decodeURIComponent ( window . location . search ) . slice ( 1 ) . split ( '&' ) . map ( function ( item ) { return item . split ( '=' ) ; } ) . reduce ( function ( o , k ) { o [ k [ 0 ] ] = k [ 1 ] ; return o ; } , { } ) ;
const [ error , result ] = await provisionModel . status ( ) ;
2025-04-01 19:04:10 +02:00
if ( error ) return console . error ( error ) ;
if ( redirectIfNeeded ( result , 'setup' ) ) return ; // redirected to some other view...
2025-04-02 12:43:59 +02:00
instanceId . value = search . instanceId ;
setupToken . value = search . setupToken ;
2025-04-01 19:04:10 +02:00
ready . value = true ;
} ) ;
< / script >
< template >
< div class = "container" v-if = "ready" >
2025-05-27 13:55:36 +02:00
< Notification / >
2025-05-28 14:51:12 +02:00
< Transition name = "slide-up" mode = "out-in" >
2025-04-02 12:43:59 +02:00
< div class = "view" v-if = "waitingForDnsSetup" style="text-align: center; max-width: unset;" >
2025-04-01 19:04:10 +02:00
< Spinner class = "pankow-spinner-large" / >
< h3 > { { progressMessage } } ... < / h3 >
2025-05-27 13:55:36 +02:00
< div style = "font-size: 13px" >
Please wait while Cloudron is setting up the dashboard . Follow the logs on the server at < span class = "highlight" @click ="onCopyToClipboard('/home/yellowtent/platformdata/logs/box.log')" > / home / yellowtent / platformdata / logs / box.log < / span >
2025-04-02 12:43:59 +02:00
< br / >
2025-05-27 13:55:36 +02:00
< span v-show = "taskMinutesActive >= 4">If setup appears stuck, it can be restarted by running <span class="highlight" @click="onCopyToClipboard('systemctl restart box')" > systemctl restart box < / span > and reloading this page. < / span >
2025-04-02 12:43:59 +02:00
< / div >
2025-04-01 19:04:10 +02:00
< / div >
< div class = "view" v-else >
2025-04-02 12:43:59 +02:00
< h1 > Cloudron Domain Setup < / h1 >
< div class = "text-danger" v-if = "formError.dnsWait" > {{ formError.dnsWait }} < / div >
< div class = "text-danger" v-if = "formError.setup" > {{ formError.setup }} < / div >
< div class = "text-danger" v-if = "formError.generic" > {{ formError.generic }} < / div >
2025-04-01 19:04:10 +02:00
2025-04-02 12:43:59 +02:00
< form ref = "form" @submit.prevent ="onSubmit()" @input ="checkValidity()" >
2025-04-01 19:04:10 +02:00
< fieldset :disabled = "busy" >
2025-05-26 13:39:36 +02:00
< input type = "submit" style = "display: none" : disabled = "busy || !isFormValid" / >
2025-04-01 19:04:10 +02:00
< FormGroup >
< label for = "domainInput" > Domain < sup > < a href = "https://docs.cloudron.io/installation/#domain-setup" class = "help" target = "_blank" > < i class = "fa fa-question-circle" > < / i > < / a > < / sup > < / label >
< TextInput id = "domainInput" v-model = "domain" placeholder="example.com" required / >
< div class = "text-danger" v-show = "domain.indexOf('my.') === 0 && domain.length > 3" > Are you sure about this domain ? The dashboard will be at < b > my . { { domain } } < / b > < / div >
2025-05-12 15:02:28 +02:00
< div style = "padding-top: 6px;" > Apps will be installed on subdomains of this domain . The dashboard will be available on the < b > my { { domain ? ` . ${ domain } ` : '' } } < / b > subdomain . You can add more domains later . < / div >
2025-04-01 19:04:10 +02:00
< / FormGroup >
2025-05-26 14:35:54 +02:00
< DomainProviderForm :disabled = "busy" v -model :provider = "provider" v -model :dns-config = "dnsConfig" v -model :tls-provider = "tlsProvider" :domain = "domain" :show-advanced = "showAdvanced" / >
2025-04-01 19:04:10 +02:00
2025-04-02 12:43:59 +02:00
< div v-show = "showAdvanced" >
2025-04-01 19:04:10 +02:00
< FormGroup >
< label for = "zoneNameInput" > DNS Zone Name ( Optional ) < sup > < a href = "https://docs.cloudron.io/domains/#zone-name" class = "help" target = "_blank" > < i class = "fa fa-question-circle" > < / i > < / a > < / sup > < / label >
< TextInput id = "zoneNameInput" v-model = "zoneName" placeholder="Defaults to TLD" / >
< / FormGroup >
<!-- IPv4 provider -- >
< FormGroup >
2025-05-06 11:45:49 +02:00
< label class = "control-label" > IPv4 Configuration < sup > < a href = "https://docs.cloudron.io/networking/#ip-configuration" class = "help" target = "_blank" > < i class = "fa fa-question-circle" > < / i > < / a > < / sup > < / label >
2025-04-02 12:43:59 +02:00
< SingleSelect v-model = "ipv4Provider" :options="ipProviders" option-key="value" option-label="name" / >
2025-04-01 19:04:10 +02:00
< / FormGroup >
<!-- IPv4 Fixed -- >
2025-04-02 12:43:59 +02:00
< FormGroup v-if = "ipv4Provider === 'fixed'" >
< label for = "ipv4AddressInput" > IPv4 Address < / label >
< TextInput id = "ipv4AddressInput" v-model = "ipv4Address" required / >
2025-04-01 19:04:10 +02:00
< / FormGroup >
<!-- IPv4 Network Interface -- >
2025-04-02 12:43:59 +02:00
< FormGroup v-if = "ipv4Provider === 'network-interface'" >
< label for = "ipv4InterfaceInput" > IPv4 Interface Name < / label >
< TextInput id = "ipv4InterfaceInput" v-model = "ipv4Interface" required / >
2025-04-01 19:04:10 +02:00
< / FormGroup >
<!-- IPv6 provider -- >
< FormGroup >
2025-05-06 11:45:49 +02:00
< label class = "control-label" > IPv6 Configuration < sup > < a href = "https://docs.cloudron.io/networking/#ip-configuration" class = "help" target = "_blank" > < i class = "fa fa-question-circle" > < / i > < / a > < / sup > < / label >
2025-04-02 12:43:59 +02:00
< SingleSelect v-model = "ipv6Provider" :options="ipProviders" option-key="value" option-label="name" / >
2025-04-01 19:04:10 +02:00
< / FormGroup >
<!-- IPv6 Fixed -- >
2025-04-02 12:43:59 +02:00
< FormGroup v-if = "ipv6Provider === 'fixed'" >
< label for = "ipv6AddressInput" > IPv6 Address < / label >
< TextInput id = "ipv6AddressInput" v-model = "ipv6Address" required / >
2025-04-01 19:04:10 +02:00
< / FormGroup >
<!-- IPv6 Network Interface -- >
2025-04-02 12:43:59 +02:00
< FormGroup v-if = "ipv6Provider === 'network-interface'" >
< label for = "ipv6InterfaceInpt" > IPv6 Interface Name < / label >
< TextInput id = "ipv6InterfaceInpt" v-model = "ipv6Interface" required / >
2025-04-01 19:04:10 +02:00
< / FormGroup >
< / div >
2025-04-02 12:43:59 +02:00
< div class = "actionable" @click ="showAdvanced = false" v-if = "showAdvanced" > Hide Advanced settings < / div >
< div class = "actionable" @click ="showAdvanced = true" v-else > Advanced settings... < / div >
2025-04-01 19:04:10 +02:00
< / fieldset >
< / form >
2025-04-02 12:43:59 +02:00
< Button @click ="onSubmit()" style = "margin-top: 12px" : disabled = "busy || !isFormValid" :loading = "busy" > Next < / Button >
< a href = "/restore.html" style = "margin-left: 10px;" > Looking to restore ? < / a >
2025-04-01 19:04:10 +02:00
< / div >
< / Transition >
< / div >
< / template >
< style scoped >
. container {
display : flex ;
2025-05-26 17:23:11 +02:00
flex - direction : column ;
2025-04-01 19:04:10 +02:00
align - items : center ;
height : 100 % ;
2025-04-02 12:43:59 +02:00
overflow : auto ;
}
h1 {
margin - top : 0 ;
2025-04-01 19:04:10 +02:00
}
. view {
2025-05-26 17:23:11 +02:00
margin : 60 px 0 ;
2025-04-01 19:04:10 +02:00
width : 100 % ;
max - width : 500 px ;
}
2025-05-27 13:55:36 +02:00
. highlight {
font - family : monospace ;
cursor : copy ;
}
2025-04-01 19:04:10 +02:00
< / style >