From 193a273557ea8f5d713029d500500cc3b8df496e Mon Sep 17 00:00:00 2001
From: Nelson Chan
Date: Fri, 3 Mar 2023 08:25:41 +0800
Subject: [PATCH 01/17] Feat: Add status page countdown to refresh
---
src/pages/StatusPage.vue | 42 ++++++++++++++++++++++++++++++++++++++--
1 file changed, 40 insertions(+), 2 deletions(-)
diff --git a/src/pages/StatusPage.vue b/src/pages/StatusPage.vue
index edf32561b..b02bf8a7b 100644
--- a/src/pages/StatusPage.vue
+++ b/src/pages/StatusPage.vue
@@ -306,6 +306,11 @@
{{ $t("Powered by") }} {{ $t("Uptime Kuma" ) }}
+
+
+
{{ $t("last update") }}:
+
{{ $t("refresh in") }}: {{ updateCountdownText }}
+
@@ -322,6 +327,7 @@
From d9558833fcf12a89061602b537e9c3641dd6a95b Mon Sep 17 00:00:00 2001
From: "niclas.koegl"
Date: Tue, 21 Mar 2023 19:45:44 +0100
Subject: [PATCH 06/17] Fix linting
---
server/notification-providers/opsgenie.js | 38 +++++++++++------------
server/notification.js | 2 +-
src/components/notifications/Opsgenie.vue | 12 +++----
3 files changed, 26 insertions(+), 26 deletions(-)
diff --git a/server/notification-providers/opsgenie.js b/server/notification-providers/opsgenie.js
index 3ccbb756c..16bf9fc60 100644
--- a/server/notification-providers/opsgenie.js
+++ b/server/notification-providers/opsgenie.js
@@ -2,14 +2,14 @@ const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { UP, DOWN } = require("../../src/util");
-const opsgenieAlertsUrlEU = "https://api.eu.opsgenie.com/v2/alerts"
-const opsgenieAlertsUrlUS = "https://api.opsgenie.com/v2/alerts"
+const opsgenieAlertsUrlEU = "https://api.eu.opsgenie.com/v2/alerts";
+const opsgenieAlertsUrlUS = "https://api.opsgenie.com/v2/alerts";
let okMsg = "Sent Successfully.";
class Opsgenie extends NotificationProvider {
-
+
name = "Opsgenie";
-
+
/**
* @inheritdoc
*/
@@ -17,7 +17,7 @@ class Opsgenie extends NotificationProvider {
let opsgenieAlertsUrl;
let priority = (notification.opsgeniePriority == "") ? 3 : notification.opsgeniePriority;
const textMsg = "Uptime Kuma Alert";
-
+
try {
switch (notification.opsgenieRegion) {
case "US":
@@ -28,7 +28,7 @@ class Opsgenie extends NotificationProvider {
break;
default:
opsgenieAlertsUrl = opsgenieAlertsUrlUS;
- };
+ }
if (heartbeatJSON == null) {
let notificationTestAlias = "uptime-kuma-notification-test";
@@ -38,12 +38,12 @@ class Opsgenie extends NotificationProvider {
"source": "Uptime Kuma",
"priority": "P5"
};
-
- return this.post(notification, opsgenieAlertsUrl, data)
- };
+
+ return this.post(notification, opsgenieAlertsUrl, data);
+ }
if (heartbeatJSON.status === DOWN) {
- let data = {
+ let data = {
"message": monitorJSON ? textMsg + `: ${monitorJSON.name}` : textMsg,
"alias": monitorJSON.name,
"description": msg,
@@ -51,24 +51,24 @@ class Opsgenie extends NotificationProvider {
"priority": `P${priority}`
};
- return this.post(notification, opsgenieAlertsUrl, data)
- };
+ return this.post(notification, opsgenieAlertsUrl, data);
+ }
if (heartbeatJSON.status === UP) {
let opsgenieAlertsCloseUrl = `${opsgenieAlertsUrl}/${encodeURIComponent(monitorJSON.name)}/close?identifierType=alias`;
- let data = {
+ let data = {
"source": "Uptime Kuma",
};
- return this.post(notification, opsgenieAlertsCloseUrl, data)
- };
+ return this.post(notification, opsgenieAlertsCloseUrl, data);
+ }
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
/**
- *
+ *
* @param {BeanModel} notification
* @param {string} url Request url
* @param {Object} data Request body
@@ -85,12 +85,12 @@ class Opsgenie extends NotificationProvider {
let res = await axios.post(url, data, config);
if (res.status == null) {
return "Opsgenie notification failed with invalid response!";
- };
+ }
if (res.status < 200 || res.status >= 300) {
return `Opsgenie notification failed with status code ${res.status}`;
- };
+ }
- return okMsg
+ return okMsg;
}
}
diff --git a/server/notification.js b/server/notification.js
index 5d110f215..f69e0a384 100644
--- a/server/notification.js
+++ b/server/notification.js
@@ -84,7 +84,7 @@ class Notification {
new Ntfy(),
new Octopush(),
new OneBot(),
- new Opsgenie(),
+ new Opsgenie(),
new PagerDuty(),
new PagerTree(),
new PromoSMS(),
diff --git a/src/components/notifications/Opsgenie.vue b/src/components/notifications/Opsgenie.vue
index 3dc96bc87..3f07d0528 100644
--- a/src/components/notifications/Opsgenie.vue
+++ b/src/components/notifications/Opsgenie.vue
@@ -18,12 +18,12 @@
-
+
From 35f300c8ebd56b916aadb23c6882113842a43029 Mon Sep 17 00:00:00 2001
From: Louis Lam
Date: Thu, 23 Mar 2023 17:17:53 +0800
Subject: [PATCH 08/17] Improve and reuse language keys
---
src/lang/en.json | 1 +
src/pages/StatusPage.vue | 4 ++--
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/lang/en.json b/src/lang/en.json
index 8f28c8e3a..2da2e4442 100644
--- a/src/lang/en.json
+++ b/src/lang/en.json
@@ -173,6 +173,7 @@
"Avg. Response": "Avg. Response",
"Entry Page": "Entry Page",
"statusPageNothing": "Nothing here, please add a group or a monitor.",
+ "statusPageRefreshIn": "Refresh in: {0}",
"No Services": "No Services",
"All Systems Operational": "All Systems Operational",
"Partially Degraded Service": "Partially Degraded Service",
diff --git a/src/pages/StatusPage.vue b/src/pages/StatusPage.vue
index b02bf8a7b..b202be305 100644
--- a/src/pages/StatusPage.vue
+++ b/src/pages/StatusPage.vue
@@ -308,8 +308,8 @@
-
{{ $t("last update") }}:
-
{{ $t("refresh in") }}: {{ updateCountdownText }}
+
{{ $t("Last Updated") }}:
+
{{ $tc("statusPageRefreshIn", [ updateCountdownText]) }}
From 7330db35631a5510af438f2793ce71df00c0f42f Mon Sep 17 00:00:00 2001
From: Louis Lam
Date: Fri, 24 Mar 2023 16:08:30 +0800
Subject: [PATCH 09/17] Improve error handling of mysqlQuery and return row
count as result
---
server/model/monitor.js | 4 +---
server/util-server.js | 29 +++++++++++++++++++----------
2 files changed, 20 insertions(+), 13 deletions(-)
diff --git a/server/model/monitor.js b/server/model/monitor.js
index 1e011c5a0..1ffae58e5 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -637,9 +637,7 @@ class Monitor extends BeanModel {
} else if (this.type === "mysql") {
let startTime = dayjs().valueOf();
- await mysqlQuery(this.databaseConnectionString, this.databaseQuery);
-
- bean.msg = "";
+ bean.msg = await mysqlQuery(this.databaseConnectionString, this.databaseQuery);
bean.status = UP;
bean.ping = dayjs().valueOf() - startTime;
} else if (this.type === "mongodb") {
diff --git a/server/util-server.js b/server/util-server.js
index 01e66ee26..1cdf33de5 100644
--- a/server/util-server.js
+++ b/server/util-server.js
@@ -322,21 +322,30 @@ exports.postgresQuery = function (connectionString, query) {
* Run a query on MySQL/MariaDB
* @param {string} connectionString The database connection string
* @param {string} query The query to validate the database with
- * @returns {Promise<(string[]|Object[]|Object)>}
+ * @returns {Promise<(string)>}
*/
exports.mysqlQuery = function (connectionString, query) {
return new Promise((resolve, reject) => {
const connection = mysql.createConnection(connectionString);
- connection.promise().query(query)
- .then(res => {
- resolve(res);
- })
- .catch(err => {
+
+ connection.on("error", (err) => {
+ reject(err);
+ });
+
+ connection.query(query, (err, res) => {
+ if (err) {
reject(err);
- })
- .finally(() => {
- connection.destroy();
- });
+ }
+
+ // Check if res is an array
+ if (Array.isArray(res)) {
+ resolve("Rows: " + res.length);
+ } else {
+ resolve("No Error, but the result is not an array. Type: " + typeof res);
+ }
+
+ connection.destroy();
+ });
});
};
From f4ee5271afeb44cac945d0f07e9bc0413956ee19 Mon Sep 17 00:00:00 2001
From: Louis Lam
Date: Fri, 24 Mar 2023 16:24:00 +0800
Subject: [PATCH 10/17] Improve error handling of mysqlQuery and return row
count as result
---
server/util-server.js | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/server/util-server.js b/server/util-server.js
index 1cdf33de5..47f6bb48c 100644
--- a/server/util-server.js
+++ b/server/util-server.js
@@ -335,15 +335,13 @@ exports.mysqlQuery = function (connectionString, query) {
connection.query(query, (err, res) => {
if (err) {
reject(err);
- }
-
- // Check if res is an array
- if (Array.isArray(res)) {
- resolve("Rows: " + res.length);
} else {
- resolve("No Error, but the result is not an array. Type: " + typeof res);
+ if (Array.isArray(res)) {
+ resolve("Rows: " + res.length);
+ } else {
+ resolve("No Error, but the result is not an array. Type: " + typeof res);
+ }
}
-
connection.destroy();
});
});
From 70572af1affd1cc7e6d203560d73a8b9a0479334 Mon Sep 17 00:00:00 2001
From: Louis Lam
Date: Fri, 24 Mar 2023 18:39:52 +0800
Subject: [PATCH 11/17] Fix #2969
---
src/pages/EditMonitor.vue | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue
index 60af4933f..24b35ca20 100644
--- a/src/pages/EditMonitor.vue
+++ b/src/pages/EditMonitor.vue
@@ -944,6 +944,14 @@ message HealthCheckResponse {
} else if (this.isEdit || this.isClone) {
this.$root.getSocket().emit("getMonitor", this.$route.params.id, (res) => {
if (res.ok) {
+
+ if (this.isClone) {
+ // Reset push token for cloned monitors
+ if (res.monitor.type === "push") {
+ res.monitor.pushToken = undefined;
+ }
+ }
+
this.monitor = res.monitor;
if (this.isClone) {
From a2014278b89a4e8178588d475ea3bb9180eee68f Mon Sep 17 00:00:00 2001
From: Louis Lam
Date: Fri, 24 Mar 2023 19:16:12 +0800
Subject: [PATCH 12/17] Fix #2969
---
src/components/CopyableInput.vue | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/src/components/CopyableInput.vue b/src/components/CopyableInput.vue
index 2e1dee766..943193f4d 100644
--- a/src/components/CopyableInput.vue
+++ b/src/components/CopyableInput.vue
@@ -13,6 +13,9 @@
:disabled="disabled"
>
+
+
+
@@ -111,24 +114,19 @@ export default {
}, 3000);
// navigator clipboard api needs a secure context (https)
+ // For http, use the text area method (else part)
if (navigator.clipboard && window.isSecureContext) {
// navigator clipboard api method'
return navigator.clipboard.writeText(textToCopy);
} else {
// text area method
- let textArea = document.createElement("textarea");
+ let textArea = this.$refs.hiddenTextarea;
textArea.value = textToCopy;
- // make the textarea out of viewport
- textArea.style.position = "fixed";
- textArea.style.left = "-999999px";
- textArea.style.top = "-999999px";
- document.body.appendChild(textArea);
textArea.focus();
textArea.select();
return new Promise((res, rej) => {
// here the magic happens
document.execCommand("copy") ? res() : rej();
- textArea.remove();
});
}
}
From bf525371d9669bf62faf3ec2521bc69f52129902 Mon Sep 17 00:00:00 2001
From: Nelson Chan
Date: Fri, 24 Mar 2023 22:42:50 +0800
Subject: [PATCH 13/17] Fix: Apply toPrecision as last step
---
server/routers/api-router.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/server/routers/api-router.js b/server/routers/api-router.js
index a36159cae..d2b32600b 100644
--- a/server/routers/api-router.js
+++ b/server/routers/api-router.js
@@ -224,7 +224,7 @@ router.get("/api/badge/:id/uptime/:duration?", cache("5 minutes"), async (reques
);
// limit the displayed uptime percentage to four (two, when displayed as percent) decimal digits
- const cleanUptime = parseFloat(uptime.toPrecision(4));
+ const cleanUptime = (uptime * 100).toPrecision(4);
// use a given, custom color or calculate one based on the uptime value
badgeValues.color = color ?? percentageToColor(uptime);
@@ -235,7 +235,7 @@ router.get("/api/badge/:id/uptime/:duration?", cache("5 minutes"), async (reques
labelPrefix,
label ?? `Uptime (${requestedDuration}${labelSuffix})`,
]);
- badgeValues.message = filterAndJoin([ prefix, `${cleanUptime * 100}`, suffix ]);
+ badgeValues.message = filterAndJoin([ prefix, cleanUptime, suffix ]);
}
// build the SVG based on given values
From 4f05912276e106cc9e708e230a8313b51c3cd1ee Mon Sep 17 00:00:00 2001
From: Nelson Chan
Date: Sat, 25 Mar 2023 02:44:15 +0800
Subject: [PATCH 14/17] Fix: Allow status badge with empty label
---
server/routers/api-router.js | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/server/routers/api-router.js b/server/routers/api-router.js
index a36159cae..84c3d3f38 100644
--- a/server/routers/api-router.js
+++ b/server/routers/api-router.js
@@ -147,7 +147,11 @@ router.get("/api/badge/:id/status", cache("5 minutes"), async (request, response
const heartbeat = await Monitor.getPreviousHeartbeat(requestedMonitorId);
const state = overrideValue !== undefined ? overrideValue : heartbeat.status;
- badgeValues.label = label ?? "Status";
+ if (label === undefined) {
+ badgeValues.label = "Status";
+ } else {
+ badgeValues.label = label;
+ }
switch (state) {
case DOWN:
badgeValues.color = downColor;
From 86579d245f169c2b0ddc6ce254d048666b95ca2f Mon Sep 17 00:00:00 2001
From: Louis Lam
Date: Sat, 25 Mar 2023 23:06:23 +0800
Subject: [PATCH 15/17] Update ask-for-help.yaml
---
.github/ISSUE_TEMPLATE/ask-for-help.yaml | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/.github/ISSUE_TEMPLATE/ask-for-help.yaml b/.github/ISSUE_TEMPLATE/ask-for-help.yaml
index 3442e8b73..fe9339af8 100644
--- a/.github/ISSUE_TEMPLATE/ask-for-help.yaml
+++ b/.github/ISSUE_TEMPLATE/ask-for-help.yaml
@@ -26,6 +26,13 @@ body:
label: "📝 Describe your problem"
description: "Please walk us through it step by step."
placeholder: "Describe what are you asking for..."
+ - type: textarea
+ id: error-msg
+ validations:
+ required: true
+ attributes:
+ label: "📝 Error Message(s) or Log"
+ placeholder: ""
- type: input
id: uptime-kuma-version
attributes:
From 5266e713e61bfd67c2c46a8f34fc8165562d581b Mon Sep 17 00:00:00 2001
From: Louis Lam
Date: Sat, 25 Mar 2023 23:06:51 +0800
Subject: [PATCH 16/17] Update ask-for-help.yaml
---
.github/ISSUE_TEMPLATE/ask-for-help.yaml | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/.github/ISSUE_TEMPLATE/ask-for-help.yaml b/.github/ISSUE_TEMPLATE/ask-for-help.yaml
index fe9339af8..9c30b2dc8 100644
--- a/.github/ISSUE_TEMPLATE/ask-for-help.yaml
+++ b/.github/ISSUE_TEMPLATE/ask-for-help.yaml
@@ -29,10 +29,9 @@ body:
- type: textarea
id: error-msg
validations:
- required: true
+ required: false
attributes:
label: "📝 Error Message(s) or Log"
- placeholder: ""
- type: input
id: uptime-kuma-version
attributes:
From b64c835ceed723960b90c81b33141164bd1405bf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Faruk=20Gen=C3=A7?=
Date: Sat, 25 Mar 2023 19:56:01 +0300
Subject: [PATCH 17/17] Add Twilio Sms Notification Provider
---
server/notification-providers/twilio.js | 41 +++++++++++++++++++++++++
server/notification.js | 2 ++
src/components/NotificationDialog.vue | 1 +
src/components/notifications/Twilio.vue | 27 ++++++++++++++++
src/components/notifications/index.js | 2 ++
src/lang/en.json | 6 +++-
6 files changed, 78 insertions(+), 1 deletion(-)
create mode 100644 server/notification-providers/twilio.js
create mode 100644 src/components/notifications/Twilio.vue
diff --git a/server/notification-providers/twilio.js b/server/notification-providers/twilio.js
new file mode 100644
index 000000000..8f4db0404
--- /dev/null
+++ b/server/notification-providers/twilio.js
@@ -0,0 +1,41 @@
+const NotificationProvider = require("./notification-provider");
+const axios = require("axios");
+
+class Twilio extends NotificationProvider {
+
+ name = "twilio";
+
+ async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
+
+ let okMsg = "Sent Successfully.";
+
+ let accountSID = notification.twilioAccountSID;
+ let authToken = notification.twilioAuthToken;
+
+ try {
+
+ let config = {
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
+ "Authorization": "Basic " + Buffer.from(accountSID + ":" + authToken).toString("base64"),
+ }
+ };
+
+ let data = new URLSearchParams();
+ data.append("To", notification.twilioToNumber);
+ data.append("From", notification.twilioFromNumber);
+ data.append("Body", msg);
+
+ let url = "https://api.twilio.com/2010-04-01/Accounts/" + accountSID + "/Messages.json";
+
+ await axios.post(url, data, config);
+
+ return okMsg;
+ } catch (error) {
+ this.throwGeneralAxiosError(error);
+ }
+ }
+
+}
+
+module.exports = Twilio;
diff --git a/server/notification.js b/server/notification.js
index 1897f5cc0..68f675e94 100644
--- a/server/notification.js
+++ b/server/notification.js
@@ -41,6 +41,7 @@ const Stackfield = require("./notification-providers/stackfield");
const Teams = require("./notification-providers/teams");
const TechulusPush = require("./notification-providers/techulus-push");
const Telegram = require("./notification-providers/telegram");
+const Twilio = require("./notification-providers/twilio");
const Splunk = require("./notification-providers/splunk");
const Webhook = require("./notification-providers/webhook");
const WeCom = require("./notification-providers/wecom");
@@ -103,6 +104,7 @@ class Notification {
new Teams(),
new TechulusPush(),
new Telegram(),
+ new Twilio(),
new Splunk(),
new Webhook(),
new WeCom(),
diff --git a/src/components/NotificationDialog.vue b/src/components/NotificationDialog.vue
index c3851b568..557e75fc4 100644
--- a/src/components/NotificationDialog.vue
+++ b/src/components/NotificationDialog.vue
@@ -143,6 +143,7 @@ export default {
"stackfield": "Stackfield",
"teams": "Microsoft Teams",
"telegram": "Telegram",
+ "twilio": "Twilio",
"Splunk": "Splunk",
"webhook": "Webhook",
"GoAlert": "GoAlert",
diff --git a/src/components/notifications/Twilio.vue b/src/components/notifications/Twilio.vue
new file mode 100644
index 000000000..3edf1e3df
--- /dev/null
+++ b/src/components/notifications/Twilio.vue
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/notifications/index.js b/src/components/notifications/index.js
index ed9dde0f1..8017b622e 100644
--- a/src/components/notifications/index.js
+++ b/src/components/notifications/index.js
@@ -41,6 +41,7 @@ import STMP from "./SMTP.vue";
import Teams from "./Teams.vue";
import TechulusPush from "./TechulusPush.vue";
import Telegram from "./Telegram.vue";
+import Twilio from "./Twilio.vue";
import Webhook from "./Webhook.vue";
import WeCom from "./WeCom.vue";
import GoAlert from "./GoAlert.vue";
@@ -95,6 +96,7 @@ const NotificationFormList = {
"stackfield": Stackfield,
"teams": Teams,
"telegram": Telegram,
+ "twilio": Twilio,
"Splunk": Splunk,
"webhook": Webhook,
"WeCom": WeCom,
diff --git a/src/lang/en.json b/src/lang/en.json
index 0c9fc3840..e7656c474 100644
--- a/src/lang/en.json
+++ b/src/lang/en.json
@@ -707,5 +707,9 @@
"wayToGetPagerTreeIntegrationURL": "After creating the Uptime Kuma integration in PagerTree, copy the Endpoint. See full details {0}",
"lunaseaTarget": "Target",
"lunaseaDeviceID": "Device ID",
- "lunaseaUserID": "User ID"
+ "lunaseaUserID": "User ID",
+ "twilioAccountSID": "Account SID",
+ "twilioAuthToken": "Auth Token",
+ "twilioFromNumber": "From Number",
+ "twilioToNumber": "To Number"
}