diff --git a/server/model/monitor.js b/server/model/monitor.js index 741fb940e..8d70aa54b 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -636,7 +636,17 @@ class Monitor extends BeanModel { bean.duration = beatInterval; throw new Error("No heartbeat in the time window"); } - + } else if (this.type === "manual") { + const lastHeartbeat = await Monitor.getPreviousHeartbeat(this.id); + if (lastHeartbeat) { + bean.status = lastHeartbeat.status; + bean.msg = lastHeartbeat.msg || "Manual monitoring"; + } else { + bean.status = PENDING; + bean.msg = "Manual monitoring - No previous status"; + } + bean.time = new Date().getTime(); + retries = 0; } else if (this.type === "steam") { const steamApiUrl = "https://api.steampowered.com/IGameServersService/GetServerList/v1/"; const steamAPIKey = await setting("steamAPIKey"); diff --git a/server/monitor-types/manual.js b/server/monitor-types/manual.js new file mode 100644 index 000000000..1ae2cda6a --- /dev/null +++ b/server/monitor-types/manual.js @@ -0,0 +1,26 @@ +const { MonitorType } = require("./monitor-type"); + +class ManualMonitorType extends MonitorType { + name = "Manual"; + type = "manual"; + description = "Manual monitoring"; + supportsConditions = false; + conditionVariables = []; + + /** + * Checks the status of the monitor manually + * @param {object} monitor - Monitor object + * @param {object} heartbeat - Object to write the status of the check + * @param {object} server - Server object + * @returns {Promise} + */ + async check(monitor, heartbeat, server) { + heartbeat.status = monitor.status; + heartbeat.msg = monitor.msg || "Manual monitoring"; + heartbeat.time = new Date().getTime(); + } +} + +module.exports = { + ManualMonitorType +}; diff --git a/server/server.js b/server/server.js index cba02174d..0a418595b 100644 --- a/server/server.js +++ b/server/server.js @@ -1542,6 +1542,34 @@ let needSetup = false; } }); + socket.on("addHeartbeat", async (heartbeat, callback) => { + try { + checkLogin(socket); + + let bean = R.dispense("heartbeat"); + bean.monitor_id = heartbeat.monitorID; + bean.status = heartbeat.status; + bean.msg = heartbeat.msg; + bean.time = heartbeat.time; + bean.ping = heartbeat.ping; + bean.important = true; + + await R.store(bean); + + io.to(socket.userID).emit("heartbeat", bean.toJSON()); + + callback({ + ok: true, + }); + + } catch (e) { + callback({ + ok: false, + msg: e.message, + }); + } + }); + socket.on("clearStatistics", async (callback) => { try { checkLogin(socket); diff --git a/server/uptime-calculator.js b/server/uptime-calculator.js index 71d1d458c..bdc487f07 100644 --- a/server/uptime-calculator.js +++ b/server/uptime-calculator.js @@ -543,6 +543,10 @@ class UptimeCalculator { * @throws {Error} Invalid status */ flatStatus(status) { + if (typeof status !== "number") { + throw new Error("Invalid status: status must be a number"); + } + switch (status) { case UP: case MAINTENANCE: @@ -550,8 +554,9 @@ class UptimeCalculator { case DOWN: case PENDING: return DOWN; + default: + throw new Error("Invalid status: " + status); } - throw new Error("Invalid status"); } /** diff --git a/server/uptime-kuma-server.js b/server/uptime-kuma-server.js index 1f75b72cc..a04e6bd49 100644 --- a/server/uptime-kuma-server.js +++ b/server/uptime-kuma-server.js @@ -118,6 +118,7 @@ class UptimeKumaServer { UptimeKumaServer.monitorTypeList["snmp"] = new SNMPMonitorType(); UptimeKumaServer.monitorTypeList["mongodb"] = new MongodbMonitorType(); UptimeKumaServer.monitorTypeList["rabbitmq"] = new RabbitMqMonitorType(); + UptimeKumaServer.monitorTypeList["manual"] = new ManualMonitorType(); // Allow all CORS origins (polling) in development let cors = undefined; @@ -558,4 +559,5 @@ const { GroupMonitorType } = require("./monitor-types/group"); const { SNMPMonitorType } = require("./monitor-types/snmp"); const { MongodbMonitorType } = require("./monitor-types/mongodb"); const { RabbitMqMonitorType } = require("./monitor-types/rabbitmq"); +const { ManualMonitorType } = require("./monitor-types/manual"); const Monitor = require("./model/monitor"); diff --git a/src/components/Datetime.vue b/src/components/Datetime.vue index 0d4e182cd..72ed970d9 100644 --- a/src/components/Datetime.vue +++ b/src/components/Datetime.vue @@ -7,7 +7,7 @@ export default { props: { /** Value of date time */ value: { - type: String, + type: [ String, Number ], default: null, }, /** Should only the date be displayed? */ diff --git a/src/lang/en.json b/src/lang/en.json index ef4d92fcb..a8796c2cd 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -1109,5 +1109,9 @@ "Phone numbers": "Phone numbers", "Sender name": "Sender name", "smsplanetNeedToApproveName": "Needs to be approved in the client panel", - "Disable URL in Notification": "Disable URL in Notification" + "Disable URL in Notification": "Disable URL in Notification", + "Manual": "Manual", + "Manual Status": "Manual Status", + "Manual status set to": "Manual status set to", + "Status updated": "Status updated" } diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index 95b29aa58..8a974e427 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -55,6 +55,9 @@ + @@ -770,6 +773,21 @@
+ +
+ +
+ + + +
+
@@ -1181,7 +1199,10 @@ export default { VueMultiselect, EditMonitorConditions, }, - + setup() { + const toast = useToast(); + return { toast }; + }, data() { return { minInterval: MIN_INTERVAL_SECOND, @@ -1208,7 +1229,6 @@ export default { remoteBrowsersEnabled: false, }; }, - computed: { timeoutStep() { return this.monitor.type === "ping" ? 1 : 0.1; @@ -2015,6 +2035,24 @@ message HealthCheckResponse { } }, + setManualStatus(status) { + let updatedMonitor = { ...this.monitor }; + updatedMonitor.id = this.monitor.id; + + this.$root.getSocket().emit("addHeartbeat", { + monitorID: this.monitor.id, + status: status === "up" ? 1 : status === "down" ? 0 : status === "maintenance" ? 3 : 2, + msg: status === "up" ? "Up" : status === "down" ? "Down" : "Maintenance", + time: new Date().getTime(), + ping: 0 + }, (res) => { + if (res.ok) { + this.toast.success(this.$t("Success")); + } else { + this.toast.error(res.msg); + } + }); + }, }, };