mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-07-21 14:08:53 -04:00
Merge branch 'master' into 1.23.X-merge-to-2.X.X
# Conflicts: # docker/debian-base.dockerfile # package-lock.json # server/database.js # server/model/monitor.js # server/uptime-kuma-server.js # server/util-server.js
This commit is contained in:
commit
ace1fe00c2
316 changed files with 9487 additions and 4997 deletions
494
server/server.js
494
server/server.js
|
@ -38,9 +38,7 @@ if (!semver.satisfies(nodeVersion, requiredNodeVersions)) {
|
|||
|
||||
const args = require("args-parser")(process.argv);
|
||||
const { sleep, log, getRandomInt, genSecret, isDev } = require("../src/util");
|
||||
const config = require("./config");
|
||||
|
||||
log.info("server", "Welcome to Uptime Kuma");
|
||||
log.debug("server", "Arguments");
|
||||
log.debug("server", args);
|
||||
|
||||
|
@ -48,13 +46,13 @@ if (! process.env.NODE_ENV) {
|
|||
process.env.NODE_ENV = "production";
|
||||
}
|
||||
|
||||
log.info("server", "Node Env: " + process.env.NODE_ENV);
|
||||
log.info("server", "Inside Container: " + (process.env.UPTIME_KUMA_IS_CONTAINER === "1"));
|
||||
log.info("server", "Env: " + process.env.NODE_ENV);
|
||||
log.debug("server", "Inside Container: " + (process.env.UPTIME_KUMA_IS_CONTAINER === "1"));
|
||||
|
||||
log.info("server", "Importing Node libraries");
|
||||
const fs = require("fs");
|
||||
const checkVersion = require("./check-version");
|
||||
log.info("server", "Uptime Kuma Version: " + checkVersion.version);
|
||||
|
||||
log.info("server", "Importing 3rd-party libraries");
|
||||
log.info("server", "Loading modules");
|
||||
|
||||
log.debug("server", "Importing express");
|
||||
const express = require("express");
|
||||
|
@ -67,8 +65,6 @@ log.debug("server", "Importing http-graceful-shutdown");
|
|||
const gracefulShutdown = require("http-graceful-shutdown");
|
||||
log.debug("server", "Importing prometheus-api-metrics");
|
||||
const prometheusAPIMetrics = require("prometheus-api-metrics");
|
||||
log.debug("server", "Importing compare-versions");
|
||||
const compareVersions = require("compare-versions");
|
||||
const { passwordStrength } = require("check-password-strength");
|
||||
|
||||
log.debug("server", "Importing 2FA Modules");
|
||||
|
@ -76,26 +72,23 @@ const notp = require("notp");
|
|||
const base32 = require("thirty-two");
|
||||
|
||||
const { UptimeKumaServer } = require("./uptime-kuma-server");
|
||||
|
||||
const server = UptimeKumaServer.getInstance(args);
|
||||
const io = module.exports.io = server.io;
|
||||
const app = server.app;
|
||||
|
||||
log.info("server", "Importing this project modules");
|
||||
log.debug("server", "Importing Monitor");
|
||||
const Monitor = require("./model/monitor");
|
||||
const User = require("./model/user");
|
||||
|
||||
log.debug("server", "Importing Settings");
|
||||
const { getSettings, setSettings, setting, initJWTSecret, checkLogin, startUnitTest, FBSD, doubleCheckPassword, startE2eTests, shake256, SHAKE256_LENGTH
|
||||
const { getSettings, setSettings, setting, initJWTSecret, checkLogin, FBSD, doubleCheckPassword, startE2eTests, shake256, SHAKE256_LENGTH, allowDevAllOrigin,
|
||||
} = require("./util-server");
|
||||
|
||||
log.debug("server", "Importing Notification");
|
||||
const { Notification } = require("./notification");
|
||||
Notification.init();
|
||||
|
||||
log.debug("server", "Importing Proxy");
|
||||
const { Proxy } = require("./proxy");
|
||||
|
||||
log.debug("server", "Importing Database");
|
||||
const Database = require("./database");
|
||||
|
||||
|
@ -107,9 +100,6 @@ const { apiAuth } = require("./auth");
|
|||
const { login } = require("./auth");
|
||||
const passwordHash = require("./password-hash");
|
||||
|
||||
const checkVersion = require("./check-version");
|
||||
log.info("server", "Version: " + checkVersion.version);
|
||||
|
||||
// If host is omitted, the server will accept connections on the unspecified IPv6 address (::) when IPv6 is available and the unspecified IPv4 address (0.0.0.0) otherwise.
|
||||
// Dual-stack support for (::)
|
||||
// Also read HOST if not FreeBSD, as HOST is a system environment variable in FreeBSD
|
||||
|
@ -140,12 +130,8 @@ const twoFAVerifyOptions = {
|
|||
const testMode = !!args["test"] || false;
|
||||
const e2eTestMode = !!args["e2e"] || false;
|
||||
|
||||
if (config.demoMode) {
|
||||
log.info("server", "==== Demo Mode ====");
|
||||
}
|
||||
|
||||
// Must be after io instantiation
|
||||
const { sendNotificationList, sendHeartbeatList, sendImportantHeartbeatList, sendInfo, sendProxyList, sendDockerHostList, sendAPIKeyList } = require("./client");
|
||||
const { sendNotificationList, sendHeartbeatList, sendInfo, sendProxyList, sendDockerHostList, sendAPIKeyList } = require("./client");
|
||||
const { statusPageSocketHandler } = require("./socket-handlers/status-page-socket-handler");
|
||||
const databaseSocketHandler = require("./socket-handlers/database-socket-handler");
|
||||
const TwoFA = require("./2fa");
|
||||
|
@ -160,6 +146,8 @@ const { Settings } = require("./settings");
|
|||
const { CacheableDnsHttpAgent } = require("./cacheable-dns-http-agent");
|
||||
const apicache = require("./modules/apicache");
|
||||
const { resetChrome } = require("./monitor-types/real-browser-monitor-type");
|
||||
const { EmbeddedMariaDB } = require("./embedded-mariadb");
|
||||
const { SetupDatabase } = require("./setup-database");
|
||||
|
||||
app.use(express.json());
|
||||
|
||||
|
@ -179,13 +167,30 @@ app.use(function (req, res, next) {
|
|||
let needSetup = false;
|
||||
|
||||
(async () => {
|
||||
Database.init(args);
|
||||
await initDatabase(testMode);
|
||||
// Create a data directory
|
||||
Database.initDataDir(args);
|
||||
|
||||
// Check if is chosen a database type
|
||||
let setupDatabase = new SetupDatabase(args, server);
|
||||
if (setupDatabase.isNeedSetup()) {
|
||||
// Hold here and start a special setup page until user choose a database type
|
||||
await setupDatabase.start(hostname, port);
|
||||
}
|
||||
|
||||
// Connect to database
|
||||
try {
|
||||
await initDatabase(testMode);
|
||||
} catch (e) {
|
||||
log.error("server", "Failed to prepare your database: " + e.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Database should be ready now
|
||||
await server.initAfterDatabaseReady();
|
||||
server.entryPage = await Settings.get("entryPage");
|
||||
await StatusPage.loadDomainMappingList();
|
||||
|
||||
log.info("server", "Adding route");
|
||||
log.debug("server", "Adding route");
|
||||
|
||||
// ***************************
|
||||
// Normal Router here
|
||||
|
@ -218,6 +223,14 @@ let needSetup = false;
|
|||
}
|
||||
});
|
||||
|
||||
app.get("/setup-database-info", (request, response) => {
|
||||
allowDevAllOrigin(response);
|
||||
response.json({
|
||||
runningSetup: false,
|
||||
needSetup: false,
|
||||
});
|
||||
});
|
||||
|
||||
if (isDev) {
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
app.post("/test-webhook", async (request, response) => {
|
||||
|
@ -225,6 +238,12 @@ let needSetup = false;
|
|||
log.debug("test", request.body);
|
||||
response.send("OK");
|
||||
});
|
||||
|
||||
app.post("/test-x-www-form-urlencoded", async (request, response) => {
|
||||
log.debug("test", request.headers);
|
||||
log.debug("test", request.body);
|
||||
response.send("OK");
|
||||
});
|
||||
}
|
||||
|
||||
// Robots.txt
|
||||
|
@ -271,7 +290,7 @@ let needSetup = false;
|
|||
}
|
||||
});
|
||||
|
||||
log.info("server", "Adding socket handler");
|
||||
log.debug("server", "Adding socket handler");
|
||||
io.on("connection", async (socket) => {
|
||||
|
||||
sendInfo(socket, true);
|
||||
|
@ -320,7 +339,8 @@ let needSetup = false;
|
|||
|
||||
callback({
|
||||
ok: false,
|
||||
msg: "The user is inactive or deleted.",
|
||||
msg: "authUserInactiveOrDeleted",
|
||||
msgi18n: true,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
|
@ -330,7 +350,8 @@ let needSetup = false;
|
|||
}
|
||||
callback({
|
||||
ok: false,
|
||||
msg: "Invalid token.",
|
||||
msg: "authInvalidToken",
|
||||
msgi18n: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -351,7 +372,7 @@ let needSetup = false;
|
|||
}
|
||||
|
||||
// Login Rate Limit
|
||||
if (! await loginRateLimiter.pass(callback)) {
|
||||
if (!await loginRateLimiter.pass(callback)) {
|
||||
log.info("auth", `Too many failed requests for user ${data.username}. IP=${clientIP}`);
|
||||
return;
|
||||
}
|
||||
|
@ -402,7 +423,8 @@ let needSetup = false;
|
|||
|
||||
callback({
|
||||
ok: false,
|
||||
msg: "Invalid Token!",
|
||||
msg: "authInvalidToken",
|
||||
msgi18n: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -412,7 +434,8 @@ let needSetup = false;
|
|||
|
||||
callback({
|
||||
ok: false,
|
||||
msg: "Incorrect username or password.",
|
||||
msg: "authIncorrectCreds",
|
||||
msgi18n: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -420,7 +443,7 @@ let needSetup = false;
|
|||
|
||||
socket.on("logout", async (callback) => {
|
||||
// Rate Limit
|
||||
if (! await loginRateLimiter.pass(callback)) {
|
||||
if (!await loginRateLimiter.pass(callback)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -434,7 +457,7 @@ let needSetup = false;
|
|||
|
||||
socket.on("prepare2FA", async (currentPassword, callback) => {
|
||||
try {
|
||||
if (! await twoFaRateLimiter.pass(callback)) {
|
||||
if (!await twoFaRateLimiter.pass(callback)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -468,7 +491,8 @@ let needSetup = false;
|
|||
} else {
|
||||
callback({
|
||||
ok: false,
|
||||
msg: "2FA is already enabled.",
|
||||
msg: "2faAlreadyEnabled",
|
||||
msgi18n: true,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
|
@ -483,7 +507,7 @@ let needSetup = false;
|
|||
const clientIP = await server.getClientIP(socket);
|
||||
|
||||
try {
|
||||
if (! await twoFaRateLimiter.pass(callback)) {
|
||||
if (!await twoFaRateLimiter.pass(callback)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -498,7 +522,8 @@ let needSetup = false;
|
|||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "2FA Enabled.",
|
||||
msg: "2faEnabled",
|
||||
msgi18n: true,
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
|
@ -515,7 +540,7 @@ let needSetup = false;
|
|||
const clientIP = await server.getClientIP(socket);
|
||||
|
||||
try {
|
||||
if (! await twoFaRateLimiter.pass(callback)) {
|
||||
if (!await twoFaRateLimiter.pass(callback)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -527,7 +552,8 @@ let needSetup = false;
|
|||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "2FA Disabled.",
|
||||
msg: "2faDisabled",
|
||||
msgi18n: true,
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
|
@ -559,7 +585,8 @@ let needSetup = false;
|
|||
} else {
|
||||
callback({
|
||||
ok: false,
|
||||
msg: "Invalid Token.",
|
||||
msg: "authInvalidToken",
|
||||
msgi18n: true,
|
||||
valid: false,
|
||||
});
|
||||
}
|
||||
|
@ -609,7 +636,7 @@ let needSetup = false;
|
|||
throw new Error("Password is too weak. It should contain alphabetic and numeric characters. It must be at least 6 characters in length.");
|
||||
}
|
||||
|
||||
if ((await R.count("user")) !== 0) {
|
||||
if ((await R.knex("user").count("id as count").first()).count !== 0) {
|
||||
throw new Error("Uptime Kuma has been initialized. If you want to run setup again, please delete the database.");
|
||||
}
|
||||
|
||||
|
@ -622,7 +649,8 @@ let needSetup = false;
|
|||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Added Successfully.",
|
||||
msg: "successAdded",
|
||||
msgi18n: true,
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
|
@ -675,7 +703,8 @@ let needSetup = false;
|
|||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Added Successfully.",
|
||||
msg: "successAdded",
|
||||
msgi18n: true,
|
||||
monitorID: bean.id,
|
||||
});
|
||||
|
||||
|
@ -746,6 +775,11 @@ let needSetup = false;
|
|||
bean.game = monitor.game;
|
||||
bean.maxretries = monitor.maxretries;
|
||||
bean.port = parseInt(monitor.port);
|
||||
|
||||
if (isNaN(bean.port)) {
|
||||
bean.port = null;
|
||||
}
|
||||
|
||||
bean.keyword = monitor.keyword;
|
||||
bean.invertKeyword = monitor.invertKeyword;
|
||||
bean.ignoreTls = monitor.ignoreTls;
|
||||
|
@ -813,6 +847,7 @@ let needSetup = false;
|
|||
callback({
|
||||
ok: true,
|
||||
msg: "Saved.",
|
||||
msgi18n: true,
|
||||
monitorID: bean.id,
|
||||
});
|
||||
|
||||
|
@ -875,14 +910,17 @@ let needSetup = false;
|
|||
throw new Error("Invalid period.");
|
||||
}
|
||||
|
||||
const sqlHourOffset = Database.sqlHourOffset();
|
||||
|
||||
let list = await R.getAll(`
|
||||
SELECT * FROM heartbeat
|
||||
WHERE monitor_id = ? AND
|
||||
time > DATETIME('now', '-' || ? || ' hours')
|
||||
SELECT *
|
||||
FROM heartbeat
|
||||
WHERE monitor_id = ?
|
||||
AND time > ${sqlHourOffset}
|
||||
ORDER BY time ASC
|
||||
`, [
|
||||
monitorID,
|
||||
period,
|
||||
-period,
|
||||
]);
|
||||
|
||||
callback({
|
||||
|
@ -906,7 +944,8 @@ let needSetup = false;
|
|||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Resumed Successfully.",
|
||||
msg: "successResumed",
|
||||
msgi18n: true,
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
|
@ -925,7 +964,8 @@ let needSetup = false;
|
|||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Paused Successfully.",
|
||||
msg: "successPaused",
|
||||
msgi18n: true,
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
|
@ -963,12 +1003,11 @@ let needSetup = false;
|
|||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Deleted Successfully.",
|
||||
msg: "successDeleted",
|
||||
msgi18n: true,
|
||||
});
|
||||
|
||||
await server.sendMonitorList(socket);
|
||||
// Clear heartbeat list on client
|
||||
await sendImportantHeartbeatList(socket, monitorID, true, true);
|
||||
|
||||
} catch (e) {
|
||||
callback({
|
||||
|
@ -1027,7 +1066,8 @@ let needSetup = false;
|
|||
if (bean == null) {
|
||||
callback({
|
||||
ok: false,
|
||||
msg: "Tag not found",
|
||||
msg: "tagNotFound",
|
||||
msgi18n: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@ -1037,7 +1077,8 @@ let needSetup = false;
|
|||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Saved",
|
||||
msg: "Saved.",
|
||||
msgi18n: true,
|
||||
tag: await bean.toJSON(),
|
||||
});
|
||||
|
||||
|
@ -1057,7 +1098,8 @@ let needSetup = false;
|
|||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Deleted Successfully.",
|
||||
msg: "successDeleted",
|
||||
msgi18n: true,
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
|
@ -1080,7 +1122,8 @@ let needSetup = false;
|
|||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Added Successfully.",
|
||||
msg: "successAdded",
|
||||
msgi18n: true,
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
|
@ -1103,7 +1146,8 @@ let needSetup = false;
|
|||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Edited Successfully.",
|
||||
msg: "successEdited",
|
||||
msgi18n: true,
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
|
@ -1126,7 +1170,8 @@ let needSetup = false;
|
|||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Deleted Successfully.",
|
||||
msg: "successDeleted",
|
||||
msgi18n: true,
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
|
@ -1137,11 +1182,77 @@ let needSetup = false;
|
|||
}
|
||||
});
|
||||
|
||||
socket.on("monitorImportantHeartbeatListCount", async (monitorID, callback) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
|
||||
let count;
|
||||
if (monitorID == null) {
|
||||
count = await R.count("heartbeat", "important = 1");
|
||||
} else {
|
||||
count = await R.count("heartbeat", "monitor_id = ? AND important = 1", [
|
||||
monitorID,
|
||||
]);
|
||||
}
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
count: count,
|
||||
});
|
||||
} catch (e) {
|
||||
callback({
|
||||
ok: false,
|
||||
msg: e.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("monitorImportantHeartbeatListPaged", async (monitorID, offset, count, callback) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
|
||||
let list;
|
||||
if (monitorID == null) {
|
||||
list = await R.find("heartbeat", `
|
||||
important = 1
|
||||
ORDER BY time DESC
|
||||
LIMIT ?
|
||||
OFFSET ?
|
||||
`, [
|
||||
count,
|
||||
offset,
|
||||
]);
|
||||
} else {
|
||||
list = await R.find("heartbeat", `
|
||||
monitor_id = ?
|
||||
AND important = 1
|
||||
ORDER BY time DESC
|
||||
LIMIT ?
|
||||
OFFSET ?
|
||||
`, [
|
||||
monitorID,
|
||||
count,
|
||||
offset,
|
||||
]);
|
||||
}
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
data: list,
|
||||
});
|
||||
} catch (e) {
|
||||
callback({
|
||||
ok: false,
|
||||
msg: e.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("changePassword", async (password, callback) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
|
||||
if (! password.newPassword) {
|
||||
if (!password.newPassword) {
|
||||
throw new Error("Invalid new password");
|
||||
}
|
||||
|
||||
|
@ -1154,7 +1265,8 @@ let needSetup = false;
|
|||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Password has been updated successfully.",
|
||||
msg: "successAuthChangePassword",
|
||||
msgi18n: true,
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
|
@ -1231,7 +1343,8 @@ let needSetup = false;
|
|||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Saved"
|
||||
msg: "Saved.",
|
||||
msgi18n: true,
|
||||
});
|
||||
|
||||
sendInfo(socket);
|
||||
|
@ -1255,7 +1368,8 @@ let needSetup = false;
|
|||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Saved",
|
||||
msg: "Saved.",
|
||||
msgi18n: true,
|
||||
id: notificationBean.id,
|
||||
});
|
||||
|
||||
|
@ -1276,7 +1390,8 @@ let needSetup = false;
|
|||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Deleted",
|
||||
msg: "successDeleted",
|
||||
msgi18n: true,
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
|
@ -1317,211 +1432,6 @@ let needSetup = false;
|
|||
}
|
||||
});
|
||||
|
||||
socket.on("uploadBackup", async (uploadedJSON, importHandle, callback) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
|
||||
let backupData = JSON.parse(uploadedJSON);
|
||||
|
||||
log.info("manage", `Importing Backup, User ID: ${socket.userID}, Version: ${backupData.version}`);
|
||||
|
||||
let notificationListData = backupData.notificationList;
|
||||
let proxyListData = backupData.proxyList;
|
||||
let monitorListData = backupData.monitorList;
|
||||
|
||||
let version17x = compareVersions.compare(backupData.version, "1.7.0", ">=");
|
||||
|
||||
// If the import option is "overwrite" it'll clear most of the tables, except "settings" and "user"
|
||||
if (importHandle === "overwrite") {
|
||||
// Stops every monitor first, so it doesn't execute any heartbeat while importing
|
||||
for (let id in server.monitorList) {
|
||||
let monitor = server.monitorList[id];
|
||||
await monitor.stop();
|
||||
}
|
||||
await R.exec("DELETE FROM heartbeat");
|
||||
await R.exec("DELETE FROM monitor_notification");
|
||||
await R.exec("DELETE FROM monitor_tls_info");
|
||||
await R.exec("DELETE FROM notification");
|
||||
await R.exec("DELETE FROM monitor_tag");
|
||||
await R.exec("DELETE FROM tag");
|
||||
await R.exec("DELETE FROM monitor");
|
||||
await R.exec("DELETE FROM proxy");
|
||||
}
|
||||
|
||||
// Only starts importing if the backup file contains at least one notification
|
||||
if (notificationListData.length >= 1) {
|
||||
// Get every existing notification name and puts them in one simple string
|
||||
let notificationNameList = await R.getAll("SELECT name FROM notification");
|
||||
let notificationNameListString = JSON.stringify(notificationNameList);
|
||||
|
||||
for (let i = 0; i < notificationListData.length; i++) {
|
||||
// Only starts importing the notification if the import option is "overwrite", "keep" or "skip" but the notification doesn't exists
|
||||
if ((importHandle === "skip" && notificationNameListString.includes(notificationListData[i].name) === false) || importHandle === "keep" || importHandle === "overwrite") {
|
||||
|
||||
let notification = JSON.parse(notificationListData[i].config);
|
||||
await Notification.save(notification, null, socket.userID);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only starts importing if the backup file contains at least one proxy
|
||||
if (proxyListData && proxyListData.length >= 1) {
|
||||
const proxies = await R.findAll("proxy");
|
||||
|
||||
// Loop over proxy list and save proxies
|
||||
for (const proxy of proxyListData) {
|
||||
const exists = proxies.find(item => item.id === proxy.id);
|
||||
|
||||
// Do not process when proxy already exists in import handle is skip and keep
|
||||
if ([ "skip", "keep" ].includes(importHandle) && !exists) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Save proxy as new entry if exists update exists one
|
||||
await Proxy.save(proxy, exists ? proxy.id : undefined, proxy.userId);
|
||||
}
|
||||
}
|
||||
|
||||
// Only starts importing if the backup file contains at least one monitor
|
||||
if (monitorListData.length >= 1) {
|
||||
// Get every existing monitor name and puts them in one simple string
|
||||
let monitorNameList = await R.getAll("SELECT name FROM monitor");
|
||||
let monitorNameListString = JSON.stringify(monitorNameList);
|
||||
|
||||
for (let i = 0; i < monitorListData.length; i++) {
|
||||
// Only starts importing the monitor if the import option is "overwrite", "keep" or "skip" but the notification doesn't exists
|
||||
if ((importHandle === "skip" && monitorNameListString.includes(monitorListData[i].name) === false) || importHandle === "keep" || importHandle === "overwrite") {
|
||||
|
||||
// Define in here every new variable for monitors which where implemented after the first version of the Import/Export function (1.6.0)
|
||||
// --- Start ---
|
||||
|
||||
// Define default values
|
||||
let retryInterval = 0;
|
||||
let timeout = monitorListData[i].timeout || (monitorListData[i].interval * 0.8); // fallback to old value
|
||||
|
||||
/*
|
||||
Only replace the default value with the backup file data for the specific version, where it appears the first time
|
||||
More information about that where "let version" will be defined
|
||||
*/
|
||||
if (version17x) {
|
||||
retryInterval = monitorListData[i].retryInterval;
|
||||
}
|
||||
|
||||
// --- End ---
|
||||
|
||||
let monitor = {
|
||||
// Define the new variable from earlier here
|
||||
name: monitorListData[i].name,
|
||||
description: monitorListData[i].description,
|
||||
type: monitorListData[i].type,
|
||||
url: monitorListData[i].url,
|
||||
method: monitorListData[i].method || "GET",
|
||||
body: monitorListData[i].body,
|
||||
headers: monitorListData[i].headers,
|
||||
authMethod: monitorListData[i].authMethod,
|
||||
basic_auth_user: monitorListData[i].basic_auth_user,
|
||||
basic_auth_pass: monitorListData[i].basic_auth_pass,
|
||||
authWorkstation: monitorListData[i].authWorkstation,
|
||||
authDomain: monitorListData[i].authDomain,
|
||||
timeout,
|
||||
interval: monitorListData[i].interval,
|
||||
retryInterval: retryInterval,
|
||||
resendInterval: monitorListData[i].resendInterval || 0,
|
||||
hostname: monitorListData[i].hostname,
|
||||
maxretries: monitorListData[i].maxretries,
|
||||
port: monitorListData[i].port,
|
||||
keyword: monitorListData[i].keyword,
|
||||
invertKeyword: monitorListData[i].invertKeyword,
|
||||
ignoreTls: monitorListData[i].ignoreTls,
|
||||
upsideDown: monitorListData[i].upsideDown,
|
||||
maxredirects: monitorListData[i].maxredirects,
|
||||
accepted_statuscodes: monitorListData[i].accepted_statuscodes,
|
||||
dns_resolve_type: monitorListData[i].dns_resolve_type,
|
||||
dns_resolve_server: monitorListData[i].dns_resolve_server,
|
||||
notificationIDList: monitorListData[i].notificationIDList,
|
||||
proxy_id: monitorListData[i].proxy_id || null,
|
||||
};
|
||||
|
||||
if (monitorListData[i].pushToken) {
|
||||
monitor.pushToken = monitorListData[i].pushToken;
|
||||
}
|
||||
|
||||
let bean = R.dispense("monitor");
|
||||
|
||||
let notificationIDList = monitor.notificationIDList;
|
||||
delete monitor.notificationIDList;
|
||||
|
||||
monitor.accepted_statuscodes_json = JSON.stringify(monitor.accepted_statuscodes);
|
||||
delete monitor.accepted_statuscodes;
|
||||
|
||||
bean.import(monitor);
|
||||
bean.user_id = socket.userID;
|
||||
await R.store(bean);
|
||||
|
||||
// Only for backup files with the version 1.7.0 or higher, since there was the tag feature implemented
|
||||
if (version17x) {
|
||||
// Only import if the specific monitor has tags assigned
|
||||
for (const oldTag of monitorListData[i].tags) {
|
||||
|
||||
// Check if tag already exists and get data ->
|
||||
let tag = await R.findOne("tag", " name = ?", [
|
||||
oldTag.name,
|
||||
]);
|
||||
|
||||
let tagId;
|
||||
if (! tag) {
|
||||
// -> If it doesn't exist, create new tag from backup file
|
||||
let beanTag = R.dispense("tag");
|
||||
beanTag.name = oldTag.name;
|
||||
beanTag.color = oldTag.color;
|
||||
await R.store(beanTag);
|
||||
|
||||
tagId = beanTag.id;
|
||||
} else {
|
||||
// -> If it already exist, set tagId to value from database
|
||||
tagId = tag.id;
|
||||
}
|
||||
|
||||
// Assign the new created tag to the monitor
|
||||
await R.exec("INSERT INTO monitor_tag (tag_id, monitor_id, value) VALUES (?, ?, ?)", [
|
||||
tagId,
|
||||
bean.id,
|
||||
oldTag.value,
|
||||
]);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
await updateMonitorNotification(bean.id, notificationIDList);
|
||||
|
||||
// If monitor was active start it immediately, otherwise pause it
|
||||
if (monitorListData[i].active === 1) {
|
||||
await startMonitor(socket.userID, bean.id);
|
||||
} else {
|
||||
await pauseMonitor(socket.userID, bean.id);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
await sendNotificationList(socket);
|
||||
await server.sendMonitorList(socket);
|
||||
}
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "Backup successfully restored.",
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
callback({
|
||||
ok: false,
|
||||
msg: e.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("clearEvents", async (monitorID, callback) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
|
@ -1534,8 +1444,6 @@ let needSetup = false;
|
|||
monitorID,
|
||||
]);
|
||||
|
||||
await sendImportantHeartbeatList(socket, monitorID, true, true);
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
});
|
||||
|
@ -1619,11 +1527,12 @@ let needSetup = false;
|
|||
|
||||
});
|
||||
|
||||
log.info("server", "Init the server");
|
||||
log.debug("server", "Init the server");
|
||||
|
||||
server.httpServer.once("error", async (err) => {
|
||||
console.error("Cannot listen: " + err.message);
|
||||
log.error("server", "Cannot listen: " + err.message);
|
||||
await shutdownFunction();
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
server.start();
|
||||
|
@ -1637,10 +1546,6 @@ let needSetup = false;
|
|||
startMonitors();
|
||||
checkVersion.startInterval();
|
||||
|
||||
if (testMode) {
|
||||
startUnitTest();
|
||||
}
|
||||
|
||||
if (e2eTestMode) {
|
||||
startE2eTests();
|
||||
}
|
||||
|
@ -1677,8 +1582,8 @@ async function updateMonitorNotification(monitorID, notificationIDList) {
|
|||
|
||||
/**
|
||||
* Check if a given user owns a specific monitor
|
||||
* @param {number} userID
|
||||
* @param {number} monitorID
|
||||
* @param {number} userID ID of user to check
|
||||
* @param {number} monitorID ID of monitor to check
|
||||
* @returns {Promise<void>}
|
||||
* @throws {Error} The specified user does not own the monitor
|
||||
*/
|
||||
|
@ -1697,7 +1602,7 @@ async function checkOwner(userID, monitorID) {
|
|||
* Function called after user login
|
||||
* This function is used to send the heartbeat list of a monitor.
|
||||
* @param {Socket} socket Socket.io instance
|
||||
* @param {Object} user User object
|
||||
* @param {object} user User object
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function afterLogin(socket, user) {
|
||||
|
@ -1720,10 +1625,6 @@ async function afterLogin(socket, user) {
|
|||
await sendHeartbeatList(socket, monitorID);
|
||||
}
|
||||
|
||||
for (let monitorID in monitorList) {
|
||||
await sendImportantHeartbeatList(socket, monitorID);
|
||||
}
|
||||
|
||||
for (let monitorID in monitorList) {
|
||||
await Monitor.sendStats(io, monitorID, user.id);
|
||||
}
|
||||
|
@ -1738,19 +1639,14 @@ async function afterLogin(socket, user) {
|
|||
|
||||
/**
|
||||
* Initialize the database
|
||||
* @param {boolean} [testMode=false] Should the connection be
|
||||
* @param {boolean} testMode Should the connection be
|
||||
* started in test mode?
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function initDatabase(testMode = false) {
|
||||
if (! fs.existsSync(Database.path)) {
|
||||
log.info("server", "Copying Database");
|
||||
fs.copyFileSync(Database.templatePath, Database.path);
|
||||
}
|
||||
|
||||
log.info("server", "Connecting to the Database");
|
||||
log.debug("server", "Connecting to the database");
|
||||
await Database.connect(testMode);
|
||||
log.info("server", "Connected");
|
||||
log.info("server", "Connected to the database");
|
||||
|
||||
// Patch the database
|
||||
await Database.patch();
|
||||
|
@ -1764,11 +1660,11 @@ async function initDatabase(testMode = false) {
|
|||
jwtSecretBean = await initJWTSecret();
|
||||
log.info("server", "Stored JWT secret into database");
|
||||
} else {
|
||||
log.info("server", "Load JWT secret from database.");
|
||||
log.debug("server", "Load JWT secret from database.");
|
||||
}
|
||||
|
||||
// If there is no record in user table, it is a new Uptime Kuma instance, need to setup
|
||||
if ((await R.count("user")) === 0) {
|
||||
if ((await R.knex("user").count("id as count").first()).count === 0) {
|
||||
log.info("server", "No user, need setup");
|
||||
needSetup = true;
|
||||
}
|
||||
|
@ -1835,7 +1731,10 @@ async function pauseMonitor(userID, monitorID) {
|
|||
}
|
||||
}
|
||||
|
||||
/** Resume active monitors */
|
||||
/**
|
||||
* Resume active monitors
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function startMonitors() {
|
||||
let list = await R.find("monitor", " active = 1 ");
|
||||
|
||||
|
@ -1870,12 +1769,19 @@ async function shutdownFunction(signal) {
|
|||
await sleep(2000);
|
||||
await Database.close();
|
||||
|
||||
if (EmbeddedMariaDB.hasInstance()) {
|
||||
EmbeddedMariaDB.getInstance().stop();
|
||||
}
|
||||
|
||||
stopBackgroundJobs();
|
||||
await cloudflaredStop();
|
||||
Settings.stopCacheCleaner();
|
||||
}
|
||||
|
||||
/** Final function called before application exits */
|
||||
/**
|
||||
* Final function called before application exits
|
||||
* @returns {void}
|
||||
*/
|
||||
function finalFunction() {
|
||||
log.info("server", "Graceful shutdown successful!");
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue