diff --git a/db/knex_migrations/2025-05-11-0000-feat-notification-trigger.js b/db/knex_migrations/2025-05-11-0000-feat-notification-trigger.js new file mode 100644 index 000000000..53296b54d --- /dev/null +++ b/db/knex_migrations/2025-05-11-0000-feat-notification-trigger.js @@ -0,0 +1,25 @@ +exports.up = async function (knex) { + await knex.schema.alterTable("notification", function (table) { + table.text("trigger").notNullable().defaultTo("up,down,certificate"); + }); + + await knex("notification").whereNull("trigger").update({ + trigger: "up,down,certificate", + }); + + const notifications = await knex("notification").select("*"); + for (let n of notifications) { + await knex("notification").where("id", n.id).update({ + config: JSON.stringify({ + ...JSON.parse(n.config), + trigger: "up,down,certificate", + }), + }); + } +}; + +exports.down = function (knex) { + return knex.schema.alterTable("notification", function (table) { + table.dropColumn("trigger"); + }); +}; diff --git a/server/model/monitor.js b/server/model/monitor.js index 08d666b78..095b7997b 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -1293,6 +1293,7 @@ class Monitor extends BeanModel { for (let notification of notificationList) { try { + const triggers = notification.trigger.split(","); const heartbeatJSON = bean.toJSON(); const monitorData = [{ id: monitor.id, active: monitor.active, @@ -1309,7 +1310,9 @@ class Monitor extends BeanModel { heartbeatJSON["timezoneOffset"] = UptimeKumaServer.getInstance().getTimezoneOffset(); heartbeatJSON["localDateTime"] = dayjs.utc(heartbeatJSON["time"]).tz(heartbeatJSON["timezone"]).format(SQL_DATETIME_FORMAT); - await Notification.send(JSON.parse(notification.config), msg, monitor.toJSON(preloadData, false), heartbeatJSON); + if ((bean.status === UP && triggers.includes("up")) || (bean.status === DOWN && triggers.includes("down"))) { + await Notification.send(JSON.parse(notification.config), msg, monitor.toJSON(preloadData, false), heartbeatJSON); + } } catch (e) { log.error("monitor", "Cannot send notification to " + notification.name); log.error("monitor", e); @@ -1401,6 +1404,11 @@ class Monitor extends BeanModel { log.debug("monitor", "Send certificate notification"); for (let notification of notificationList) { + const triggers = notification.trigger.split(","); + if (!triggers.includes("certificate")) { + log.debug("monitor", "Notification does not trigger on certificate"); + continue; + } try { log.debug("monitor", "Sending to " + notification.name); await Notification.send(JSON.parse(notification.config), `[${this.name}][${this.url}] ${certType} certificate ${certCN} will expire in ${daysRemaining} days`); diff --git a/server/notification.js b/server/notification.js index fd8c23d67..c66d8564a 100644 --- a/server/notification.js +++ b/server/notification.js @@ -229,6 +229,7 @@ class Notification { bean.user_id = userID; bean.config = JSON.stringify(notification); bean.is_default = notification.isDefault || false; + bean.trigger = notification.trigger; await R.store(bean); if (notification.applyExisting) { diff --git a/src/components/NotificationDialog.vue b/src/components/NotificationDialog.vue index acfcde6a2..1d8cd2870 100644 --- a/src/components/NotificationDialog.vue +++ b/src/components/NotificationDialog.vue @@ -39,6 +39,56 @@ {{ $t("enableDefaultNotificationDescription") }} +
+ + + + + +
+ {{ $t("NotificationTriggerDescription") }} +

@@ -75,10 +125,12 @@ import { Modal } from "bootstrap"; import Confirm from "./Confirm.vue"; import NotificationFormList from "./notifications"; +import MonitorListFilterDropdown from "./MonitorListFilterDropdown.vue"; export default { components: { Confirm, + MonitorListFilterDropdown, }, props: {}, emits: [ "added" ], @@ -275,6 +327,7 @@ export default { name: "", type: "telegram", isDefault: false, + trigger: "up,down,certificate", }; } @@ -287,6 +340,9 @@ export default { */ submit() { this.processing = true; + if (!this.notification.trigger) { + this.notification.trigger = "up,down,certificate"; + } this.$root.getSocket().emit("addNotification", this.notification, this.id, (res) => { this.$root.toastRes(res); this.processing = false; @@ -361,6 +417,21 @@ export default { console.warn("Modal hide failed:", e); } } + }, + + updateTriggers(trigger) { + let triggers; + if (!this.notification.trigger) { + triggers = []; + } else { + triggers = this.notification.trigger.split(","); + } + if (triggers.includes(trigger)) { + triggers = triggers.filter(t => t !== trigger); + } else { + triggers.push(trigger); + } + this.notification.trigger = triggers.join(","); } }, }; diff --git a/src/lang/en.json b/src/lang/en.json index c8555bf12..bd3a8f81b 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -1098,5 +1098,11 @@ "Phone numbers": "Phone numbers", "Sender name": "Sender name", "smsplanetNeedToApproveName": "Needs to be approved in the client panel", - "Disable URL in Notification": "Disable URL in Notification" + "Disable URL in Notification": "Disable URL in Notification", + "UpTrigger": "Notify on up status", + "DownTrigger": "Notify on down status", + "Certificate": "Certificate", + "CertificateTrigger": "Notify on certificate expiration", + "NotificationTriggerDescription": "Customise which alerts trigger this notification provider", + "MonitorNotification": "Notification will be sent on {0} status changes" } diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index bf4e6889d..1741c2c33 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -748,7 +748,12 @@ {{ $t("Not available, please setup.") }}

-
+