From 3d2fe4cf650c9325547d0786dd16d2f6746905ef Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 18 Oct 2023 20:12:29 +0200 Subject: [PATCH 1/3] feat(webhooks): add webhook management form --- src/components/misc/Icon.ts | 2 + .../project/project-settings-dropdown.vue | 6 + src/i18n/lang/en.json | 15 ++ src/modelTypes/IWebhook.ts | 14 ++ src/models/webhook.ts | 25 ++ src/router/index.ts | 9 + src/services/webhook.ts | 29 +++ src/views/project/settings/webhooks.vue | 215 ++++++++++++++++++ 8 files changed, 315 insertions(+) create mode 100644 src/modelTypes/IWebhook.ts create mode 100644 src/models/webhook.ts create mode 100644 src/services/webhook.ts create mode 100644 src/views/project/settings/webhooks.vue diff --git a/src/components/misc/Icon.ts b/src/components/misc/Icon.ts index 57acbc8f5..b3ea6bdc3 100644 --- a/src/components/misc/Icon.ts +++ b/src/components/misc/Icon.ts @@ -8,6 +8,7 @@ import { faArrowUpFromBracket, faBars, faBell, + faBolt, faCalendar, faCheck, faCheckDouble, @@ -144,6 +145,7 @@ library.add(faUsers) library.add(faArrowUpFromBracket) library.add(faX) library.add(faAnglesUp) +library.add(faBolt) // overwriting the wrong types export default FontAwesomeIcon as unknown as FontAwesomeIconFixedTypes \ No newline at end of file diff --git a/src/components/project/project-settings-dropdown.vue b/src/components/project/project-settings-dropdown.vue index 0f237aa2e..5c6e3d561 100644 --- a/src/components/project/project-settings-dropdown.vue +++ b/src/components/project/project-settings-dropdown.vue @@ -72,6 +72,12 @@ @update:model-value="setSubscriptionInStore" type="dropdown" /> + + {{ $t('project.webhooks.title') }} + implements IWebhook { + id = 0 + projectId = 0 + secret = '' + targetUrl = '' + events = [] + createdBy = null + + created: Date + updated: Date + + constructor(data: Partial = {}) { + super() + this.assignData(data) + + this.createdBy = new UserModel(this.createdBy) + + this.created = new Date(this.created) + this.updated = new Date(this.updated) + } +} \ No newline at end of file diff --git a/src/router/index.ts b/src/router/index.ts index d00b6d4c4..ccc0d36e4 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -46,6 +46,7 @@ const ProjectSettingEdit = () => import('@/views/project/settings/edit.vue') const ProjectSettingBackground = () => import('@/views/project/settings/background.vue') const ProjectSettingDuplicate = () => import('@/views/project/settings/duplicate.vue') const ProjectSettingShare = () => import('@/views/project/settings/share.vue') +const ProjectSettingWebhooks = () => import('@/views/project/settings/webhooks.vue') const ProjectSettingDelete = () => import('@/views/project/settings/delete.vue') const ProjectSettingArchive = () => import('@/views/project/settings/archive.vue') @@ -286,6 +287,14 @@ const router = createRouter({ showAsModal: true, }, }, + { + path: '/projects/:projectId/settings/webhooks', + name: 'project.settings.webhooks', + component: ProjectSettingWebhooks, + meta: { + showAsModal: true, + }, + }, { path: '/projects/:projectId/settings/delete', name: 'project.settings.delete', diff --git a/src/services/webhook.ts b/src/services/webhook.ts new file mode 100644 index 000000000..f2830c889 --- /dev/null +++ b/src/services/webhook.ts @@ -0,0 +1,29 @@ +import AbstractService from '@/services/abstractService' +import type {IWebhook} from '@/modelTypes/IWebhook' +import WebhookModel from '@/models/webhook' + +export default class WebhookService extends AbstractService { + constructor() { + super({ + getAll: '/projects/{projectId}/webhooks', + create: '/projects/{projectId}/webhooks', + update: '/projects/{projectId}/webhooks/{id}', + delete: '/projects/{projectId}/webhooks/{id}', + }) + } + + modelFactory(data) { + return new WebhookModel(data) + } + + async getAvailableEvents(): Promise { + const cancel = this.setLoading() + + try { + const response = await this.http.get('/webhooks/events') + return response.data + } finally { + cancel() + } + } +} diff --git a/src/views/project/settings/webhooks.vue b/src/views/project/settings/webhooks.vue new file mode 100644 index 000000000..9b9bf1363 --- /dev/null +++ b/src/views/project/settings/webhooks.vue @@ -0,0 +1,215 @@ + + + + + From 779aad1b2d5d765cb325b7a875a0311916195f5a Mon Sep 17 00:00:00 2001 From: kolaente Date: Fri, 20 Oct 2023 12:32:46 +0200 Subject: [PATCH 2/3] feat(webhooks): add form validation --- src/helpers/isValidHttpUrl.ts | 11 +++++++ src/i18n/lang/en.json | 1 + src/views/project/settings/webhooks.vue | 40 ++++++++++++++++++++++--- 3 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 src/helpers/isValidHttpUrl.ts 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') }} From 0e5415a2c9b2eab314fc726c3856d525b9821fcc Mon Sep 17 00:00:00 2001 From: kolaente Date: Fri, 20 Oct 2023 12:32:55 +0200 Subject: [PATCH 3/3] fix(webhooks): styling --- src/views/project/settings/webhooks.vue | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/views/project/settings/webhooks.vue b/src/views/project/settings/webhooks.vue index f1acd3386..8f86ab164 100644 --- a/src/views/project/settings/webhooks.vue +++ b/src/views/project/settings/webhooks.vue @@ -121,6 +121,7 @@ function validateSelectedEvents() { @@ -245,3 +246,10 @@ function validateSelectedEvents() { + +