diff --git a/src/helpers/isValidHttpUrl.ts b/src/helpers/isValidHttpUrl.ts new file mode 100644 index 000000000..d8f0898c5 --- /dev/null +++ b/src/helpers/isValidHttpUrl.ts @@ -0,0 +1,11 @@ +export function isValidHttpUrl(urlToCheck: string): boolean { + let url + + try { + url = new URL(urlToCheck) + } catch (_) { + return false + } + + return url.protocol === 'http:' || url.protocol === 'https:' +} diff --git a/src/i18n/lang/en.json b/src/i18n/lang/en.json index f2a5068cf..d4cb3c0a5 100644 --- a/src/i18n/lang/en.json +++ b/src/i18n/lang/en.json @@ -366,6 +366,7 @@ "targetUrlInvalid": "Please provide a valid URL.", "events": "Events", "eventsHint": "Select all events this webhook should recieve updates for (within the current project).", + "mustSelectEvents": "You must select at least one event.", "delete": "Delete this webhook", "deleteText": "Are you sure you want to delete this webhook? External targets will not be notified of its events anymore.", "deleteSuccess": "The webhook was successfully deleted.", diff --git a/src/views/project/settings/webhooks.vue b/src/views/project/settings/webhooks.vue index 9b9bf1363..f1acd3386 100644 --- a/src/views/project/settings/webhooks.vue +++ b/src/views/project/settings/webhooks.vue @@ -23,6 +23,7 @@ import WebhookModel from '@/models/webhook' import BaseButton from '@/components/base/BaseButton.vue' import Fancycheckbox from '@/components/input/fancycheckbox.vue' import {success} from '@/message' +import {isValidHttpUrl} from '@/helpers/isValidHttpUrl' const {t} = useI18n({useScope: 'global'}) @@ -73,10 +74,20 @@ const newWebhook = ref(new WebhookModel()) const newWebhookEvents = ref({}) async function create() { - const selectedEvents = Object.entries(newWebhookEvents.value) - .filter(([event, use]) => use) - .map(([event]) => event) + + validateTargetUrl() + if (!webhookTargetUrlValid.value) { + return + } + + const selectedEvents = getSelectedEventsArray() newWebhook.value.events = selectedEvents + + validateSelectedEvents() + if (!selectedEventsValid.value) { + return + } + newWebhook.value.projectId = project.value.id const created = await webhookService.create(newWebhook.value) webhooks.value.push(created) @@ -85,8 +96,24 @@ async function create() { } const webhookTargetUrlValid = ref(true) + function validateTargetUrl() { - + webhookTargetUrlValid.value = isValidHttpUrl(newWebhook.value.targetUrl) +} + +const selectedEventsValid = ref(true) + +function getSelectedEventsArray() { + return Object.entries(newWebhookEvents.value) + .filter(([_, use]) => use) + .map(([event]) => event) +} + +function validateSelectedEvents() { + const events = getSelectedEventsArray() + if (events.length === 0) { + selectedEventsValid.value = false + } } @@ -115,6 +142,7 @@ function validateTargetUrl() { class="input" :placeholder="$t('project.webhooks.targetUrl')" v-model="newWebhook.targetUrl" + @focusout="validateTargetUrl" />

@@ -152,10 +180,14 @@ function validateTargetUrl() { :key="event" class="mr-2" v-model="newWebhookEvents[event]" + @update:model-value="validateSelectedEvents" > {{ event }} +

+ {{ $t('project.webhooks.mustSelectEvents') }} +

{{ $t('project.webhooks.create') }}