Merge branch 'master' into dns-monitor

This commit is contained in:
LouisLam
2021-08-26 01:05:46 +08:00
30 changed files with 1571 additions and 3972 deletions

View File

@@ -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>}

View File

@@ -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);
}
/**

View File

@@ -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"]
}
]
}

View File

@@ -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) {