Most of the domain setup dialog bits are done

This commit is contained in:
Johannes Zellner
2025-01-28 21:25:12 +01:00
parent f990095ddf
commit fd3bb37c48
4 changed files with 397 additions and 10 deletions

View File

@@ -3,12 +3,84 @@
const API_ORIGIN = import.meta.env.VITE_API_ORIGIN ? import.meta.env.VITE_API_ORIGIN : window.location.origin;
import { ref, useTemplateRef } from 'vue';
import { Dialog } from 'pankow';
import { Dialog, TextInput, FormGroup, Dropdown, Checkbox } from 'pankow';
import { ENDPOINTS_OVH } from '../constants.js';
// currently, validation of wildcard with various provider is done server side
const tlsProviders = [
{ name: 'Let\'s Encrypt Prod', value: 'letsencrypt-prod' },
{ name: 'Let\'s Encrypt Prod - Wildcard', value: 'letsencrypt-prod-wildcard' },
{ name: 'Let\'s Encrypt Staging', value: 'letsencrypt-staging' },
{ name: 'Let\'s Encrypt Staging - Wildcard', value: 'letsencrypt-staging-wildcard' },
{ name: 'Custom Wildcard Certificate', value: 'fallback' },
];
// keep in sync with setup.js
const domainProviders = [
{ name: 'AWS Route53', value: 'route53' },
{ name: 'Bunny', value: 'bunny' },
{ name: 'Cloudflare', value: 'cloudflare' },
{ name: 'deSEC', value: 'desec' },
{ name: 'DigitalOcean', value: 'digitalocean' },
{ name: 'DNSimple', value: 'dnsimple' },
{ name: 'Gandi LiveDNS', value: 'gandi' },
{ name: 'GoDaddy', value: 'godaddy' },
{ name: 'Google Cloud DNS', value: 'gcdns' },
{ name: 'Hetzner', value: 'hetzner' },
{ name: 'INWX', value: 'inwx' },
{ name: 'Linode', value: 'linode' },
{ name: 'Name.com', value: 'namecom' },
{ name: 'Namecheap', value: 'namecheap' },
{ name: 'Netcup', value: 'netcup' },
{ name: 'OVH', value: 'ovh' },
{ name: 'Porkbun', value: 'porkbun' },
{ name: 'Vultr', value: 'vultr' },
{ name: 'Wildcard', value: 'wildcard' },
{ name: 'Manual (not recommended)', value: 'manual' },
{ name: 'No-op (only for development)', value: 'noop' }
];
const dialog = useTemplateRef('dialog');
const busy = ref(false);
const error = ref('');
const domain = ref('');
const zoneName = ref('');
const provider = ref('');
const tlsProvider = ref('');
const accessKeyId = ref('');
const secretAccessKey = ref('');
const digitalOceanToken = ref('');
const gandiTokenType = ref('');
const gandiApiKey = ref('');
const godaddyApiKey = ref('');
const godaddyApiSecret = ref('');
const netcupCustomerNumber = ref('');
const netcupApiKey = ref('');
const netcupApiPassword = ref('');
const ovhEndpoint = ref('');
const ovhConsumerKey = ref('');
const ovhAppKey = ref('');
const ovhAppSecret = ref('');
const porkbunSecretapikey = ref('');
const porkbunApikey = ref('');
const cloudflareTokenType = ref('');
const cloudflareToken = ref('');
const cloudflareEmail = ref('');
const cloudflareDefaultProxyStatus = ref(false);
const linodeToken = ref('');
const bunnyAccessKey = ref('');
const dnsimpleAccessToken = ref('');
const hetznerToken = ref('');
const vultrToken = ref('');
const deSecToken = ref('');
const nameComUsername = ref('');
const nameComToken = ref('');
const namecheapUsername = ref('');
const namecheapApiKey = ref('');
const inwxUsername = ref('');
const inwxPassword = ref('');
defineExpose({
open(d) {
@@ -16,8 +88,45 @@ defineExpose({
d = d || {};
domain.value = d.domain;
provider.value = d.provider;
busy.value = false;
error.value = '';
domain.value = d.domain || '';
zoneName.value = d.zoneName || '';
provider.value = d.provider || '';
tlsProvider.value = d.tlsProvider || '';
accessKeyId.value = d.accessKeyId || '';
secretAccessKey.value = d.secretAccessKey || '';
digitalOceanToken.value = d.digitalOceanToken || '';
gandiTokenType.value = d.gandiTokenType || '';
gandiApiKey.value = d.gandiApiKey || '';
godaddyApiKey.value = d.godaddyApiKey || '';
godaddyApiSecret.value = d.godaddyApiSecret || '';
netcupCustomerNumber.value = d.netcupCustomerNumber || '';
netcupApiKey.value = d.netcupApiKey || '';
netcupApiPassword.value = d.netcupApiPassword || '';
ovhEndpoint.value = d.ovhEndpoint || '';
ovhConsumerKey.value = d.ovhConsumerKey || '';
ovhAppKey.value = d.ovhAppKey || '';
ovhAppSecret.value = d.ovhAppSecret || '';
porkbunSecretapikey.value = d.porkbunSecretapikey || '';
porkbunApikey.value = d.porkbunApikey || '';
cloudflareTokenType.value = d.cloudflareTokenType || 'ApiToken';
cloudflareToken.value = d.cloudflareToken || '';
cloudflareEmail.value = d.cloudflareEmail || '';
cloudflareDefaultProxyStatus.value = d.cloudflareDefaultProxyStatus || false;
linodeToken.value = d.linodeToken || '';
bunnyAccessKey.value = d.bunnyAccessKey || '';
dnsimpleAccessToken.value = d.dnsimpleAccessToken || '';
hetznerToken.value = d.hetznerToken || '';
vultrToken.value = d.vultrToken || '';
deSecToken.value = d.deSecToken || '';
nameComUsername.value = d.nameComUsername || '';
nameComToken.value = d.nameComToken || '';
namecheapUsername.value = d.namecheapUsername || '';
namecheapApiKey.value = d.namecheapApiKey || '';
inwxUsername.value = d.inwxUsername || '';
inwxPassword.value = d.inwxPassword || '';
dialog.value.open();
}
@@ -27,6 +136,251 @@ defineExpose({
<template>
<Dialog ref="dialog"
:title="domain ? $t('domains.domainDialog.editTitle', { domain: domain }) : $t('domains.domainDialog.addTitle')">
:title="domain ? $t('domains.domainDialog.editTitle', { domain: domain }) : $t('domains.domainDialog.addTitle')"
>
<p v-show="!domain" v-html="$t('domains.domainDialog.addDescription')"></p>
<form novalidate @submit.prevent="onSubmit()" autocomplete="off">
<fieldset>
<input style="display: none;" type="submit" :disabled="busy"/>
<p class="has-error text-center" v-show="error">{{ error }}</p>
<FormGroup>
<label for="domainInput">{{ $t('domains.domainDialog.domain') }}</label>
<TextInput id="domainInput" v-model="domain" placeholder="example.com" required />
</FormGroup>
<FormGroup>
<label for="providerInput">{{ $t('domains.domainDialog.provider') }} <sup><a ng-href="https://docs.cloudron.io/domains/#dns-providers" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
<!-- on change set default TLS provider also -->
<Dropdown id="providerInput" v-model="provider" :options="domainProviders" option-key="value" option-label="name" />
</FormGroup>
<!-- Route53 -->
<FormGroup v-if="provider === 'route53'">
<label for="accessKeyIdInput">{{ $t('domains.domainDialog.route53AccessKeyId') }}</label>
<TextInput id="accessKeyIdInput" v-model="accessKeyId" minlength="16" maxlength="32" required />
</FormGroup>
<FormGroup v-if="provider === 'route53'">
<label for="secretAccessKeyInput">{{ $t('domains.domainDialog.route53SecretAccessKey') }}</label>
<TextInput id="secretAccessKeyInput" v-model="secretAccessKey" required />
</FormGroup>
<!-- TODO Google Cloud DNS -->
<FormGroup v-if="provider === 'gcdns'">
<label class="control-label">{{ $t('domains.domainDialog.gcdnsServiceAccountKey') }}</label>
<div class="input-group">
<input type="file" id="gcdnsKeyFileInput" style="display:none"/>
<input type="text" class="form-control" placeholder="Service Account Key" ng-model="domainConfigure.gcdnsKey.keyFileName" id="gcdnsKeyInput" name="cert" onclick="getElementById('gcdnsKeyFileInput').click();" style="cursor: pointer;" ng-disabled="domainConfigure.busy" ng-required="domainConfigure.provider === 'gcdns'">
<span class="input-group-addon">
<i class="fa fa-upload" onclick="getElementById('gcdnsKeyFileInput').click();"></i>
</span>
</div>
</FormGroup>
<!-- DigitalOcean -->
<FormGroup v-if="provider === 'digitalocean'">
<label for="digitalOceanTokenInput">{{ $t('domains.domainDialog.digitalOceanToken') }}</label>
<TextInput id="digitalOceanTokenInput" v-model="digitalOceanToken" required />
</FormGroup>
<!-- Gandi -->
<FormGroup v-if="provider === 'gandi'">
<label for="gandiTokenTypeInput">{{ $t('domains.domainDialog.gandiTokenType') }}</label>
<select id="gandiTokenTypeInput" v-model="gandiTokenType">
<option value="ApiKey">{{ $t('domains.domainDialog.gandiTokenTypeApiKey') }}</option>
<option value="PAT">{{ $t('domains.domainDialog.gandiTokenTypePAT') }}</option>
</select>
</FormGroup>
<FormGroup v-if="provider === 'gandi'">
<label for="gandiApiKeyInput">{{ $t('domains.domainDialog.gandiApiKey') }}</label>
<TextInput id="gandiApiKeyInput" v-model="gandiApiKey" required />
</FormGroup>
<!-- GoDaddy -->
<FormGroup v-if="provider === 'godaddy'">
<label for="godaddyApiKeyInput">{{ $t('domains.domainDialog.goDaddyApiKey') }}</label>
<TextInput id="godaddyApiKeyInput" v-model="godaddyApiKey" minlength="1" required />
</FormGroup>
<FormGroup v-if="provider === 'godaddy'">
<label for="godaddyApiSecretInput">{{ $t('domains.domainDialog.goDaddyApiSecret') }}</label>
<TextInput for="godaddyApiSecretInput" v-model="godaddyApiSecret" required />
</FormGroup>
<!-- Netcup -->
<FormGroup v-if="provider === 'netcup'">
<label for="netcupCustomerNumberInput">{{ $t('domains.domainDialog.netcupCustomerNumber') }}</label>
<TextInput id="netcupCustomerNumberInput" v-model="netcupCustomerNumber" required />
</FormGroup>
<FormGroup v-if="provider === 'netcup'">
<label for="netcupApiKeyInput">{{ $t('domains.domainDialog.netcupApiKey') }}</label>
<TextInput id="netcupApiKeyInput" v-model="netcupApiKey" minlength="1" required />
</FormGroup>
<FormGroup v-if="provider === 'netcup'">
<label for="netcupApiPasswordInput">{{ $t('domains.domainDialog.netcupApiPassword') }}</label>
<TextInput id="netcupApiPasswordInput" v-model="netcupApiPassword" required />
</FormGroup>
<!-- OVH -->
<FormGroup v-if="provider === 'ovh'">
<label for="ovhEndpointInput">{{ $t('domains.domainDialog.ovhEndpoint') }}</label>
<select id="ovhEndpointInput" v-model="ovhEndpoint">
<option v-for="endpoint in ENDPOINTS_OVH" :value="endpoint.value" :key="endpoint.value">{{ endpoint.name }}</option>
</select>
</FormGroup>
<FormGroup v-if="provider === 'ovh'">
<label for="ovhConsumerKeyInput">{{ $t('domains.domainDialog.ovhConsumerKey') }}</label>
<TextInput id="ovhConsumerKeyInput" v-model="ovhConsumerKey" required />
</FormGroup>
<FormGroup v-if="provider === 'ovh'">
<label for="ovhAppKeyInput">{{ $t('domains.domainDialog.ovhAppKey') }}</label>
<TextInput id="ovhAppKeyInput" v-model="ovhAppKey" minlength="1" required />
</FormGroup>
<FormGroup v-if="provider === 'ovh'">
<label for="ovhAppSecretInput">{{ $t('domains.domainDialog.ovhAppSecret') }}</label>
<TextInput id="ovhAppSecretInput" v-model="ovhAppSecret" required />
</FormGroup>
<!-- Porkbun -->
<FormGroup v-if="provider === 'porkbun'">
<label for="porkbunApikeyInput">{{ $t('domains.domainDialog.porkbunApikey') }}</label>
<TextInput id="porkbunApikeyInput" v-model="porkbunApikey" minlength="1" required />
</FormGroup>
<FormGroup v-if="provider === 'porkbun'">
<label for="porkbunSecretapikeyInput">{{ $t('domains.domainDialog.porkbunSecretapikey') }}</label>
<TextInput id="porkbunSecretapikeyInput" v-model="porkbunSecretapikey" required />
</FormGroup>
<!-- Cloudflare -->
<FormGroup v-if="provider === 'cloudflare'">
<label for="cloudflareTokenTypeInput">{{ $t('domains.domainDialog.cloudflareTokenType') }}</label>
<select id="cloudflareTokenTypeInput" v-model="cloudflareTokenType">
<option value="GlobalApiKey">{{ $t('domains.domainDialog.cloudflareTokenTypeGlobalApiKey') }}</option>
<option value="ApiToken">{{ $t('domains.domainDialog.cloudflareTokenTypeApiToken') }}</option>
</select>
</FormGroup>
<FormGroup v-if="provider === 'cloudflare'">
<label for="cloudflareTokenInput" v-show="cloudflareTokenType === 'GlobalApiKey'">{{ $t('domains.domainDialog.cloudflareTokenTypeGlobalApiKey') }}</label>
<label for="cloudflareTokenInput" v-show="cloudflareTokenType === 'ApiToken'">{{ $t('domains.domainDialog.cloudflareTokenTypeApiToken') }}</label>
<TextInput id="cloudflareTokenInput" v-model="cloudflareToken" required />
</FormGroup>
<FormGroup v-if="provider === 'cloudflare' && cloudflareTokenType === 'GlobalApiKey'">
<label for="cloudflareEmailInput">{{ $t('domains.domainDialog.cloudflareEmail') }}</label>
<TextInput id="cloudflareEmailInput" type="email" v-model="cloudflareEmail" :required="cloudflareTokenType === 'GlobalApiKey'" />
</FormGroup>
<Checkbox v-if="provider === 'cloudflare'" v-model="cloudflareDefaultProxyStatus" :label="$t('domains.domainDialog.cloudflareDefaultProxyStatus')" /> <sup><a ng-href="https://docs.cloudron.io/domains/#cloudflare-dns" class="help" target="_blank" tabIndex="-1"><i class="fa fa-question-circle"></i></a></sup>
<!-- Linode -->
<FormGroup v-if="provider === 'linode'">
<label for="linodeTokenInput">{{ $t('domains.domainDialog.linodeToken') }}</label>
<TextInput id="linodeTokenInput" v-model="linodeToken" required />
</FormGroup>
<!-- Bunny -->
<FormGroup v-if="provider === 'bunny'">
<label for="bunnyAccessKeyInput">{{ $t('domains.domainDialog.bunnyAccessKey') }}</label>
<TextInput id="bunnyAccessKeyInput" v-model="bunnyAccessKey" required />
</FormGroup>
<!-- dnsimple -->
<FormGroup v-if="provider === 'dnsimple'">
<label for="dnsimpleAccessTokenInput">{{ $t('domains.domainDialog.dnsimpleAccessToken') }}</label>
<TextInput id="dnsimpleAccessTokenInput" v-model="dnsimpleAccessToken" required />
</FormGroup>
<!-- Hetzner -->
<FormGroup v-if="provider === 'hetzner'">
<label for="hetznerTokenInput">{{ $t('domains.domainDialog.hetznerToken') }}</label>
<TextInput id="hetznerTokenInput" v-model="hetznerToken" required />
</FormGroup>
<!-- Vultr -->
<FormGroup v-if="provider === 'vultr'">
<label for="vultrTokenInput">{{ $t('domains.domainDialog.vultrToken') }}</label>
<TextInput id="vultrTokenInput" v-model="vultrToken" required />
</FormGroup>
<!-- deSEC -->
<FormGroup v-if="provider === 'desec'">
<label for="deSecTokenInput">{{ $t('domains.domainDialog.deSecToken') }}</label>
<TextInput id="deSecTokenInput" v-model="deSecToken" required />
</FormGroup>
<!-- Name.com -->
<FormGroup v-if="provider === 'namecom'">
<label for="nameComUsernameInput">{{ $t('domains.domainDialog.nameComUsername') }}</label>
<TextInput id="nameComUsernameInput" v-model="nameComUsername" required />
</FormGroup>
<FormGroup v-if="provider === 'namecom'">
<label for="nameComTokenInput">{{ $t('domains.domainDialog.nameComApiToken') }}</label>
<TextInput id="nameComTokenInput" v-model="nameComToken" required />
</FormGroup>
<!-- Namecheap -->
<FormGroup v-if="provider === 'namecheap'">
<label for="namecheapUsernameInput">{{ $t('domains.domainDialog.namecheapUsername') }}</label>
<TextInput id="namecheapUsernameInput" v-model="namecheapUsername" required />
</FormGroup>
<FormGroup v-if="provider === 'namecheap'">
<label for="namecheapApiKeyInput">{{ $t('domains.domainDialog.namecheapApiKey') }}</label>
<TextInput id="namecheapApiKeyInput" v-model="namecheapApiKey" required />
</FormGroup>
<p class="small text-info text-bold" v-if="provider === 'namecheap'" v-html="$t('domains.domainDialog.namecheapInfo')"></p>
<!-- INWX -->
<FormGroup v-if="provider === 'inwx'">
<label for="inwxUsernameInput">{{ $t('domains.domainDialog.inwxUsername') }}</label>
<TextInput id="inwxUsernameInput" v-model="inwxUsername" required />
</FormGroup>
<FormGroup v-if="provider === 'inwx'">
<label for="inwxPasswordInput">{{ $t('domains.domainDialog.inwxPassword') }}</label>
<TextInput id="inwxPasswordInput" v-model="inwxPassword" required />
</FormGroup>
<p class="small text-info text-bold" v-show="provider === 'wildcard'" v-html="$t('domains.domainDialog.wildcardInfo', { domain: domain })"></p>
<p class="small text-info text-bold" v-show="provider === 'manual'" v-html="$t('domains.domainDialog.manualInfo')"></p>
<!-- <p class="small text-info text-bold" v-show="needsPort80(provider, tlsConfig.provider)" ng-bind-html="'domains.domainDialog.letsEncryptInfo' | tr"></p> -->
<p>TODO {{ $t('domains.domainDialog.advancedAction') }}</p>
<FormGroup>
<label for="zoneNameInput">{{ $t('domains.domainDialog.zoneName') }} <sup><a ng-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" />
</FormGroup>
<FormGroup>
<label for="tlsProviderInput">{{ $t('domains.domainDialog.certProvider') }} <sup><a ng-href="https://docs.cloudron.io/certificates/#certificate-providers" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
<select id="tlsProviderInput" v-model="tlsProvider">
<option v-for="provider in tlsProviders" :value="provider.value" :key="provider.value">{{ provider.name }}</option>
</select>
</FormGroup>
<!-- Fallback certificate -->
<div v-if="tlsProvider !== 'fallback'">
<label>{{ $t('domains.domainDialog.fallbackCert') }}</label>
<p v-html="$t('domains.domainDialog.fallbackCertInfo')"></p>
</div>
<div v-if="tlsProvider === 'fallback'">
<label >{{ $t('domains.domainDialog.fallbackCertCustomCert') }}</label>
<p v-html="$t('domains.domainDialog.fallbackCertCustomCertInfo', { customCertLink: 'https://docs.cloudron.io/certificates/#custom-certificates' })"></p>
</div>
<FormGroup>
<div class="input-group">
<input type="file" id="fallbackCertFileInput" style="display:none"/>
<input type="text" class="form-control" :placeholder="$t('domains.domainDialog.fallbackCertCertificatePlaceholder')" ng-model="domainConfigure.fallbackCert.certificateFileName" name="cert" onclick="getElementById('fallbackCertFileInput').click();" style="cursor: pointer;" ng-disabled="domainConfigure.busy">
<span class="input-group-addon"><i class="fa fa-upload" onclick="getElementById('fallbackCertFileInput').click();"></i></span>
</div>
</FormGroup>
<FormGroup>
<div class="input-group">
<input type="file" id="fallbackKeyFileInput" style="display:none"/>
<input type="text" class="form-control" :placeholder="$t('domains.domainDialog.fallbackCertKeyPlaceholder')" ng-model="domainConfigure.fallbackCert.keyFileName" id="fallbackKeyInput" name="key" onclick="getElementById('fallbackKeyFileInput').click();" style="cursor: pointer;" ng-disabled="domainConfigure.busy">
<span class="input-group-addon"><i class="fa fa-upload" onclick="getElementById('fallbackKeyFileInput').click();"></i></span>
</div>
</FormGroup>
</fieldset>
</form>
</Dialog>
</template>