Compare commits

...

20 Commits

Author SHA1 Message Date
vishalsabhaya
21523db08e
Merge 416ba88f6b into 46d90a6a99 2024-09-29 15:06:28 +00:00
Louis Lam
46d90a6a99
1.23.14 changes merge to 2.0.0 (#5138) 2024-09-29 23:06:09 +08:00
Louis Lam
3479992302 Resolve conflict of package-lock.json 2024-09-29 22:13:05 +08:00
Louis Lam
030bb1c0b8 Merge branch 'master' into 1.23.14-to-2.0.0
# Conflicts:
#	package-lock.json
2024-09-29 22:12:25 +08:00
Louis Lam
7da401662f Merge branch '1.23.X' into 1.23.14-to-2.0.0
# Conflicts:
#	package-lock.json
#	package.json
2024-09-29 22:05:23 +08:00
Louis Lam
243726b03c Update to 1.23.14 2024-09-29 21:46:19 +08:00
Louis Lam
936665aac3
[1.23.X] Update dependencies (#5132) 2024-09-28 03:43:54 +08:00
vishalsabhaya
416ba88f6b notificationIDList not display properly
revert to original insted of map
2024-09-18 16:35:08 +09:00
vishalsabhaya
39f3e6311d linting error fix 2024-09-18 11:31:23 +09:00
vishalsabhaya
de48fe78ad
Merge branch 'master' into load-performance-improve 2024-09-18 11:26:47 +09:00
vishalsabhaya
59aea398b7 fix linting error 2024-09-05 17:21:40 +09:00
vishalsabhaya
cb1b414365 optimisation for creation, update, list and delete 2024-09-05 17:16:26 +09:00
vishalsabhaya
043f1a2ed9 remove async from tojson method
move path to preload function
2024-09-04 20:42:39 +09:00
vishalsabhaya
b0281e44b8 update fix linting error 2024-09-04 16:29:25 +09:00
vishalsabhaya
a81317fa0e object to map to make it faster
made code clean
make it faster
2024-09-04 16:24:59 +09:00
vishalsabhaya
96a1010b30 depricate inner promise
move inner promise to outer promise
2024-09-04 16:24:22 +09:00
vishalsabhaya
bb499e2aa1 removed unneccesory comment 2024-09-03 16:40:39 +09:00
vishalsabhaya
ae7f01b3ea performance improve while insert/update/delete 2024-08-19 11:36:23 +09:00
vishalsabhaya
32f2fe140d missed changes from other branch
moved changes while testing
2024-08-18 18:06:53 +09:00
vishal sabhaya
84f261c81b improve page load performance of large amount urls
1) fix loop to async
2) query n+1 problem
2024-08-17 15:37:40 +09:00
8 changed files with 3359 additions and 2394 deletions

5419
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -47,7 +47,7 @@
"build-docker-nightly-local": "npm run build && docker build -f docker/dockerfile -t louislam/uptime-kuma:nightly2 --target nightly .",
"build-docker-pr-test": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64 -t louislam/uptime-kuma:pr-test2 --target pr-test2 . --push",
"upload-artifacts": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg VERSION --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain",
"setup": "git checkout 1.23.13 && npm ci --production && npm run download-dist",
"setup": "git checkout 1.23.14 && npm ci --production && npm run download-dist",
"download-dist": "node extra/download-dist.js",
"mark-as-nightly": "node extra/mark-as-nightly.js",
"reset-password": "node extra/reset-password.js",
@ -72,7 +72,7 @@
"start-server-node14-win": "private\\node14\\node.exe server/server.js"
},
"dependencies": {
"@grpc/grpc-js": "~1.7.3",
"@grpc/grpc-js": "~1.8.22",
"@louislam/ping": "~0.4.4-mod.1",
"@louislam/sqlite3": "15.1.6",
"@vvo/tzdb": "^6.125.0",
@ -91,7 +91,7 @@
"dayjs": "~1.11.5",
"dev-null": "^0.1.1",
"dotenv": "~16.0.3",
"express": "~4.19.2",
"express": "~4.21.0",
"express-basic-auth": "~1.2.1",
"express-static-gzip": "~2.1.7",
"feed": "^4.2.2",
@ -138,8 +138,8 @@
"redbean-node": "~0.3.0",
"redis": "~4.5.1",
"semver": "~7.5.4",
"socket.io": "~4.7.5",
"socket.io-client": "~4.7.5",
"socket.io": "~4.8.0",
"socket.io-client": "~4.8.0",
"socks-proxy-agent": "6.1.1",
"tar": "~6.2.1",
"tcp-ping": "~0.1.1",
@ -172,7 +172,7 @@
"cross-env": "~7.0.3",
"delay": "^5.0.0",
"dns2": "~2.0.1",
"dompurify": "~3.0.11",
"dompurify": "~3.1.7",
"eslint": "~8.14.0",
"eslint-plugin-jsdoc": "~46.4.6",
"eslint-plugin-vue": "~8.7.1",

View File

@ -72,31 +72,22 @@ class Monitor extends BeanModel {
/**
* Return an object that ready to parse to JSON
* @param {object} preloadData Include precalculate data in
* @param {boolean} includeSensitiveData Include sensitive data in
* JSON
* @returns {Promise<object>} Object ready to parse
*/
async toJSON(includeSensitiveData = true) {
let notificationIDList = {};
let list = await R.find("monitor_notification", " monitor_id = ? ", [
this.id,
]);
for (let bean of list) {
notificationIDList[bean.notification_id] = true;
}
const tags = await this.getTags();
toJSON(preloadData = {}, includeSensitiveData = true) {
const tags = preloadData.tags.get(this.id) || [];
const notificationIDList = preloadData.notifications.get(this.id) || {};
let screenshot = null;
if (this.type === "real-browser") {
screenshot = "/screenshots/" + jwt.sign(this.id, UptimeKumaServer.getInstance().jwtSecret) + ".png";
}
const path = await this.getPath();
const path = preloadData.paths.get(this.id) || [];
const pathName = path.join(" / ");
let data = {
@ -106,15 +97,15 @@ class Monitor extends BeanModel {
path,
pathName,
parent: this.parent,
childrenIDs: await Monitor.getAllChildrenIDs(this.id),
childrenIDs: preloadData.childrenIDs.get(this.id) || [],
url: this.url,
method: this.method,
hostname: this.hostname,
port: this.port,
maxretries: this.maxretries,
weight: this.weight,
active: await this.isActive(),
forceInactive: !await Monitor.isParentActive(this.id),
active: preloadData.activeStatus.get(this.id),
forceInactive: preloadData.forceInactive.get(this.id),
type: this.type,
timeout: this.timeout,
interval: this.interval,
@ -135,8 +126,8 @@ class Monitor extends BeanModel {
docker_host: this.docker_host,
proxyId: this.proxy_id,
notificationIDList,
tags: tags,
maintenance: await Monitor.isUnderMaintenance(this.id),
tags,
maintenance: preloadData.maintenanceStatus.get(this.id),
mqttTopic: this.mqttTopic,
mqttSuccessMessage: this.mqttSuccessMessage,
mqttCheckType: this.mqttCheckType,
@ -202,16 +193,6 @@ class Monitor extends BeanModel {
return data;
}
/**
* Checks if the monitor is active based on itself and its parents
* @returns {Promise<boolean>} Is the monitor active?
*/
async isActive() {
const parentActive = await Monitor.isParentActive(this.id);
return (this.active === 1) && parentActive;
}
/**
* Get all tags applied to this monitor
* @returns {Promise<LooseObject<any>[]>} List of tags on the
@ -1197,6 +1178,18 @@ class Monitor extends BeanModel {
return checkCertificateResult;
}
/**
* Checks if the monitor is active based on itself and its parents
* @param {number} monitorID ID of monitor to send
* @param {boolean} active is active
* @returns {Promise<boolean>} Is the monitor active?
*/
static async isActive(monitorID, active) {
const parentActive = await Monitor.isParentActive(monitorID);
return (active === 1) && parentActive;
}
/**
* Send statistics to clients
* @param {Server} io Socket server instance
@ -1333,7 +1326,10 @@ class Monitor extends BeanModel {
for (let notification of notificationList) {
try {
const heartbeatJSON = bean.toJSON();
const monitorData = [{ id: monitor.id,
active: monitor.active
}];
const preloadData = await Monitor.preparePreloadData(monitorData);
// Prevent if the msg is undefined, notifications such as Discord cannot send out.
if (!heartbeatJSON["msg"]) {
heartbeatJSON["msg"] = "N/A";
@ -1344,7 +1340,7 @@ class Monitor extends BeanModel {
heartbeatJSON["timezoneOffset"] = UptimeKumaServer.getInstance().getTimezoneOffset();
heartbeatJSON["localDateTime"] = dayjs.utc(heartbeatJSON["time"]).tz(heartbeatJSON["timezone"]).format(SQL_DATETIME_FORMAT);
await Notification.send(JSON.parse(notification.config), msg, await monitor.toJSON(false), heartbeatJSON);
await Notification.send(JSON.parse(notification.config), msg, monitor.toJSON(preloadData, false), heartbeatJSON);
} catch (e) {
log.error("monitor", "Cannot send notification to " + notification.name);
log.error("monitor", e);
@ -1507,6 +1503,111 @@ class Monitor extends BeanModel {
}
}
/**
* Gets monitor notification of multiple monitor
* @param {Array} monitorIDs IDs of monitor to get
* @returns {Promise<LooseObject<any>>} object
*/
static async getMonitorNotification(monitorIDs) {
return await R.getAll(`
SELECT monitor_notification.monitor_id, monitor_notification.notification_id
FROM monitor_notification
WHERE monitor_notification.monitor_id IN (?)
`, [
monitorIDs,
]);
}
/**
* Gets monitor tags of multiple monitor
* @param {Array} monitorIDs IDs of monitor to get
* @returns {Promise<LooseObject<any>>} object
*/
static async getMonitorTag(monitorIDs) {
return await R.getAll(`
SELECT monitor_tag.monitor_id, tag.name, tag.color
FROM monitor_tag
JOIN tag ON monitor_tag.tag_id = tag.id
WHERE monitor_tag.monitor_id IN (?)
`, [
monitorIDs,
]);
}
/**
* prepare preloaded data for efficient access
* @param {Array} monitorData IDs & active field of monitor to get
* @returns {Promise<LooseObject<any>>} object
*/
static async preparePreloadData(monitorData) {
const notificationsMap = new Map();
const tagsMap = new Map();
const maintenanceStatusMap = new Map();
const childrenIDsMap = new Map();
const activeStatusMap = new Map();
const forceInactiveMap = new Map();
const pathsMap = new Map();
if (monitorData.length > 0) {
const monitorIDs = monitorData.map(monitor => monitor.id);
const notifications = await Monitor.getMonitorNotification(monitorIDs);
const tags = await Monitor.getMonitorTag(monitorIDs);
const maintenanceStatuses = await Promise.all(monitorData.map(monitor => Monitor.isUnderMaintenance(monitor.id)));
const childrenIDs = await Promise.all(monitorData.map(monitor => Monitor.getAllChildrenIDs(monitor.id)));
const activeStatuses = await Promise.all(monitorData.map(monitor => Monitor.isActive(monitor.id, monitor.active)));
const forceInactiveStatuses = await Promise.all(monitorData.map(monitor => Monitor.isParentActive(monitor.id)));
const paths = await Promise.all(monitorData.map(monitor => Monitor.getAllPath(monitor.id, monitor.name)));
notifications.forEach(row => {
if (!notificationsMap.has(row.monitor_id)) {
notificationsMap.set(row.monitor_id, {});
}
notificationsMap.get(row.monitor_id)[row.notification_id] = true;
});
tags.forEach(row => {
if (!tagsMap.has(row.monitor_id)) {
tagsMap.set(row.monitor_id, []);
}
tagsMap.get(row.monitor_id).push({
name: row.name,
color: row.color
});
});
monitorData.forEach((monitor, index) => {
maintenanceStatusMap.set(monitor.id, maintenanceStatuses[index]);
});
monitorData.forEach((monitor, index) => {
childrenIDsMap.set(monitor.id, childrenIDs[index]);
});
monitorData.forEach((monitor, index) => {
activeStatusMap.set(monitor.id, activeStatuses[index]);
});
monitorData.forEach((monitor, index) => {
forceInactiveMap.set(monitor.id, !forceInactiveStatuses[index]);
});
monitorData.forEach((monitor, index) => {
pathsMap.set(monitor.id, paths[index]);
});
}
return {
notifications: notificationsMap,
tags: tagsMap,
maintenanceStatus: maintenanceStatusMap,
childrenIDs: childrenIDsMap,
activeStatus: activeStatusMap,
forceInactive: forceInactiveMap,
paths: pathsMap,
};
}
/**
* Gets Parent of the monitor
* @param {number} monitorID ID of monitor to get
@ -1539,16 +1640,18 @@ class Monitor extends BeanModel {
/**
* Gets the full path
* @param {number} monitorID ID of the monitor to get
* @param {string} name of the monitor to get
* @returns {Promise<string[]>} Full path (includes groups and the name) of the monitor
*/
async getPath() {
const path = [ this.name ];
static async getAllPath(monitorID, name) {
const path = [ name ];
if (this.parent === null) {
return path;
}
let parent = await Monitor.getParent(this.id);
let parent = await Monitor.getParent(monitorID);
while (parent !== null) {
path.unshift(parent.name);
parent = await Monitor.getParent(parent.id);

View File

@ -726,7 +726,7 @@ let needSetup = false;
await updateMonitorNotification(bean.id, notificationIDList);
await server.sendMonitorList(socket);
await server.sendUpdateMonitorIntoList(socket, bean.id);
if (monitor.active !== false) {
await startMonitor(socket.userID, bean.id);
@ -879,11 +879,11 @@ let needSetup = false;
await updateMonitorNotification(bean.id, monitor.notificationIDList);
if (await bean.isActive()) {
if (await Monitor.isActive(bean.id, bean.active)) {
await restartMonitor(socket.userID, bean.id);
}
await server.sendMonitorList(socket);
await server.sendUpdateMonitorIntoList(socket, bean.id);
callback({
ok: true,
@ -923,14 +923,17 @@ let needSetup = false;
log.info("monitor", `Get Monitor: ${monitorID} User ID: ${socket.userID}`);
let bean = await R.findOne("monitor", " id = ? AND user_id = ? ", [
let monitor = await R.findOne("monitor", " id = ? AND user_id = ? ", [
monitorID,
socket.userID,
]);
const monitorData = [{ id: monitor.id,
active: monitor.active
}];
const preloadData = await Monitor.preparePreloadData(monitorData);
callback({
ok: true,
monitor: await bean.toJSON(),
monitor: monitor.toJSON(preloadData),
});
} catch (e) {
@ -981,7 +984,7 @@ let needSetup = false;
try {
checkLogin(socket);
await startMonitor(socket.userID, monitorID);
await server.sendMonitorList(socket);
await server.sendUpdateMonitorIntoList(socket, monitorID);
callback({
ok: true,
@ -1001,7 +1004,7 @@ let needSetup = false;
try {
checkLogin(socket);
await pauseMonitor(socket.userID, monitorID);
await server.sendMonitorList(socket);
await server.sendUpdateMonitorIntoList(socket, monitorID);
callback({
ok: true,
@ -1047,8 +1050,7 @@ let needSetup = false;
msg: "successDeleted",
msgi18n: true,
});
await server.sendMonitorList(socket);
await server.sendDeleteMonitorFromList(socket, monitorID);
} catch (e) {
callback({
@ -1678,13 +1680,13 @@ async function afterLogin(socket, user) {
await StatusPage.sendStatusPageList(io, socket);
const monitorPromises = [];
for (let monitorID in monitorList) {
await sendHeartbeatList(socket, monitorID);
monitorPromises.push(sendHeartbeatList(socket, monitorID));
monitorPromises.push(Monitor.sendStats(io, monitorID, user.id));
}
for (let monitorID in monitorList) {
await Monitor.sendStats(io, monitorID, user.id);
}
await Promise.all(monitorPromises);
// Set server timezone from client browser if not set
// It should be run once only

View File

@ -205,24 +205,69 @@ class UptimeKumaServer {
return list;
}
/**
* Update Monitor into list
* @param {Socket} socket Socket to send list on
* @param {number} monitorID update or deleted monitor id
* @returns {Promise<object>} List of monitors
*/
async sendUpdateMonitorIntoList(socket, monitorID) {
let list = await this.getMonitorJSONList(socket.userID, monitorID);
this.io.to(socket.userID).emit("updateMonitorIntoList", list);
return list;
}
/**
* Delete Monitor from list
* @param {Socket} socket Socket to send list on
* @param {number} monitorID update or deleted monitor id
* @returns {number} deletion monitor id
*/
async sendDeleteMonitorFromList(socket, monitorID) {
this.io.to(socket.userID).emit("deleteMonitorFromList", monitorID);
return monitorID;
}
/**
* Get a list of monitors for the given user.
* @param {string} userID - The ID of the user to get monitors for.
* @param {number} monitorID - The ID of monitor for.
* @returns {Promise<object>} A promise that resolves to an object with monitor IDs as keys and monitor objects as values.
*
* Generated by Trelent
*/
async getMonitorJSONList(userID) {
async getMonitorJSONList(userID, monitorID = null) {
let result = {};
let monitorList = await R.find("monitor", " user_id = ? ORDER BY weight DESC, name", [
userID,
]);
let query = " user_id = ? ";
let queryParams = [ userID ];
for (let monitor of monitorList) {
result[monitor.id] = await monitor.toJSON();
if (monitorID) {
query += "AND id = ? ";
queryParams.push(monitorID);
}
let monitorList = await R.find("monitor", query + "ORDER BY weight DESC, name", queryParams);
const monitorData = monitorList.map(monitor => ({
id: monitor.id,
active: monitor.active,
name: monitor.name,
}));
const preloadData = await Monitor.preparePreloadData(monitorData);
const monitorPromises = monitorList.map(monitor => {
const json = monitor.toJSON(preloadData);
return { id: monitor.id,
json
};
});
const monitors = await Promise.all(monitorPromises);
monitors.forEach(monitor => {
result[monitor.id] = monitor.json;
});
return result;
}
@ -520,3 +565,4 @@ const { DnsMonitorType } = require("./monitor-types/dns");
const { MqttMonitorType } = require("./monitor-types/mqtt");
const { SNMPMonitorType } = require("./monitor-types/snmp");
const { MongodbMonitorType } = require("./monitor-types/mongodb");
const Monitor = require("./model/monitor");

View File

@ -141,19 +141,20 @@ export default {
});
socket.on("monitorList", (data) => {
// Add Helper function
Object.entries(data).forEach(([ monitorID, monitor ]) => {
monitor.getUrl = () => {
try {
return new URL(monitor.url);
} catch (_) {
return null;
}
};
});
this.assignMonitorUrlParser(data);
console.log(data);
this.monitorList = data;
});
socket.on("updateMonitorIntoList", (data) => {
this.assignMonitorUrlParser(data);
this.monitorList = this.updateMonitorList(data);
});
socket.on("deleteMonitorFromList", (monitorID) => {
this.monitorList = this.deleteMonitorList(monitorID);
});
socket.on("monitorTypeList", (data) => {
this.monitorTypeList = data;
});
@ -289,6 +290,46 @@ export default {
location.reload();
});
},
/**
* parse all urls from list.
* @param {object} data Monitor data to modify
* @returns {object} list
*/
assignMonitorUrlParser(data) {
Object.entries(data).forEach(([ monitorID, monitor ]) => {
monitor.getUrl = () => {
try {
return new URL(monitor.url);
} catch (_) {
return null;
}
};
});
return data;
},
/**
* update into existing list
* @param {object} list add, updated, pause & resume list
* @returns {object} list
*/
updateMonitorList(list) {
Object.entries(list).forEach(([ monitorID, updatedMonitor ]) => {
this.monitorList[monitorID] = updatedMonitor;
});
return this.monitorList;
},
/**
* delete from existing list
* @param {number} monitorID deleted monitorID
* @returns {object} list
*/
deleteMonitorList(monitorID) {
if (this.monitorList[monitorID]) {
delete this.monitorList[monitorID];
}
return this.monitorList;
},
/**
* The storage currently in use

View File

@ -1689,7 +1689,6 @@ message HealthCheckResponse {
await this.startParentGroupMonitor();
}
this.processing = false;
this.$root.getMonitorList();
this.$router.push("/dashboard/" + res.monitorID);
} else {
this.processing = false;

View File

@ -83,6 +83,7 @@ exports.CONSOLE_STYLE_BgMagenta = "\x1b[45m";
exports.CONSOLE_STYLE_BgCyan = "\x1b[46m";
exports.CONSOLE_STYLE_BgWhite = "\x1b[47m";
exports.CONSOLE_STYLE_BgGray = "\x1b[100m";
const consoleModuleColors = [
exports.CONSOLE_STYLE_FgCyan,
exports.CONSOLE_STYLE_FgGreen,