feat: filter which event (UP/DOWN/CERT-EXPIRY) triggers alerts

This commit is contained in:
dvdandroid 2025-05-11 23:20:38 +02:00
parent cd6dc144a7
commit 3e54c3ef8c
6 changed files with 119 additions and 3 deletions

View file

@ -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");
});
};

View file

@ -1292,6 +1292,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,
@ -1308,7 +1309,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);
@ -1400,6 +1403,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`);

View file

@ -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) {

View file

@ -39,6 +39,56 @@
{{ $t("enableDefaultNotificationDescription") }}
</div>
<br>
<MonitorListFilterDropdown :filter-active="false" style="margin-left: -5px;">
<template #status>
<span>{{ notification.trigger?.split(',')
?.map(tt => $t(String(tt).charAt(0).toUpperCase() + String(tt).slice(1)))
?.join(", ") || $t("Default") }}</span>
</template>
<template #dropdown>
<li>
<div class="dropdown-item" tabindex="0" @click.stop="updateTriggers('up')">
<div class="d-flex align-items-center justify-content-between">
<span>{{ $t("UpTrigger") }}</span>
<span class="ps-3">
<span v-if="notification.trigger?.split(',').includes('up')" class="px-1 filter-active">
<font-awesome-icon icon="check" />
</span>
</span>
</div>
</div>
</li>
<li>
<div class="dropdown-item" tabindex="0" @click.stop="updateTriggers('down')">
<div class="d-flex align-items-center justify-content-between">
<span>{{ $t("DownTrigger") }}</span>
<span class="ps-3">
<span v-if="notification.trigger?.split(',').includes('down')" class="px-1 filter-active">
<font-awesome-icon icon="check" />
</span>
</span>
</div>
</div>
</li>
<li>
<div class="dropdown-item" tabindex="0" @click.stop="updateTriggers('certificate')">
<div class="d-flex align-items-center justify-content-between">
<span>{{ $t("CertificateTrigger") }}</span>
<span class="ps-3">
<span v-if="notification.trigger?.split(',').includes('certificate')" class="px-1 filter-active">
<font-awesome-icon icon="check" />
</span>
</span>
</div>
</div>
</li>
</template>
</MonitorListFilterDropdown>
<div class="form-text">
{{ $t("NotificationTriggerDescription") }}
</div>
<br>
<div class="form-check form-switch">
@ -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(",");
}
},
};

View file

@ -1097,5 +1097,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"
}

View file

@ -733,7 +733,12 @@
{{ $t("Not available, please setup.") }}
</p>
<div v-for="notification in $root.notificationList" :key="notification.id" class="form-check form-switch my-3">
<div
v-for="notification in $root.notificationList" :key="notification.id" class="form-check form-switch my-3"
:title="`${$t('MonitorNotification', [notification.trigger.split(',')
.map(tt => $t(String(tt).charAt(0).toUpperCase() + String(tt).slice(1)))
.join(', ')])}`"
>
<input :id=" 'notification' + notification.id" v-model="monitor.notificationIDList[notification.id]" class="form-check-input" type="checkbox">
<label class="form-check-label" :for=" 'notification' + notification.id">