Add domain edit form validation

This commit is contained in:
Johannes Zellner
2025-01-29 12:15:53 +01:00
parent 74f4849144
commit 60140087a5
2 changed files with 110 additions and 69 deletions

View File

@@ -1,9 +1,7 @@
<script setup>
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, TextInput, FormGroup, Dropdown, Checkbox } from 'pankow';
import { Dialog, TextInput, FormGroup, Checkbox } from 'pankow';
import { ENDPOINTS_OVH } from '../constants.js';
// currently, validation of wildcard with various provider is done server side
@@ -17,37 +15,39 @@ import { ENDPOINTS_OVH } from '../constants.js';
// 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' }
{ 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 editing = ref(false);
const domain = ref('');
const zoneName = ref('');
const provider = ref('');
const tlsProvider = ref('');
const tlsProvider = ref('letsencrypt-prod-wildcard');
const showAdvanced = ref(false);
const accessKeyId = ref('');
const secretAccessKey = ref('');
@@ -65,7 +65,7 @@ const ovhAppKey = ref('');
const ovhAppSecret = ref('');
const porkbunSecretapikey = ref('');
const porkbunApikey = ref('');
const cloudflareTokenType = ref('');
const cloudflareTokenType = ref('ApiToken');
const cloudflareToken = ref('');
const cloudflareEmail = ref('');
const cloudflareDefaultProxyStatus = ref(false);
@@ -82,6 +82,33 @@ const namecheapApiKey = ref('');
const inwxUsername = ref('');
const inwxPassword = ref('');
function needsPort80(dnsProvider, tlsProvider) {
return ((dnsProvider === 'manual' || dnsProvider === 'noop' || dnsProvider === 'wildcard') &&
(tlsProvider === 'letsencrypt-prod' || tlsProvider === 'letsencrypt-staging'));
}
function setDefaultTlsProvider() {
const dnsProvider = provider.value;
// wildcard LE won't work without automated DNS
if (dnsProvider === 'manual' || dnsProvider === 'noop' || dnsProvider === 'wildcard') {
tlsProvider.value = 'letsencrypt-prod';
} else {
tlsProvider.value = 'letsencrypt-prod-wildcard';
}
}
const isFormValid = ref(false);
function checkValidity() {
isFormValid.value = form.value.checkValidity();
}
const form = useTemplateRef('form');
function onSubmit() {
if (!form.value.reportValidity()) return;
console.log('all good?')
}
defineExpose({
open(d) {
console.log(d);
@@ -90,10 +117,11 @@ defineExpose({
busy.value = false;
error.value = '';
editing.value = !!d.domain;
domain.value = d.domain || '';
zoneName.value = d.zoneName || '';
provider.value = d.provider || '';
tlsProvider.value = d.tlsProvider || '';
tlsProvider.value = d.tlsProvider || 'letsencrypt-prod-wildcard';
accessKeyId.value = d.accessKeyId || '';
secretAccessKey.value = d.secretAccessKey || '';
@@ -136,11 +164,17 @@ defineExpose({
<template>
<Dialog ref="dialog"
:title="domain ? $t('domains.domainDialog.editTitle', { domain: domain }) : $t('domains.domainDialog.addTitle')"
:title="editing ? $t('domains.domainDialog.editTitle', { domain: domain }) : $t('domains.domainDialog.addTitle')"
:confirm-loading="busy"
:confirm-active="isFormValid"
:confirm-label="$t('main.dialog.save')"
:reject-label="$t('main.dialog.cancel')"
reject-style="secondary"
@confirm="onSubmit()"
>
<p v-show="!domain" v-html="$t('domains.domainDialog.addDescription')"></p>
<p v-show="!editing" v-html="$t('domains.domainDialog.addDescription')"></p>
<form novalidate @submit.prevent="onSubmit()" autocomplete="off">
<form ref="form" @submit.prevent="onSubmit()" autocomplete="off" @input="checkValidity()">
<fieldset>
<input style="display: none;" type="submit" :disabled="busy"/>
<p class="has-error text-center" v-show="error">{{ error }}</p>
@@ -152,8 +186,9 @@ defineExpose({
<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" />
<select id="providerInput" v-model="provider" @change="setDefaultTlsProvider()" required>
<option v-for="p in domainProviders" :value="p.value" :key="p.value">{{ p.name }}</option>
</select>
</FormGroup>
<!-- Route53 -->
@@ -268,7 +303,10 @@ defineExpose({
<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>
<div v-if="provider === 'cloudflare'">
<Checkbox 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>
</div>
<!-- Linode -->
<FormGroup v-if="provider === 'linode'">
@@ -339,47 +377,49 @@ defineExpose({
<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 class="small text-info text-bold" v-show="needsPort80(provider, tlsProvider)" v-html="$t('domains.domainDialog.letsEncryptInfo')"></p>
<p>TODO {{ $t('domains.domainDialog.advancedAction') }}</p>
<p style="margin-top: 15px" v-show="!showAdvanced" @click="showAdvanced = true" class="actionable">{{ $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>
<div v-show="showAdvanced">
<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>
<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>
<!-- Fallback certificate -->
<div v-if="tlsProvider !== 'fallback'">
<label>{{ $t('domains.domainDialog.fallbackCert') }}</label>
<p v-html="$t('domains.domainDialog.fallbackCertInfo')"></p>
</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 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>
<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>
</div>
</fieldset>
</form>
</Dialog>