diff --git a/db/knex_migrations/2025-05-09-0000-add-custom-url.js b/db/knex_migrations/2025-05-09-0000-add-custom-url.js
new file mode 100644
index 000000000..b3465c87f
--- /dev/null
+++ b/db/knex_migrations/2025-05-09-0000-add-custom-url.js
@@ -0,0 +1,13 @@
+// Add column custom_url to monitor_group table
+exports.up = function (knex) {
+ return knex.schema
+ .alterTable("monitor_group", function (table) {
+ table.text("custom_url", "text");
+ });
+};
+
+exports.down = function (knex) {
+ return knex.schema.alterTable("monitor_group", function (table) {
+ table.dropColumn("custom_url");
+ });
+};
diff --git a/server/database.js b/server/database.js
index 0e6a7405d..582f19c29 100644
--- a/server/database.js
+++ b/server/database.js
@@ -736,7 +736,7 @@ class Database {
if (Database.dbConfig.type === "sqlite") {
return "DATETIME('now', ? || ' hours')";
} else {
- return "DATE_ADD(NOW(), INTERVAL ? HOUR)";
+ return "DATE_ADD(UTC_TIMESTAMP(), INTERVAL ? HOUR)";
}
}
diff --git a/server/model/group.js b/server/model/group.js
index bd2c30189..16c482759 100644
--- a/server/model/group.js
+++ b/server/model/group.js
@@ -33,7 +33,7 @@ class Group extends BeanModel {
*/
async getMonitorList() {
return R.convertToBeans("monitor", await R.getAll(`
- SELECT monitor.*, monitor_group.send_url FROM monitor, monitor_group
+ SELECT monitor.*, monitor_group.send_url, monitor_group.custom_url FROM monitor, monitor_group
WHERE monitor.id = monitor_group.monitor_id
AND group_id = ?
ORDER BY monitor_group.weight
diff --git a/server/model/monitor.js b/server/model/monitor.js
index 626849b91..08d666b78 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -53,7 +53,7 @@ class Monitor extends BeanModel {
};
if (this.sendUrl) {
- obj.url = this.url;
+ obj.url = this.customUrl ?? this.url;
}
if (showTags) {
diff --git a/server/notification-providers/discord.js b/server/notification-providers/discord.js
index 6a52f8f3e..9ea260410 100644
--- a/server/notification-providers/discord.js
+++ b/server/notification-providers/discord.js
@@ -46,10 +46,10 @@ class Discord extends NotificationProvider {
name: "Service Name",
value: monitorJSON["name"],
},
- {
+ ...(!notification.disableUrl ? [{
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
value: this.extractAddress(monitorJSON),
- },
+ }] : []),
{
name: `Time (${heartbeatJSON["timezone"]})`,
value: heartbeatJSON["localDateTime"],
@@ -83,10 +83,10 @@ class Discord extends NotificationProvider {
name: "Service Name",
value: monitorJSON["name"],
},
- {
+ ...(!notification.disableUrl ? [{
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
value: this.extractAddress(monitorJSON),
- },
+ }] : []),
{
name: `Time (${heartbeatJSON["timezone"]})`,
value: heartbeatJSON["localDateTime"],
diff --git a/server/notification-providers/notifery.js b/server/notification-providers/notifery.js
new file mode 100644
index 000000000..772556497
--- /dev/null
+++ b/server/notification-providers/notifery.js
@@ -0,0 +1,53 @@
+const { getMonitorRelativeURL, UP } = require("../../src/util");
+const { setting } = require("../util-server");
+const NotificationProvider = require("./notification-provider");
+const axios = require("axios");
+
+class Notifery extends NotificationProvider {
+ name = "notifery";
+
+ /**
+ * @inheritdoc
+ */
+ async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
+ const okMsg = "Sent Successfully.";
+ const url = "https://api.notifery.com/event";
+
+ let data = {
+ title: notification.notiferyTitle || "Uptime Kuma Alert",
+ message: msg,
+ };
+
+ if (notification.notiferyGroup) {
+ data.group = notification.notiferyGroup;
+ }
+
+ // Link to the monitor
+ const baseURL = await setting("primaryBaseURL");
+ if (baseURL && monitorJSON) {
+ data.message += `\n\nMonitor: ${baseURL}${getMonitorRelativeURL(monitorJSON.id)}`;
+ }
+
+ if (heartbeatJSON) {
+ data.code = heartbeatJSON.status === UP ? 0 : 1;
+
+ if (heartbeatJSON.ping) {
+ data.duration = heartbeatJSON.ping;
+ }
+ }
+
+ try {
+ const headers = {
+ "Content-Type": "application/json",
+ "x-api-key": notification.notiferyApiKey,
+ };
+
+ await axios.post(url, data, { headers });
+ return okMsg;
+ } catch (error) {
+ this.throwGeneralAxiosError(error);
+ }
+ }
+}
+
+module.exports = Notifery;
diff --git a/server/notification-providers/slack.js b/server/notification-providers/slack.js
index 5e25a1fbc..455d787c7 100644
--- a/server/notification-providers/slack.js
+++ b/server/notification-providers/slack.js
@@ -145,6 +145,7 @@ class Slack extends NotificationProvider {
const title = "Uptime Kuma Alert";
let data = {
+ "text": msg,
"channel": notification.slackchannel,
"username": notification.slackusername,
"icon_emoji": notification.slackiconemo,
diff --git a/server/notification.js b/server/notification.js
index 468d026c0..fd8c23d67 100644
--- a/server/notification.js
+++ b/server/notification.js
@@ -13,6 +13,7 @@ const DingDing = require("./notification-providers/dingding");
const Discord = require("./notification-providers/discord");
const Elks = require("./notification-providers/46elks");
const Feishu = require("./notification-providers/feishu");
+const Notifery = require("./notification-providers/notifery");
const FreeMobile = require("./notification-providers/freemobile");
const GoogleChat = require("./notification-providers/google-chat");
const Gorush = require("./notification-providers/gorush");
@@ -169,6 +170,7 @@ class Notification {
new YZJ(),
new SMSPlanet(),
new SpugPush(),
+ new Notifery(),
];
for (let item of list) {
if (! item.name) {
diff --git a/server/routers/api-router.js b/server/routers/api-router.js
index ed6db2cd1..3568f2abf 100644
--- a/server/routers/api-router.js
+++ b/server/routers/api-router.js
@@ -98,15 +98,15 @@ router.all("/api/push/:pushToken", async (request, response) => {
// Reset down count
bean.downCount = 0;
- log.debug("monitor", `[${this.name}] sendNotification`);
+ log.debug("monitor", `[${monitor.name}] sendNotification`);
await Monitor.sendNotification(isFirstBeat, monitor, bean);
} else {
- if (bean.status === DOWN && this.resendInterval > 0) {
+ if (bean.status === DOWN && monitor.resendInterval > 0) {
++bean.downCount;
- if (bean.downCount >= this.resendInterval) {
+ if (bean.downCount >= monitor.resendInterval) {
// Send notification again, because we are still DOWN
- log.debug("monitor", `[${this.name}] sendNotification again: Down Count: ${bean.downCount} | Resend Interval: ${this.resendInterval}`);
- await Monitor.sendNotification(isFirstBeat, this, bean);
+ log.debug("monitor", `[${monitor.name}] sendNotification again: Down Count: ${bean.downCount} | Resend Interval: ${monitor.resendInterval}`);
+ await Monitor.sendNotification(isFirstBeat, monitor, bean);
// Reset down count
bean.downCount = 0;
diff --git a/server/socket-handlers/status-page-socket-handler.js b/server/socket-handlers/status-page-socket-handler.js
index 1114d81fd..952ec2fa7 100644
--- a/server/socket-handlers/status-page-socket-handler.js
+++ b/server/socket-handlers/status-page-socket-handler.js
@@ -211,6 +211,10 @@ module.exports.statusPageSocketHandler = (socket) => {
relationBean.send_url = monitor.sendUrl;
}
+ if (monitor.url !== undefined) {
+ relationBean.custom_url = monitor.url;
+ }
+
await R.store(relationBean);
}
diff --git a/src/components/MonitorSettingDialog.vue b/src/components/MonitorSettingDialog.vue
index e6b2cd1ef..8723c4862 100644
--- a/src/components/MonitorSettingDialog.vue
+++ b/src/components/MonitorSettingDialog.vue
@@ -10,7 +10,7 @@
+
+
+
+ changeUrl(monitor.group_index, monitor.monitor_index, e.target!.value)">
+
+
+ {{ $t("customUrlDescription") }}
+
+
+