mirror of
https://github.com/louislam/uptime-kuma.git
synced 2024-10-30 11:10:40 +00:00
Merge branch 'master' into clean-mobile-table
This commit is contained in:
commit
3f12afce28
11 changed files with 412 additions and 10479 deletions
|
@ -2,10 +2,10 @@
|
|||
FROM node:14-alpine3.12 AS release
|
||||
WORKDIR /app
|
||||
|
||||
# split the sqlite install here, so that it can caches the arm prebuilt
|
||||
# split the sqlite install here, so that it can caches the prebuilt
|
||||
RUN apk add --no-cache --virtual .build-deps make g++ python3 python3-dev && \
|
||||
ln -s /usr/bin/python3 /usr/bin/python && \
|
||||
npm install @louislam/sqlite3@5.0.3 bcrypt@5.0.1 && \
|
||||
npm install better-sqlite3@7.4.3 bcrypt@5.0.1 && \
|
||||
apk del .build-deps && \
|
||||
rm -f /usr/bin/python
|
||||
|
||||
|
|
10724
package-lock.json
generated
10724
package-lock.json
generated
File diff suppressed because it is too large
Load diff
12
package.json
12
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "uptime-kuma",
|
||||
"version": "1.3.1",
|
||||
"version": "1.3.2",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -17,10 +17,10 @@
|
|||
"update": "",
|
||||
"build": "vite build",
|
||||
"vite-preview-dist": "vite preview --host",
|
||||
"build-docker": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.3.1 --target release . --push",
|
||||
"build-docker": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.3.2 --target release . --push",
|
||||
"build-docker-nightly": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push",
|
||||
"build-docker-nightly-amd64": "docker buildx build --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push",
|
||||
"setup": "git checkout 1.3.1 && npm install && npm run build",
|
||||
"build-docker-nightly-amd64": "docker buildx build --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain",
|
||||
"setup": "git checkout 1.3.2 && npm install && npm run build",
|
||||
"update-version": "node extra/update-version.js",
|
||||
"mark-as-nightly": "node extra/mark-as-nightly.js",
|
||||
"reset-password": "node extra/reset-password.js",
|
||||
|
@ -36,11 +36,11 @@
|
|||
"@fortawesome/free-regular-svg-icons": "^5.15.4",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
||||
"@fortawesome/vue-fontawesome": "^3.0.0-4",
|
||||
"@louislam/sqlite3": "^5.0.3",
|
||||
"@popperjs/core": "^2.9.3",
|
||||
"args-parser": "^1.3.0",
|
||||
"axios": "^0.21.1",
|
||||
"bcrypt": "^5.0.1",
|
||||
"better-sqlite3": "^7.4.3",
|
||||
"bootstrap": "^5.1.0",
|
||||
"chart.js": "^3.5.0",
|
||||
"chartjs-adapter-dayjs": "^1.0.0",
|
||||
|
@ -56,7 +56,7 @@
|
|||
"password-hash": "^1.2.2",
|
||||
"prom-client": "^13.2.0",
|
||||
"prometheus-api-metrics": "^3.2.0",
|
||||
"redbean-node": "0.0.21",
|
||||
"redbean-node": "0.1.1",
|
||||
"socket.io": "^4.1.3",
|
||||
"socket.io-client": "^4.1.3",
|
||||
"tcp-ping": "^0.1.1",
|
||||
|
|
|
@ -28,7 +28,7 @@ exports.startInterval = () => {
|
|||
};
|
||||
|
||||
check();
|
||||
interval = setInterval(check, 3600 * 48);
|
||||
interval = setInterval(check, 3600 * 1000 * 48);
|
||||
};
|
||||
|
||||
exports.enableCheckUpdate = async (value) => {
|
||||
|
|
|
@ -3,7 +3,6 @@ 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 +13,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: 5,
|
||||
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 +37,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");
|
||||
|
||||
}
|
||||
|
||||
static async patch() {
|
||||
|
|
|
@ -110,10 +110,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: {
|
||||
|
@ -121,7 +120,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,
|
||||
|
@ -241,7 +240,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -258,22 +258,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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -150,7 +150,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 +178,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,
|
||||
|
@ -561,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")
|
||||
|
@ -621,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,
|
||||
|
@ -631,6 +633,9 @@ async function sendNotificationList(socket) {
|
|||
}
|
||||
|
||||
io.to(socket.userID).emit("notificationList", result)
|
||||
|
||||
timeLogger.print("Send Notification List");
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
|
@ -639,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,
|
||||
])
|
||||
|
||||
|
@ -786,6 +794,8 @@ async function sendHeartbeatList(socket, monitorID) {
|
|||
}
|
||||
|
||||
socket.emit("heartbeatList", monitorID, result)
|
||||
|
||||
timeLogger.print(`[Monitor: ${monitorID}] sendHeartbeatList`)
|
||||
}
|
||||
|
||||
async function sendImportantHeartbeatList(socket, monitorID) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<transition name="slide-fade" appear>
|
||||
<div>
|
||||
<div v-if="monitor">
|
||||
<h1> {{ monitor.name }}</h1>
|
||||
<p class="url">
|
||||
<a v-if="monitor.type === 'http' || monitor.type === 'keyword' " :href="monitor.url" target="_blank">{{ monitor.url }}</a>
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
<template>
|
||||
<MonitorList />
|
||||
<transition name="slide-fade" appear>
|
||||
<MonitorList />
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
24
src/util.ts
24
src/util.ts
|
@ -1,4 +1,3 @@
|
|||
// @ts-nocheck
|
||||
// Common Util for frontend and backend
|
||||
// Backend uses the compiled file util.js
|
||||
// Frontend uses util.ts
|
||||
|
@ -13,7 +12,7 @@ export const DOWN = 0;
|
|||
export const UP = 1;
|
||||
export const PENDING = 2;
|
||||
|
||||
export function flipStatus(s) {
|
||||
export function flipStatus(s: number) {
|
||||
if (s === UP) {
|
||||
return DOWN;
|
||||
}
|
||||
|
@ -25,7 +24,7 @@ export function flipStatus(s) {
|
|||
return s;
|
||||
}
|
||||
|
||||
export function sleep(ms) {
|
||||
export function sleep(ms: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
|
@ -33,8 +32,8 @@ export function sleep(ms) {
|
|||
* PHP's ucfirst
|
||||
* @param str
|
||||
*/
|
||||
export function ucfirst(str) {
|
||||
if (! str) {
|
||||
export function ucfirst(str: string) {
|
||||
if (!str) {
|
||||
return str;
|
||||
}
|
||||
|
||||
|
@ -42,12 +41,15 @@ export function ucfirst(str) {
|
|||
return firstLetter.toUpperCase() + str.substr(1);
|
||||
}
|
||||
|
||||
export function debug(msg) {
|
||||
export function debug(msg: any) {
|
||||
if (isDev) {
|
||||
console.log(msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
declare global { interface String { replaceAll(str: string, newStr: string): string; } }
|
||||
|
||||
export function polyfill() {
|
||||
/**
|
||||
* String.prototype.replaceAll() polyfill
|
||||
|
@ -56,7 +58,7 @@ export function polyfill() {
|
|||
* @license MIT
|
||||
*/
|
||||
if (!String.prototype.replaceAll) {
|
||||
String.prototype.replaceAll = function (str, newStr) {
|
||||
String.prototype.replaceAll = function (str: string, newStr: string) {
|
||||
|
||||
// If a regex pattern
|
||||
if (Object.prototype.toString.call(str).toLowerCase() === "[object regexp]") {
|
||||
|
@ -71,11 +73,13 @@ export function polyfill() {
|
|||
}
|
||||
|
||||
export class TimeLogger {
|
||||
startTime: number;
|
||||
|
||||
constructor() {
|
||||
this.startTime = dayjs().valueOf();
|
||||
}
|
||||
|
||||
print(name) {
|
||||
print(name: string) {
|
||||
if (isDev) {
|
||||
console.log(name + ": " + (dayjs().valueOf() - this.startTime) + "ms")
|
||||
}
|
||||
|
@ -85,7 +89,7 @@ export class TimeLogger {
|
|||
/**
|
||||
* Returns a random number between min (inclusive) and max (exclusive)
|
||||
*/
|
||||
export function getRandomArbitrary(min, max) {
|
||||
export function getRandomArbitrary(min: number, max: number) {
|
||||
return Math.random() * (max - min) + min;
|
||||
}
|
||||
|
||||
|
@ -98,7 +102,7 @@ export function getRandomArbitrary(min, max) {
|
|||
* lower than max if max isn't an integer).
|
||||
* Using Math.round() will give you a non-uniform distribution!
|
||||
*/
|
||||
export function getRandomInt(min, max) {
|
||||
export function getRandomInt(min: number, max: number) {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
|
|
|
@ -4,14 +4,15 @@
|
|||
"target": "es2018",
|
||||
"module": "commonjs",
|
||||
"lib": [
|
||||
"es2020"
|
||||
"es2020",
|
||||
"DOM",
|
||||
],
|
||||
"removeComments": true,
|
||||
"preserveConstEnums": true,
|
||||
"sourceMap": false,
|
||||
"files.insertFinalNewline": true
|
||||
"strict": true
|
||||
},
|
||||
"files": [
|
||||
"./server/util.ts"
|
||||
"./src/util.ts"
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue