Merge pull request #3088 from chakflying/feat/webhook-custom-body

Feat: Custom request body for Webhook Notifications
This commit is contained in:
Louis Lam 2023-07-09 20:42:50 +08:00 committed by GitHub
commit cfb4bbc6cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 113 additions and 32 deletions

28
package-lock.json generated
View File

@ -41,6 +41,7 @@
"jsonwebtoken": "~9.0.0", "jsonwebtoken": "~9.0.0",
"jwt-decode": "~3.1.2", "jwt-decode": "~3.1.2",
"limiter": "~2.1.0", "limiter": "~2.1.0",
"liquidjs": "^10.7.0",
"mongodb": "~4.14.0", "mongodb": "~4.14.0",
"mqtt": "~4.3.7", "mqtt": "~4.3.7",
"mssql": "~8.1.4", "mssql": "~8.1.4",
@ -13139,6 +13140,33 @@
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"dev": true "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": { "node_modules/listr2": {
"version": "3.14.0", "version": "3.14.0",
"resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz", "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz",

View File

@ -100,6 +100,7 @@
"jsonwebtoken": "~9.0.0", "jsonwebtoken": "~9.0.0",
"jwt-decode": "~3.1.2", "jwt-decode": "~3.1.2",
"limiter": "~2.1.0", "limiter": "~2.1.0",
"liquidjs": "^10.7.0",
"mongodb": "~4.14.0", "mongodb": "~4.14.0",
"mqtt": "~4.3.7", "mqtt": "~4.3.7",
"mssql": "~8.1.4", "mssql": "~8.1.4",

View File

@ -1,6 +1,7 @@
const NotificationProvider = require("./notification-provider"); const NotificationProvider = require("./notification-provider");
const axios = require("axios"); const axios = require("axios");
const FormData = require("form-data"); const FormData = require("form-data");
const { Liquid } = require("liquidjs");
class Webhook extends NotificationProvider { class Webhook extends NotificationProvider {
@ -15,17 +16,27 @@ class Webhook extends NotificationProvider {
monitor: monitorJSON, monitor: monitorJSON,
msg, msg,
}; };
let finalData;
let config = { let config = {
headers: {} headers: {}
}; };
if (notification.webhookContentType === "form-data") { if (notification.webhookContentType === "form-data") {
finalData = new FormData(); const formData = new FormData();
finalData.append("data", JSON.stringify(data)); formData.append("data", JSON.stringify(data));
config.headers = finalData.getHeaders(); config.headers = formData.getHeaders();
} else { data = formData;
finalData = data; } 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
data = await engine.render(tpl,
{
msg,
heartbeatJSON,
monitorJSON
});
} }
if (notification.webhookAdditionalHeaders) { if (notification.webhookAdditionalHeaders) {
@ -39,7 +50,7 @@ class Webhook extends NotificationProvider {
} }
} }
await axios.post(notification.webhookURL, finalData, config); await axios.post(notification.webhookURL, data, config);
return okMsg; return okMsg;
} catch (error) { } catch (error) {

View File

@ -208,6 +208,7 @@ let needSetup = false;
}); });
if (isDev) { if (isDev) {
app.use(express.urlencoded({ extended: true }));
app.post("/test-webhook", async (request, response) => { app.post("/test-webhook", async (request, response) => {
log.debug("test", request.headers); log.debug("test", request.headers);
log.debug("test", request.body); log.debug("test", request.body);

View File

@ -12,61 +12,97 @@
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="webhook-content-type" class="form-label">{{ <label for="webhook-request-body" class="form-label">{{
$t("Content Type") $t("Request Body")
}}</label> }}</label>
<select <select
id="webhook-content-type" id="webhook-request-body"
v-model="$parent.notification.webhookContentType" v-model="$parent.notification.webhookContentType"
class="form-select" class="form-select"
required required
> >
<option value="json">application/json</option> <option value="json">{{ $t("webhookBodyPresetOption", ["application/json"]) }}</option>
<option value="form-data">multipart/form-data</option> <option value="form-data">{{ $t("webhookBodyPresetOption", ["multipart/form-data"]) }}</option>
<option value="custom">{{ $t("webhookBodyCustomOption") }}</option>
</select> </select>
<div class="form-text"> <div class="form-text">
<div v-if="$parent.notification.webhookContentType == 'json'">
<p>{{ $t("webhookJsonDesc", ['"application/json"']) }}</p> <p>{{ $t("webhookJsonDesc", ['"application/json"']) }}</p>
</div>
<div v-if="$parent.notification.webhookContentType == 'form-data'">
<i18n-t tag="p" keypath="webhookFormDataDesc"> <i18n-t tag="p" keypath="webhookFormDataDesc">
<template #multipart>"multipart/form-data"</template> <template #multipart>multipart/form-data"</template>
<template #decodeFunction> <template #decodeFunction>
<strong>json_decode($_POST['data'])</strong> <strong>json_decode($_POST['data'])</strong>
</template> </template>
</i18n-t> </i18n-t>
</div> </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>
<div class="mb-3"> <div class="mb-3">
<i18n-t <div class="form-check form-switch">
tag="label" <input v-model="showAdditionalHeadersField" class="form-check-input" type="checkbox">
class="form-label" <label class="form-check-label">{{ $t("webhookAdditionalHeadersTitle") }}</label>
for="additionalHeaders" </div>
keypath="webhookAdditionalHeadersTitle" <div class="form-text">
> <i18n-t tag="p" keypath="webhookAdditionalHeadersDesc"> </i18n-t>
</i18n-t> </div>
<textarea <textarea
v-if="showAdditionalHeadersField"
id="additionalHeaders" id="additionalHeaders"
v-model="$parent.notification.webhookAdditionalHeaders" v-model="$parent.notification.webhookAdditionalHeaders"
class="form-control" class="form-control"
:placeholder="headersPlaceholder" :placeholder="headersPlaceholder"
></textarea> ></textarea>
<div class="form-text">
<i18n-t tag="p" keypath="webhookAdditionalHeadersDesc"> </i18n-t>
</div>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
data() {
return {
showAdditionalHeadersField: this.$parent.notification.webhookAdditionalHeaders != null,
};
},
computed: { computed: {
headersPlaceholder() { headersPlaceholder() {
return this.$t("Example:", [ return this.$t("Example:", [
` `
{ {
"HeaderName": "HeaderValue" "Authorization": "Authorization Token"
}`, }`,
]); ]);
}, },
customBodyPlaceholder() {
return `Example:
{
"Title": "Uptime Kuma Alert - {{ monitorJSON['name'] }}",
"Body": "{{ msg }}"
}`;
}
}, },
}; };
</script> </script>

View File

@ -196,8 +196,11 @@
"Content Type": "Content Type", "Content Type": "Content Type",
"webhookJsonDesc": "{0} is good for any modern HTTP servers such as Express.js", "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}", "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", "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.",
"webhookBodyPresetOption": "Preset - {0}",
"webhookBodyCustomOption": "Custom Body",
"Webhook URL": "Webhook URL", "Webhook URL": "Webhook URL",
"Application Token": "Application Token", "Application Token": "Application Token",
"Server URL": "Server URL", "Server URL": "Server URL",
@ -757,5 +760,6 @@
"Group": "Group", "Group": "Group",
"Monitor Group": "Monitor Group", "Monitor Group": "Monitor Group",
"noGroupMonitorMsg": "Not Available. Create a Group Monitor First.", "noGroupMonitorMsg": "Not Available. Create a Group Monitor First.",
"Close": "Close" "Close": "Close",
"Request Body": "Request Body"
} }