Merge e191d1329570dfc2600252e740212f0ffc3e3342 into 510056fbbcfd5fea271dd412e139520b4c62ec3b

This commit is contained in:
DVDAndroid 2025-04-17 23:35:20 +00:00 committed by GitHub
commit 507e2debf6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 158 additions and 22 deletions

View File

@ -0,0 +1,27 @@
exports.up = function (knex) {
return knex.schema
.alterTable("monitor_notification", function (table) {
table.text("trigger").notNullable().defaultTo("always");
})
.alterTable("notification", function (table) {
table.text("default_trigger").notNullable().defaultTo("always");
})
.then(() => {
knex("monitor_notification").whereNull("trigger").update({
trigger: "always",
});
knex("notification").whereNull("default_trigger").update({
default_trigger: "always",
});
});
};
exports.down = function (knex) {
return knex.schema
.alterTable("monitor_notification", function (table) {
table.dropColumn("trigger");
})
.alterTable("notification", function (table) {
table.dropColumn("default_trigger");
});
};

View File

@ -1340,7 +1340,14 @@ 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 (
notification.trigger === "always" ||
notification.trigger === "up_down" ||
((notification.trigger === "up" || notification.trigger === "up_certificate") && bean.status === UP) ||
((notification.trigger === "down" || notification.trigger === "down_certificate") && bean.status === 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);
@ -1355,7 +1362,7 @@ class Monitor extends BeanModel {
* @returns {Promise<LooseObject<any>[]>} List of notifications
*/
static async getNotificationList(monitor) {
let notificationList = await R.getAll("SELECT notification.* FROM notification, monitor_notification WHERE monitor_id = ? AND monitor_notification.notification_id = notification.id ", [
let notificationList = await R.getAll("SELECT notification.*, monitor_notification.trigger FROM notification, monitor_notification WHERE monitor_id = ? AND monitor_notification.notification_id = notification.id ", [
monitor.id,
]);
return notificationList;
@ -1432,6 +1439,9 @@ class Monitor extends BeanModel {
log.debug("monitor", "Send certificate notification");
for (let notification of notificationList) {
if (notification.trigger !== "always" && notification.trigger !== "certificate" && notification.trigger !== "up_certificate" && notification.trigger !== "down_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`);
@ -1509,7 +1519,7 @@ class Monitor extends BeanModel {
*/
static async getMonitorNotification(monitorIDs) {
return await R.getAll(`
SELECT monitor_notification.monitor_id, monitor_notification.notification_id
SELECT monitor_notification.monitor_id, monitor_notification.notification_id, monitor_notification.trigger
FROM monitor_notification
WHERE monitor_notification.monitor_id IN (${monitorIDs.map((_) => "?").join(",")})
`, monitorIDs);
@ -1558,7 +1568,10 @@ class Monitor extends BeanModel {
if (!notificationsMap.has(row.monitor_id)) {
notificationsMap.set(row.monitor_id, {});
}
notificationsMap.get(row.monitor_id)[row.notification_id] = true;
notificationsMap.get(row.monitor_id)[row.notification_id] = {
active: true,
trigger: row.trigger,
};
});
tags.forEach(row => {

View File

@ -219,6 +219,7 @@ class Notification {
bean.user_id = userID;
bean.config = JSON.stringify(notification);
bean.is_default = notification.isDefault || false;
bean.default_trigger = notification.defaultTrigger;
await R.store(bean);
if (notification.applyExisting) {
@ -271,15 +272,20 @@ async function applyNotificationEveryMonitor(notificationID, userID) {
]);
for (let i = 0; i < monitors.length; i++) {
let checkNotification = await R.findOne("monitor_notification", " monitor_id = ? AND notification_id = ? ", [
let checkNotification = await R.getRow(`
SELECT n.default_trigger FROM notification AS n
LEFT JOIN monitor_notification mn on n.id = mn.notification_id AND mn.monitor_id = ? AND mn.notification_id = ?
WHERE mn.monitor_id IS NULL
`, [
monitors[i].id,
notificationID,
]);
if (! checkNotification) {
if (checkNotification) {
let relation = R.dispense("monitor_notification");
relation.monitor_id = monitors[i].id;
relation.notification_id = notificationID;
relation.trigger = checkNotification.default_trigger;
await R.store(relation);
}
}

View File

@ -1426,6 +1426,7 @@ let needSetup = false;
ok: true,
msg: "Saved.",
msgi18n: true,
defaultTrigger: notificationBean.default_trigger,
id: notificationBean.id,
});
@ -1636,10 +1637,11 @@ async function updateMonitorNotification(monitorID, notificationIDList) {
]);
for (let notificationID in notificationIDList) {
if (notificationIDList[notificationID]) {
if (notificationIDList[notificationID].active) {
let relation = R.dispense("monitor_notification");
relation.monitor_id = monitorID;
relation.notification_id = notificationID;
relation.trigger = notificationIDList[notificationID].trigger;
await R.store(relation);
}
}

View File

@ -41,6 +41,35 @@
<br>
<select id="notificationTrigger" v-model="notification.defaultTrigger" class="form-select">
<option value="always">
{{ $t("notificationTriggerAlways") }}
</option>
<option value="up_down">
{{ $t("notificationTriggerUpDown") }}
</option>
<option value="up">
{{ $t("notificationTriggerUp") }}
</option>
<option value="down">
{{ $t("notificationTriggerDown") }}
</option>
<option value="certificate">
{{ $t("notificationTriggerCertificate") }}
</option>
<option value="up_certificate">
{{ $t("notificationTriggerUpCertificate") }}
</option>
<option value="down_certificate">
{{ $t("notificationTriggerDownCertificate") }}
</option>
</select>
<div class="form-text">
{{ $t("enableDefaultTriggerNotificationDescription") }}
</div>
<br>
<div class="form-check form-switch">
<input v-model="notification.applyExisting" class="form-check-input" type="checkbox">
<label class="form-check-label">{{ $t("Apply on all existing monitors") }}</label>
@ -95,6 +124,7 @@ export default {
/** @type { null | keyof NotificationFormList } */
type: null,
isDefault: false,
defaultTrigger: "always",
// Do not set default value here, please scroll to show()
}
};
@ -270,6 +300,7 @@ export default {
name: "",
type: "telegram",
isDefault: false,
defaultTrigger: "always",
};
}
@ -291,7 +322,7 @@ export default {
// Emit added event, doesn't emit edit.
if (! this.id) {
this.$emit("added", res.id);
this.$emit("added", res.id, res.defaultTrigger);
}
}

View File

@ -1067,5 +1067,14 @@
"YZJ Robot Token": "YZJ Robot token",
"Plain Text": "Plain Text",
"Message Template": "Message Template",
"Template Format": "Template Format"
"Template Format": "Template Format",
"notificationTriggerAlways": "Notify on up, down state or certificate expiry",
"notificationTriggerUpDown": "Notify on up or down state",
"notificationTriggerUp": "Notify on up state only",
"notificationTriggerDown": "Notify on down state only",
"notificationTriggerUpCertificate": "Notify on up state or certificate expiry",
"notificationTriggerDownCertificate": "Notify on down state or certificate expiry",
"notificationTriggerCertificate": "Notify on certificate expiry only",
"notificationTriggerCertificateWarning": "Certificate expiry notifications are disabled for this monitor. You will not receive alerts when the certificate is about to expire.",
"enableDefaultTriggerNotificationDescription": "This notification trigger will be the default for new monitors. You can still edit the notification trigger separately for each monitor."
}

View File

@ -733,17 +733,49 @@
{{ $t("Not available, please setup.") }}
</p>
<div v-for="notification in $root.notificationList" :key="notification.id" class="form-check form-switch my-3">
<input :id=" 'notification' + notification.id" v-model="monitor.notificationIDList[notification.id]" class="form-check-input" type="checkbox">
<div v-if="Object.keys(monitor.notificationIDList).length > 0">
<div v-for="notification in $root.notificationList" :key="notification.id" class="form-check form-switch my-3">
<input :id=" 'notification' + notification.id" v-model="monitor.notificationIDList[notification.id].active" class="form-check-input" type="checkbox">
<label class="form-check-label" :for=" 'notification' + notification.id">
{{ notification.name }}
<a href="#" @click="$refs.notificationDialog.show(notification.id)">{{ $t("Edit") }}</a>
</label>
<label class="form-check-label" :for=" 'notification' + notification.id">
{{ notification.name }}
<a href="#" @click="$refs.notificationDialog.show(notification.id)">{{ $t("Edit") }}</a>
</label>
<span v-if="notification.isDefault == true" class="badge bg-primary ms-2">{{ $t("Default") }}</span>
<span v-if="notification.isDefault == true" class="badge bg-primary ms-2">{{ $t("Default") }}</span>
<div v-if="monitor.notificationIDList[notification.id].active">
<select id="notificationTrigger" v-model="monitor.notificationIDList[notification.id].trigger" class="form-select">
<option value="always">
{{ $t("notificationTriggerAlways") }}
</option>
<option value="up_down">
{{ $t("notificationTriggerUpDown") }}
</option>
<option value="up">
{{ $t("notificationTriggerUp") }}
</option>
<option value="down">
{{ $t("notificationTriggerDown") }}
</option>
<option value="certificate">
{{ $t("notificationTriggerCertificate") }}
</option>
<option value="up_certificate">
{{ $t("notificationTriggerUpCertificate") }}
</option>
<option value="down_certificate">
{{ $t("notificationTriggerDownCertificate") }}
</option>
</select>
<div
v-if="(monitor.notificationIDList[notification.id].trigger === 'certificate' || monitor.notificationIDList[notification.id].trigger === 'up_certificate' || monitor.notificationIDList[notification.id].trigger === 'down_certificate') && (!(monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query') || !monitor.expiryNotification)"
class="alert alert-warning my-2" role="alert"
>
{{ $t("notificationTriggerCertificateWarning") }}
</div>
</div>
</div>
</div>
<button class="btn btn-primary me-2" type="button" @click="$refs.notificationDialog.show()">
{{ $t("Setup Notification") }}
</button>
@ -1576,9 +1608,11 @@ message HealthCheckResponse {
}
for (let i = 0; i < this.$root.notificationList.length; i++) {
if (this.$root.notificationList[i].isDefault === true) {
this.monitor.notificationIDList[this.$root.notificationList[i].id] = true;
}
let notification = this.$root.notificationList[i];
this.monitor.notificationIDList[notification.id] = {
active: notification.isDefault === true,
trigger: notification.defaultTrigger,
};
}
} else if (this.isEdit || this.isClone) {
this.$root.getSocket().emit("getMonitor", this.$route.params.id, (res) => {
@ -1593,6 +1627,16 @@ message HealthCheckResponse {
this.monitor = res.monitor;
for (let i = 0; i < this.$root.notificationList.length; i++) {
let notification = this.$root.notificationList[i];
if (!this.monitor.notificationIDList[notification.id]) {
this.monitor.notificationIDList[notification.id] = {
active: notification.isDefault === true,
trigger: notification.defaultTrigger,
};
}
}
if (this.isClone) {
/*
* Cloning a monitor will include properties that can not be posted to backend
@ -1794,10 +1838,14 @@ message HealthCheckResponse {
* Added a Notification Event
* Enable it if the notification is added in EditMonitor.vue
* @param {number} id ID of notification to add
* @param {string} defaultTrigger default notification trigger (both, up, down, certificate, up_certificate, down_certificate)
* @returns {void}
*/
addedNotification(id) {
this.monitor.notificationIDList[id] = true;
addedNotification(id, defaultTrigger) {
this.monitor.notificationIDList[id] = {
active: true,
trigger: defaultTrigger,
};
},
/**