mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-08-08 23:37:27 +08:00
Merge branch 'master' into dns-monitor
This commit is contained in:
@@ -1,9 +1,6 @@
|
||||
const fs = require("fs");
|
||||
const { sleep, debug, isDev } = require("../src/util");
|
||||
const { R } = require("redbean-node");
|
||||
const { setSetting, setting } = require("./util-server");
|
||||
const knex = require("knex");
|
||||
const sqlite3 = require("@louislam/sqlite3");
|
||||
|
||||
class Database {
|
||||
|
||||
@@ -14,48 +11,23 @@ class Database {
|
||||
static sqliteInstance = null;
|
||||
|
||||
static async connect() {
|
||||
const acquireConnectionTimeout = 120 * 1000;
|
||||
|
||||
if (! this.sqliteInstance) {
|
||||
this.sqliteInstance = new sqlite3.Database(Database.path);
|
||||
this.sqliteInstance.run("PRAGMA journal_mode = WAL");
|
||||
}
|
||||
R.useBetterSQLite3 = true;
|
||||
R.betterSQLite3Options.timeout = acquireConnectionTimeout;
|
||||
|
||||
const Dialect = require("knex/lib/dialects/sqlite3/index.js");
|
||||
Dialect.prototype._driver = () => sqlite3;
|
||||
|
||||
if (isDev) {
|
||||
Dialect.prototype.acquireConnectionOrg = Dialect.prototype.acquireConnection;
|
||||
|
||||
Dialect.prototype.acquireConnection = async function () {
|
||||
let a = await this.acquireConnectionOrg();
|
||||
debug("acquired Connection");
|
||||
return a;
|
||||
};
|
||||
}
|
||||
|
||||
// Always return same connection.
|
||||
Dialect.prototype.acquireRawConnection = async function () {
|
||||
return Database.sqliteInstance;
|
||||
};
|
||||
|
||||
Dialect.prototype.destroyRawConnection = async () => { }
|
||||
|
||||
const knexInstance = knex({
|
||||
client: Dialect,
|
||||
connection: { }, // Do not remove, Leave it empty is ok
|
||||
R.setup("sqlite", {
|
||||
filename: Database.path,
|
||||
useNullAsDefault: true,
|
||||
pool: {
|
||||
min: 1,
|
||||
max: 1,
|
||||
idleTimeoutMillis: 30000,
|
||||
}
|
||||
acquireConnectionTimeout: acquireConnectionTimeout,
|
||||
}, {
|
||||
min: 1,
|
||||
max: 1,
|
||||
idleTimeoutMillis: 120 * 1000,
|
||||
propagateCreateError: false,
|
||||
acquireTimeoutMillis: acquireConnectionTimeout,
|
||||
});
|
||||
|
||||
console.log( knexInstance.pool)
|
||||
console.log("pool size")
|
||||
|
||||
R.setup(knexInstance);
|
||||
|
||||
if (process.env.SQL_LOG === "1") {
|
||||
R.debug(true);
|
||||
}
|
||||
@@ -63,6 +35,10 @@ class Database {
|
||||
// Auto map the model to a bean object
|
||||
R.freeze(true)
|
||||
await R.autoloadModels("./server/model");
|
||||
|
||||
// Change to WAL
|
||||
await R.exec("PRAGMA journal_mode = WAL");
|
||||
console.log(await R.getAll("PRAGMA journal_mode"));
|
||||
}
|
||||
|
||||
static async patch() {
|
||||
@@ -148,11 +124,18 @@ class Database {
|
||||
return statement !== "";
|
||||
})
|
||||
|
||||
// Use better-sqlite3 to run, prevent "This statement does not return data. Use run() instead"
|
||||
const db = await this.getBetterSQLite3Database();
|
||||
|
||||
for (let statement of statements) {
|
||||
await R.exec(statement);
|
||||
db.prepare(statement).run();
|
||||
}
|
||||
}
|
||||
|
||||
static getBetterSQLite3Database() {
|
||||
return R.knex.client.acquireConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Special handle, because tarn.js throw a promise reject that cannot be caught
|
||||
* @returns {Promise<void>}
|
||||
|
@@ -112,10 +112,9 @@ class Monitor extends BeanModel {
|
||||
|
||||
try {
|
||||
if (this.type === "http" || this.type === "keyword") {
|
||||
// Do not do any queries/high loading things before the "bean.ping"
|
||||
let startTime = dayjs().valueOf();
|
||||
|
||||
// Use Custom agent to disable session reuse
|
||||
// https://github.com/nodejs/node/issues/3940
|
||||
let res = await axios.get(this.url, {
|
||||
timeout: this.interval * 1000 * 0.8,
|
||||
headers: {
|
||||
@@ -123,7 +122,7 @@ class Monitor extends BeanModel {
|
||||
"User-Agent": "Uptime-Kuma/" + version,
|
||||
},
|
||||
httpsAgent: new https.Agent({
|
||||
maxCachedSessions: 0,
|
||||
maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
|
||||
rejectUnauthorized: ! this.getIgnoreTls(),
|
||||
}),
|
||||
maxRedirects: this.maxredirects,
|
||||
@@ -276,7 +275,8 @@ class Monitor extends BeanModel {
|
||||
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.error("Cannot send notification to " + notification.name);
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -293,22 +293,22 @@ class Monitor extends BeanModel {
|
||||
console.warn(`Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Type: ${this.type}`)
|
||||
}
|
||||
|
||||
prometheus.update(bean, tlsInfo)
|
||||
|
||||
io.to(this.user_id).emit("heartbeat", bean.toJSON());
|
||||
|
||||
await R.store(bean)
|
||||
Monitor.sendStats(io, this.id, this.user_id)
|
||||
|
||||
await R.store(bean);
|
||||
prometheus.update(bean, tlsInfo);
|
||||
|
||||
previousBeat = bean;
|
||||
|
||||
this.heartbeatInterval = setTimeout(beat, this.interval * 1000);
|
||||
}
|
||||
|
||||
beat();
|
||||
this.heartbeatInterval = setInterval(beat, this.interval * 1000);
|
||||
}
|
||||
|
||||
stop() {
|
||||
clearInterval(this.heartbeatInterval)
|
||||
clearTimeout(this.heartbeatInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -279,6 +279,116 @@ class Notification {
|
||||
throwGeneralAxiosError(error)
|
||||
}
|
||||
|
||||
} else if (notification.type === "mattermost") {
|
||||
try {
|
||||
const mattermostUserName = notification.mattermostusername || "Uptime Kuma";
|
||||
// If heartbeatJSON is null, assume we're testing.
|
||||
if (heartbeatJSON == null) {
|
||||
let mattermostTestData = {
|
||||
username: mattermostUserName,
|
||||
text: msg,
|
||||
}
|
||||
await axios.post(notification.mattermostWebhookUrl, mattermostTestData)
|
||||
return okMsg;
|
||||
}
|
||||
|
||||
const mattermostChannel = notification.mattermostchannel;
|
||||
const mattermostIconEmoji = notification.mattermosticonemo;
|
||||
const mattermostIconUrl = notification.mattermosticonurl;
|
||||
|
||||
if (heartbeatJSON["status"] == 0) {
|
||||
let mattermostdowndata = {
|
||||
username: mattermostUserName,
|
||||
text: "Uptime Kuma Alert",
|
||||
channel: mattermostChannel,
|
||||
icon_emoji: mattermostIconEmoji,
|
||||
icon_url: mattermostIconUrl,
|
||||
attachments: [
|
||||
{
|
||||
fallback:
|
||||
"Your " +
|
||||
monitorJSON["name"] +
|
||||
" service went down.",
|
||||
color: "#FF0000",
|
||||
title:
|
||||
"❌ " +
|
||||
monitorJSON["name"] +
|
||||
" service went down. ❌",
|
||||
title_link: monitorJSON["url"],
|
||||
fields: [
|
||||
{
|
||||
short: true,
|
||||
title: "Service Name",
|
||||
value: monitorJSON["name"],
|
||||
},
|
||||
{
|
||||
short: true,
|
||||
title: "Time (UTC)",
|
||||
value: heartbeatJSON["time"],
|
||||
},
|
||||
{
|
||||
short: false,
|
||||
title: "Error",
|
||||
value: heartbeatJSON["msg"],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
await axios.post(
|
||||
notification.mattermostWebhookUrl,
|
||||
mattermostdowndata
|
||||
);
|
||||
return okMsg;
|
||||
} else if (heartbeatJSON["status"] == 1) {
|
||||
let mattermostupdata = {
|
||||
username: mattermostUserName,
|
||||
text: "Uptime Kuma Alert",
|
||||
channel: mattermostChannel,
|
||||
icon_emoji: mattermostIconEmoji,
|
||||
icon_url: mattermostIconUrl,
|
||||
attachments: [
|
||||
{
|
||||
fallback:
|
||||
"Your " +
|
||||
monitorJSON["name"] +
|
||||
" service went up!",
|
||||
color: "#32CD32",
|
||||
title:
|
||||
"✅ " +
|
||||
monitorJSON["name"] +
|
||||
" service went up! ✅",
|
||||
title_link: monitorJSON["url"],
|
||||
fields: [
|
||||
{
|
||||
short: true,
|
||||
title: "Service Name",
|
||||
value: monitorJSON["name"],
|
||||
},
|
||||
{
|
||||
short: true,
|
||||
title: "Time (UTC)",
|
||||
value: heartbeatJSON["time"],
|
||||
},
|
||||
{
|
||||
short: false,
|
||||
title: "Ping",
|
||||
value: heartbeatJSON["ping"] + "ms",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
await axios.post(
|
||||
notification.mattermostWebhookUrl,
|
||||
mattermostupdata
|
||||
);
|
||||
return okMsg;
|
||||
}
|
||||
} catch (error) {
|
||||
throwGeneralAxiosError(error);
|
||||
}
|
||||
|
||||
} else if (notification.type === "pushover") {
|
||||
let pushoverlink = "https://api.pushover.net/1/messages.json"
|
||||
try {
|
||||
@@ -404,7 +514,7 @@ class Notification {
|
||||
"messages": [
|
||||
{
|
||||
"type": "text",
|
||||
"text":"Test Successful!"
|
||||
"text": "Test Successful!"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -415,7 +525,7 @@ class Notification {
|
||||
"messages": [
|
||||
{
|
||||
"type": "text",
|
||||
"text":"UptimeKuma Alert: [🔴 Down]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"]
|
||||
"text": "UptimeKuma Alert: [🔴 Down]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -426,7 +536,7 @@ class Notification {
|
||||
"messages": [
|
||||
{
|
||||
"type": "text",
|
||||
"text":"UptimeKuma Alert: [✅ Up]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"]
|
||||
"text": "UptimeKuma Alert: [✅ Up]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@@ -12,8 +12,6 @@ debug("Importing express");
|
||||
const express = require("express");
|
||||
debug("Importing socket.io");
|
||||
const { Server } = require("socket.io");
|
||||
debug("Importing dayjs");
|
||||
const dayjs = require("dayjs");
|
||||
debug("Importing redbean-node");
|
||||
const { R } = require("redbean-node");
|
||||
debug("Importing jsonwebtoken");
|
||||
@@ -150,7 +148,7 @@ let indexHTML = fs.readFileSync("./dist/index.html").toString();
|
||||
if (user) {
|
||||
debug("afterLogin")
|
||||
|
||||
await afterLogin(socket, user)
|
||||
afterLogin(socket, user)
|
||||
|
||||
debug("afterLogin ok")
|
||||
|
||||
@@ -178,7 +176,7 @@ let indexHTML = fs.readFileSync("./dist/index.html").toString();
|
||||
let user = await login(data.username, data.password)
|
||||
|
||||
if (user) {
|
||||
await afterLogin(socket, user)
|
||||
afterLogin(socket, user)
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
@@ -563,7 +561,7 @@ let indexHTML = fs.readFileSync("./dist/index.html").toString();
|
||||
debug("check auto login")
|
||||
if (await setting("disableAuth")) {
|
||||
console.log("Disabled Auth: auto login to admin")
|
||||
await afterLogin(socket, await R.findOne("user"))
|
||||
afterLogin(socket, await R.findOne("user"))
|
||||
socket.emit("autoLogin")
|
||||
} else {
|
||||
debug("need auth")
|
||||
@@ -623,6 +621,8 @@ async function sendMonitorList(socket) {
|
||||
}
|
||||
|
||||
async function sendNotificationList(socket) {
|
||||
const timeLogger = new TimeLogger();
|
||||
|
||||
let result = [];
|
||||
let list = await R.find("notification", " user_id = ? ", [
|
||||
socket.userID,
|
||||
@@ -633,6 +633,9 @@ async function sendNotificationList(socket) {
|
||||
}
|
||||
|
||||
io.to(socket.userID).emit("notificationList", result)
|
||||
|
||||
timeLogger.print("Send Notification List");
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
@@ -641,24 +644,27 @@ async function afterLogin(socket, user) {
|
||||
socket.join(user.id)
|
||||
|
||||
let monitorList = await sendMonitorList(socket)
|
||||
|
||||
sendNotificationList(socket)
|
||||
|
||||
// Delay a bit, so that it let the main page to query the data first, since SQLite can process one sql at the same time only.
|
||||
// For example, query the edit data first.
|
||||
setTimeout(async () => {
|
||||
for (let monitorID in monitorList) {
|
||||
sendHeartbeatList(socket, monitorID);
|
||||
sendImportantHeartbeatList(socket, monitorID);
|
||||
Monitor.sendStats(io, monitorID, user.id)
|
||||
}
|
||||
}, 500);
|
||||
await sleep(500);
|
||||
|
||||
for (let monitorID in monitorList) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
async function getMonitorJSONList(userID) {
|
||||
let result = {};
|
||||
|
||||
let monitorList = await R.find("monitor", " user_id = ? ", [
|
||||
let monitorList = await R.find("monitor", " user_id = ? ORDER BY weight DESC, name", [
|
||||
userID,
|
||||
])
|
||||
|
||||
@@ -788,6 +794,8 @@ async function sendHeartbeatList(socket, monitorID) {
|
||||
}
|
||||
|
||||
socket.emit("heartbeatList", monitorID, result)
|
||||
|
||||
timeLogger.print(`[Monitor: ${monitorID}] sendHeartbeatList`)
|
||||
}
|
||||
|
||||
async function sendImportantHeartbeatList(socket, monitorID) {
|
||||
|
Reference in New Issue
Block a user