diff --git a/package.json b/package.json index 1ba25baf9..806eff074 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "uptime-kuma", - "version": "1.16.0-beta.0", + "version": "1.16.0", "license": "MIT", "repository": { "type": "git", @@ -39,7 +39,7 @@ "build-docker-nightly-alpine": "docker buildx build -f docker/dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly-alpine --target nightly . --push", "build-docker-nightly-amd64": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain", "upload-artifacts": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg VERSION --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain", - "setup": "git checkout 1.15.1 && npm ci --production && npm run download-dist", + "setup": "git checkout 1.16.0 && npm ci --production && npm run download-dist", "download-dist": "node extra/download-dist.js", "mark-as-nightly": "node extra/mark-as-nightly.js", "reset-password": "node extra/reset-password.js", diff --git a/server/model/monitor.js b/server/model/monitor.js index 2bc40b5f2..9449b7b5c 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -334,7 +334,7 @@ class Monitor extends BeanModel { let startTime = dayjs().valueOf(); let dnsMessage = ""; - let dnsRes = await dnsResolve(this.hostname, this.dns_resolve_server, this.dns_resolve_type); + let dnsRes = await dnsResolve(this.hostname, this.dns_resolve_server, this.port, this.dns_resolve_type); bean.ping = dayjs().valueOf() - startTime; if (this.dns_resolve_type === "A" || this.dns_resolve_type === "AAAA" || this.dns_resolve_type === "TXT") { diff --git a/server/notification-providers/discord.js b/server/notification-providers/discord.js index 77b04d9d3..28ead7b7a 100644 --- a/server/notification-providers/discord.js +++ b/server/notification-providers/discord.js @@ -55,8 +55,8 @@ class Discord extends NotificationProvider { value: monitorJSON["name"], }, { - name: "Service URL / Address", - value: address, + name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL", + value: monitorJSON["type"] === "push" ? "Heartbeat" : address, }, { name: "Time (UTC)", @@ -90,8 +90,8 @@ class Discord extends NotificationProvider { value: monitorJSON["name"], }, { - name: "Service URL", - value: address.startsWith("http") ? "[Visit Service](" + address + ")" : address, + name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL", + value: monitorJSON["type"] === "push" ? "Heartbeat" : address.startsWith("http") ? "[Visit Service](" + address + ")" : address, }, { name: "Time (UTC)", @@ -99,7 +99,7 @@ class Discord extends NotificationProvider { }, { name: "Ping", - value: heartbeatJSON["ping"] + "ms", + value: heartbeatJSON["ping"] == null ? "N/A" : heartbeatJSON["ping"] + " ms", }, ], }], diff --git a/server/notification-providers/pagerduty.js b/server/notification-providers/pagerduty.js new file mode 100644 index 000000000..86e9a0992 --- /dev/null +++ b/server/notification-providers/pagerduty.js @@ -0,0 +1,113 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); +const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util"); +const { setting } = require("../util-server"); +let successMessage = "Sent Successfully."; + +class PagerDuty extends NotificationProvider { + name = "PagerDuty"; + + /** + * @inheritdoc + */ + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + try { + if (heartbeatJSON == null) { + const title = "Uptime Kuma Alert"; + const monitor = { + type: "ping", + url: "Uptime Kuma Test Button", + }; + return this.postNotification(notification, title, msg, monitor); + } + + if (heartbeatJSON.status === UP) { + const title = "Uptime Kuma Monitor ✅ Up"; + const eventAction = notification.pagerdutyAutoResolve || null; + + return this.postNotification(notification, title, heartbeatJSON.msg, monitorJSON, eventAction); + } + + if (heartbeatJSON.status === DOWN) { + const title = "Uptime Kuma Monitor 🔴 Down"; + return this.postNotification(notification, title, heartbeatJSON.msg, monitorJSON, "trigger"); + } + } catch (error) { + this.throwGeneralAxiosError(error); + } + } + + /** + * Check if result is successful, result code should be in range 2xx + * @param {Object} result Axios response object + * @throws {Error} The status code is not in range 2xx + */ + checkResult(result) { + if (result.status == null) { + throw new Error("PagerDuty notification failed with invalid response!"); + } + if (result.status < 200 || result.status >= 300) { + throw new Error("PagerDuty notification failed with status code " + result.status); + } + } + + /** + * Send the message + * @param {BeanModel} notification Message title + * @param {string} title Message title + * @param {string} body Message + * @param {Object} monitorInfo Monitor details (For Up/Down only) + * @param {?string} eventAction Action event for PagerDuty (trigger, acknowledge, resolve) + * @returns {string} + */ + async postNotification(notification, title, body, monitorInfo, eventAction = "trigger") { + + if (eventAction == null) { + return "No action required"; + } + + let monitorUrl; + if (monitorInfo.type === "port") { + monitorUrl = monitorInfo.hostname; + if (monitorInfo.port) { + monitorUrl += ":" + monitorInfo.port; + } + } else if (monitorInfo.hostname != null) { + monitorUrl = monitorInfo.hostname; + } else { + monitorUrl = monitorInfo.url; + } + + const options = { + method: "POST", + url: notification.pagerdutyIntegrationUrl, + headers: { "Content-Type": "application/json" }, + data: { + payload: { + summary: `[${title}] [${monitorInfo.name}] ${body}`, + severity: notification.pagerdutyPriority || "warning", + source: monitorUrl, + }, + routing_key: notification.pagerdutyIntegrationKey, + event_action: eventAction, + dedup_key: "Uptime Kuma/" + monitorInfo.id, + } + }; + + const baseURL = await setting("primaryBaseURL"); + if (baseURL && monitorInfo) { + options.client = "Uptime Kuma"; + options.client_url = baseURL + getMonitorRelativeURL(monitorInfo.id); + } + + let result = await axios.request(options); + this.checkResult(result); + if (result.statusText != null) { + return "PagerDuty notification succeed: " + result.statusText; + } + + return successMessage; + } +} + +module.exports = PagerDuty; diff --git a/server/notification.js b/server/notification.js index 269e94440..d0b6f40d9 100644 --- a/server/notification.js +++ b/server/notification.js @@ -29,6 +29,7 @@ const SerwerSMS = require("./notification-providers/serwersms"); const Stackfield = require("./notification-providers/stackfield"); const WeCom = require("./notification-providers/wecom"); const GoogleChat = require("./notification-providers/google-chat"); +const PagerDuty = require("./notification-providers/pagerduty"); const Gorush = require("./notification-providers/gorush"); const Alerta = require("./notification-providers/alerta"); const OneBot = require("./notification-providers/onebot"); @@ -74,6 +75,7 @@ class Notification { new Stackfield(), new WeCom(), new GoogleChat(), + new PagerDuty(), new Gorush(), new Alerta(), new OneBot(), diff --git a/server/util-server.js b/server/util-server.js index 54974e148..db7f525c7 100644 --- a/server/util-server.js +++ b/server/util-server.js @@ -176,12 +176,16 @@ exports.mqttAsync = function (hostname, topic, okMessage, options = {}) { * Resolves a given record using the specified DNS server * @param {string} hostname The hostname of the record to lookup * @param {string} resolverServer The DNS server to use + * @param {string} resolverPort Port the DNS server is listening on * @param {string} rrtype The type of record to request * @returns {Promise<(string[]|Object[]|Object)>} */ -exports.dnsResolve = function (hostname, resolverServer, rrtype) { +exports.dnsResolve = function (hostname, resolverServer, resolverPort, rrtype) { const resolver = new Resolver(); - resolver.setServers([ resolverServer ]); + // Remove brackets from IPv6 addresses so we can re-add them to + // prevent issues with ::1:5300 (::1 port 5300) + resolverServer = resolverServer.replace("[", "").replace("]", ""); + resolver.setServers([`[${resolverServer}]:${resolverPort}`]); return new Promise((resolve, reject) => { if (rrtype === "PTR") { resolver.reverse(hostname, (err, records) => { diff --git a/src/components/notifications/PagerDuty.vue b/src/components/notifications/PagerDuty.vue new file mode 100644 index 000000000..059a9aef2 --- /dev/null +++ b/src/components/notifications/PagerDuty.vue @@ -0,0 +1,45 @@ + + + diff --git a/src/components/notifications/index.js b/src/components/notifications/index.js index 496d35fa0..37beb24d3 100644 --- a/src/components/notifications/index.js +++ b/src/components/notifications/index.js @@ -27,6 +27,7 @@ import SerwerSMS from "./SerwerSMS.vue"; import Stackfield from "./Stackfield.vue"; import WeCom from "./WeCom.vue"; import GoogleChat from "./GoogleChat.vue"; +import PagerDuty from "./PagerDuty.vue"; import Gorush from "./Gorush.vue"; import Alerta from "./Alerta.vue"; import OneBot from "./OneBot.vue"; @@ -67,6 +68,7 @@ const NotificationFormList = { "stackfield": Stackfield, "WeCom": WeCom, "GoogleChat": GoogleChat, + "PagerDuty": PagerDuty, "gorush": Gorush, "alerta": Alerta, "OneBot": OneBot, diff --git a/src/components/settings/Security.vue b/src/components/settings/Security.vue index 87bb745a1..a5d42f82b 100644 --- a/src/components/settings/Security.vue +++ b/src/components/settings/Security.vue @@ -206,7 +206,7 @@ @@ -234,6 +234,12 @@

Vui lòng cẩn thận.

+ +