location: fix up form validation

This commit is contained in:
Girish Ramakrishnan
2026-01-20 17:00:19 +01:00
parent 7f8143f06f
commit e34cf8f6a6
+28 -28
View File
@@ -1,6 +1,6 @@
<script setup>
import { ref, onMounted, computed, inject } from 'vue';
import { ref, useTemplateRef, onMounted, computed } from 'vue';
import { Button, SingleSelect, InputGroup, FormGroup, TextInput, Checkbox } from '@cloudron/pankow';
import { isValidDomain } from '@cloudron/pankow/utils';
import { ISTATES } from '../../constants.js';
@@ -13,7 +13,6 @@ const props = defineProps([ 'app' ]);
const appsModel = AppsModel.create();
const domainsModel = DomainsModel.create();
const dashboardDomain = inject('dashboardDomain');
const domains = ref([]);
const busy = ref(false);
const errorMessage = ref('');
@@ -55,30 +54,28 @@ function onAddRedirect() {
});
}
const formValid = computed(() => {
if (!domain.value) return false;
const form = useTemplateRef('form');
const isFormValid = ref(false);
function checkValidity() {
isFormValid.value = form.value ? form.value.checkValidity() : false;
const checkForDomains = [{
domain: domain.value,
subdomain: subdomain.value,
}];
for (const d in secondaryDomains.value) checkForDomains.push({ domain: secondaryDomains.value[d].domain, subdomain: secondaryDomains.value[d].subdomain });
for (const d of redirects.value) checkForDomains.push({ domain: d.domain, subdomain: d.subdomain });
for (const d of aliases.value) {
let subdomain = d.subdomain;
// see apps.js:validateLocations()
if (d.subdomain.startsWith('*')) {
if (subdomain === '*') continue;
subdomain = subdomain.replace(/^\*\./, ''); // remove *.
if (isFormValid.value) {
const checkForDomains = [{ domain: domain.value, subdomain: subdomain.value }];
for (const d in secondaryDomains.value) checkForDomains.push({ domain: secondaryDomains.value[d].domain, subdomain: secondaryDomains.value[d].subdomain });
for (const d of redirects.value) checkForDomains.push({ domain: d.domain, subdomain: d.subdomain });
for (const d of aliases.value) {
let subdomain = d.subdomain;
// see apps.js:validateLocations()
if (d.subdomain.startsWith('*')) {
if (subdomain === '*') continue;
subdomain = subdomain.replace(/^\*\./, ''); // remove *.
}
checkForDomains.push({ domain: d.domain, subdomain: subdomain });
}
checkForDomains.push({ domain: d.domain, subdomain: subdomain });
if (checkForDomains.find(d => !isValidDomain((d.subdomain ? (d.subdomain + '.') : '') + d.domain))) isFormValid.value = false;
}
if (checkForDomains.find(d => !isValidDomain((d.subdomain ? (d.subdomain + '.') : '') + d.domain))) return false;
return true;
});
}
function onRemoveRedirect(index) {
redirects.value.splice(index, 1);
@@ -190,15 +187,17 @@ onMounted(async () => {
}
else console.error(`Portbinding ${p} not known in manifest!`);
}
setTimeout(checkValidity, 100); // update state of the confirm button
});
</script>
<template>
<div>
<form @submit.prevent="onSubmit()" autocomplete="off" novalidate>
<form ref="form" @submit.prevent="onSubmit()" autocomplete="off" @input="checkValidity()">
<fieldset :disabled="busy">
<input type="submit" style="display: none;" :disabled="(app.error && app.error.installationState !== ISTATES.PENDING_LOCATION_CHANGE) || app.taskId || !formValid"/>
<input type="submit" style="display: none;" :disabled="(app.error && app.error.installationState !== ISTATES.PENDING_LOCATION_CHANGE) || app.taskId"/>
<FormGroup>
<label>{{ $t('app.location.location') }}</label>
@@ -206,7 +205,7 @@ onMounted(async () => {
<div style="display: flex; gap: 10px;">
<InputGroup style="flex-grow: 1">
<TextInput style="flex-grow: 1" v-model="subdomain" :placeholder="$t('app.location.locationPlaceholder')"/>
<SingleSelect :disabled="busy" :options="domains" v-model="domain" option-key="domain" option-label="label" :search-threshold="10"/>
<SingleSelect :disabled="busy" :options="domains" v-model="domain" option-key="domain" option-label="label" :search-threshold="10" required/>
</InputGroup>
<div class="warning-label" v-if="isNoopOrManual(domain)" v-html="$t('appstore.installDialog.manualWarning', { location: ((subdomain ? subdomain + '.' : '') + domain) })"></div>
<!-- Button just to offset the same margin on the right to align location input when alias or redirects are visible -->
@@ -219,7 +218,7 @@ onMounted(async () => {
<small>{{ item.description }}</small>
<InputGroup style="flex-grow: 1">
<TextInput style="flex-grow: 1" :id="'secondaryDomainInput' + item.containerPort" v-model="item.subdomain" :placeholder="$t('appstore.installDialog.locationPlaceholder')"/>
<SingleSelect :disabled="busy" :options="domains" v-model="item.domain" option-key="domain" option-label="label" :search-threshold="10"/>
<SingleSelect :disabled="busy" :options="domains" v-model="item.domain" option-key="domain" option-label="label" :search-threshold="10" required/>
</InputGroup>
<div class="warning-label" v-if="isNoopOrManual(item.domain)" v-html="$t('appstore.installDialog.manualWarning', { location: ((item.subdomain ? item.subdomain + '.' : '') + item.domain) })"></div>
</FormGroup>
@@ -271,11 +270,12 @@ onMounted(async () => {
<br/>
<div class="error-label" v-if="errorMessage">{{ errorMessage }}</div>
<br v-if="errorMessage"/>
<Checkbox v-if="needsOverwriteDns" v-model="overwriteDns" :label="$t('app.location.dnsoverwrite')"/>
<br v-if="needsOverwriteDns"/>
<Button @click="onSubmit()" :loading="busy" :disabled="busy || (app.error && app.error.installationState !== ISTATES.PENDING_LOCATION_CHANGE) || app.taskId || !formValid">{{ $t('app.location.saveAction') }}</Button>
<Button @click="onSubmit()" :loading="busy" :disabled="busy || (app.error && app.error.installationState !== ISTATES.PENDING_LOCATION_CHANGE) || app.taskId || !isFormValid">{{ $t('app.location.saveAction') }}</Button>
</div>
</template>