diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index 72d0f1b40..d7d01b279 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "dependencies": { - "@cloudron/pankow": "^3.5.13", + "@cloudron/pankow": "^3.5.14", "@fontsource/inter": "^5.2.8", "@fortawesome/fontawesome-free": "^7.1.0", "@vitejs/plugin-vue": "^6.0.2", @@ -76,9 +76,9 @@ } }, "node_modules/@cloudron/pankow": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@cloudron/pankow/-/pankow-3.5.13.tgz", - "integrity": "sha512-7f3rtPdb//yxnP1jNa4ieWDah36MK68dqvM/PfW/SPljKLFBz7JGEjp8eRY2/fCBD+OiUrGdi/dkaDR1eVe3tg==", + "version": "3.5.14", + "resolved": "https://registry.npmjs.org/@cloudron/pankow/-/pankow-3.5.14.tgz", + "integrity": "sha512-8fI82oF0mMA1fd1/FRS+TS6V5Sa8Mu2SoF7/Og7MSkHevuYXDT2QDt9DaGxScQf6Ho/bkgzrbHCEZmKHMJdI+g==", "license": "ISC", "dependencies": { "@fontsource/inter": "^5.2.8", @@ -2667,9 +2667,9 @@ } }, "@cloudron/pankow": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@cloudron/pankow/-/pankow-3.5.13.tgz", - "integrity": "sha512-7f3rtPdb//yxnP1jNa4ieWDah36MK68dqvM/PfW/SPljKLFBz7JGEjp8eRY2/fCBD+OiUrGdi/dkaDR1eVe3tg==", + "version": "3.5.14", + "resolved": "https://registry.npmjs.org/@cloudron/pankow/-/pankow-3.5.14.tgz", + "integrity": "sha512-8fI82oF0mMA1fd1/FRS+TS6V5Sa8Mu2SoF7/Og7MSkHevuYXDT2QDt9DaGxScQf6Ho/bkgzrbHCEZmKHMJdI+g==", "requires": { "@fontsource/inter": "^5.2.8", "@fortawesome/fontawesome-free": "^7.1.0", diff --git a/dashboard/package.json b/dashboard/package.json index ed8528af9..1d377f1a0 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -7,7 +7,7 @@ }, "type": "module", "dependencies": { - "@cloudron/pankow": "^3.5.13", + "@cloudron/pankow": "^3.5.14", "@fontsource/inter": "^5.2.8", "@fortawesome/fontawesome-free": "^7.1.0", "@vitejs/plugin-vue": "^6.0.2", diff --git a/dashboard/src/components/ApiTokens.vue b/dashboard/src/components/ApiTokens.vue index bc8328091..9f0319e5b 100644 --- a/dashboard/src/components/ApiTokens.vue +++ b/dashboard/src/components/ApiTokens.vue @@ -5,7 +5,7 @@ const i18n = useI18n(); const t = i18n.t; import moment from 'moment-timezone'; -import { ref, onMounted, computed, useTemplateRef } from 'vue'; +import { ref, onMounted, useTemplateRef } from 'vue'; import { Button, Menu, ClipboardButton, Dialog, InputDialog, FormGroup, Radiobutton, TableView, TextInput, InputGroup } from '@cloudron/pankow'; import { prettyLongDate } from '@cloudron/pankow/utils'; import { TOKEN_TYPES } from '../constants.js'; @@ -60,11 +60,15 @@ function onActionMenu(apiToken, event) { actionMenuElement.value.open(event, event.currentTarget); } -const isValid = computed(() => { - if (!tokenName.value) return false; - if (!(tokenScope.value === 'r' || tokenScope.value === 'rw')) return false; - return true; -}); +const form = useTemplateRef('form'); +const isFormValid = ref(false); +function checkValidity() { + isFormValid.value = form.value ? form.value.checkValidity() : false; + + if (isFormValid.value) { + if (!(tokenScope.value === 'r' || tokenScope.value === 'rw')) isFormValid.value = false; + } +} async function refreshApiTokens() { const [error, tokens] = await tokensModel.list(); @@ -74,7 +78,7 @@ async function refreshApiTokens() { } async function onSubmitAddApiToken(){ - if (!isValid.value) return; + if (!form.value.reportValidity()) return; const scope = { '*': tokenScope.value }; const allowedIpRanges = tokenAllowedIpRanges.value; @@ -96,6 +100,7 @@ function onReset() { tokenScope.value = 'rw'; tokenAllowedIpRanges.value = ''; tokenAllowedIpRangesError.value = ''; + setTimeout(checkValidity, 100); // update state of the confirm button }, 500); } @@ -131,7 +136,7 @@ onMounted(async () => { {
-
- + + diff --git a/dashboard/src/components/AppImportDialog.vue b/dashboard/src/components/AppImportDialog.vue index e5682a4bb..95d34ae9b 100644 --- a/dashboard/src/components/AppImportDialog.vue +++ b/dashboard/src/components/AppImportDialog.vue @@ -10,7 +10,6 @@ import { REGIONS_CONTABO, REGIONS_VULTR, REGIONS_IONOS, REGIONS_OVH, REGIONS_LIN const appsModel = AppsModel.create(); const dialog = useTemplateRef('dialog'); -const form = useTemplateRef('form'); const backupConfigInput = useTemplateRef('backupConfigInput'); const appId = ref(''); const busy = ref(false); @@ -24,6 +23,7 @@ const encryptionPasswordHint = ref(''); const encryptionPassword = ref(''); const encryptedFilenames = ref(false); +const form = useTemplateRef('form'); const isFormValid = ref(false); function checkValidity() { isFormValid.value = form.value ? form.value.checkValidity() : false; @@ -279,6 +279,8 @@ defineExpose({ encryptionPasswordHint.value = ''; dialog.value.open(); + + setTimeout(checkValidity, 100); // update state of the confirm button } }); diff --git a/dashboard/src/components/AppPasswords.vue b/dashboard/src/components/AppPasswords.vue index 0949fcb1d..f5b38a381 100644 --- a/dashboard/src/components/AppPasswords.vue +++ b/dashboard/src/components/AppPasswords.vue @@ -5,7 +5,7 @@ const i18n = useI18n(); const t = i18n.t; import moment from 'moment-timezone'; -import { ref, onMounted, useTemplateRef, computed } from 'vue'; +import { ref, onMounted, useTemplateRef } from 'vue'; import { Button, Menu, ClipboardButton, Dialog, SingleSelect, FormGroup, TextInput, TableView, InputDialog, InputGroup } from '@cloudron/pankow'; import { prettyLongDate } from '@cloudron/pankow/utils'; import Section from './Section.vue'; @@ -77,22 +77,23 @@ async function refresh() { passwords.value = result; } +const form = useTemplateRef('form'); +const isFormValid = ref(false); +function checkValidity() { + isFormValid.value = form.value ? form.value.checkValidity() : false; +} + function onReset() { setTimeout(() => { passwordName.value = ''; identifier.value = ''; addedPassword.value = ''; + setTimeout(checkValidity, 100); // update state of the confirm button }, 500); } -const isValid = computed(() => { - if (!passwordName.value) return false; - if (!identifier.value) return false; - return true; -}); - async function onSubmit() { - if (!isValid.value) return; + if (!form.value.reportValidity()) return; addedPassword.value = ''; @@ -163,7 +164,7 @@ onMounted(async () => { {
- - + + @@ -183,7 +184,7 @@ onMounted(async () => { - +
diff --git a/dashboard/src/components/ApplinkDialog.vue b/dashboard/src/components/ApplinkDialog.vue index 547b92437..6784d11bc 100644 --- a/dashboard/src/components/ApplinkDialog.vue +++ b/dashboard/src/components/ApplinkDialog.vue @@ -38,26 +38,29 @@ const accessRestriction = ref({ groups: [], }); -const isValid = computed(() => { - if (busy.value) return false; - if (!upstreamUri.value) return false; - - try { - new URL(upstreamUri.value); - // eslint-disable-next-line no-unused-vars - } catch (e) { - return false; - } - - return true; -}); - let iconFile = 'src'; function onIconChanged(file) { iconFile = file; } +const form = useTemplateRef('form'); +const isFormValid = ref(false); +function checkValidity() { + isFormValid.value = form.value ? form.value.checkValidity() : false; + + if (isFormValid.value) { + try { + new URL(upstreamUri.value); + // eslint-disable-next-line no-unused-vars + } catch (e) { + isFormValid.value = false; + } + } +} + async function onSubmit() { + if (!form.value.reportValidity()) return; + busy.value = true; const data = { @@ -134,6 +137,8 @@ defineExpose({ groups.value = result; applinkDialog.value.open(); + + setTimeout(checkValidity, 100); // update state of the confirm button } }); @@ -147,16 +152,16 @@ defineExpose({ :reject-label="$t('main.dialog.cancel')" reject-style="secondary" :confirm-label="mode === 'edit' ? $t('main.dialog.save') : $t('main.action.add')" - :confirm-active="isValid" + :confirm-active="!busy && isFormValid" :confirm-busy="busy" @confirm="onSubmit()" @alternate="onRemove()" > -
+
- +

{{ error.generic }}

diff --git a/dashboard/src/components/DisableTwoFADialog.vue b/dashboard/src/components/DisableTwoFADialog.vue index d5e8d62c8..ba31d315c 100644 --- a/dashboard/src/components/DisableTwoFADialog.vue +++ b/dashboard/src/components/DisableTwoFADialog.vue @@ -1,6 +1,6 @@ @@ -73,9 +72,9 @@ onMounted(async () => {
{{ $t('users.exposedLdap.description') }}
- +
- + @@ -108,6 +107,6 @@ onMounted(async () => {
{{ editError.generic }}

- +
diff --git a/dashboard/src/components/PasswordChangeDialog.vue b/dashboard/src/components/PasswordChangeDialog.vue index 209bfa723..ce331c5c5 100644 --- a/dashboard/src/components/PasswordChangeDialog.vue +++ b/dashboard/src/components/PasswordChangeDialog.vue @@ -15,16 +15,18 @@ const newPassword = ref(''); const newPasswordRepeat = ref(''); const password = ref(''); -const isFormValid = computed(() => { - if (!newPassword.value) return false; - if (newPasswordRepeat.value !== newPassword.value) return false; - if (!password.value) return false; +const form = useTemplateRef('form'); +const isFormValid = ref(false); +function checkValidity() { + isFormValid.value = form.value ? form.value.checkValidity() : false; - return true; -}); + if (isFormValid.value) { + if (newPasswordRepeat.value !== newPassword.value) isFormValid.value = false; + } +} async function onSubmit() { - if (!isFormValid.value) return; + if (!form.value.reportValidity()) return; busy.value = true; formError.value = {}; @@ -58,6 +60,8 @@ defineExpose({ busy.value = false; formError.value = {}; dialog.value.open(); + + setTimeout(checkValidity, 100); // update state of the confirm button } }); @@ -75,27 +79,27 @@ defineExpose({ reject-style="secondary" @confirm="onSubmit" > - +
- +
{{ formError.generic }}
- +
{{ formError.newPassword }}
- +
{{ $t('profile.changePassword.errorPasswordsDontMatch') }}
- +
{{ formError.password }}
diff --git a/dashboard/src/components/PrimaryEmailDialog.vue b/dashboard/src/components/PrimaryEmailDialog.vue index 4f574a575..81ac0a380 100644 --- a/dashboard/src/components/PrimaryEmailDialog.vue +++ b/dashboard/src/components/PrimaryEmailDialog.vue @@ -1,6 +1,6 @@