From 7f88aacbe735c3632a2d3664888e7a96f2c8e245 Mon Sep 17 00:00:00 2001 From: Thomas Spalinger Date: Thu, 23 Feb 2023 16:16:49 +0000 Subject: [PATCH 01/58] make monitor start() and stop() async --- server/model/monitor.js | 4 ++-- server/server.js | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/server/model/monitor.js b/server/model/monitor.js index 4bb859e9a..c7388607d 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -199,7 +199,7 @@ class Monitor extends BeanModel { * Start monitor * @param {Server} io Socket server instance */ - start(io) { + async start(io) { let previousBeat = null; let retries = 0; @@ -836,7 +836,7 @@ class Monitor extends BeanModel { } /** Stop monitor */ - stop() { + async stop() { clearTimeout(this.heartbeatInterval); this.isStop = true; diff --git a/server/server.js b/server/server.js index 18598171e..1dd012afa 100644 --- a/server/server.js +++ b/server/server.js @@ -872,7 +872,7 @@ let needSetup = false; log.info("manage", `Delete Monitor: ${monitorID} User ID: ${socket.userID}`); if (monitorID in server.monitorList) { - server.monitorList[monitorID].stop(); + await server.monitorList[monitorID].stop(); delete server.monitorList[monitorID]; } @@ -1694,11 +1694,11 @@ async function startMonitor(userID, monitorID) { ]); if (monitor.id in server.monitorList) { - server.monitorList[monitor.id].stop(); + await server.monitorList[monitor.id].stop(); } server.monitorList[monitor.id] = monitor; - monitor.start(io); + await monitor.start(io); } /** @@ -1728,7 +1728,7 @@ async function pauseMonitor(userID, monitorID) { ]); if (monitorID in server.monitorList) { - server.monitorList[monitorID].stop(); + await server.monitorList[monitorID].stop(); } } @@ -1741,7 +1741,7 @@ async function startMonitors() { } for (let monitor of list) { - monitor.start(io); + await monitor.start(io); // Give some delays, so all monitors won't make request at the same moment when just start the server. await sleep(getRandomInt(300, 1000)); } @@ -1762,7 +1762,7 @@ async function shutdownFunction(signal) { log.info("server", "Stopping all monitors"); for (let id in server.monitorList) { let monitor = server.monitorList[id]; - monitor.stop(); + await monitor.stop(); } await sleep(2000); await Database.close(); From 20b69acde21a44c202215963a49912f395e84429 Mon Sep 17 00:00:00 2001 From: Philip Klostermann Date: Tue, 23 Jan 2024 10:10:31 -0500 Subject: [PATCH 02/58] [Ntfy] Only include action link if monitor url is defined #3274 --- server/notification-providers/ntfy.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/server/notification-providers/ntfy.js b/server/notification-providers/ntfy.js index 86fc0e081..b51b0f238 100644 --- a/server/notification-providers/ntfy.js +++ b/server/notification-providers/ntfy.js @@ -54,14 +54,17 @@ class Ntfy extends NotificationProvider { "priority": priority, "title": monitorJSON.name + " " + status + " [Uptime-Kuma]", "tags": tags, - "actions": [ + }; + + if (monitorJSON.url) { + data.actions = [ { "action": "view", "label": "Open " + monitorJSON.name, "url": monitorJSON.url, - } - ] - }; + }, + ]; + } if (notification.ntfyIcon) { data.icon = notification.ntfyIcon; From 95125cc4172c2d196f99748fc3dac06a9285bc75 Mon Sep 17 00:00:00 2001 From: Philip Klostermann Date: Tue, 23 Jan 2024 11:16:10 -0500 Subject: [PATCH 03/58] [Ntfy] don't include url action with defaut URL value --- server/notification-providers/ntfy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/notification-providers/ntfy.js b/server/notification-providers/ntfy.js index b51b0f238..12c9258b8 100644 --- a/server/notification-providers/ntfy.js +++ b/server/notification-providers/ntfy.js @@ -56,7 +56,7 @@ class Ntfy extends NotificationProvider { "tags": tags, }; - if (monitorJSON.url) { + if (monitorJSON.url && monitorJSON.url !== "https://") { data.actions = [ { "action": "view", From dc3abc68f0c90681727c7ea574a35f8588174108 Mon Sep 17 00:00:00 2001 From: Frank Elsinga Date: Tue, 30 Jan 2024 17:54:04 +0100 Subject: [PATCH 04/58] Fixed type anotation --- server/model/monitor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/model/monitor.js b/server/model/monitor.js index 30c631a75..0c322b92c 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -324,7 +324,7 @@ class Monitor extends BeanModel { /** * Start monitor * @param {Server} io Socket server instance - * @returns {void} + * @returns {Promise} */ async start(io) { let previousBeat = null; From 93ac212aef23f8bdb8a64968ecb6a6ca5f704407 Mon Sep 17 00:00:00 2001 From: Humberto Evans Date: Mon, 12 Feb 2024 15:58:54 -0800 Subject: [PATCH 05/58] Add Heii On-Call --- server/notification-providers/heii-oncall.js | 68 +++++++++++ server/notification.js | 40 ++++--- src/components/NotificationDialog.vue | 118 +++++++++++++++---- src/components/notifications/HeiiOnCall.vue | 35 ++++++ src/components/notifications/index.js | 108 ++++++++--------- src/lang/en.json | 3 +- 6 files changed, 283 insertions(+), 89 deletions(-) create mode 100644 server/notification-providers/heii-oncall.js create mode 100644 src/components/notifications/HeiiOnCall.vue diff --git a/server/notification-providers/heii-oncall.js b/server/notification-providers/heii-oncall.js new file mode 100644 index 000000000..7babd907e --- /dev/null +++ b/server/notification-providers/heii-oncall.js @@ -0,0 +1,68 @@ +const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util"); +const { setting } = require("../util-server"); + +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); + +const heiiOnCallBaseUrl = "http://192.168.2.1:3005"; + +class HeiiOnCall extends NotificationProvider { + name = "HeiiOnCall"; + + /** + * @inheritdoc + */ + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + // Payload to Heii On-Call is the entire heartbat JSON + const payload = heartbeatJSON ? heartbeatJSON : {}; + + if (!heartbeatJSON) { + // Test button was clicked on Notification Setup, trigger the alert as a test + payload["message"] = "Testing UptimeKuma Trigger"; + return this.postNotification(notification, "alert", payload); + } + + // If we can add url back to mintor to payload + const baseURL = await setting("primaryBaseURL"); + if (baseURL && monitorJSON) { + payload["url"] = baseURL + getMonitorRelativeURL(monitorJSON.id); + } + + if (heartbeatJSON.status === DOWN) { + // Monitor is DOWN, alert on Heii On-Call + return this.postNotification(notification, "alert", payload); + } + + if (heartbeatJSON.status === UP) { + // Monitor is UP, resolve on Heii On-Call + return this.postNotification(notification, "resolve", payload); + } + } + + /** + * Post to Heii On-Call + * @param {BeanModel} notification Message title + * @param {string} action Trigger Action (alert, resovle) + * @param {object} payload Data for Heii On-Call + * @returns {Promise} Success message + */ + async postNotification(notification, action, payload) { + const config = { + headers: { + Accept: "application/json", + "Content-Type": "application/json", + Authorization: "Bearer " + notification.heiiOnCallApiKey, + }, + }; + + // Post to Heii On-Call Trigger https://heiioncall.com/docs#manual-triggers + await axios.post( + `${heiiOnCallBaseUrl}/triggers/${notification.heiiOnCallTriggerId}/${action}`, + payload, + config + ); + return "Sent Successfully."; + } +} + +module.exports = HeiiOnCall; diff --git a/server/notification.js b/server/notification.js index 5e76d6eb1..722cf90c2 100644 --- a/server/notification.js +++ b/server/notification.js @@ -16,6 +16,7 @@ const Gorush = require("./notification-providers/gorush"); const Gotify = require("./notification-providers/gotify"); const GrafanaOncall = require("./notification-providers/grafana-oncall"); const HomeAssistant = require("./notification-providers/home-assistant"); +const HeiiOnCall = require("./notification-providers/heii-oncall"); const Kook = require("./notification-providers/kook"); const Line = require("./notification-providers/line"); const LineNotify = require("./notification-providers/linenotify"); @@ -56,7 +57,6 @@ const ServerChan = require("./notification-providers/serverchan"); const ZohoCliq = require("./notification-providers/zoho-cliq"); class Notification { - providerList = {}; /** @@ -87,6 +87,7 @@ class Notification { new Gotify(), new GrafanaOncall(), new HomeAssistant(), + new HeiiOnCall(), new Kook(), new Line(), new LineNotify(), @@ -124,10 +125,10 @@ class Notification { new Webhook(), new WeCom(), new GoAlert(), - new ZohoCliq() + new ZohoCliq(), ]; for (let item of list) { - if (! item.name) { + if (!item.name) { throw new Error("Notification provider without name"); } @@ -147,9 +148,19 @@ class Notification { * @returns {Promise} Successful msg * @throws Error with fail msg */ - static async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + static async send( + notification, + msg, + monitorJSON = null, + heartbeatJSON = null + ) { if (this.providerList[notification.type]) { - return this.providerList[notification.type].send(notification, msg, monitorJSON, heartbeatJSON); + return this.providerList[notification.type].send( + notification, + msg, + monitorJSON, + heartbeatJSON + ); } else { throw new Error("Notification type is not supported"); } @@ -171,10 +182,9 @@ class Notification { userID, ]); - if (! bean) { + if (!bean) { throw new Error("notification not found"); } - } else { bean = R.dispense("notification"); } @@ -204,7 +214,7 @@ class Notification { userID, ]); - if (! bean) { + if (!bean) { throw new Error("notification not found"); } @@ -220,7 +230,6 @@ class Notification { let exists = commandExistsSync("apprise"); return exists; } - } /** @@ -231,16 +240,17 @@ class Notification { */ async function applyNotificationEveryMonitor(notificationID, userID) { let monitors = await R.getAll("SELECT id FROM monitor WHERE user_id = ?", [ - userID + userID, ]); for (let i = 0; i < monitors.length; i++) { - let checkNotification = await R.findOne("monitor_notification", " monitor_id = ? AND notification_id = ? ", [ - monitors[i].id, - notificationID, - ]); + let checkNotification = await R.findOne( + "monitor_notification", + " monitor_id = ? AND notification_id = ? ", + [monitors[i].id, notificationID] + ); - if (! checkNotification) { + if (!checkNotification) { let relation = R.dispense("monitor_notification"); relation.monitor_id = monitors[i].id; relation.notification_id = notificationID; diff --git a/src/components/NotificationDialog.vue b/src/components/NotificationDialog.vue index 57a2fdf2d..4a16fdf3e 100644 --- a/src/components/NotificationDialog.vue +++ b/src/components/NotificationDialog.vue @@ -1,62 +1,132 @@ @@ -121,6 +197,7 @@ export default { "gotify": "Gotify", "GrafanaOncall": "Grafana Oncall", "HomeAssistant": "Home Assistant", + "HeiiOnCall": "Heii On-Call", "Kook": "Kook", "line": "LINE Messenger", "LineNotify": "LINE Notify", @@ -330,7 +407,8 @@ export default { @import "../assets/vars.scss"; .dark { - .modal-dialog .form-text, .modal-dialog p { + .modal-dialog .form-text, + .modal-dialog p { color: $dark-font-color; } } diff --git a/src/components/notifications/HeiiOnCall.vue b/src/components/notifications/HeiiOnCall.vue new file mode 100644 index 000000000..f773dbaef --- /dev/null +++ b/src/components/notifications/HeiiOnCall.vue @@ -0,0 +1,35 @@ + + + diff --git a/src/components/notifications/index.js b/src/components/notifications/index.js index 0606d41af..c71736a79 100644 --- a/src/components/notifications/index.js +++ b/src/components/notifications/index.js @@ -14,6 +14,7 @@ import Gorush from "./Gorush.vue"; import Gotify from "./Gotify.vue"; import GrafanaOncall from "./GrafanaOncall.vue"; import HomeAssistant from "./HomeAssistant.vue"; +import HeiiOnCall from "./HeiiOnCall.vue"; import Kook from "./Kook.vue"; import Line from "./Line.vue"; import LineNotify from "./LineNotify.vue"; @@ -58,60 +59,61 @@ import Splunk from "./Splunk.vue"; * @type { Record } */ const NotificationFormList = { - "alerta": Alerta, - "AlertNow": AlertNow, - "AliyunSMS": AliyunSMS, - "apprise": Apprise, - "Bark": Bark, - "clicksendsms": ClickSendSMS, - "smsc": SMSC, - "DingDing": DingDing, - "discord": Discord, - "Feishu": Feishu, - "FreeMobile": FreeMobile, - "GoogleChat": GoogleChat, - "gorush": Gorush, - "gotify": Gotify, - "GrafanaOncall": GrafanaOncall, - "HomeAssistant": HomeAssistant, - "Kook": Kook, - "line": Line, - "LineNotify": LineNotify, - "lunasea": LunaSea, - "matrix": Matrix, - "mattermost": Mattermost, - "nostr": Nostr, - "ntfy": Ntfy, - "octopush": Octopush, - "OneBot": OneBot, - "Opsgenie": Opsgenie, - "PagerDuty": PagerDuty, - "FlashDuty": FlashDuty, - "PagerTree": PagerTree, - "promosms": PromoSMS, - "pushbullet": Pushbullet, - "PushByTechulus": TechulusPush, - "PushDeer": PushDeer, - "pushover": Pushover, - "pushy": Pushy, + alerta: Alerta, + AlertNow: AlertNow, + AliyunSMS: AliyunSMS, + apprise: Apprise, + Bark: Bark, + clicksendsms: ClickSendSMS, + smsc: SMSC, + DingDing: DingDing, + discord: Discord, + Feishu: Feishu, + FreeMobile: FreeMobile, + GoogleChat: GoogleChat, + gorush: Gorush, + gotify: Gotify, + GrafanaOncall: GrafanaOncall, + HomeAssistant: HomeAssistant, + HeiiOnCall: HeiiOnCall, + Kook: Kook, + line: Line, + LineNotify: LineNotify, + lunasea: LunaSea, + matrix: Matrix, + mattermost: Mattermost, + nostr: Nostr, + ntfy: Ntfy, + octopush: Octopush, + OneBot: OneBot, + Opsgenie: Opsgenie, + PagerDuty: PagerDuty, + FlashDuty: FlashDuty, + PagerTree: PagerTree, + promosms: PromoSMS, + pushbullet: Pushbullet, + PushByTechulus: TechulusPush, + PushDeer: PushDeer, + pushover: Pushover, + pushy: Pushy, "rocket.chat": RocketChat, - "serwersms": SerwerSMS, - "signal": Signal, - "SMSManager": SMSManager, - "slack": Slack, - "squadcast": Squadcast, - "SMSEagle": SMSEagle, - "smtp": STMP, - "stackfield": Stackfield, - "teams": Teams, - "telegram": Telegram, - "twilio": Twilio, - "Splunk": Splunk, - "webhook": Webhook, - "WeCom": WeCom, - "GoAlert": GoAlert, - "ServerChan": ServerChan, - "ZohoCliq": ZohoCliq + serwersms: SerwerSMS, + signal: Signal, + SMSManager: SMSManager, + slack: Slack, + squadcast: Squadcast, + SMSEagle: SMSEagle, + smtp: STMP, + stackfield: Stackfield, + teams: Teams, + telegram: Telegram, + twilio: Twilio, + Splunk: Splunk, + webhook: Webhook, + WeCom: WeCom, + GoAlert: GoAlert, + ServerChan: ServerChan, + ZohoCliq: ZohoCliq, }; export default NotificationFormList; diff --git a/src/lang/en.json b/src/lang/en.json index 0f59e62ae..c81403d06 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -883,5 +883,6 @@ "deleteRemoteBrowserMessage": "Are you sure want to delete this Remote Browser for all monitors?", "GrafanaOncallUrl": "Grafana Oncall URL", "Browser Screenshot": "Browser Screenshot", - "What is a Remote Browser?": "What is a Remote Browser?" + "What is a Remote Browser?": "What is a Remote Browser?", + "Trigger ID": "Trigger ID" } From e8fada938675d5b7655291492d0d22382365737b Mon Sep 17 00:00:00 2001 From: Humberto Evans Date: Mon, 12 Feb 2024 16:14:41 -0800 Subject: [PATCH 06/58] set url to heii on call prod --- server/notification-providers/heii-oncall.js | 23 ++++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/server/notification-providers/heii-oncall.js b/server/notification-providers/heii-oncall.js index 7babd907e..6332fee85 100644 --- a/server/notification-providers/heii-oncall.js +++ b/server/notification-providers/heii-oncall.js @@ -4,7 +4,7 @@ const { setting } = require("../util-server"); const NotificationProvider = require("./notification-provider"); const axios = require("axios"); -const heiiOnCallBaseUrl = "http://192.168.2.1:3005"; +const heiiOnCallBaseUrl = "https://heiioncall.com"; class HeiiOnCall extends NotificationProvider { name = "HeiiOnCall"; @@ -22,7 +22,7 @@ class HeiiOnCall extends NotificationProvider { return this.postNotification(notification, "alert", payload); } - // If we can add url back to mintor to payload + // If we can, add url back to mintor to payload const baseURL = await setting("primaryBaseURL"); if (baseURL && monitorJSON) { payload["url"] = baseURL + getMonitorRelativeURL(monitorJSON.id); @@ -42,7 +42,7 @@ class HeiiOnCall extends NotificationProvider { /** * Post to Heii On-Call * @param {BeanModel} notification Message title - * @param {string} action Trigger Action (alert, resovle) + * @param {string} action Trigger action (alert, resovle) * @param {object} payload Data for Heii On-Call * @returns {Promise} Success message */ @@ -56,12 +56,17 @@ class HeiiOnCall extends NotificationProvider { }; // Post to Heii On-Call Trigger https://heiioncall.com/docs#manual-triggers - await axios.post( - `${heiiOnCallBaseUrl}/triggers/${notification.heiiOnCallTriggerId}/${action}`, - payload, - config - ); - return "Sent Successfully."; + try { + await axios.post( + `${heiiOnCallBaseUrl}/triggers/${notification.heiiOnCallTriggerId}/${action}`, + payload, + config + ); + } catch (error) { + this.throwGeneralAxiosError(error); + } + + return "Heii On-Call post sent successfully."; } } From 80bdc455da6cd255ee95d5714ae803dab3bec8a2 Mon Sep 17 00:00:00 2001 From: Humberto Evans Date: Mon, 12 Feb 2024 16:31:58 -0800 Subject: [PATCH 07/58] lint --- server/notification.js | 2 +- src/components/notifications/HeiiOnCall.vue | 12 ++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/server/notification.js b/server/notification.js index 722cf90c2..71310b000 100644 --- a/server/notification.js +++ b/server/notification.js @@ -247,7 +247,7 @@ async function applyNotificationEveryMonitor(notificationID, userID) { let checkNotification = await R.findOne( "monitor_notification", " monitor_id = ? AND notification_id = ? ", - [monitors[i].id, notificationID] + [ monitors[i].id, notificationID ] ); if (!checkNotification) { diff --git a/src/components/notifications/HeiiOnCall.vue b/src/components/notifications/HeiiOnCall.vue index f773dbaef..64c0a9e09 100644 --- a/src/components/notifications/HeiiOnCall.vue +++ b/src/components/notifications/HeiiOnCall.vue @@ -1,9 +1,7 @@ diff --git a/src/components/notifications/index.js b/src/components/notifications/index.js index 6cb0c9fd9..f9e6eb8fa 100644 --- a/src/components/notifications/index.js +++ b/src/components/notifications/index.js @@ -13,6 +13,7 @@ import GoogleChat from "./GoogleChat.vue"; import Gorush from "./Gorush.vue"; import Gotify from "./Gotify.vue"; import GrafanaOncall from "./GrafanaOncall.vue"; +import GtxMessaging from "./GtxMessaging.vue"; import HomeAssistant from "./HomeAssistant.vue"; import HeiiOnCall from "./HeiiOnCall.vue"; import Kook from "./Kook.vue"; @@ -113,7 +114,8 @@ const NotificationFormList = { "WeCom": WeCom, "GoAlert": GoAlert, "ServerChan": ServerChan, - "ZohoCliq": ZohoCliq + "ZohoCliq": ZohoCliq, + "gtxmessaging": GtxMessaging, }; export default NotificationFormList; diff --git a/src/lang/en.json b/src/lang/en.json index 11190a080..b406f67c5 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -887,5 +887,10 @@ "Browser Screenshot": "Browser Screenshot", "What is a Remote Browser?": "What is a Remote Browser?", "wayToGetHeiiOnCallDetails": "How to get the Trigger ID and API Keys is explained in the {documentation}", - "documentationOf": "{0} Documentation" + "documentationOf": "{0} Documentation", + "gtxMessagingApiKeyHint": "You can find your API key at: My Routing Accounts > Show Account Information > API Credentials > REST API (v2.x)", + "From Phone Number / Transmission Path Originating Address (TPOA)": "From Phone Number / Transmission Path Originating Address (TPOA)", + "gtxMessagingFromHint": "On mobile phones, your recipients sees the TPOA displayed as the sender of the message. Allowed are up to 11 alphanumeric characters, a shortcode, the local longcode or international numbers ({e164}, {e212} or {e214})", + "To Phone Number": "To Phone Number", + "gtxMessagingToHint": "International format, with leading \"+\" ({e164}, {e212} or {e214})" } From b8858f46058b8951aee9d9c0aa25f42d3657f5e0 Mon Sep 17 00:00:00 2001 From: Nelson Chan <3271800+chakflying@users.noreply.github.com> Date: Wed, 27 Mar 2024 10:19:25 +0800 Subject: [PATCH 50/58] Feat: Handle maintenance in `UptimeCalculator` (#4406) Co-authored-by: Frank Elsinga --- .../2024-01-22-0000-stats-extras.js | 26 ++++++ server/uptime-calculator.js | 86 +++++++++++++++---- 2 files changed, 94 insertions(+), 18 deletions(-) create mode 100644 db/knex_migrations/2024-01-22-0000-stats-extras.js diff --git a/db/knex_migrations/2024-01-22-0000-stats-extras.js b/db/knex_migrations/2024-01-22-0000-stats-extras.js new file mode 100644 index 000000000..b92e8892a --- /dev/null +++ b/db/knex_migrations/2024-01-22-0000-stats-extras.js @@ -0,0 +1,26 @@ +exports.up = function (knex) { + return knex.schema + .alterTable("stat_daily", function (table) { + table.text("extras").defaultTo(null).comment("Extra statistics during this time period"); + }) + .alterTable("stat_minutely", function (table) { + table.text("extras").defaultTo(null).comment("Extra statistics during this time period"); + }) + .alterTable("stat_hourly", function (table) { + table.text("extras").defaultTo(null).comment("Extra statistics during this time period"); + }); + +}; + +exports.down = function (knex) { + return knex.schema + .alterTable("stat_daily", function (table) { + table.dropColumn("extras"); + }) + .alterTable("stat_minutely", function (table) { + table.dropColumn("extras"); + }) + .alterTable("stat_hourly", function (table) { + table.dropColumn("extras"); + }); +}; diff --git a/server/uptime-calculator.js b/server/uptime-calculator.js index 9632e6eac..7b5326748 100644 --- a/server/uptime-calculator.js +++ b/server/uptime-calculator.js @@ -116,14 +116,23 @@ class UptimeCalculator { ]); for (let bean of minutelyStatBeans) { - let key = bean.timestamp; - this.minutelyUptimeDataList.push(key, { + let data = { up: bean.up, down: bean.down, avgPing: bean.ping, minPing: bean.pingMin, maxPing: bean.pingMax, - }); + }; + + if (bean.extras != null) { + data = { + ...data, + ...JSON.parse(bean.extras), + }; + } + + let key = bean.timestamp; + this.minutelyUptimeDataList.push(key, data); } // Load hourly data from database (recent 30 days only) @@ -133,14 +142,22 @@ class UptimeCalculator { ]); for (let bean of hourlyStatBeans) { - let key = bean.timestamp; - this.hourlyUptimeDataList.push(key, { + let data = { up: bean.up, down: bean.down, avgPing: bean.ping, minPing: bean.pingMin, maxPing: bean.pingMax, - }); + }; + + if (bean.extras != null) { + data = { + ...data, + ...JSON.parse(bean.extras), + }; + } + + this.hourlyUptimeDataList.push(bean.timestamp, data); } // Load daily data from database (recent 365 days only) @@ -150,14 +167,22 @@ class UptimeCalculator { ]); for (let bean of dailyStatBeans) { - let key = bean.timestamp; - this.dailyUptimeDataList.push(key, { + let data = { up: bean.up, down: bean.down, avgPing: bean.ping, minPing: bean.pingMin, maxPing: bean.pingMax, - }); + }; + + if (bean.extras != null) { + data = { + ...data, + ...JSON.parse(bean.extras), + }; + } + + this.dailyUptimeDataList.push(bean.timestamp, data); } } @@ -170,11 +195,6 @@ class UptimeCalculator { async update(status, ping = 0) { let date = this.getCurrentDate(); - // Don't count MAINTENANCE into uptime - if (status === MAINTENANCE) { - return date; - } - let flatStatus = this.flatStatus(status); if (flatStatus === DOWN && ping > 0) { @@ -189,7 +209,12 @@ class UptimeCalculator { let hourlyData = this.hourlyUptimeDataList[hourlyKey]; let dailyData = this.dailyUptimeDataList[dailyKey]; - if (flatStatus === UP) { + if (status === MAINTENANCE) { + minutelyData.maintenance = minutelyData.maintenance ? minutelyData.maintenance + 1 : 1; + hourlyData.maintenance = hourlyData.maintenance ? hourlyData.maintenance + 1 : 1; + dailyData.maintenance = dailyData.maintenance ? dailyData.maintenance + 1 : 1; + + } else if (flatStatus === UP) { minutelyData.up += 1; hourlyData.up += 1; dailyData.up += 1; @@ -233,7 +258,7 @@ class UptimeCalculator { } } - } else { + } else if (flatStatus === DOWN) { minutelyData.down += 1; hourlyData.down += 1; dailyData.down += 1; @@ -263,6 +288,13 @@ class UptimeCalculator { dailyStatBean.ping = dailyData.avgPing; dailyStatBean.pingMin = dailyData.minPing; dailyStatBean.pingMax = dailyData.maxPing; + { + // eslint-disable-next-line no-unused-vars + const { up, down, avgPing, minPing, maxPing, ...extras } = dailyData; + if (Object.keys(extras).length > 0) { + dailyStatBean.extras = JSON.stringify(extras); + } + } await R.store(dailyStatBean); let hourlyStatBean = await this.getHourlyStatBean(hourlyKey); @@ -271,6 +303,13 @@ class UptimeCalculator { hourlyStatBean.ping = hourlyData.avgPing; hourlyStatBean.pingMin = hourlyData.minPing; hourlyStatBean.pingMax = hourlyData.maxPing; + { + // eslint-disable-next-line no-unused-vars + const { up, down, avgPing, minPing, maxPing, ...extras } = hourlyData; + if (Object.keys(extras).length > 0) { + hourlyStatBean.extras = JSON.stringify(extras); + } + } await R.store(hourlyStatBean); let minutelyStatBean = await this.getMinutelyStatBean(divisionKey); @@ -279,6 +318,13 @@ class UptimeCalculator { minutelyStatBean.ping = minutelyData.avgPing; minutelyStatBean.pingMin = minutelyData.minPing; minutelyStatBean.pingMax = minutelyData.maxPing; + { + // eslint-disable-next-line no-unused-vars + const { up, down, avgPing, minPing, maxPing, ...extras } = minutelyData; + if (Object.keys(extras).length > 0) { + minutelyStatBean.extras = JSON.stringify(extras); + } + } await R.store(minutelyStatBean); // Remove the old data @@ -474,7 +520,7 @@ class UptimeCalculator { flatStatus(status) { switch (status) { case UP: - // case MAINTENANCE: + case MAINTENANCE: return UP; case DOWN: case PENDING: @@ -606,7 +652,11 @@ class UptimeCalculator { avgPing = totalPing / total.up; } - uptimeData.uptime = total.up / (total.up + total.down); + if (total.up + total.down === 0) { + uptimeData.uptime = 0; + } else { + uptimeData.uptime = total.up / (total.up + total.down); + } uptimeData.avgPing = avgPing; return uptimeData; } From 88187b66ebdd0d301d2c2eec25e79839741b7205 Mon Sep 17 00:00:00 2001 From: Chongyi Zheng Date: Wed, 27 Mar 2024 19:30:33 -0400 Subject: [PATCH 51/58] Drop Node.js 14 and 16 (#3747) Co-authored-by: Frank Elsinga Co-authored-by: Adam Stachowicz --- .github/ISSUE_TEMPLATE/bug_report.yaml | 2 +- .github/workflows/auto-test.yml | 14 +++++--------- .github/workflows/close-incorrect-issue.yml | 2 +- CONTRIBUTING.md | 4 ++-- README.md | 5 +---- package.json | 2 +- 6 files changed, 11 insertions(+), 18 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 50b919d3e..9745a76a6 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -79,7 +79,7 @@ body: label: "🖥️ Deployment Environment" description: | examples: - - **Runtime**: Docker 20.10.9 / nodejs 14.18.0 / K8S via ... v1.3.3 / .. + - **Runtime**: Docker 20.10.9 / nodejs 18.17.1 / K8S via ... v1.3.3 / .. - **Database**: sqlite/embedded mariadb/external mariadb - **Filesystem used to store the database on**: Windows/ZFS/btrfs/NFSv3 on a SSD/HDD/eMMC - **number of monitors**: 42 diff --git a/.github/workflows/auto-test.yml b/.github/workflows/auto-test.yml index d0629f7b5..c791bc880 100644 --- a/.github/workflows/auto-test.yml +++ b/.github/workflows/auto-test.yml @@ -22,7 +22,7 @@ jobs: strategy: matrix: os: [macos-latest, ubuntu-latest, windows-latest, ARM64] - node: [ 16, 20.5 ] + node: [ 18, 20.5 ] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: @@ -33,13 +33,9 @@ jobs: uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} - - - name: Install - run: npm install - - name: Build - run: npm run build - - name: Test Backend - run: npm run test-backend + - run: npm install + - run: npm run build + - run: npm run test-backend env: HEADLESS_TEST: 1 JUST_FOR_TEST: ${{ secrets.JUST_FOR_TEST }} @@ -53,7 +49,7 @@ jobs: strategy: matrix: os: [ ARMv7 ] - node: [ 14, 20 ] + node: [ 18, 20 ] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: diff --git a/.github/workflows/close-incorrect-issue.yml b/.github/workflows/close-incorrect-issue.yml index e26cf5e5e..3ef5ba378 100644 --- a/.github/workflows/close-incorrect-issue.yml +++ b/.github/workflows/close-incorrect-issue.yml @@ -11,7 +11,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - node-version: [16] + node-version: [18] steps: - uses: actions/checkout@v4 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2eedd548b..d9c0952cb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -230,8 +230,8 @@ The goal is to make the Uptime Kuma installation as easy as installing a mobile ## Tools -- [`Node.js`](https://nodejs.org/) >= 14 -- [`npm`](https://www.npmjs.com/) >= 8.5 +- [`Node.js`](https://nodejs.org/) >= 18 +- [`npm`](https://www.npmjs.com/) >= 9.3 - [`git`](https://git-scm.com/) - IDE that supports [`ESLint`](https://eslint.org/) and EditorConfig (I am using [`IntelliJ IDEA`](https://www.jetbrains.com/idea/)) - A SQLite GUI tool (f.ex. [`SQLite Expert Personal`](https://www.sqliteexpert.com/download.html) or [`DBeaver Community`](https://dbeaver.io/download/)) diff --git a/README.md b/README.md index ebeca5259..0459692ae 100644 --- a/README.md +++ b/README.md @@ -56,15 +56,12 @@ Requirements: - ✅ Major Linux distros such as Debian, Ubuntu, CentOS, Fedora and ArchLinux etc. - ✅ Windows 10 (x64), Windows Server 2012 R2 (x64) or higher - ❌ Replit / Heroku -- [Node.js](https://nodejs.org/en/download/) 14 / 16 / 18 / 20.4 +- [Node.js](https://nodejs.org/en/download/) 18 / 20.4 - [npm](https://docs.npmjs.com/cli/) 9 - [Git](https://git-scm.com/downloads) - [pm2](https://pm2.keymetrics.io/) - For running Uptime Kuma in the background ```bash -# Update your npm -npm install npm@9 -g - git clone https://github.com/louislam/uptime-kuma.git cd uptime-kuma npm run setup diff --git a/package.json b/package.json index e8adc4915..567efa1b5 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "url": "https://github.com/louislam/uptime-kuma.git" }, "engines": { - "node": "14 || 16 || 18 || >= 20.4.0" + "node": "18 || >= 20.4.0" }, "scripts": { "lint:js": "eslint --ext \".js,.vue\" --ignore-path .gitignore .", From 0923d053175463061af9f987500e0425286bd128 Mon Sep 17 00:00:00 2001 From: Simon Nilsson Date: Fri, 29 Mar 2024 16:27:54 +0100 Subject: [PATCH 52/58] Cellsynt mobile services (#4625) Co-authored-by: Frank Elsinga --- server/notification-providers/cellsynt.js | 39 ++++++++++++++++ server/notification.js | 2 + src/components/NotificationDialog.vue | 3 +- src/components/notifications/Cellsynt.vue | 54 +++++++++++++++++++++++ src/components/notifications/index.js | 2 + src/lang/en.json | 15 ++++++- 6 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 server/notification-providers/cellsynt.js create mode 100644 src/components/notifications/Cellsynt.vue diff --git a/server/notification-providers/cellsynt.js b/server/notification-providers/cellsynt.js new file mode 100644 index 000000000..e842237b6 --- /dev/null +++ b/server/notification-providers/cellsynt.js @@ -0,0 +1,39 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); + +class Cellsynt extends NotificationProvider { + name = "Cellsynt"; + + /** + * @inheritdoc + */ + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + const okMsg = "Sent Successfully."; + const data = { + // docs at https://www.cellsynt.com/en/sms/api-integration + params: { + "username": notification.cellsyntLogin, + "password": notification.cellsyntPassword, + "destination": notification.cellsyntDestination, + "text": msg.replace(/[^\x00-\x7F]/g, ""), + "originatortype": notification.cellsyntOriginatortype, + "originator": notification.cellsyntOriginator, + "allowconcat": notification.cellsyntAllowLongSMS ? 6 : 1 + } + }; + try { + const resp = await axios.post("https://se-1.cellsynt.net/sms.php", null, data); + if (resp.data == null ) { + throw new Error("Could not connect to Cellsynt, please try again."); + } else if (resp.data.includes("Error:")) { + resp.data = resp.data.replaceAll("Error:", ""); + throw new Error(resp.data); + } + return okMsg; + } catch (error) { + this.throwGeneralAxiosError(error); + } + } +} + +module.exports = Cellsynt; diff --git a/server/notification.js b/server/notification.js index 29867019c..1d847104e 100644 --- a/server/notification.js +++ b/server/notification.js @@ -56,6 +56,7 @@ const SMSManager = require("./notification-providers/smsmanager"); const ServerChan = require("./notification-providers/serverchan"); const ZohoCliq = require("./notification-providers/zoho-cliq"); const GtxMessaging = require("./notification-providers/gtx-messaging"); +const Cellsynt = require("./notification-providers/cellsynt"); class Notification { @@ -129,6 +130,7 @@ class Notification { new GoAlert(), new ZohoCliq(), new GtxMessaging(), + new Cellsynt(), ]; for (let item of list) { if (! item.name) { diff --git a/src/components/NotificationDialog.vue b/src/components/NotificationDialog.vue index 12209f33f..3e7569453 100644 --- a/src/components/NotificationDialog.vue +++ b/src/components/NotificationDialog.vue @@ -153,7 +153,8 @@ export default { "webhook": "Webhook", "GoAlert": "GoAlert", "ZohoCliq": "ZohoCliq", - "gtxmessaging": "GtxMessaging" + "gtxmessaging": "GtxMessaging", + "Cellsynt": "Cellsynt" }; // Put notifications here if it's not supported in most regions or its documentation is not in English diff --git a/src/components/notifications/Cellsynt.vue b/src/components/notifications/Cellsynt.vue new file mode 100644 index 000000000..2e8a6de71 --- /dev/null +++ b/src/components/notifications/Cellsynt.vue @@ -0,0 +1,54 @@ + + + diff --git a/src/components/notifications/index.js b/src/components/notifications/index.js index f9e6eb8fa..b422c4938 100644 --- a/src/components/notifications/index.js +++ b/src/components/notifications/index.js @@ -54,6 +54,7 @@ import WeCom from "./WeCom.vue"; import GoAlert from "./GoAlert.vue"; import ZohoCliq from "./ZohoCliq.vue"; import Splunk from "./Splunk.vue"; +import Cellsynt from "./Cellsynt.vue"; /** * Manage all notification form. @@ -116,6 +117,7 @@ const NotificationFormList = { "ServerChan": ServerChan, "ZohoCliq": ZohoCliq, "gtxmessaging": GtxMessaging, + "Cellsynt": Cellsynt, }; export default NotificationFormList; diff --git a/src/lang/en.json b/src/lang/en.json index b406f67c5..9a44b2cdf 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -892,5 +892,18 @@ "From Phone Number / Transmission Path Originating Address (TPOA)": "From Phone Number / Transmission Path Originating Address (TPOA)", "gtxMessagingFromHint": "On mobile phones, your recipients sees the TPOA displayed as the sender of the message. Allowed are up to 11 alphanumeric characters, a shortcode, the local longcode or international numbers ({e164}, {e212} or {e214})", "To Phone Number": "To Phone Number", - "gtxMessagingToHint": "International format, with leading \"+\" ({e164}, {e212} or {e214})" + "gtxMessagingToHint": "International format, with leading \"+\" ({e164}, {e212} or {e214})", + "Originator type": "Originator type", + "Alphanumeric (recommended)": "Alphanumeric (recommended)", + "Telephone number": "Telephone number", + "cellsyntOriginatortypeAlphanumeric": "Alphanumeric string (max 11 alphanumeric characters). Recipients can not reply to the message.", + "cellsyntOriginatortypeNumeric": "Numeric value (max 15 digits) with telephone number on international format without leading 00 (example UK number 07920 110 000 should be set as 447920110000). Recipients can reply to the message.", + "Originator": "Originator", + "cellsyntOriginator": "Visible on recipient's mobile phone as originator of the message. Allowed values and function depends on parameter originatortype.", + "Destination": "Destination", + "cellsyntDestination": "Recipient's telephone number using international format with leading 00 followed by country code, e.g. 00447920110000 for the UK number 07920 110 000 (max 17 digits in total). Max 25000 comma separated recipients per HTTP request.", + "Allow Long SMS": "Allow Long SMS", + "cellsyntSplitLongMessages": "Split long messages into up to 6 parts. 153 x 6 = 918 characters.", + "max 15 digits": "max 15 digits", + "max 11 alphanumeric characters": "max 11 alphanumeric characters" } From 08f75b0b99a96a919158ce818d6f59ed61ff0dd2 Mon Sep 17 00:00:00 2001 From: Nelson Chan <3271800+chakflying@users.noreply.github.com> Date: Sun, 31 Mar 2024 12:04:22 +0800 Subject: [PATCH 53/58] Fix: Add missing FK for monitor-tls-info table (#4632) --- db/knex_init_db.js | 5 ++++- .../patch-monitor-tls-info-add-fk.sql | 18 ++++++++++++++++++ server/database.js | 3 ++- 3 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 db/old_migrations/patch-monitor-tls-info-add-fk.sql diff --git a/db/knex_init_db.js b/db/knex_init_db.js index 7ce1bb48e..46bff4bfa 100644 --- a/db/knex_init_db.js +++ b/db/knex_init_db.js @@ -318,7 +318,10 @@ async function createTables() { // monitor_tls_info await knex.schema.createTable("monitor_tls_info", (table) => { table.increments("id"); - table.integer("monitor_id").unsigned().notNullable(); //TODO: no fk ? + table.integer("monitor_id").unsigned().notNullable() + .references("id").inTable("monitor") + .onDelete("CASCADE") + .onUpdate("CASCADE"); table.text("info_json"); }); diff --git a/db/old_migrations/patch-monitor-tls-info-add-fk.sql b/db/old_migrations/patch-monitor-tls-info-add-fk.sql new file mode 100644 index 000000000..9b9c2d2cb --- /dev/null +++ b/db/old_migrations/patch-monitor-tls-info-add-fk.sql @@ -0,0 +1,18 @@ +BEGIN TRANSACTION; + +PRAGMA writable_schema = TRUE; + +UPDATE + SQLITE_MASTER +SET + sql = replace(sql, + 'monitor_id INTEGER NOT NULL', + 'monitor_id INTEGER NOT NULL REFERENCES [monitor] ([id]) ON DELETE CASCADE ON UPDATE CASCADE' +) +WHERE + name = 'monitor_tls_info' + AND type = 'table'; + +PRAGMA writable_schema = RESET; + +COMMIT; diff --git a/server/database.js b/server/database.js index cfe14fe71..ed4b9e681 100644 --- a/server/database.js +++ b/server/database.js @@ -105,7 +105,8 @@ class Database { "patch-add-gamedig-given-port.sql": true, "patch-notification-config.sql": true, "patch-fix-kafka-producer-booleans.sql": true, - "patch-timeout.sql": true, // The last file so far converted to a knex migration file + "patch-timeout.sql": true, + "patch-monitor-tls-info-add-fk.sql": true, // The last file so far converted to a knex migration file }; /** From effd0197ac9899ff6574810189755d62e81c71d1 Mon Sep 17 00:00:00 2001 From: Daan Meijer Date: Tue, 2 Apr 2024 02:43:54 +0200 Subject: [PATCH 54/58] [Slack] restructure alert actions, add 'visit site' button (#3886) Co-authored-by: Frank Elsinga Co-authored-by: Nelson Chan <3271800+chakflying@users.noreply.github.com> --- server/notification-providers/slack.js | 134 +++++++++++++++++-------- 1 file changed, 92 insertions(+), 42 deletions(-) diff --git a/server/notification-providers/slack.js b/server/notification-providers/slack.js index 9347b07c9..439f5e905 100644 --- a/server/notification-providers/slack.js +++ b/server/notification-providers/slack.js @@ -26,6 +26,93 @@ class Slack extends NotificationProvider { } } + /** + * Builds the actions available in the slack message + * @param {string} baseURL Uptime Kuma base URL + * @param {object} monitorJSON The monitor config + * @returns {Array} The relevant action objects + */ + static buildActions(baseURL, monitorJSON) { + const actions = []; + + if (baseURL) { + actions.push({ + "type": "button", + "text": { + "type": "plain_text", + "text": "Visit Uptime Kuma", + }, + "value": "Uptime-Kuma", + "url": baseURL + getMonitorRelativeURL(monitorJSON.id), + }); + + } + + if (monitorJSON.url) { + actions.push({ + "type": "button", + "text": { + "type": "plain_text", + "text": "Visit site", + }, + "value": "Site", + "url": monitorJSON.url, + }); + } + + return actions; + } + + /** + * Builds the different blocks the Slack message consists of. + * @param {string} baseURL Uptime Kuma base URL + * @param {object} monitorJSON The monitor object + * @param {object} heartbeatJSON The heartbeat object + * @param {string} title The message title + * @param {string} msg The message body + * @returns {Array} The rich content blocks for the Slack message + */ + static buildBlocks(baseURL, monitorJSON, heartbeatJSON, title, msg) { + + //create an array to dynamically add blocks + const blocks = []; + + // the header block + blocks.push({ + "type": "header", + "text": { + "type": "plain_text", + "text": title, + }, + }); + + // the body block, containing the details + blocks.push({ + "type": "section", + "fields": [ + { + "type": "mrkdwn", + "text": "*Message*\n" + msg, + }, + { + "type": "mrkdwn", + "text": `*Time (${heartbeatJSON["timezone"]})*\n${heartbeatJSON["localDateTime"]}`, + } + ], + }); + + const actions = this.buildActions(baseURL, monitorJSON); + if (actions.length > 0) { + //the actions block, containing buttons + blocks.push({ + "type": "actions", + "elements": actions, + }); + } + + return blocks; + } + /** * @inheritdoc */ @@ -48,35 +135,18 @@ class Slack extends NotificationProvider { return okMsg; } - const textMsg = "Uptime Kuma Alert"; + const baseURL = await setting("primaryBaseURL"); + + const title = "Uptime Kuma Alert"; let data = { - "text": `${textMsg}\n${msg}`, + "text": `${title}\n${msg}`, "channel": notification.slackchannel, "username": notification.slackusername, "icon_emoji": notification.slackiconemo, "attachments": [ { "color": (heartbeatJSON["status"] === UP) ? "#2eb886" : "#e01e5a", - "blocks": [ - { - "type": "header", - "text": { - "type": "plain_text", - "text": textMsg, - }, - }, - { - "type": "section", - "fields": [{ - "type": "mrkdwn", - "text": "*Message*\n" + msg, - }, - { - "type": "mrkdwn", - "text": `*Time (${heartbeatJSON["timezone"]})*\n${heartbeatJSON["localDateTime"]}`, - }], - } - ], + "blocks": Slack.buildBlocks(baseURL, monitorJSON, heartbeatJSON, title, msg), } ] }; @@ -85,26 +155,6 @@ class Slack extends NotificationProvider { await Slack.deprecateURL(notification.slackbutton); } - const baseURL = await setting("primaryBaseURL"); - - // Button - if (baseURL) { - data.attachments.forEach(element => { - element.blocks.push({ - "type": "actions", - "elements": [{ - "type": "button", - "text": { - "type": "plain_text", - "text": "Visit Uptime Kuma", - }, - "value": "Uptime-Kuma", - "url": baseURL + getMonitorRelativeURL(monitorJSON.id), - }], - }); - }); - } - await axios.post(notification.slackwebhookURL, data); return okMsg; } catch (error) { From 937c8a9a7baa387e430d93fc1affd7ed0ea23a3c Mon Sep 17 00:00:00 2001 From: Merlin <50715457+BothimTV@users.noreply.github.com> Date: Tue, 2 Apr 2024 21:39:45 +0200 Subject: [PATCH 55/58] New notification provider: CallMeBot API (#4605) Co-authored-by: Frank Elsinga --- server/notification-providers/call-me-bot.js | 23 ++++++++++++++++++++ server/notification.js | 2 ++ src/components/NotificationDialog.vue | 1 + src/components/notifications/CallMeBot.vue | 13 +++++++++++ src/components/notifications/index.js | 2 ++ src/lang/en.json | 1 + 6 files changed, 42 insertions(+) create mode 100644 server/notification-providers/call-me-bot.js create mode 100644 src/components/notifications/CallMeBot.vue diff --git a/server/notification-providers/call-me-bot.js b/server/notification-providers/call-me-bot.js new file mode 100644 index 000000000..daa9ccdeb --- /dev/null +++ b/server/notification-providers/call-me-bot.js @@ -0,0 +1,23 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); + +class CallMeBot extends NotificationProvider { + name = "CallMeBot"; + + /** + * @inheritdoc + */ + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + const okMsg = "Sent Successfully."; + try { + const url = new URL(notification.callMeBotEndpoint); + url.searchParams.set("text", msg); + await axios.get(url.toString()); + return okMsg; + } catch (error) { + this.throwGeneralAxiosError(error); + } + } +} + +module.exports = CallMeBot; diff --git a/server/notification.js b/server/notification.js index 1d847104e..6f1c35d18 100644 --- a/server/notification.js +++ b/server/notification.js @@ -6,6 +6,7 @@ const AliyunSms = require("./notification-providers/aliyun-sms"); const Apprise = require("./notification-providers/apprise"); const Bark = require("./notification-providers/bark"); const ClickSendSMS = require("./notification-providers/clicksendsms"); +const CallMeBot = require("./notification-providers/call-me-bot"); const SMSC = require("./notification-providers/smsc"); const DingDing = require("./notification-providers/dingding"); const Discord = require("./notification-providers/discord"); @@ -80,6 +81,7 @@ class Notification { new Apprise(), new Bark(), new ClickSendSMS(), + new CallMeBot(), new SMSC(), new DingDing(), new Discord(), diff --git a/src/components/NotificationDialog.vue b/src/components/NotificationDialog.vue index 3e7569453..6538f4eaa 100644 --- a/src/components/NotificationDialog.vue +++ b/src/components/NotificationDialog.vue @@ -115,6 +115,7 @@ export default { "apprise": this.$t("apprise"), "Bark": "Bark", "clicksendsms": "ClickSend SMS", + "CallMeBot": "CallMeBot (WhatsApp, Telegram Call, Facebook Messanger)", "discord": "Discord", "GoogleChat": "Google Chat (Google Workspace)", "gorush": "Gorush", diff --git a/src/components/notifications/CallMeBot.vue b/src/components/notifications/CallMeBot.vue new file mode 100644 index 000000000..74c69ea35 --- /dev/null +++ b/src/components/notifications/CallMeBot.vue @@ -0,0 +1,13 @@ + diff --git a/src/components/notifications/index.js b/src/components/notifications/index.js index b422c4938..821652e24 100644 --- a/src/components/notifications/index.js +++ b/src/components/notifications/index.js @@ -4,6 +4,7 @@ import AliyunSMS from "./AliyunSms.vue"; import Apprise from "./Apprise.vue"; import Bark from "./Bark.vue"; import ClickSendSMS from "./ClickSendSMS.vue"; +import CallMeBot from "./CallMeBot.vue"; import SMSC from "./SMSC.vue"; import DingDing from "./DingDing.vue"; import Discord from "./Discord.vue"; @@ -67,6 +68,7 @@ const NotificationFormList = { "apprise": Apprise, "Bark": Bark, "clicksendsms": ClickSendSMS, + "CallMeBot": CallMeBot, "smsc": SMSC, "DingDing": DingDing, "discord": Discord, diff --git a/src/lang/en.json b/src/lang/en.json index 9a44b2cdf..8fb04ae69 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -888,6 +888,7 @@ "What is a Remote Browser?": "What is a Remote Browser?", "wayToGetHeiiOnCallDetails": "How to get the Trigger ID and API Keys is explained in the {documentation}", "documentationOf": "{0} Documentation", + "callMeBotGet": "Here you can generate an endpoint for {0}, {1} and {2}. Keep in mind that you might get rate limited. The ratelimits appear to be: {3}", "gtxMessagingApiKeyHint": "You can find your API key at: My Routing Accounts > Show Account Information > API Credentials > REST API (v2.x)", "From Phone Number / Transmission Path Originating Address (TPOA)": "From Phone Number / Transmission Path Originating Address (TPOA)", "gtxMessagingFromHint": "On mobile phones, your recipients sees the TPOA displayed as the sender of the message. Allowed are up to 11 alphanumeric characters, a shortcode, the local longcode or international numbers ({e164}, {e212} or {e214})", From 822ce5384b1c138ccea7a20112363358cdb67083 Mon Sep 17 00:00:00 2001 From: Edoardo Ridolfi <32812884+edo2313@users.noreply.github.com> Date: Wed, 3 Apr 2024 02:22:01 +0200 Subject: [PATCH 56/58] Add support for Whapi notification provider (#4323) Co-authored-by: Frank Elsinga --- server/notification-providers/whapi.js | 39 ++++++++++++++++++++++++++ server/notification.js | 2 ++ src/components/NotificationDialog.vue | 3 +- src/components/notifications/Whapi.vue | 33 ++++++++++++++++++++++ src/components/notifications/index.js | 2 ++ src/lang/en.json | 4 +++ 6 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 server/notification-providers/whapi.js create mode 100644 src/components/notifications/Whapi.vue diff --git a/server/notification-providers/whapi.js b/server/notification-providers/whapi.js new file mode 100644 index 000000000..70e0fbb4c --- /dev/null +++ b/server/notification-providers/whapi.js @@ -0,0 +1,39 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); + +class Whapi extends NotificationProvider { + name = "whapi"; + + /** + * @inheritdoc + */ + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + const okMsg = "Sent Successfully."; + + try { + const config = { + headers: { + "Accept": "application/json", + "Content-Type": "application/json", + "Authorization": "Bearer " + notification.whapiAuthToken, + } + }; + + let data = { + "to": notification.whapiRecipient, + "body": msg, + }; + + let url = (notification.whapiApiUrl || "https://gate.whapi.cloud/").replace(/\/+$/, "") + "/messages/text"; + + await axios.post(url, data, config); + + return okMsg; + } catch (error) { + this.throwGeneralAxiosError(error); + } + } + +} + +module.exports = Whapi; diff --git a/server/notification.js b/server/notification.js index 6f1c35d18..f5a952061 100644 --- a/server/notification.js +++ b/server/notification.js @@ -56,6 +56,7 @@ const GoAlert = require("./notification-providers/goalert"); const SMSManager = require("./notification-providers/smsmanager"); const ServerChan = require("./notification-providers/serverchan"); const ZohoCliq = require("./notification-providers/zoho-cliq"); +const Whapi = require("./notification-providers/whapi"); const GtxMessaging = require("./notification-providers/gtx-messaging"); const Cellsynt = require("./notification-providers/cellsynt"); @@ -131,6 +132,7 @@ class Notification { new WeCom(), new GoAlert(), new ZohoCliq(), + new Whapi(), new GtxMessaging(), new Cellsynt(), ]; diff --git a/src/components/NotificationDialog.vue b/src/components/NotificationDialog.vue index 6538f4eaa..f57e0628a 100644 --- a/src/components/NotificationDialog.vue +++ b/src/components/NotificationDialog.vue @@ -154,8 +154,9 @@ export default { "webhook": "Webhook", "GoAlert": "GoAlert", "ZohoCliq": "ZohoCliq", + "whapi": "WhatsApp (Whapi)", "gtxmessaging": "GtxMessaging", - "Cellsynt": "Cellsynt" + "Cellsynt": "Cellsynt", }; // Put notifications here if it's not supported in most regions or its documentation is not in English diff --git a/src/components/notifications/Whapi.vue b/src/components/notifications/Whapi.vue new file mode 100644 index 000000000..4c92ad2c3 --- /dev/null +++ b/src/components/notifications/Whapi.vue @@ -0,0 +1,33 @@ + + diff --git a/src/components/notifications/index.js b/src/components/notifications/index.js index 821652e24..458322384 100644 --- a/src/components/notifications/index.js +++ b/src/components/notifications/index.js @@ -55,6 +55,7 @@ import WeCom from "./WeCom.vue"; import GoAlert from "./GoAlert.vue"; import ZohoCliq from "./ZohoCliq.vue"; import Splunk from "./Splunk.vue"; +import Whapi from "./Whapi.vue"; import Cellsynt from "./Cellsynt.vue"; /** @@ -118,6 +119,7 @@ const NotificationFormList = { "GoAlert": GoAlert, "ServerChan": ServerChan, "ZohoCliq": ZohoCliq, + "whapi": Whapi, "gtxmessaging": GtxMessaging, "Cellsynt": Cellsynt, }; diff --git a/src/lang/en.json b/src/lang/en.json index 8fb04ae69..21b0eec70 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -885,6 +885,10 @@ "deleteRemoteBrowserMessage": "Are you sure want to delete this Remote Browser for all monitors?", "GrafanaOncallUrl": "Grafana Oncall URL", "Browser Screenshot": "Browser Screenshot", + "wayToWriteWhapiRecipient": "The phone number with the international prefix, but without the plus sign at the start ({0}), the Contact ID ({1}) or the Group ID ({2}).", + "wayToGetWhapiUrlAndToken": "You can get the API URL and the token by going into your desired channel from {0}", + "whapiRecipient": "Phone Number / Contact ID / Group ID", + "API URL": "API URL", "What is a Remote Browser?": "What is a Remote Browser?", "wayToGetHeiiOnCallDetails": "How to get the Trigger ID and API Keys is explained in the {documentation}", "documentationOf": "{0} Documentation", From 05606c69e7feb7cf4c033e2eb7c6f43ca7d1c5d0 Mon Sep 17 00:00:00 2001 From: msrl2000 <86884058+msrl2000@users.noreply.github.com> Date: Wed, 10 Apr 2024 22:49:19 +0300 Subject: [PATCH 57/58] Update i18n.js (#4666) --- src/i18n.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n.js b/src/i18n.js index 3a636c35f..d309e1132 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -57,7 +57,7 @@ for (let lang in languageList) { }; } -const rtlLangs = [ "fa", "ar-SY", "ur" ]; +const rtlLangs = [ "he-IL", "fa", "ar-SY", "ur" ]; export const currentLocale = () => localStorage.locale || languageList[navigator.language] && navigator.language From b25ac55a2f0702bbbd8b7d7fba736e962ea10d97 Mon Sep 17 00:00:00 2001 From: CoolCu <166844316+CoolCu@users.noreply.github.com> Date: Tue, 16 Apr 2024 22:59:07 +0800 Subject: [PATCH 58/58] chore: fix some typos in comments (#4679) Signed-off-by: CoolCu --- server/model/monitor.js | 2 +- server/uptime-calculator.js | 6 +++--- src/mixins/socket.js | 2 +- src/pages/Details.vue | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/server/model/monitor.js b/server/model/monitor.js index 1667b83a8..80cff6f97 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -1573,7 +1573,7 @@ class Monitor extends BeanModel { } /** - * Unlinks all children of the the group monitor + * Unlinks all children of the group monitor * @param {number} groupID ID of group to remove children of * @returns {Promise} */ diff --git a/server/uptime-calculator.js b/server/uptime-calculator.js index 7b5326748..3735a79a3 100644 --- a/server/uptime-calculator.js +++ b/server/uptime-calculator.js @@ -513,7 +513,7 @@ class UptimeCalculator { /** * Flat status to UP or DOWN - * @param {number} status the status which schould be turned into a flat status + * @param {number} status the status which should be turned into a flat status * @returns {UP|DOWN|PENDING} The flat status * @throws {Error} Invalid status */ @@ -594,7 +594,7 @@ class UptimeCalculator { totalPing += data.avgPing * data.up; } - // Set key to the pervious time period + // Set key to the previous time period switch (type) { case "day": key -= 86400; @@ -721,7 +721,7 @@ class UptimeCalculator { result.push(data); } - // Set key to the pervious time period + // Set key to the previous time period switch (type) { case "day": key -= 86400; diff --git a/src/mixins/socket.js b/src/mixins/socket.js index 0a8001a4d..c3845d34b 100644 --- a/src/mixins/socket.js +++ b/src/mixins/socket.js @@ -323,7 +323,7 @@ export default { }, /** - * Show success or error toast dependant on response status code + * Show success or error toast dependent on response status code * @param {object} res Response object * @returns {void} */ diff --git a/src/pages/Details.vue b/src/pages/Details.vue index 8582b82c2..231870efd 100644 --- a/src/pages/Details.vue +++ b/src/pages/Details.vue @@ -550,7 +550,7 @@ export default { /** * Return the correct title for the ping stat * @param {boolean} average Is the statistic an average? - * @returns {string} Title formatted dependant on monitor type + * @returns {string} Title formatted dependent on monitor type */ pingTitle(average = false) { let translationPrefix = "";