From 97d38ee1a88f38b5022d5e2f10016ccefd072021 Mon Sep 17 00:00:00 2001 From: Nelson Chan <chakflying@hotmail.com> Date: Wed, 19 Apr 2023 22:28:53 +0800 Subject: [PATCH 1/5] Feat: Add custom body for Webhook Notif. --- package-lock.json | 28 +++++++++ package.json | 1 + server/notification-providers/webhook.js | 26 +++++--- src/components/notifications/Webhook.vue | 78 +++++++++++++++++------- src/lang/en.json | 3 +- 5 files changed, 107 insertions(+), 29 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8796b1774..2374eccc8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,6 +41,7 @@ "jsonwebtoken": "~9.0.0", "jwt-decode": "~3.1.2", "limiter": "~2.1.0", + "liquidjs": "^10.7.0", "mongodb": "~4.14.0", "mqtt": "~4.3.7", "mssql": "~8.1.4", @@ -13761,6 +13762,33 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "node_modules/liquidjs": { + "version": "10.7.1", + "resolved": "https://registry.npmjs.org/liquidjs/-/liquidjs-10.7.1.tgz", + "integrity": "sha512-tl9nWBZrrKcC61yfih3lbtSjAn+k7e0HhwydPjQKI4+metLk927HYBfXfbf6yrCcYjnBnLzk8xMjUF83yknAQQ==", + "dependencies": { + "commander": "^10.0.0" + }, + "bin": { + "liquid": "bin/liquid.js", + "liquidjs": "bin/liquid.js" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/liquidjs" + } + }, + "node_modules/liquidjs/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "engines": { + "node": ">=14" + } + }, "node_modules/listr2": { "version": "3.14.0", "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz", diff --git a/package.json b/package.json index c099b4d87..2740ea775 100644 --- a/package.json +++ b/package.json @@ -100,6 +100,7 @@ "jsonwebtoken": "~9.0.0", "jwt-decode": "~3.1.2", "limiter": "~2.1.0", + "liquidjs": "^10.7.0", "mongodb": "~4.14.0", "mqtt": "~4.3.7", "mssql": "~8.1.4", diff --git a/server/notification-providers/webhook.js b/server/notification-providers/webhook.js index 649746b8e..e5a47fbce 100644 --- a/server/notification-providers/webhook.js +++ b/server/notification-providers/webhook.js @@ -1,6 +1,7 @@ const NotificationProvider = require("./notification-provider"); const axios = require("axios"); const FormData = require("form-data"); +const { Liquid } = require("liquidjs"); class Webhook extends NotificationProvider { @@ -15,17 +16,28 @@ class Webhook extends NotificationProvider { monitor: monitorJSON, msg, }; - let finalData; let config = { headers: {} }; if (notification.webhookContentType === "form-data") { - finalData = new FormData(); - finalData.append("data", JSON.stringify(data)); - config.headers = finalData.getHeaders(); - } else { - finalData = data; + const formData = new FormData(); + formData.append("data", JSON.stringify(data)); + config.headers = formData.getHeaders(); + data = formData; + } else if (notification.webhookContentType === "custom") { + // Initialize LiquidJS and parse the custom Body Template + const engine = new Liquid(); + const tpl = engine.parse(notification.webhookCustomBody); + + // Insert templated values into Body + const parsedBody = await engine.render(tpl, + { + msg, + heartbeatJSON, + monitorJSON + }); + data = parsedBody; } if (notification.webhookAdditionalHeaders) { @@ -39,7 +51,7 @@ class Webhook extends NotificationProvider { } } - await axios.post(notification.webhookURL, finalData, config); + await axios.post(notification.webhookURL, data, config); return okMsg; } catch (error) { diff --git a/src/components/notifications/Webhook.vue b/src/components/notifications/Webhook.vue index 1b85a5409..34c90cb04 100644 --- a/src/components/notifications/Webhook.vue +++ b/src/components/notifications/Webhook.vue @@ -13,7 +13,7 @@ <div class="mb-3"> <label for="webhook-content-type" class="form-label">{{ - $t("Content Type") + $t("Request Body") }}</label> <select id="webhook-content-type" @@ -21,52 +21,88 @@ class="form-select" required > - <option value="json">application/json</option> - <option value="form-data">multipart/form-data</option> + <option value="json">Preset - application/json</option> + <option value="form-data">Preset - multipart/form-data</option> + <option value="custom">Custom Body</option> </select> <div class="form-text"> - <p>{{ $t("webhookJsonDesc", ['"application/json"']) }}</p> - <i18n-t tag="p" keypath="webhookFormDataDesc"> - <template #multipart>"multipart/form-data"</template> - <template #decodeFunction> - <strong>json_decode($_POST['data'])</strong> - </template> - </i18n-t> + <div v-if="$parent.notification.webhookContentType == 'json'"> + <p>{{ $t("webhookJsonDesc", ['"application/json"']) }}</p> + </div> + <div v-if="$parent.notification.webhookContentType == 'form-data'"> + <i18n-t tag="p" keypath="webhookFormDataDesc"> + <template #multipart>multipart/form-data"</template> + <template #decodeFunction> + <strong>json_decode($_POST['data'])</strong> + </template> + </i18n-t> + </div> + <div v-if="$parent.notification.webhookContentType == 'custom'"> + <i18n-t tag="p" keypath="webhookCustomBodyDesc"> + <template #msg> + <code>msg</code> + </template> + <template #heartbeat> + <code>heartbeatJSON</code> + </template> + <template #monitor> + <code>monitorJSON</code> + </template> + </i18n-t> + </div> </div> + + <textarea + v-if="$parent.notification.webhookContentType == 'custom'" + id="customBody" + v-model="$parent.notification.webhookCustomBody" + class="form-control" + :placeholder="customBodyPlaceholder" + ></textarea> </div> <div class="mb-3"> - <i18n-t - tag="label" - class="form-label" - for="additionalHeaders" - keypath="webhookAdditionalHeadersTitle" - > - </i18n-t> + <div class="form-check form-switch"> + <input v-model="showAdditionalHeadersField" class="form-check-input" type="checkbox"> + <label class="form-check-label">{{ $t("webhookAdditionalHeadersTitle") }}</label> + </div> + <div class="form-text"> + <i18n-t tag="p" keypath="webhookAdditionalHeadersDesc"> </i18n-t> + </div> <textarea + v-if="showAdditionalHeadersField" id="additionalHeaders" v-model="$parent.notification.webhookAdditionalHeaders" class="form-control" :placeholder="headersPlaceholder" ></textarea> - <div class="form-text"> - <i18n-t tag="p" keypath="webhookAdditionalHeadersDesc"> </i18n-t> - </div> </div> </template> <script> export default { + data() { + return { + showAdditionalHeadersField: this.$parent.notification.webhookAdditionalHeaders != null, + }; + }, computed: { headersPlaceholder() { return this.$t("Example:", [ ` { - "HeaderName": "HeaderValue" + "Authorization": "Authorization Token" }`, ]); }, + customBodyPlaceholder() { + return `Example: +{ + "Title": "Uptime Kuma Alert - {{ monitorJSON['name'] }}", + "Body": "{{ msg }}" +}`; + } }, }; </script> diff --git a/src/lang/en.json b/src/lang/en.json index aecd34be8..7d3b7d2e8 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -195,8 +195,9 @@ "Content Type": "Content Type", "webhookJsonDesc": "{0} is good for any modern HTTP servers such as Express.js", "webhookFormDataDesc": "{multipart} is good for PHP. The JSON will need to be parsed with {decodeFunction}", + "webhookCustomBodyDesc": "Define a custom HTTP Body for the request. Template variables {msg}, {heartbeat}, {monitor} are accepted.", "webhookAdditionalHeadersTitle": "Additional Headers", - "webhookAdditionalHeadersDesc": "Sets additional headers sent with the webhook.", + "webhookAdditionalHeadersDesc": "Sets additional headers sent with the webhook. Each header should be defined as a JSON key/value.", "Webhook URL": "Webhook URL", "Application Token": "Application Token", "Server URL": "Server URL", From c9d6e576ab1b220882484b63d664ff9da5d84ad4 Mon Sep 17 00:00:00 2001 From: Nelson Chan <3271800+chakflying@users.noreply.github.com> Date: Wed, 31 May 2023 09:33:01 +0800 Subject: [PATCH 2/5] Chore: Remove redundant assign Co-authored-by: Frank Elsinga <frank@elsinga.de> --- server/notification-providers/webhook.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/notification-providers/webhook.js b/server/notification-providers/webhook.js index e5a47fbce..9f5643b4e 100644 --- a/server/notification-providers/webhook.js +++ b/server/notification-providers/webhook.js @@ -31,13 +31,12 @@ class Webhook extends NotificationProvider { const tpl = engine.parse(notification.webhookCustomBody); // Insert templated values into Body - const parsedBody = await engine.render(tpl, + data = await engine.render(tpl, { msg, heartbeatJSON, monitorJSON }); - data = parsedBody; } if (notification.webhookAdditionalHeaders) { From 7761e9a05ecbdaa3f7f54c899a2477c40541e963 Mon Sep 17 00:00:00 2001 From: Nelson Chan <chakflying@hotmail.com> Date: Wed, 31 May 2023 09:42:55 +0800 Subject: [PATCH 3/5] Chore: Add translations to options text --- src/components/notifications/Webhook.vue | 6 +++--- src/lang/en.json | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/notifications/Webhook.vue b/src/components/notifications/Webhook.vue index 34c90cb04..5ed0aa47a 100644 --- a/src/components/notifications/Webhook.vue +++ b/src/components/notifications/Webhook.vue @@ -21,9 +21,9 @@ class="form-select" required > - <option value="json">Preset - application/json</option> - <option value="form-data">Preset - multipart/form-data</option> - <option value="custom">Custom Body</option> + <option value="json">{{ $t("webhookBodyPresetOption", ["application/json"]) }}</option> + <option value="form-data">{{ $t("webhookBodyPresetOption", ["multipart/form-data"]) }}</option> + <option value="custom">{{ $t("webhookBodyCustomOption") }}</option> </select> <div class="form-text"> diff --git a/src/lang/en.json b/src/lang/en.json index 7d3b7d2e8..d6ca64bae 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -198,6 +198,8 @@ "webhookCustomBodyDesc": "Define a custom HTTP Body for the request. Template variables {msg}, {heartbeat}, {monitor} are accepted.", "webhookAdditionalHeadersTitle": "Additional Headers", "webhookAdditionalHeadersDesc": "Sets additional headers sent with the webhook. Each header should be defined as a JSON key/value.", + "webhookBodyPresetOption": "Preset - {0}", + "webhookBodyCustomOption": "Custom Body", "Webhook URL": "Webhook URL", "Application Token": "Application Token", "Server URL": "Server URL", From 8955c3816b0a37039fe303f5bb26d496c3280ed0 Mon Sep 17 00:00:00 2001 From: Nelson Chan <chakflying@hotmail.com> Date: Wed, 31 May 2023 22:07:48 +0800 Subject: [PATCH 4/5] Chore: Rename select ID & add translation --- src/components/notifications/Webhook.vue | 4 ++-- src/lang/en.json | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/notifications/Webhook.vue b/src/components/notifications/Webhook.vue index 5ed0aa47a..4262ad067 100644 --- a/src/components/notifications/Webhook.vue +++ b/src/components/notifications/Webhook.vue @@ -12,11 +12,11 @@ </div> <div class="mb-3"> - <label for="webhook-content-type" class="form-label">{{ + <label for="webhook-request-body" class="form-label">{{ $t("Request Body") }}</label> <select - id="webhook-content-type" + id="webhook-request-body" v-model="$parent.notification.webhookContentType" class="form-select" required diff --git a/src/lang/en.json b/src/lang/en.json index d6ca64bae..39c22505f 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -755,5 +755,6 @@ "Group": "Group", "Monitor Group": "Monitor Group", "noGroupMonitorMsg": "Not Available. Create a Group Monitor First.", - "Close": "Close" + "Close": "Close", + "Request Body": "Request Body" } From 293015ff35894b3972d77843d66b299caa7239c1 Mon Sep 17 00:00:00 2001 From: Louis Lam <louislam@users.noreply.github.com> Date: Sun, 9 Jul 2023 18:53:57 +0800 Subject: [PATCH 5/5] Parse `x-www-form-urlencoded` for /test-webhook --- server/server.js | 1 + 1 file changed, 1 insertion(+) diff --git a/server/server.js b/server/server.js index 976dca5de..c35af7fc1 100644 --- a/server/server.js +++ b/server/server.js @@ -208,6 +208,7 @@ let needSetup = false; }); if (isDev) { + app.use(express.urlencoded({ extended: true })); app.post("/test-webhook", async (request, response) => { log.debug("test", request.headers); log.debug("test", request.body);