diff --git a/db/patch-monitor-push_token.sql b/db/patch-monitor-push_token.sql new file mode 100644 index 000000000..8c2e7a42c --- /dev/null +++ b/db/patch-monitor-push_token.sql @@ -0,0 +1,7 @@ +-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. +BEGIN TRANSACTION; + +ALTER TABLE monitor + ADD push_token VARCHAR(20) DEFAULT NULL; + +COMMIT; diff --git a/server/database.js b/server/database.js index 4cf1e3933..47eca2835 100644 --- a/server/database.js +++ b/server/database.js @@ -48,6 +48,7 @@ class Database { "patch-add-retry-interval-monitor.sql": true, "patch-incident-table.sql": true, "patch-group-table.sql": true, + "patch-monitor-push_token.sql": true, } /** diff --git a/server/model/monitor.js b/server/model/monitor.js index a50baccfd..c551fa7d7 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -69,6 +69,7 @@ class Monitor extends BeanModel { dns_resolve_type: this.dns_resolve_type, dns_resolve_server: this.dns_resolve_server, dns_last_result: this.dns_last_result, + pushToken: this.pushToken, notificationIDList, tags: tags, }; @@ -236,6 +237,28 @@ class Monitor extends BeanModel { bean.msg = dnsMessage; bean.status = UP; + } else if (this.type === "push") { // Type: Push + const time = R.isoDateTime(dayjs.utc().subtract(this.interval, "second")); + + let heartbeatCount = await R.count("heartbeat", " monitor_id = ? AND time > ? ", [ + this.id, + time + ]); + + debug("heartbeatCount" + heartbeatCount + " " + time); + + if (heartbeatCount <= 0) { + throw new Error("No heartbeat in the time window"); + } else { + // No need to insert successful heartbeat for push type, so end here + retries = 0; + this.heartbeatInterval = setTimeout(beat, this.interval * 1000); + return; + } + + } else { + bean.msg = "Unknown Monitor Type"; + bean.status = PENDING; } if (this.isUpsideDown()) { @@ -263,6 +286,8 @@ class Monitor extends BeanModel { } } + let beatInterval = this.interval; + // * ? -> ANY STATUS = important [isFirstBeat] // UP -> PENDING = not important // * UP -> DOWN = important @@ -312,8 +337,6 @@ class Monitor extends BeanModel { bean.important = false; } - let beatInterval = this.interval; - if (bean.status === UP) { console.info(`Monitor #${this.id} '${this.name}': Successful Response: ${bean.ping} ms | Interval: ${beatInterval} seconds | Type: ${this.type}`); } else if (bean.status === PENDING) { @@ -339,7 +362,14 @@ class Monitor extends BeanModel { }; - beat(); + // Delay Push Type + if (this.type === "push") { + setTimeout(() => { + beat(); + }, this.interval * 1000); + } else { + beat(); + } } stop() { diff --git a/server/routers/api-router.js b/server/routers/api-router.js index b56efcb22..6c87fb179 100644 --- a/server/routers/api-router.js +++ b/server/routers/api-router.js @@ -4,15 +4,53 @@ const { R } = require("redbean-node"); const server = require("../server"); const apicache = require("../modules/apicache"); const Monitor = require("../model/monitor"); +const dayjs = require("dayjs"); +const { UP } = require("../../src/util"); let router = express.Router(); let cache = apicache.middleware; +let io = server.io; router.get("/api/entry-page", async (_, response) => { allowDevAllOrigin(response); response.json(server.entryPage); }); +router.get("/api/push/:pushToken", async (request, response) => { + try { + let pushToken = request.params.pushToken; + let msg = request.query.msg || "OK"; + + let monitor = await R.findOne("monitor", " push_token = ? AND active = 1 ", [ + pushToken + ]); + + if (! monitor) { + throw new Error("Monitor not found or not active."); + } + + let bean = R.dispense("heartbeat"); + bean.monitor_id = monitor.id; + bean.time = R.isoDateTime(dayjs.utc()); + bean.status = UP; + bean.msg = msg; + + await R.store(bean); + + io.to(monitor.user_id).emit("heartbeat", bean.toJSON()); + Monitor.sendStats(io, monitor.id, monitor.user_id); + + response.json({ + ok: true, + }); + } catch (e) { + response.json({ + ok: false, + msg: e.message + }); + } +}); + // Status Page Config router.get("/api/status-page/config", async (_request, response) => { allowDevAllOrigin(response); diff --git a/server/server.js b/server/server.js index ddd686951..2384acf57 100644 --- a/server/server.js +++ b/server/server.js @@ -6,7 +6,7 @@ if (! process.env.NODE_ENV) { console.log("Node Env: " + process.env.NODE_ENV); -const { sleep, debug, TimeLogger, getRandomInt } = require("../src/util"); +const { sleep, debug, getRandomInt, genSecret } = require("../src/util"); console.log("Importing Node libraries"); const fs = require("fs"); @@ -37,7 +37,7 @@ console.log("Importing this project modules"); debug("Importing Monitor"); const Monitor = require("./model/monitor"); debug("Importing Settings"); -const { getSettings, setSettings, setting, initJWTSecret, genSecret, allowDevAllOrigin, checkLogin } = require("./util-server"); +const { getSettings, setSettings, setting, initJWTSecret, checkLogin } = require("./util-server"); debug("Importing Notification"); const { Notification } = require("./notification"); @@ -71,7 +71,7 @@ if (demoMode) { console.log("==== Demo Mode ===="); } -console.log("Creating express and socket.io instance") +console.log("Creating express and socket.io instance"); const app = express(); let server; @@ -511,6 +511,7 @@ exports.entryPage = "dashboard"; bean.accepted_statuscodes_json = JSON.stringify(monitor.accepted_statuscodes); bean.dns_resolve_type = monitor.dns_resolve_type; bean.dns_resolve_server = monitor.dns_resolve_server; + bean.pushToken = monitor.pushToken; await R.store(bean); diff --git a/server/util-server.js b/server/util-server.js index 4d2b6cbe1..29e4b11fd 100644 --- a/server/util-server.js +++ b/server/util-server.js @@ -272,16 +272,6 @@ exports.getTotalClientInRoom = (io, roomName) => { } }; -exports.genSecret = () => { - let secret = ""; - let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - let charsLength = chars.length; - for ( let i = 0; i < 64; i++ ) { - secret += chars.charAt(Math.floor(Math.random() * charsLength)); - } - return secret; -}; - exports.allowDevAllOrigin = (res) => { if (process.env.NODE_ENV === "development") { exports.allowAllOrigin(res); diff --git a/src/assets/app.scss b/src/assets/app.scss index f4707df95..bc2949327 100644 --- a/src/assets/app.scss +++ b/src/assets/app.scss @@ -180,6 +180,11 @@ h2 { border-color: $dark-border-color; } + .form-control:disabled, .form-control[readonly] { + background-color: #232f3b; + opacity: 1; + } + .table-hover > tbody > tr:hover { --bs-table-accent-bg: #070a10; color: $dark-font-color; diff --git a/src/components/CopyableInput.vue b/src/components/CopyableInput.vue new file mode 100644 index 000000000..d777f4a0e --- /dev/null +++ b/src/components/CopyableInput.vue @@ -0,0 +1,122 @@ + + + diff --git a/src/icon.js b/src/icon.js index 67eb2a769..6fb914983 100644 --- a/src/icon.js +++ b/src/icon.js @@ -26,7 +26,10 @@ import { faArrowsAltV, faUnlink, faQuestionCircle, - faImages, faUpload, + faImages, + faUpload, + faCopy, + faCheck, } from "@fortawesome/free-solid-svg-icons"; library.add( @@ -54,6 +57,8 @@ library.add( faQuestionCircle, faImages, faUpload, + faCopy, + faCheck, ); export { FontAwesomeIcon }; diff --git a/src/mixins/public.js b/src/mixins/public.js index 2aa180cd9..ba8457a04 100644 --- a/src/mixins/public.js +++ b/src/mixins/public.js @@ -36,5 +36,13 @@ export default { return result; }, + + baseURL() { + if (env === "development" || localStorage.dev === "dev") { + return axios.defaults.baseURL; + } else { + return location.protocol + "//" + location.host; + } + } } }; diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index a56147011..7e136a59b 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -26,19 +26,31 @@ + +
+
+ +
+ + +
+ +
@@ -210,13 +222,17 @@