mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-06-07 13:32:35 +02:00
fix(monitor-checks): save checks to the database
This commit is contained in:
parent
07a37534b1
commit
5a29df40ec
9 changed files with 100 additions and 57 deletions
|
@ -4,6 +4,7 @@ const { setSetting, setting } = require("./util-server");
|
|||
const { debug, sleep } = require("../src/util");
|
||||
const dayjs = require("dayjs");
|
||||
const knex = require("knex");
|
||||
const MonitorCheck = require("./model/monitor-check");
|
||||
|
||||
/**
|
||||
* Database & App Data Folder
|
||||
|
@ -107,6 +108,7 @@ class Database {
|
|||
// Auto map the model to a bean object
|
||||
R.freeze(true);
|
||||
await R.autoloadModels("./server/model");
|
||||
R.modelList["monitor_checks"] = MonitorCheck;
|
||||
|
||||
// Change to WAL
|
||||
await R.exec("PRAGMA journal_mode = WAL");
|
||||
|
|
|
@ -6,12 +6,12 @@ dayjs.extend(utc);
|
|||
dayjs.extend(timezone);
|
||||
const axios = require("axios");
|
||||
const { Prometheus } = require("../prometheus");
|
||||
const { debug, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/util");
|
||||
const { debug, UP, DOWN, PENDING, flipStatus, TimeLogger, MONITOR_CHECK_SELECTOR_TYPES } = require("../../src/util");
|
||||
const { tcping, ping, dnsResolve, checkCertificate, getTotalClientInRoom } = require("../util-server");
|
||||
const { R } = require("redbean-node");
|
||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||
const { Notification } = require("../notification");
|
||||
const validateMonitorChecks = require("./validate-monitor-checks");
|
||||
const validateMonitorChecks = require("../validate-monitor-checks");
|
||||
const version = require("../../package.json").version;
|
||||
|
||||
/**
|
||||
|
@ -49,6 +49,13 @@ class Monitor extends BeanModel {
|
|||
}
|
||||
|
||||
const tags = await R.getAll("SELECT mt.*, tag.name, tag.color FROM monitor_tag mt JOIN tag ON mt.tag_id = tag.id WHERE mt.monitor_id = ?", [this.id]);
|
||||
const checks = await R.getAll("SELECT mc.* FROM monitor_checks mc WHERE mc.monitor_id = ?", [this.id]);
|
||||
|
||||
checks.forEach(check => {
|
||||
if (MONITOR_CHECK_SELECTOR_TYPES.includes(check.type) && typeof check.value === "string" && check.value.startsWith("{")) {
|
||||
check.value = JSON.parse(check.value);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
id: this.id,
|
||||
|
@ -69,6 +76,7 @@ class Monitor extends BeanModel {
|
|||
dns_resolve_server: this.dns_resolve_server,
|
||||
dns_last_result: this.dns_last_result,
|
||||
notificationIDList,
|
||||
checks: checks,
|
||||
tags: tags,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
console.log("Welcome to Uptime Kuma");
|
||||
|
||||
if (! process.env.NODE_ENV) {
|
||||
if (!process.env.NODE_ENV) {
|
||||
process.env.NODE_ENV = "production";
|
||||
}
|
||||
|
||||
console.log("Node Env: " + process.env.NODE_ENV);
|
||||
|
||||
const { sleep, debug, getRandomInt } = require("../src/util");
|
||||
const {
|
||||
sleep,
|
||||
debug,
|
||||
getRandomInt,
|
||||
} = require("../src/util");
|
||||
|
||||
console.log("Importing Node libraries");
|
||||
const fs = require("fs");
|
||||
|
@ -37,7 +41,16 @@ 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,
|
||||
genSecret,
|
||||
allowDevAllOrigin,
|
||||
checkLogin,
|
||||
updateMonitorChecks,
|
||||
} = require("./util-server");
|
||||
|
||||
debug("Importing Notification");
|
||||
const { Notification } = require("./notification");
|
||||
|
@ -80,7 +93,7 @@ if (sslKey && sslCert) {
|
|||
console.log("Server Type: HTTPS");
|
||||
server = https.createServer({
|
||||
key: fs.readFileSync(sslKey),
|
||||
cert: fs.readFileSync(sslCert)
|
||||
cert: fs.readFileSync(sslCert),
|
||||
}, app);
|
||||
} else {
|
||||
console.log("Server Type: HTTP");
|
||||
|
@ -91,7 +104,11 @@ const io = new Server(server);
|
|||
module.exports.io = io;
|
||||
|
||||
// Must be after io instantiation
|
||||
const { sendNotificationList, sendHeartbeatList, sendImportantHeartbeatList } = require("./client");
|
||||
const {
|
||||
sendNotificationList,
|
||||
sendHeartbeatList,
|
||||
sendImportantHeartbeatList,
|
||||
} = require("./client");
|
||||
const { statusPageSocketHandler } = require("./socket-handlers/status-page-socket-handler");
|
||||
|
||||
app.use(express.json());
|
||||
|
@ -143,7 +160,7 @@ exports.entryPage = "dashboard";
|
|||
// Robots.txt
|
||||
app.get("/robots.txt", async (_request, response) => {
|
||||
let txt = "User-agent: *\nDisallow:";
|
||||
if (! await setting("searchEngineIndex")) {
|
||||
if (!await setting("searchEngineIndex")) {
|
||||
txt += " /";
|
||||
}
|
||||
response.setHeader("Content-Type", "text/plain");
|
||||
|
@ -467,14 +484,7 @@ exports.entryPage = "dashboard";
|
|||
bean.user_id = socket.userID;
|
||||
await R.store(bean);
|
||||
|
||||
// Store checks info
|
||||
for (let i = 0; i < (checks || []).length; i++) {
|
||||
let checkBean = R.dispense("monitor_checks");
|
||||
checks[i].monitor_id = bean.id;
|
||||
checks[i].value = typeof checks[i].value === "object" ? JSON.stringify(checks[i].value) : checks[i].value;
|
||||
checkBean.import(checks[i]);
|
||||
await R.store(checkBean);
|
||||
}
|
||||
await updateMonitorChecks(bean.id, checks);
|
||||
|
||||
await updateMonitorNotification(bean.id, notificationIDList);
|
||||
|
||||
|
@ -500,7 +510,7 @@ exports.entryPage = "dashboard";
|
|||
try {
|
||||
checkLogin(socket);
|
||||
|
||||
let bean = await R.findOne("monitor", " id = ? ", [ monitor.id ]);
|
||||
let bean = await R.findOne("monitor", " id = ? ", [monitor.id]);
|
||||
|
||||
if (bean.user_id !== socket.userID) {
|
||||
throw new Error("Permission denied.");
|
||||
|
@ -525,13 +535,24 @@ exports.entryPage = "dashboard";
|
|||
|
||||
await R.store(bean);
|
||||
|
||||
// Store checks info
|
||||
// Store checks
|
||||
let trx = await R.begin();
|
||||
try {
|
||||
// delete existing checks for monitor
|
||||
const existingMonitorChecks = await R.find("monitor_checks", " monitor_id = ?", [bean.id]);
|
||||
await trx.trashAll(existingMonitorChecks);
|
||||
|
||||
// Replace them with new checks
|
||||
for (let i = 0; i < (checks || []).length; i++) {
|
||||
let checkBean = R.dispense("monitor_checks");
|
||||
let checkBean = trx.dispense("monitor_checks");
|
||||
checks[i].monitor_id = bean.id;
|
||||
checks[i].value = typeof checks[i].value === "object" ? JSON.stringify(checks[i].value) : checks[i].value;
|
||||
checkBean.import(checks[i]);
|
||||
await R.store(checkBean);
|
||||
await trx.store(checkBean);
|
||||
}
|
||||
} catch (err) {
|
||||
await trx.rollback();
|
||||
throw err;
|
||||
}
|
||||
|
||||
await updateMonitorNotification(bean.id, monitor.notificationIDList);
|
||||
|
@ -712,7 +733,7 @@ exports.entryPage = "dashboard";
|
|||
try {
|
||||
checkLogin(socket);
|
||||
|
||||
let bean = await R.findOne("monitor", " id = ? ", [ tag.id ]);
|
||||
let bean = await R.findOne("monitor", " id = ? ", [tag.id]);
|
||||
bean.name = tag.name;
|
||||
bean.color = tag.color;
|
||||
await R.store(bean);
|
||||
|
@ -734,7 +755,7 @@ exports.entryPage = "dashboard";
|
|||
try {
|
||||
checkLogin(socket);
|
||||
|
||||
await R.exec("DELETE FROM tag WHERE id = ? ", [ tagID ]);
|
||||
await R.exec("DELETE FROM tag WHERE id = ? ", [tagID]);
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
|
@ -825,7 +846,7 @@ exports.entryPage = "dashboard";
|
|||
try {
|
||||
checkLogin(socket);
|
||||
|
||||
if (! password.currentPassword) {
|
||||
if (!password.currentPassword) {
|
||||
throw new Error("Invalid new password");
|
||||
}
|
||||
|
||||
|
@ -879,7 +900,7 @@ exports.entryPage = "dashboard";
|
|||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Saved"
|
||||
msg: "Saved",
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
|
@ -1068,14 +1089,7 @@ exports.entryPage = "dashboard";
|
|||
bean.user_id = socket.userID;
|
||||
await R.store(bean);
|
||||
|
||||
// Store monitor checks
|
||||
for (let i = 0; i < (checks || []).length; i++) {
|
||||
let checkBean = R.dispense("monitor_checks");
|
||||
checks[i].monitor_id = bean.id;
|
||||
checks[i].value = typeof checks[i].value === "object" ? JSON.stringify(checks[i].value) : checks[i].value;
|
||||
checkBean.import(checks[i]);
|
||||
await R.store(checkBean);
|
||||
}
|
||||
await updateMonitorChecks(bean.id, checks);
|
||||
|
||||
// Only for backup files with the version 1.7.0 or higher, since there was the tag feature implemented
|
||||
if (version17x) {
|
||||
|
@ -1088,7 +1102,7 @@ exports.entryPage = "dashboard";
|
|||
]);
|
||||
|
||||
let tagId;
|
||||
if (! tag) {
|
||||
if (!tag) {
|
||||
// -> If it doesn't exist, create new tag from backup file
|
||||
let beanTag = R.dispense("tag");
|
||||
beanTag.name = oldTag.name;
|
||||
|
@ -1173,7 +1187,7 @@ exports.entryPage = "dashboard";
|
|||
console.log(`Clear Heartbeats Monitor: ${monitorID} User ID: ${socket.userID}`);
|
||||
|
||||
await R.exec("DELETE FROM heartbeat WHERE monitor_id = ?", [
|
||||
monitorID
|
||||
monitorID,
|
||||
]);
|
||||
|
||||
await sendHeartbeatList(socket, monitorID, true, true);
|
||||
|
@ -1270,7 +1284,7 @@ async function checkOwner(userID, monitorID) {
|
|||
userID,
|
||||
]);
|
||||
|
||||
if (! row) {
|
||||
if (!row) {
|
||||
throw new Error("You do not own this monitor.");
|
||||
}
|
||||
}
|
||||
|
@ -1311,14 +1325,6 @@ async function getMonitorJSONList(userID) {
|
|||
]);
|
||||
|
||||
for (let monitor of monitorList) {
|
||||
const monitorChecks = await R.find("monitor_checks", " monitor_id = ? ORDER BY id", [
|
||||
monitor.id,
|
||||
]);
|
||||
const monitorCheckObj = [];
|
||||
for (let monitorCheck of monitorChecks) {
|
||||
monitorCheckObj.push(await monitorCheck.toJSON());
|
||||
}
|
||||
monitor.checks = monitorCheckObj;
|
||||
result[monitor.id] = await monitor.toJSON();
|
||||
}
|
||||
|
||||
|
@ -1326,7 +1332,7 @@ async function getMonitorJSONList(userID) {
|
|||
}
|
||||
|
||||
async function initDatabase() {
|
||||
if (! fs.existsSync(Database.path)) {
|
||||
if (!fs.existsSync(Database.path)) {
|
||||
console.log("Copying Database");
|
||||
fs.copyFileSync(Database.templatePath, Database.path);
|
||||
}
|
||||
|
@ -1342,7 +1348,7 @@ async function initDatabase() {
|
|||
"jwtSecret",
|
||||
]);
|
||||
|
||||
if (! jwtSecretBean) {
|
||||
if (!jwtSecretBean) {
|
||||
console.log("JWT secret is not found, generate one.");
|
||||
jwtSecretBean = await initJWTSecret();
|
||||
console.log("Stored JWT secret into database");
|
||||
|
|
|
@ -298,3 +298,24 @@ exports.checkLogin = (socket) => {
|
|||
throw new Error("You are not logged in.");
|
||||
}
|
||||
};
|
||||
|
||||
exports.updateMonitorChecks = async (monitorId, checks) => {
|
||||
let trx = await R.begin();
|
||||
try {
|
||||
// delete existing checks for monitor
|
||||
const existingMonitorChecks = await R.find("monitor_checks", " monitor_id = ?", [bean.id]);
|
||||
await trx.trashAll(existingMonitorChecks);
|
||||
|
||||
// Replace them with new checks
|
||||
for (let i = 0; i < (checks || []).length; i++) {
|
||||
let checkBean = trx.dispense("monitor_checks");
|
||||
checks[i].monitor_id = monitorId;
|
||||
checks[i].value = typeof checks[i].value === "object" ? JSON.stringify(checks[i].value) : checks[i].value;
|
||||
checkBean.import(checks[i]);
|
||||
await trx.store(checkBean);
|
||||
}
|
||||
} catch (err) {
|
||||
await trx.rollback();
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const { checkStatusCode } = require("../util-server");
|
||||
const { UP } = require("../../src/util");
|
||||
const { checkStatusCode } = require("./util-server");
|
||||
const { UP } = require("../src/util");
|
||||
const get = require("lodash.get");
|
||||
|
||||
function validateMonitorChecks(res, checks, bean) {
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="monitor-check mb-4">
|
||||
<div>
|
||||
<select id="type" :value="monitorCheck.type" :class="{'form-select': true, 'mb-1': !!monitorCheck.type}" @input="changeType($event.target.value)">
|
||||
<select id="type" :value="monitorCheck.type" :class="{'form-select': true, 'mb-1': !!monitorCheck.type}" @input="changeType($event.target.value)" required>
|
||||
<option value="HTTP_STATUS_CODE_SHOULD_EQUAL">
|
||||
{{ $t("HTTP status code should equal") }}
|
||||
</option>
|
||||
|
@ -81,6 +81,7 @@
|
|||
|
||||
<script>
|
||||
import VueMultiselect from "vue-multiselect";
|
||||
import { MONITOR_CHECK_SELECTOR_TYPES, MONITOR_CHECK_STRING_TYPES } from "../util.ts";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -121,9 +122,8 @@ export default {
|
|||
this.$emit("delete");
|
||||
},
|
||||
changeType(type) {
|
||||
const stringValueTypes = ["HTTP_STATUS_CODE_SHOULD_EQUAL", "RESPONSE_SHOULD_CONTAIN_TEXT", "RESPONSE_SHOULD_NOT_CONTAIN_TEXT", "RESPONSE_SHOULD_MATCH_REGEX", "RESPONSE_SHOULD_NOT_MATCH_REGEX"];
|
||||
if (stringValueTypes.includes(type) && stringValueTypes.includes(this.monitorCheck.type) ||
|
||||
!stringValueTypes.includes(type) && !stringValueTypes.includes(this.monitorCheck.type)) {
|
||||
if (MONITOR_CHECK_STRING_TYPES.includes(type) && MONITOR_CHECK_STRING_TYPES.includes(this.monitorCheck.type) ||
|
||||
MONITOR_CHECK_SELECTOR_TYPES.includes(type) && MONITOR_CHECK_SELECTOR_TYPES.includes(this.monitorCheck.type)) {
|
||||
// Check value stays same type (string => string or object => object)
|
||||
this.$emit("change", {
|
||||
...this.monitorCheck,
|
||||
|
|
|
@ -333,7 +333,9 @@ export default {
|
|||
},
|
||||
|
||||
deleteMonitorCheck(index) {
|
||||
this.monitor.checks = this.monitor.checks.splice(index, 1);
|
||||
const newList = [...this.monitor.checks];
|
||||
newList.splice(index, 1);
|
||||
this.monitor.checks = newList;
|
||||
},
|
||||
|
||||
async submit() {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
// Backend uses the compiled file util.js
|
||||
// Frontend uses util.ts
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
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.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.MONITOR_CHECK_SELECTOR_TYPES = exports.MONITOR_CHECK_STRING_TYPES = 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 = _dayjs;
|
||||
exports.isDev = process.env.NODE_ENV === "development";
|
||||
|
@ -18,6 +18,8 @@ exports.PENDING = 2;
|
|||
exports.STATUS_PAGE_ALL_DOWN = 0;
|
||||
exports.STATUS_PAGE_ALL_UP = 1;
|
||||
exports.STATUS_PAGE_PARTIAL_DOWN = 2;
|
||||
exports.MONITOR_CHECK_STRING_TYPES = ["HTTP_STATUS_CODE_SHOULD_EQUAL", "RESPONSE_SHOULD_CONTAIN_TEXT", "RESPONSE_SHOULD_NOT_CONTAIN_TEXT", "RESPONSE_SHOULD_MATCH_REGEX", "RESPONSE_SHOULD_NOT_MATCH_REGEX"];
|
||||
exports.MONITOR_CHECK_SELECTOR_TYPES = ["RESPONSE_SELECTOR_SHOULD_EQUAL", "RESPONSE_SELECTOR_SHOULD_NOT_EQUAL", "RESPONSE_SELECTOR_SHOULD_MATCH_REGEX", "RESPONSE_SELECTOR_SHOULD_NOT_MATCH_REGEX"];
|
||||
function flipStatus(s) {
|
||||
if (s === exports.UP) {
|
||||
return exports.DOWN;
|
||||
|
|
|
@ -19,6 +19,8 @@ export const STATUS_PAGE_ALL_DOWN = 0;
|
|||
export const STATUS_PAGE_ALL_UP = 1;
|
||||
export const STATUS_PAGE_PARTIAL_DOWN = 2;
|
||||
|
||||
export const MONITOR_CHECK_STRING_TYPES = ["HTTP_STATUS_CODE_SHOULD_EQUAL", "RESPONSE_SHOULD_CONTAIN_TEXT", "RESPONSE_SHOULD_NOT_CONTAIN_TEXT", "RESPONSE_SHOULD_MATCH_REGEX", "RESPONSE_SHOULD_NOT_MATCH_REGEX"];
|
||||
export const MONITOR_CHECK_SELECTOR_TYPES = ["RESPONSE_SELECTOR_SHOULD_EQUAL", "RESPONSE_SELECTOR_SHOULD_NOT_EQUAL", "RESPONSE_SELECTOR_SHOULD_MATCH_REGEX", "RESPONSE_SELECTOR_SHOULD_NOT_MATCH_REGEX"];
|
||||
|
||||
export function flipStatus(s: number) {
|
||||
if (s === UP) {
|
||||
|
|
Loading…
Add table
Reference in a new issue