[Push Type] fix missing important flag and missing up notification

pull/529/head
Louis Lam 3 years ago
parent a7d2a34dae
commit 3b74b727f2

@ -292,54 +292,13 @@ class Monitor extends BeanModel {
let beatInterval = this.interval; let beatInterval = this.interval;
// * ? -> ANY STATUS = important [isFirstBeat] let isImportant = Monitor.isImportantBeat(isFirstBeat, previousBeat.status, bean.status);
// UP -> PENDING = not important
// * UP -> DOWN = important
// UP -> UP = not important
// PENDING -> PENDING = not important
// * PENDING -> DOWN = important
// PENDING -> UP = not important
// DOWN -> PENDING = this case not exists
// DOWN -> DOWN = not important
// * DOWN -> UP = important
let isImportant = isFirstBeat ||
(previousBeat.status === UP && bean.status === DOWN) ||
(previousBeat.status === DOWN && bean.status === UP) ||
(previousBeat.status === PENDING && bean.status === DOWN);
// Mark as important if status changed, ignore pending pings, // Mark as important if status changed, ignore pending pings,
// Don't notify if disrupted changes to up // Don't notify if disrupted changes to up
if (isImportant) { if (isImportant) {
bean.important = true; bean.important = true;
await Monitor.sendNotification(isFirstBeat, this, bean);
// Send only if the first beat is DOWN
if (!isFirstBeat || bean.status === DOWN) {
let notificationList = await R.getAll("SELECT notification.* FROM notification, monitor_notification WHERE monitor_id = ? AND monitor_notification.notification_id = notification.id ", [
this.id,
]);
let text;
if (bean.status === UP) {
text = "✅ Up";
} else {
text = "🔴 Down";
}
let msg = `[${this.name}] [${text}] ${bean.msg}`;
for (let notification of notificationList) {
try {
await Notification.send(JSON.parse(notification.config), msg, await this.toJSON(), bean.toJSON());
} catch (e) {
console.error("Cannot send notification to " + notification.name);
console.log(e);
}
}
// Clear Status Page Cache
apicache.clear();
}
} else { } else {
bean.important = false; bean.important = false;
} }
@ -546,6 +505,53 @@ class Monitor extends BeanModel {
io.to(userID).emit("uptime", monitorID, duration, uptime); io.to(userID).emit("uptime", monitorID, duration, uptime);
} }
static isImportantBeat(isFirstBeat, previousBeatStatus, currentBeatStatus) {
// * ? -> ANY STATUS = important [isFirstBeat]
// UP -> PENDING = not important
// * UP -> DOWN = important
// UP -> UP = not important
// PENDING -> PENDING = not important
// * PENDING -> DOWN = important
// PENDING -> UP = not important
// DOWN -> PENDING = this case not exists
// DOWN -> DOWN = not important
// * DOWN -> UP = important
let isImportant = isFirstBeat ||
(previousBeatStatus === UP && currentBeatStatus === DOWN) ||
(previousBeatStatus === DOWN && currentBeatStatus === UP) ||
(previousBeatStatus === PENDING && currentBeatStatus === DOWN);
return isImportant;
}
static async sendNotification(isFirstBeat, monitor, bean) {
if (!isFirstBeat || bean.status === DOWN) {
let notificationList = await R.getAll("SELECT notification.* FROM notification, monitor_notification WHERE monitor_id = ? AND monitor_notification.notification_id = notification.id ", [
monitor.id,
]);
let text;
if (bean.status === UP) {
text = "✅ Up";
} else {
text = "🔴 Down";
}
let msg = `[${monitor.name}] [${text}] ${bean.msg}`;
for (let notification of notificationList) {
try {
await Notification.send(JSON.parse(notification.config), msg, await monitor.toJSON(), bean.toJSON());
} catch (e) {
console.error("Cannot send notification to " + notification.name);
console.log(e);
}
}
// Clear Status Page Cache
apicache.clear();
}
}
} }
module.exports = Monitor; module.exports = Monitor;

@ -5,7 +5,7 @@ const server = require("../server");
const apicache = require("../modules/apicache"); const apicache = require("../modules/apicache");
const Monitor = require("../model/monitor"); const Monitor = require("../model/monitor");
const dayjs = require("dayjs"); const dayjs = require("dayjs");
const { UP } = require("../../src/util"); const { UP, flipStatus, debug } = require("../../src/util");
let router = express.Router(); let router = express.Router();
let cache = apicache.middleware; let cache = apicache.middleware;
@ -21,7 +21,7 @@ router.get("/api/push/:pushToken", async (request, response) => {
let pushToken = request.params.pushToken; let pushToken = request.params.pushToken;
let msg = request.query.msg || "OK"; let msg = request.query.msg || "OK";
let ping = request.query.ping; let ping = request.query.ping || null;
let monitor = await R.findOne("monitor", " push_token = ? AND active = 1 ", [ let monitor = await R.findOne("monitor", " push_token = ? AND active = 1 ", [
pushToken pushToken
@ -31,20 +31,40 @@ router.get("/api/push/:pushToken", async (request, response) => {
throw new Error("Monitor not found or not active."); throw new Error("Monitor not found or not active.");
} }
const previousHeartbeatTime = await R.getCell(` const previousHeartbeat = await R.getRow(`
SELECT time FROM heartbeat SELECT status, time FROM heartbeat
WHERE id = (select MAX(id) from heartbeat where monitor_id = ?) WHERE id = (select MAX(id) from heartbeat where monitor_id = ?)
`, [ `, [
monitor.id monitor.id
]); ]);
let status = UP;
if (monitor.isUpsideDown()) {
status = flipStatus(status);
}
let isFirstBeat = true;
let previousStatus = status;
let duration = 0;
let bean = R.dispense("heartbeat"); let bean = R.dispense("heartbeat");
bean.monitor_id = monitor.id;
bean.time = R.isoDateTime(dayjs.utc()); bean.time = R.isoDateTime(dayjs.utc());
bean.status = UP;
if (previousHeartbeat) {
isFirstBeat = false;
previousStatus = previousHeartbeat.status;
duration = dayjs(bean.time).diff(dayjs(previousHeartbeat.time), "second");
}
debug("PreviousStatus: " + previousStatus);
debug("Current Status: " + status);
bean.important = Monitor.isImportantBeat(isFirstBeat, previousStatus, status);
bean.monitor_id = monitor.id;
bean.status = status;
bean.msg = msg; bean.msg = msg;
bean.ping = ping; bean.ping = ping;
bean.duration = dayjs(bean.time).diff(dayjs(previousHeartbeatTime), "second"); bean.duration = duration;
await R.store(bean); await R.store(bean);
@ -54,6 +74,11 @@ router.get("/api/push/:pushToken", async (request, response) => {
response.json({ response.json({
ok: true, ok: true,
}); });
if (bean.important) {
await Monitor.sendNotification(isFirstBeat, monitor, bean);
}
} catch (e) { } catch (e) {
response.json({ response.json({
ok: false, ok: false,

@ -140,7 +140,7 @@
</label> </label>
</div> </div>
<div v-if="monitor.type !== 'push'" class="my-3 form-check"> <div class="my-3 form-check">
<input id="upside-down" v-model="monitor.upsideDown" class="form-check-input" type="checkbox"> <input id="upside-down" v-model="monitor.upsideDown" class="form-check-input" type="checkbox">
<label class="form-check-label" for="upside-down"> <label class="form-check-label" for="upside-down">
{{ $t("Upside Down Mode") }} {{ $t("Upside Down Mode") }}

@ -1,118 +1,118 @@
"use strict"; "use strict";
// Common Util for frontend and backend // Common Util for frontend and backend
// //
// DOT NOT MODIFY util.js! // DOT NOT MODIFY util.js!
// Need to run "tsc" to compile if there are any changes. // Need to run "tsc" to compile if there are any changes.
// //
// Backend uses the compiled file util.js // Backend uses the compiled file util.js
// Frontend uses util.ts // Frontend uses util.ts
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.getMonitorRelativeURL = exports.genSecret = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isDev = void 0; exports.getMonitorRelativeURL = exports.genSecret = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isDev = void 0;
const _dayjs = require("dayjs"); const _dayjs = require("dayjs");
const dayjs = _dayjs; const dayjs = _dayjs;
exports.isDev = process.env.NODE_ENV === "development"; exports.isDev = process.env.NODE_ENV === "development";
exports.appName = "Uptime Kuma"; exports.appName = "Uptime Kuma";
exports.DOWN = 0; exports.DOWN = 0;
exports.UP = 1; exports.UP = 1;
exports.PENDING = 2; exports.PENDING = 2;
exports.STATUS_PAGE_ALL_DOWN = 0; exports.STATUS_PAGE_ALL_DOWN = 0;
exports.STATUS_PAGE_ALL_UP = 1; exports.STATUS_PAGE_ALL_UP = 1;
exports.STATUS_PAGE_PARTIAL_DOWN = 2; exports.STATUS_PAGE_PARTIAL_DOWN = 2;
function flipStatus(s) { function flipStatus(s) {
if (s === exports.UP) { if (s === exports.UP) {
return exports.DOWN; return exports.DOWN;
} }
if (s === exports.DOWN) { if (s === exports.DOWN) {
return exports.UP; return exports.UP;
} }
return s; return s;
} }
exports.flipStatus = flipStatus; exports.flipStatus = flipStatus;
function sleep(ms) { function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms)); return new Promise(resolve => setTimeout(resolve, ms));
} }
exports.sleep = sleep; exports.sleep = sleep;
/** /**
* PHP's ucfirst * PHP's ucfirst
* @param str * @param str
*/ */
function ucfirst(str) { function ucfirst(str) {
if (!str) { if (!str) {
return str; return str;
} }
const firstLetter = str.substr(0, 1); const firstLetter = str.substr(0, 1);
return firstLetter.toUpperCase() + str.substr(1); return firstLetter.toUpperCase() + str.substr(1);
} }
exports.ucfirst = ucfirst; exports.ucfirst = ucfirst;
function debug(msg) { function debug(msg) {
if (exports.isDev) { if (exports.isDev) {
console.log(msg); console.log(msg);
} }
} }
exports.debug = debug; exports.debug = debug;
function polyfill() { function polyfill() {
/** /**
* String.prototype.replaceAll() polyfill * String.prototype.replaceAll() polyfill
* https://gomakethings.com/how-to-replace-a-section-of-a-string-with-another-one-with-vanilla-js/ * https://gomakethings.com/how-to-replace-a-section-of-a-string-with-another-one-with-vanilla-js/
* @author Chris Ferdinandi * @author Chris Ferdinandi
* @license MIT * @license MIT
*/ */
if (!String.prototype.replaceAll) { if (!String.prototype.replaceAll) {
String.prototype.replaceAll = function (str, newStr) { String.prototype.replaceAll = function (str, newStr) {
// If a regex pattern // If a regex pattern
if (Object.prototype.toString.call(str).toLowerCase() === "[object regexp]") { if (Object.prototype.toString.call(str).toLowerCase() === "[object regexp]") {
return this.replace(str, newStr); return this.replace(str, newStr);
} }
// If a string // If a string
return this.replace(new RegExp(str, "g"), newStr); return this.replace(new RegExp(str, "g"), newStr);
}; };
} }
} }
exports.polyfill = polyfill; exports.polyfill = polyfill;
class TimeLogger { class TimeLogger {
constructor() { constructor() {
this.startTime = dayjs().valueOf(); this.startTime = dayjs().valueOf();
} }
print(name) { print(name) {
if (exports.isDev && process.env.TIMELOGGER === "1") { if (exports.isDev && process.env.TIMELOGGER === "1") {
console.log(name + ": " + (dayjs().valueOf() - this.startTime) + "ms"); console.log(name + ": " + (dayjs().valueOf() - this.startTime) + "ms");
} }
} }
} }
exports.TimeLogger = TimeLogger; exports.TimeLogger = TimeLogger;
/** /**
* Returns a random number between min (inclusive) and max (exclusive) * Returns a random number between min (inclusive) and max (exclusive)
*/ */
function getRandomArbitrary(min, max) { function getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min; return Math.random() * (max - min) + min;
} }
exports.getRandomArbitrary = getRandomArbitrary; exports.getRandomArbitrary = getRandomArbitrary;
/** /**
* From: https://stackoverflow.com/questions/1527803/generating-random-whole-numbers-in-javascript-in-a-specific-range * From: https://stackoverflow.com/questions/1527803/generating-random-whole-numbers-in-javascript-in-a-specific-range
* *
* Returns a random integer between min (inclusive) and max (inclusive). * Returns a random integer between min (inclusive) and max (inclusive).
* The value is no lower than min (or the next integer greater than min * The value is no lower than min (or the next integer greater than min
* if min isn't an integer) and no greater than max (or the next integer * if min isn't an integer) and no greater than max (or the next integer
* lower than max if max isn't an integer). * lower than max if max isn't an integer).
* Using Math.round() will give you a non-uniform distribution! * Using Math.round() will give you a non-uniform distribution!
*/ */
function getRandomInt(min, max) { function getRandomInt(min, max) {
min = Math.ceil(min); min = Math.ceil(min);
max = Math.floor(max); max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min; return Math.floor(Math.random() * (max - min + 1)) + min;
} }
exports.getRandomInt = getRandomInt; exports.getRandomInt = getRandomInt;
function genSecret(length = 64) { function genSecret(length = 64) {
let secret = ""; let secret = "";
let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let charsLength = chars.length; let charsLength = chars.length;
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) {
secret += chars.charAt(Math.floor(Math.random() * charsLength)); secret += chars.charAt(Math.floor(Math.random() * charsLength));
} }
return secret; return secret;
} }
exports.genSecret = genSecret; exports.genSecret = genSecret;
function getMonitorRelativeURL(id) { function getMonitorRelativeURL(id) {
return "/dashboard/" + id; return "/dashboard/" + id;
} }
exports.getMonitorRelativeURL = getMonitorRelativeURL; exports.getMonitorRelativeURL = getMonitorRelativeURL;

Loading…
Cancel
Save