Merge branch 'master' into websocket_test

This commit is contained in:
Louis Lam
2025-11-27 20:50:07 +08:00
committed by GitHub
265 changed files with 16477 additions and 4296 deletions
+122 -20
View File
@@ -59,7 +59,7 @@ if (process.env.UPTIME_KUMA_WS_ORIGIN_CHECK === "bypass") {
}
const checkVersion = require("./check-version");
log.info("server", "Uptime Kuma Version: " + checkVersion.version);
log.info("server", "Uptime Kuma Version:", checkVersion.version);
log.info("server", "Loading modules");
@@ -96,6 +96,8 @@ const { getSettings, setSettings, setting, initJWTSecret, checkLogin, doubleChec
log.debug("server", "Importing Notification");
const { Notification } = require("./notification");
Notification.init();
log.debug("server", "Importing Web-Push");
const webpush = require("web-push");
log.debug("server", "Importing Database");
const Database = require("./database");
@@ -108,6 +110,9 @@ const { apiAuth } = require("./auth");
const { login } = require("./auth");
const passwordHash = require("./password-hash");
const { Prometheus } = require("./prometheus");
const { UptimeCalculator } = require("./uptime-calculator");
const hostname = config.hostname;
if (hostname) {
@@ -192,6 +197,9 @@ let needSetup = false;
server.entryPage = await Settings.get("entryPage");
await StatusPage.loadDomainMappingList();
log.debug("server", "Initializing Prometheus");
await Prometheus.init();
log.debug("server", "Adding route");
// ***************************
@@ -674,7 +682,7 @@ let needSetup = false;
let user = R.dispense("user");
user.username = username;
user.password = passwordHash.generate(password);
user.password = await passwordHash.generate(password);
await R.store(user);
needSetup = false;
@@ -720,6 +728,17 @@ let needSetup = false;
monitor.rabbitmqNodes = JSON.stringify(monitor.rabbitmqNodes);
/*
* List of frontend-only properties that should not be saved to the database.
* Should clean up before saving to the database.
*/
const frontendOnlyProperties = [ "humanReadableInterval" ];
for (const prop of frontendOnlyProperties) {
if (prop in monitor) {
delete monitor[prop];
}
}
bean.import(monitor);
bean.user_id = socket.userID;
@@ -794,6 +813,7 @@ let needSetup = false;
bean.wsSubprotocol = monitor.wsSubprotocol;
bean.method = monitor.method;
bean.body = monitor.body;
bean.ipFamily = monitor.ipFamily;
bean.headers = monitor.headers;
bean.basic_auth_user = monitor.basic_auth_user;
bean.basic_auth_pass = monitor.basic_auth_pass;
@@ -803,6 +823,7 @@ let needSetup = false;
bean.oauth_auth_method = monitor.oauth_auth_method;
bean.oauth_token_url = monitor.oauth_token_url;
bean.oauth_scopes = monitor.oauth_scopes;
bean.oauth_audience = monitor.oauth_audience;
bean.tlsCa = monitor.tlsCa;
bean.tlsCert = monitor.tlsCert;
bean.tlsKey = monitor.tlsKey;
@@ -837,6 +858,7 @@ let needSetup = false;
bean.mqttTopic = monitor.mqttTopic;
bean.mqttSuccessMessage = monitor.mqttSuccessMessage;
bean.mqttCheckType = monitor.mqttCheckType;
bean.mqttWebsocketPath = monitor.mqttWebsocketPath;
bean.databaseConnectionString = monitor.databaseConnectionString;
bean.databaseQuery = monitor.databaseQuery;
bean.authMethod = monitor.authMethod;
@@ -868,6 +890,7 @@ let needSetup = false;
monitor.kafkaProducerAllowAutoTopicCreation;
bean.gamedigGivenPortOnly = monitor.gamedigGivenPortOnly;
bean.remote_browser = monitor.remote_browser;
bean.smtpSecurity = monitor.smtpSecurity;
bean.snmpVersion = monitor.snmpVersion;
bean.snmpOid = monitor.snmpOid;
bean.jsonPathOperator = monitor.jsonPathOperator;
@@ -876,6 +899,12 @@ let needSetup = false;
bean.rabbitmqUsername = monitor.rabbitmqUsername;
bean.rabbitmqPassword = monitor.rabbitmqPassword;
bean.conditions = JSON.stringify(monitor.conditions);
bean.manual_status = monitor.manual_status;
// ping advanced options
bean.ping_numeric = monitor.ping_numeric;
bean.ping_count = monitor.ping_count;
bean.ping_per_request_timeout = monitor.ping_per_request_timeout;
bean.validate();
@@ -1028,30 +1057,78 @@ let needSetup = false;
}
});
socket.on("deleteMonitor", async (monitorID, callback) => {
socket.on("deleteMonitor", async (monitorID, deleteChildren, callback) => {
try {
checkLogin(socket);
log.info("manage", `Delete Monitor: ${monitorID} User ID: ${socket.userID}`);
if (monitorID in server.monitorList) {
await server.monitorList[monitorID].stop();
delete server.monitorList[monitorID];
// Backward compatibility: if deleteChildren is omitted, the second parameter is the callback
if (typeof deleteChildren === "function") {
callback = deleteChildren;
deleteChildren = false;
}
checkLogin(socket);
const startTime = Date.now();
await R.exec("DELETE FROM monitor WHERE id = ? AND user_id = ? ", [
// Check if this is a group monitor
const monitor = await R.findOne("monitor", " id = ? AND user_id = ? ", [
monitorID,
socket.userID,
]);
// Log with context about deletion type
if (monitor && monitor.type === "group") {
if (deleteChildren) {
log.info("manage", `Delete Group and Children: ${monitorID} User ID: ${socket.userID}`);
} else {
log.info("manage", `Delete Group (unlink children): ${monitorID} User ID: ${socket.userID}`);
}
} else {
log.info("manage", `Delete Monitor: ${monitorID} User ID: ${socket.userID}`);
}
if (monitor && monitor.type === "group") {
// Get all children before processing
const children = await Monitor.getChildren(monitorID);
if (deleteChildren) {
// Delete all child monitors recursively
if (children && children.length > 0) {
for (const child of children) {
await Monitor.deleteMonitorRecursively(child.id, socket.userID);
await server.sendDeleteMonitorFromList(socket, child.id);
}
}
} else {
// Unlink all children from the group (set parent to null)
await Monitor.unlinkAllChildren(monitorID);
// Notify frontend to update each child monitor's parent to null
if (children && children.length > 0) {
for (const child of children) {
await server.sendUpdateMonitorIntoList(socket, child.id);
}
}
}
}
// Delete the monitor itself
await Monitor.deleteMonitor(monitorID, socket.userID);
// Fix #2880
apicache.clear();
const endTime = Date.now();
log.info("DB", `Delete Monitor completed in : ${endTime - startTime} ms`);
// Log completion with context about children handling
if (monitor && monitor.type === "group") {
if (deleteChildren) {
log.info("DB", `Delete Monitor completed (group and children deleted) in: ${endTime - startTime} ms`);
} else {
log.info("DB", `Delete Monitor completed (group deleted, children unlinked) in: ${endTime - startTime} ms`);
}
} else {
log.info("DB", `Delete Monitor completed in: ${endTime - startTime} ms`);
}
callback({
ok: true,
@@ -1484,12 +1561,38 @@ let needSetup = false;
socket.on("checkApprise", async (callback) => {
try {
checkLogin(socket);
callback(Notification.checkApprise());
callback(await Notification.checkApprise());
} catch (e) {
callback(false);
}
});
socket.on("getWebpushVapidPublicKey", async (callback) => {
try {
let publicVapidKey = await Settings.get("webpushPublicVapidKey");
if (!publicVapidKey) {
log.debug("webpush", "Generating new VAPID keys");
const vapidKeys = webpush.generateVAPIDKeys();
await Settings.set("webpushPublicVapidKey", vapidKeys.publicKey);
await Settings.set("webpushPrivateVapidKey", vapidKeys.privateKey);
publicVapidKey = vapidKeys.publicKey;
}
callback({
ok: true,
msg: publicVapidKey,
});
} catch (e) {
callback({
ok: false,
msg: e.message,
});
}
});
socket.on("clearEvents", async (monitorID, callback) => {
try {
checkLogin(socket);
@@ -1520,9 +1623,11 @@ let needSetup = false;
log.info("manage", `Clear Heartbeats Monitor: ${monitorID} User ID: ${socket.userID}`);
await R.exec("DELETE FROM heartbeat WHERE monitor_id = ?", [
monitorID
]);
await UptimeCalculator.clearStatistics(monitorID);
if (monitorID in server.monitorList) {
await restartMonitor(socket.userID, monitorID);
}
await sendHeartbeatList(socket, monitorID, true, true);
@@ -1544,10 +1649,7 @@ let needSetup = false;
log.info("manage", `Clear Statistics User ID: ${socket.userID}`);
await R.exec("DELETE FROM heartbeat");
await R.exec("DELETE FROM stat_daily");
await R.exec("DELETE FROM stat_hourly");
await R.exec("DELETE FROM stat_minutely");
await UptimeCalculator.clearAllStatistics();
// Restart all monitors to reset the stats
for (let monitorID in server.monitorList) {