2021-08-08 15:03:10 +02:00
console . log ( "Welcome to Uptime Kuma" ) ;
2022-01-14 19:25:28 +02:00
// Check Node.js Version
const nodeVersion = parseInt ( process . versions . node . split ( "." ) [ 0 ] ) ;
const requiredVersion = 14 ;
console . log ( ` Your Node.js version: ${ nodeVersion } ` ) ;
if ( nodeVersion < requiredVersion ) {
console . error ( ` Error: Your Node.js version is not supported, please upgrade to Node.js >= ${ requiredVersion } . ` ) ;
process . exit ( - 1 ) ;
}
2021-10-09 20:36:20 +02:00
const args = require ( "args-parser" ) ( process . argv ) ;
2021-10-10 07:09:30 +02:00
const { sleep , debug , getRandomInt , genSecret } = require ( "../src/util" ) ;
2021-10-15 18:57:26 +02:00
const config = require ( "./config" ) ;
2021-10-09 20:36:20 +02:00
debug ( args ) ;
2021-09-14 17:28:38 +02:00
if ( ! process . env . NODE _ENV ) {
process . env . NODE _ENV = "production" ;
}
2021-08-08 15:03:10 +02:00
console . log ( "Node Env: " + process . env . NODE _ENV ) ;
2021-07-31 15:57:58 +02:00
2021-09-21 15:22:35 +02:00
console . log ( "Importing Node libraries" ) ;
2021-07-31 15:57:58 +02:00
const fs = require ( "fs" ) ;
2021-07-27 19:47:13 +02:00
const http = require ( "http" ) ;
2021-09-02 14:18:27 +02:00
const https = require ( "https" ) ;
2021-07-31 15:57:58 +02:00
2021-09-21 15:22:35 +02:00
console . log ( "Importing 3rd-party libraries" ) ;
2021-07-31 15:57:58 +02:00
debug ( "Importing express" ) ;
const express = require ( "express" ) ;
debug ( "Importing socket.io" ) ;
2021-06-25 15:55:49 +02:00
const { Server } = require ( "socket.io" ) ;
2021-07-31 15:57:58 +02:00
debug ( "Importing redbean-node" ) ;
2021-07-27 19:47:13 +02:00
const { R } = require ( "redbean-node" ) ;
2021-07-31 15:57:58 +02:00
debug ( "Importing jsonwebtoken" ) ;
2021-07-27 19:47:13 +02:00
const jwt = require ( "jsonwebtoken" ) ;
2021-07-31 15:57:58 +02:00
debug ( "Importing http-graceful-shutdown" ) ;
2021-07-27 19:47:13 +02:00
const gracefulShutdown = require ( "http-graceful-shutdown" ) ;
2021-07-31 15:57:58 +02:00
debug ( "Importing prometheus-api-metrics" ) ;
2021-07-27 19:47:13 +02:00
const prometheusAPIMetrics = require ( "prometheus-api-metrics" ) ;
2021-09-23 17:31:01 +02:00
debug ( "Importing compare-versions" ) ;
const compareVersions = require ( "compare-versions" ) ;
2021-10-21 16:54:04 +02:00
const { passwordStrength } = require ( "check-password-strength" ) ;
2021-07-31 15:57:58 +02:00
2021-09-09 21:10:31 +02:00
debug ( "Importing 2FA Modules" ) ;
2021-09-09 21:10:31 +02:00
const notp = require ( "notp" ) ;
const base32 = require ( "thirty-two" ) ;
2021-07-31 15:57:58 +02:00
console . log ( "Importing this project modules" ) ;
debug ( "Importing Monitor" ) ;
const Monitor = require ( "./model/monitor" ) ;
debug ( "Importing Settings" ) ;
2022-01-11 19:44:01 +02:00
const { getSettings , setSettings , setting , initJWTSecret , checkLogin , startUnitTest , FBSD , errorLog } = require ( "./util-server" ) ;
2021-09-07 16:42:46 +02:00
2021-07-31 15:57:58 +02:00
debug ( "Importing Notification" ) ;
const { Notification } = require ( "./notification" ) ;
2021-09-07 16:42:46 +02:00
Notification . init ( ) ;
2021-07-31 15:57:58 +02:00
debug ( "Importing Database" ) ;
const Database = require ( "./database" ) ;
2021-09-27 17:40:38 +02:00
debug ( "Importing Background Jobs" ) ;
const { initBackgroundJobs } = require ( "./jobs" ) ;
2021-10-23 10:35:13 +02:00
const { loginRateLimiter } = require ( "./rate-limiter" ) ;
2021-09-27 17:40:38 +02:00
2021-07-27 18:52:31 +02:00
const { basicAuth } = require ( "./auth" ) ;
2021-07-27 19:47:13 +02:00
const { login } = require ( "./auth" ) ;
2021-07-28 14:35:55 +02:00
const passwordHash = require ( "./password-hash" ) ;
2021-07-31 15:57:58 +02:00
2021-08-21 13:50:22 +02:00
const checkVersion = require ( "./check-version" ) ;
console . info ( "Version: " + checkVersion . version ) ;
2021-08-10 10:36:21 +02:00
2021-08-10 10:45:37 +02:00
// If host is omitted, the server will accept connections on the unspecified IPv6 address (::) when IPv6 is available and the unspecified IPv4 address (0.0.0.0) otherwise.
// Dual-stack support for (::)
2021-10-14 08:09:16 +02:00
let hostname = process . env . UPTIME _KUMA _HOST || args . host ;
2022-01-11 19:44:01 +02:00
// Also read HOST if not FreeBSD, as HOST is a system environment variable in FreeBSD
if ( ! hostname && ! FBSD ) {
2021-10-14 08:09:16 +02:00
hostname = process . env . HOST ;
}
if ( hostname ) {
console . log ( "Custom hostname: " + hostname ) ;
}
const port = parseInt ( process . env . UPTIME _KUMA _PORT || process . env . PORT || args . port || 3001 ) ;
2021-06-25 15:55:49 +02:00
2021-09-02 14:18:27 +02:00
// SSL
2021-10-14 08:09:16 +02:00
const sslKey = process . env . UPTIME _KUMA _SSL _KEY || process . env . SSL _KEY || args [ "ssl-key" ] || undefined ;
const sslCert = process . env . UPTIME _KUMA _SSL _CERT || process . env . SSL _CERT || args [ "ssl-cert" ] || undefined ;
2021-10-19 10:29:09 +02:00
const disableFrameSameOrigin = ! ! process . env . UPTIME _KUMA _DISABLE _FRAME _SAMEORIGIN || args [ "disable-frame-sameorigin" ] || false ;
2021-09-02 14:18:27 +02:00
2021-10-11 20:18:40 +02:00
// 2FA / notp verification defaults
const twofa _verification _opts = {
"window" : 1 ,
"time" : 30
2021-10-18 11:15:28 +02:00
} ;
2021-09-02 14:18:27 +02:00
2021-10-05 13:13:57 +02:00
/ * *
* Run unit test after the server is ready
* @ type { boolean }
* /
const testMode = ! ! args [ "test" ] || false ;
2021-09-02 16:37:51 +02:00
2021-10-15 18:57:26 +02:00
if ( config . demoMode ) {
2021-09-02 16:37:51 +02:00
console . log ( "==== Demo Mode ====" ) ;
2021-09-02 15:10:18 +02:00
}
2021-09-02 14:27:18 +02:00
2021-09-30 18:09:43 +02:00
console . log ( "Creating express and socket.io instance" ) ;
2021-07-15 19:44:51 +02:00
const app = express ( ) ;
2021-09-02 14:18:27 +02:00
let server ;
if ( sslKey && sslCert ) {
2021-09-02 14:27:18 +02:00
console . log ( "Server Type: HTTPS" ) ;
2021-09-02 15:13:59 +02:00
server = https . createServer ( {
2021-09-02 15:16:04 +02:00
key : fs . readFileSync ( sslKey ) ,
cert : fs . readFileSync ( sslCert )
2021-09-02 15:13:59 +02:00
} , app ) ;
2021-09-02 14:18:27 +02:00
} else {
2021-09-02 14:27:18 +02:00
console . log ( "Server Type: HTTP" ) ;
server = http . createServer ( app ) ;
2021-09-02 14:18:27 +02:00
}
2021-07-15 19:44:51 +02:00
const io = new Server ( server ) ;
2021-09-04 20:03:40 +02:00
module . exports . io = io ;
// Must be after io instantiation
2021-10-08 14:03:52 +02:00
const { sendNotificationList , sendHeartbeatList , sendImportantHeartbeatList , sendInfo } = require ( "./client" ) ;
2021-09-16 16:48:28 +02:00
const { statusPageSocketHandler } = require ( "./socket-handlers/status-page-socket-handler" ) ;
2021-10-26 17:02:32 +02:00
const databaseSocketHandler = require ( "./socket-handlers/database-socket-handler" ) ;
2021-11-18 12:22:03 +02:00
const TwoFA = require ( "./2fa" ) ;
2022-03-10 15:34:30 +02:00
const StatusPage = require ( "./model/status_page" ) ;
2021-09-04 20:03:40 +02:00
app . use ( express . json ( ) ) ;
2021-07-09 13:33:22 +02:00
2021-10-19 08:26:10 +02:00
// Global Middleware
app . use ( function ( req , res , next ) {
2021-10-19 10:29:09 +02:00
if ( ! disableFrameSameOrigin ) {
2021-10-19 08:41:05 +02:00
res . setHeader ( "X-Frame-Options" , "SAMEORIGIN" ) ;
}
2021-10-19 08:26:10 +02:00
res . removeHeader ( "X-Powered-By" ) ;
next ( ) ;
} ) ;
2021-07-21 20:02:35 +02:00
/ * *
* Total WebSocket client connected to server currently , no actual use
* @ type { number }
* /
2021-06-25 15:55:49 +02:00
let totalClient = 0 ;
2021-07-21 20:02:35 +02:00
/ * *
* Use for decode the auth object
* @ type { null }
* /
2021-06-25 15:55:49 +02:00
let jwtSecret = null ;
2021-07-21 20:02:35 +02:00
/ * *
* Main monitor list
* @ type { { } }
* /
2021-06-25 15:55:49 +02:00
let monitorList = { } ;
2021-07-21 20:02:35 +02:00
/ * *
* Show Setup Page
* @ type { boolean }
* /
2021-07-11 07:47:57 +02:00
let needSetup = false ;
2021-06-25 15:55:49 +02:00
2021-07-28 17:40:50 +02:00
/ * *
* Cache Index HTML
* @ type { string }
* /
2021-10-19 19:32:19 +02:00
let indexHTML = "" ;
try {
indexHTML = fs . readFileSync ( "./dist/index.html" ) . toString ( ) ;
} catch ( e ) {
// "dist/index.html" is not necessary for development
if ( process . env . NODE _ENV !== "development" ) {
console . error ( "Error: Cannot find 'dist/index.html', did you install correctly?" ) ;
process . exit ( 1 ) ;
}
}
2021-07-28 17:40:50 +02:00
2021-09-15 14:40:26 +02:00
exports . entryPage = "dashboard" ;
2021-06-25 15:55:49 +02:00
( async ( ) => {
2021-09-20 10:29:18 +02:00
Database . init ( args ) ;
2021-11-04 17:19:31 +02:00
await initDatabase ( testMode ) ;
2021-06-25 15:55:49 +02:00
2021-09-15 14:40:26 +02:00
exports . entryPage = await setting ( "entryPage" ) ;
2021-07-27 18:52:31 +02:00
2021-09-15 14:40:26 +02:00
console . log ( "Adding route" ) ;
2021-07-27 18:52:31 +02:00
2021-09-11 13:40:03 +02:00
// ***************************
2021-07-27 18:52:31 +02:00
// Normal Router here
2021-09-11 13:40:03 +02:00
// ***************************
2021-07-27 18:52:31 +02:00
2021-11-02 15:48:46 +02:00
// Entry Page
app . get ( "/" , async ( _request , response ) => {
2022-03-17 16:44:47 +02:00
if ( exports . entryPage && exports . entryPage . startsWith ( "statusPage-" ) ) {
response . redirect ( "/status/" + exports . entryPage . replace ( "statusPage-" , "" ) ) ;
2021-11-02 15:48:46 +02:00
} else {
response . redirect ( "/dashboard" ) ;
}
} ) ;
2021-08-09 12:16:27 +02:00
// Robots.txt
app . get ( "/robots.txt" , async ( _request , response ) => {
let txt = "User-agent: *\nDisallow:" ;
if ( ! await setting ( "searchEngineIndex" ) ) {
txt += " /" ;
}
response . setHeader ( "Content-Type" , "text/plain" ) ;
response . send ( txt ) ;
} ) ;
2021-06-25 15:55:49 +02:00
2021-07-27 18:52:31 +02:00
// Basic Auth Router here
// Prometheus API metrics /metrics
// With Basic Auth using the first user's username/password
2021-08-09 12:16:27 +02:00
app . get ( "/metrics" , basicAuth , prometheusAPIMetrics ( ) ) ;
app . use ( "/" , express . static ( "dist" ) ) ;
2021-07-22 09:22:15 +02:00
2021-09-21 15:22:35 +02:00
// ./data/upload
app . use ( "/upload" , express . static ( Database . uploadDir ) ) ;
2021-09-14 06:10:25 +02:00
app . get ( "/.well-known/change-password" , async ( _ , response ) => {
response . redirect ( "https://github.com/louislam/uptime-kuma/wiki/Reset-Password-via-CLI" ) ;
} ) ;
2021-09-14 08:55:45 +02:00
// API Router
const apiRouter = require ( "./routers/api-router" ) ;
app . use ( apiRouter ) ;
2021-09-11 13:40:03 +02:00
2021-10-19 08:26:10 +02:00
// Universal Route Handler, must be at the end of all express routes.
2021-08-09 12:16:27 +02:00
app . get ( "*" , async ( _request , response ) => {
2021-09-21 15:22:35 +02:00
if ( _request . originalUrl . startsWith ( "/upload/" ) ) {
response . status ( 404 ) . send ( "File not found." ) ;
} else {
response . send ( indexHTML ) ;
}
2021-07-09 08:14:03 +02:00
} ) ;
2021-09-21 15:22:35 +02:00
console . log ( "Adding socket handler" ) ;
2021-07-27 19:47:13 +02:00
io . on ( "connection" , async ( socket ) => {
2021-07-13 12:08:12 +02:00
2021-10-08 14:03:52 +02:00
sendInfo ( socket ) ;
2021-07-13 12:08:12 +02:00
2021-06-25 15:55:49 +02:00
totalClient ++ ;
2021-07-11 07:47:57 +02:00
if ( needSetup ) {
2021-09-21 15:22:35 +02:00
console . log ( "Redirect to setup page" ) ;
socket . emit ( "setup" ) ;
2021-07-11 07:47:57 +02:00
}
2021-07-27 19:47:13 +02:00
socket . on ( "disconnect" , ( ) => {
2021-06-25 15:55:49 +02:00
totalClient -- ;
} ) ;
2021-07-30 05:33:44 +02:00
// ***************************
2021-09-11 13:40:03 +02:00
// Public Socket API
2021-07-30 05:33:44 +02:00
// ***************************
2021-06-25 15:55:49 +02:00
socket . on ( "loginByToken" , async ( token , callback ) => {
try {
let decoded = jwt . verify ( token , jwtSecret ) ;
2021-09-21 15:22:35 +02:00
console . log ( "Username from JWT: " + decoded . username ) ;
2021-06-25 15:55:49 +02:00
let user = await R . findOne ( "user" , " username = ? AND active = 1 " , [
2021-07-27 19:47:13 +02:00
decoded . username ,
2021-09-21 15:22:35 +02:00
] ) ;
2021-06-25 15:55:49 +02:00
if ( user ) {
2021-09-21 15:22:35 +02:00
debug ( "afterLogin" ) ;
2021-08-03 19:03:40 +02:00
2021-09-21 15:22:35 +02:00
afterLogin ( socket , user ) ;
2021-06-25 15:55:49 +02:00
2021-09-21 15:22:35 +02:00
debug ( "afterLogin ok" ) ;
2021-08-03 19:03:40 +02:00
2021-06-25 15:55:49 +02:00
callback ( {
ok : true ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-06-25 15:55:49 +02:00
} else {
callback ( {
ok : false ,
2021-07-27 19:47:13 +02:00
msg : "The user is inactive or deleted." ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-06-25 15:55:49 +02:00
}
} catch ( error ) {
callback ( {
ok : false ,
2021-07-27 19:47:13 +02:00
msg : "Invalid token." ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-06-25 15:55:49 +02:00
}
} ) ;
socket . on ( "login" , async ( data , callback ) => {
2021-09-21 15:22:35 +02:00
console . log ( "Login" ) ;
2021-06-25 15:55:49 +02:00
2021-10-23 10:35:13 +02:00
// Login Rate Limit
if ( ! await loginRateLimiter . pass ( callback ) ) {
return ;
}
2021-09-21 15:22:35 +02:00
let user = await login ( data . username , data . password ) ;
2021-07-13 16:22:46 +02:00
2021-07-27 18:52:31 +02:00
if ( user ) {
2021-10-19 00:42:33 +02:00
if ( user . twofa _status == 0 ) {
2021-10-29 20:35:05 +02:00
afterLogin ( socket , user ) ;
2021-09-09 21:10:31 +02:00
callback ( {
ok : true ,
token : jwt . sign ( {
username : data . username ,
} , jwtSecret ) ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-09-09 21:10:31 +02:00
}
2021-10-19 00:42:33 +02:00
if ( user . twofa _status == 1 && ! data . token ) {
2021-09-09 21:10:31 +02:00
callback ( {
tokenRequired : true ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-09-09 21:10:31 +02:00
}
if ( data . token ) {
2021-10-11 20:18:40 +02:00
let verify = notp . totp . verify ( data . token , user . twofa _secret , twofa _verification _opts ) ;
2021-09-09 21:10:31 +02:00
2021-10-19 00:42:33 +02:00
if ( user . twofa _last _token !== data . token && verify ) {
2021-10-29 20:35:05 +02:00
afterLogin ( socket , user ) ;
2021-10-19 00:42:33 +02:00
await R . exec ( "UPDATE `user` SET twofa_last_token = ? WHERE id = ? " , [
data . token ,
socket . userID ,
] ) ;
2021-09-09 21:10:31 +02:00
callback ( {
ok : true ,
token : jwt . sign ( {
username : data . username ,
} , jwtSecret ) ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-09-09 21:10:31 +02:00
} else {
callback ( {
ok : false ,
2021-09-11 14:34:12 +02:00
msg : "Invalid Token!" ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-09-09 21:10:31 +02:00
}
}
2021-06-25 15:55:49 +02:00
} else {
callback ( {
ok : false ,
2021-07-27 19:47:13 +02:00
msg : "Incorrect username or password." ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-06-25 15:55:49 +02:00
}
} ) ;
socket . on ( "logout" , async ( callback ) => {
2021-09-21 15:22:35 +02:00
socket . leave ( socket . userID ) ;
2021-06-25 15:55:49 +02:00
socket . userID = null ;
callback ( ) ;
2021-07-11 07:47:57 +02:00
} ) ;
2021-09-09 21:10:31 +02:00
socket . on ( "prepare2FA" , async ( callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-09-09 21:10:31 +02:00
let user = await R . findOne ( "user" , " id = ? AND active = 1 " , [
socket . userID ,
2021-09-21 15:22:35 +02:00
] ) ;
2021-09-09 21:10:31 +02:00
if ( user . twofa _status == 0 ) {
2021-10-18 11:37:11 +02:00
let newSecret = genSecret ( ) ;
2021-09-09 21:10:31 +02:00
let encodedSecret = base32 . encode ( newSecret ) ;
2021-09-30 18:23:18 +02:00
// Google authenticator doesn't like equal signs
// The fix is found at https://github.com/guyht/notp
// Related issue: https://github.com/louislam/uptime-kuma/issues/486
encodedSecret = encodedSecret . toString ( ) . replace ( /=/g , "" ) ;
2021-09-11 20:25:51 +02:00
let uri = ` otpauth://totp/Uptime%20Kuma: ${ user . username } ?secret= ${ encodedSecret } ` ;
2021-09-09 21:10:31 +02:00
await R . exec ( "UPDATE `user` SET twofa_secret = ? WHERE id = ? " , [
newSecret ,
socket . userID ,
] ) ;
callback ( {
ok : true ,
uri : uri ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-09-09 21:10:31 +02:00
} else {
callback ( {
ok : false ,
msg : "2FA is already enabled." ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-09-09 21:10:31 +02:00
}
} catch ( error ) {
callback ( {
ok : false ,
msg : "Error while trying to prepare 2FA." ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-09-09 21:10:31 +02:00
}
} ) ;
socket . on ( "save2FA" , async ( callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-09-09 21:10:31 +02:00
await R . exec ( "UPDATE `user` SET twofa_status = 1 WHERE id = ? " , [
socket . userID ,
] ) ;
callback ( {
ok : true ,
msg : "2FA Enabled." ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-09-09 21:10:31 +02:00
} catch ( error ) {
callback ( {
ok : false ,
msg : "Error while trying to change 2FA." ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-09-09 21:10:31 +02:00
}
} ) ;
socket . on ( "disable2FA" , async ( callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-11-18 12:22:03 +02:00
await TwoFA . disable2FA ( socket . userID ) ;
2021-09-09 21:10:31 +02:00
callback ( {
ok : true ,
msg : "2FA Disabled." ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-09-09 21:10:31 +02:00
} catch ( error ) {
callback ( {
ok : false ,
msg : "Error while trying to change 2FA." ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-09-09 21:10:31 +02:00
}
} ) ;
socket . on ( "verifyToken" , async ( token , callback ) => {
let user = await R . findOne ( "user" , " id = ? AND active = 1 " , [
socket . userID ,
2021-09-21 15:22:35 +02:00
] ) ;
2021-09-09 21:10:31 +02:00
2021-10-11 20:18:40 +02:00
let verify = notp . totp . verify ( token , user . twofa _secret , twofa _verification _opts ) ;
2021-09-09 21:10:31 +02:00
2021-10-19 00:42:33 +02:00
if ( user . twofa _last _token !== token && verify ) {
2021-09-09 21:10:31 +02:00
callback ( {
ok : true ,
valid : true ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-09-09 21:10:31 +02:00
} else {
callback ( {
ok : false ,
2021-09-11 14:34:19 +02:00
msg : "Invalid Token." ,
2021-09-09 21:10:31 +02:00
valid : false ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-09-09 21:10:31 +02:00
}
} ) ;
socket . on ( "twoFAStatus" , async ( callback ) => {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-09-09 21:10:31 +02:00
try {
let user = await R . findOne ( "user" , " id = ? AND active = 1 " , [
socket . userID ,
2021-09-21 15:22:35 +02:00
] ) ;
2021-09-09 21:10:31 +02:00
if ( user . twofa _status == 1 ) {
callback ( {
ok : true ,
status : true ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-09-09 21:10:31 +02:00
} else {
callback ( {
ok : true ,
status : false ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-09-09 21:10:31 +02:00
}
} catch ( error ) {
callback ( {
ok : false ,
msg : "Error while trying to get 2FA status." ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-09-09 21:10:31 +02:00
}
} ) ;
2021-07-11 07:47:57 +02:00
socket . on ( "needSetup" , async ( callback ) => {
callback ( needSetup ) ;
} ) ;
socket . on ( "setup" , async ( username , password , callback ) => {
try {
2021-10-21 16:54:04 +02:00
if ( passwordStrength ( password ) . value === "Too weak" ) {
throw new Error ( "Password is too weak. It should contain alphabetic and numeric characters. It must be at least 6 characters in length." ) ;
}
2021-07-11 07:47:57 +02:00
if ( ( await R . count ( "user" ) ) !== 0 ) {
2021-10-18 22:35:47 +02:00
throw new Error ( "Uptime Kuma has been initialized. If you want to run setup again, please delete the database." ) ;
2021-07-11 07:47:57 +02:00
}
2021-09-21 15:22:35 +02:00
let user = R . dispense ( "user" ) ;
2021-07-11 07:47:57 +02:00
user . username = username ;
2021-09-21 15:22:35 +02:00
user . password = passwordHash . generate ( password ) ;
await R . store ( user ) ;
2021-07-11 07:47:57 +02:00
needSetup = false ;
callback ( {
ok : true ,
2021-07-27 19:47:13 +02:00
msg : "Added Successfully." ,
2021-07-11 07:47:57 +02:00
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
2021-07-27 19:47:13 +02:00
msg : e . message ,
2021-07-11 07:47:57 +02:00
} ) ;
}
2021-06-25 15:55:49 +02:00
} ) ;
2021-07-30 05:33:44 +02:00
// ***************************
2021-06-25 15:55:49 +02:00
// Auth Only API
2021-07-30 05:33:44 +02:00
// ***************************
2021-06-25 15:55:49 +02:00
2021-07-30 13:18:26 +02:00
// Add a new monitor
2021-06-25 15:55:49 +02:00
socket . on ( "add" , async ( monitor , callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
let bean = R . dispense ( "monitor" ) ;
2021-07-09 11:55:48 +02:00
let notificationIDList = monitor . notificationIDList ;
delete monitor . notificationIDList ;
2021-08-06 20:10:38 +02:00
monitor . accepted _statuscodes _json = JSON . stringify ( monitor . accepted _statuscodes ) ;
delete monitor . accepted _statuscodes ;
2021-09-21 15:22:35 +02:00
bean . import ( monitor ) ;
bean . user _id = socket . userID ;
await R . store ( bean ) ;
2021-06-25 15:55:49 +02:00
2021-09-21 15:22:35 +02:00
await updateMonitorNotification ( bean . id , notificationIDList ) ;
2021-07-09 11:55:48 +02:00
2021-06-27 10:10:55 +02:00
await sendMonitorList ( socket ) ;
2021-11-16 05:35:04 +02:00
await startMonitor ( socket . userID , bean . id ) ;
2021-06-27 10:10:55 +02:00
2021-06-25 15:55:49 +02:00
callback ( {
ok : true ,
msg : "Added Successfully." ,
2021-07-27 19:47:13 +02:00
monitorID : bean . id ,
2021-06-25 15:55:49 +02:00
} ) ;
2021-06-27 10:10:55 +02:00
} catch ( e ) {
callback ( {
ok : false ,
2021-07-27 19:47:13 +02:00
msg : e . message ,
2021-06-27 10:10:55 +02:00
} ) ;
}
} ) ;
2021-07-30 13:18:26 +02:00
// Edit a monitor
2021-06-27 10:10:55 +02:00
socket . on ( "editMonitor" , async ( monitor , callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-06-27 10:10:55 +02:00
2021-09-21 15:22:35 +02:00
let bean = await R . findOne ( "monitor" , " id = ? " , [ monitor . id ] ) ;
2021-06-27 10:10:55 +02:00
if ( bean . user _id !== socket . userID ) {
2021-09-21 15:22:35 +02:00
throw new Error ( "Permission denied." ) ;
2021-06-27 10:10:55 +02:00
}
2022-01-07 06:26:26 +02:00
// Reset Prometheus labels
2022-01-07 08:29:42 +02:00
monitorList [ monitor . id ] ? . prometheus ( ) ? . remove ( ) ;
2022-01-07 06:26:26 +02:00
2021-09-21 15:22:35 +02:00
bean . name = monitor . name ;
bean . type = monitor . type ;
bean . url = monitor . url ;
2021-10-02 16:48:27 +02:00
bean . method = monitor . method ;
bean . body = monitor . body ;
bean . headers = monitor . headers ;
2021-11-04 11:12:06 +02:00
bean . basic _auth _user = monitor . basic _auth _user ;
bean . basic _auth _pass = monitor . basic _auth _pass ;
2021-09-21 15:22:35 +02:00
bean . interval = monitor . interval ;
2021-09-11 18:54:55 +02:00
bean . retryInterval = monitor . retryInterval ;
2022-01-23 16:22:57 +02:00
bean . resendInterval = monitor . resendInterval ;
2021-07-01 08:03:06 +02:00
bean . hostname = monitor . hostname ;
2021-07-19 18:23:06 +02:00
bean . maxretries = monitor . maxretries ;
2021-07-01 08:03:06 +02:00
bean . port = monitor . port ;
2021-07-01 11:19:28 +02:00
bean . keyword = monitor . keyword ;
2021-07-30 13:18:26 +02:00
bean . ignoreTls = monitor . ignoreTls ;
bean . upsideDown = monitor . upsideDown ;
2021-08-08 18:23:51 +02:00
bean . maxredirects = monitor . maxredirects ;
2021-08-05 13:04:38 +02:00
bean . accepted _statuscodes _json = JSON . stringify ( monitor . accepted _statuscodes ) ;
2021-08-23 00:05:48 +02:00
bean . dns _resolve _type = monitor . dns _resolve _type ;
bean . dns _resolve _server = monitor . dns _resolve _server ;
2021-09-30 18:09:43 +02:00
bean . pushToken = monitor . pushToken ;
2021-06-27 10:10:55 +02:00
2021-09-21 15:22:35 +02:00
await R . store ( bean ) ;
2021-06-27 10:10:55 +02:00
2021-09-21 15:22:35 +02:00
await updateMonitorNotification ( bean . id , monitor . notificationIDList ) ;
2021-07-09 11:55:48 +02:00
2021-06-27 10:10:55 +02:00
if ( bean . active ) {
2021-09-21 15:22:35 +02:00
await restartMonitor ( socket . userID , bean . id ) ;
2021-06-27 10:10:55 +02:00
}
2021-06-25 15:55:49 +02:00
await sendMonitorList ( socket ) ;
2021-06-27 10:10:55 +02:00
callback ( {
ok : true ,
msg : "Saved." ,
2021-07-27 19:47:13 +02:00
monitorID : bean . id ,
2021-06-27 10:10:55 +02:00
} ) ;
2021-06-25 15:55:49 +02:00
} catch ( e ) {
2021-09-21 15:22:35 +02:00
console . error ( e ) ;
2021-06-25 15:55:49 +02:00
callback ( {
ok : false ,
2021-07-27 19:47:13 +02:00
msg : e . message ,
2021-06-25 15:55:49 +02:00
} ) ;
}
} ) ;
2021-08-26 12:55:19 +02:00
socket . on ( "getMonitorList" , async ( callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-08-26 12:55:19 +02:00
await sendMonitorList ( socket ) ;
callback ( {
ok : true ,
} ) ;
} catch ( e ) {
2021-09-21 15:22:35 +02:00
console . error ( e ) ;
2021-08-26 12:55:19 +02:00
callback ( {
ok : false ,
msg : e . message ,
} ) ;
}
} ) ;
2021-06-25 15:55:49 +02:00
socket . on ( "getMonitor" , async ( monitorID , callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-06-25 15:55:49 +02:00
2021-09-21 15:22:35 +02:00
console . log ( ` Get Monitor: ${ monitorID } User ID: ${ socket . userID } ` ) ;
2021-06-25 15:55:49 +02:00
let bean = await R . findOne ( "monitor" , " id = ? AND user_id = ? " , [
monitorID ,
socket . userID ,
2021-09-21 15:22:35 +02:00
] ) ;
2021-06-25 15:55:49 +02:00
callback ( {
ok : true ,
2021-07-09 11:55:48 +02:00
monitor : await bean . toJSON ( ) ,
2021-06-25 15:55:49 +02:00
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
2021-07-27 19:47:13 +02:00
msg : e . message ,
2021-06-25 15:55:49 +02:00
} ) ;
}
} ) ;
2021-10-18 13:00:39 +02:00
socket . on ( "getMonitorBeats" , async ( monitorID , period , callback ) => {
try {
checkLogin ( socket ) ;
console . log ( ` Get Monitor Beats: ${ monitorID } User ID: ${ socket . userID } ` ) ;
2021-10-22 12:38:41 +02:00
if ( period == null ) {
throw new Error ( "Invalid period." ) ;
}
let list = await R . getAll ( `
SELECT * FROM heartbeat
WHERE monitor _id = ? AND
time > DATETIME ( 'now' , '-' || ? || ' hours' )
ORDER BY time ASC
` , [
monitorID ,
period ,
] ) ;
2021-10-18 13:00:39 +02:00
callback ( {
2021-10-22 12:38:41 +02:00
ok : true ,
data : list ,
2021-10-18 13:00:39 +02:00
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
msg : e . message ,
} ) ;
}
} ) ;
2021-06-25 15:55:49 +02:00
// Start or Resume the monitor
socket . on ( "resumeMonitor" , async ( monitorID , callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-06-25 15:55:49 +02:00
await startMonitor ( socket . userID , monitorID ) ;
await sendMonitorList ( socket ) ;
callback ( {
ok : true ,
2021-07-27 19:47:13 +02:00
msg : "Resumed Successfully." ,
2021-06-25 15:55:49 +02:00
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
2021-07-27 19:47:13 +02:00
msg : e . message ,
2021-06-25 15:55:49 +02:00
} ) ;
}
} ) ;
socket . on ( "pauseMonitor" , async ( monitorID , callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
await pauseMonitor ( socket . userID , monitorID ) ;
2021-06-25 15:55:49 +02:00
await sendMonitorList ( socket ) ;
callback ( {
ok : true ,
2021-07-27 19:47:13 +02:00
msg : "Paused Successfully." ,
2021-06-25 15:55:49 +02:00
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
2021-07-27 19:47:13 +02:00
msg : e . message ,
2021-06-25 15:55:49 +02:00
} ) ;
}
} ) ;
socket . on ( "deleteMonitor" , async ( monitorID , callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-06-25 15:55:49 +02:00
2021-09-21 15:22:35 +02:00
console . log ( ` Delete Monitor: ${ monitorID } User ID: ${ socket . userID } ` ) ;
2021-06-25 15:55:49 +02:00
if ( monitorID in monitorList ) {
monitorList [ monitorID ] . stop ( ) ;
2021-09-21 15:22:35 +02:00
delete monitorList [ monitorID ] ;
2021-06-25 15:55:49 +02:00
}
await R . exec ( "DELETE FROM monitor WHERE id = ? AND user_id = ? " , [
monitorID ,
2021-07-27 19:47:13 +02:00
socket . userID ,
2021-06-25 15:55:49 +02:00
] ) ;
callback ( {
ok : true ,
2021-07-27 19:47:13 +02:00
msg : "Deleted Successfully." ,
2021-06-25 15:55:49 +02:00
} ) ;
await sendMonitorList ( socket ) ;
2021-09-23 17:21:08 +02:00
// Clear heartbeat list on client
await sendImportantHeartbeatList ( socket , monitorID , true , true ) ;
2021-06-25 15:55:49 +02:00
} catch ( e ) {
2021-08-26 12:55:19 +02:00
callback ( {
ok : false ,
msg : e . message ,
} ) ;
}
} ) ;
socket . on ( "getTags" , async ( callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-08-26 12:55:19 +02:00
2021-09-21 15:22:35 +02:00
const list = await R . findAll ( "tag" ) ;
2021-08-26 12:55:19 +02:00
callback ( {
ok : true ,
tags : list . map ( bean => bean . toJSON ( ) ) ,
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
msg : e . message ,
} ) ;
}
} ) ;
socket . on ( "addTag" , async ( tag , callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-08-26 12:55:19 +02:00
2021-09-21 15:22:35 +02:00
let bean = R . dispense ( "tag" ) ;
bean . name = tag . name ;
bean . color = tag . color ;
await R . store ( bean ) ;
2021-08-26 12:55:19 +02:00
callback ( {
ok : true ,
tag : await bean . toJSON ( ) ,
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
msg : e . message ,
} ) ;
}
} ) ;
socket . on ( "editTag" , async ( tag , callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-08-26 12:55:19 +02:00
2021-09-21 15:22:35 +02:00
let bean = await R . findOne ( "monitor" , " id = ? " , [ tag . id ] ) ;
bean . name = tag . name ;
bean . color = tag . color ;
await R . store ( bean ) ;
2021-08-26 12:55:19 +02:00
callback ( {
ok : true ,
tag : await bean . toJSON ( ) ,
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
msg : e . message ,
} ) ;
}
} ) ;
socket . on ( "deleteTag" , async ( tagID , callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-08-26 12:55:19 +02:00
2021-09-21 15:22:35 +02:00
await R . exec ( "DELETE FROM tag WHERE id = ? " , [ tagID ] ) ;
2021-08-26 12:55:19 +02:00
callback ( {
ok : true ,
msg : "Deleted Successfully." ,
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
msg : e . message ,
} ) ;
}
} ) ;
socket . on ( "addMonitorTag" , async ( tagID , monitorID , value , callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-08-26 12:55:19 +02:00
await R . exec ( "INSERT INTO monitor_tag (tag_id, monitor_id, value) VALUES (?, ?, ?)" , [
tagID ,
monitorID ,
value ,
2021-09-21 15:22:35 +02:00
] ) ;
2021-08-26 12:55:19 +02:00
callback ( {
ok : true ,
msg : "Added Successfully." ,
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
msg : e . message ,
} ) ;
}
} ) ;
socket . on ( "editMonitorTag" , async ( tagID , monitorID , value , callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-08-26 12:55:19 +02:00
await R . exec ( "UPDATE monitor_tag SET value = ? WHERE tag_id = ? AND monitor_id = ?" , [
value ,
tagID ,
monitorID ,
2021-09-21 15:22:35 +02:00
] ) ;
2021-08-26 12:55:19 +02:00
callback ( {
ok : true ,
msg : "Edited Successfully." ,
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
msg : e . message ,
} ) ;
}
} ) ;
2021-09-10 08:22:34 +02:00
socket . on ( "deleteMonitorTag" , async ( tagID , monitorID , value , callback ) => {
2021-08-26 12:55:19 +02:00
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-08-26 12:55:19 +02:00
2021-09-10 08:22:34 +02:00
await R . exec ( "DELETE FROM monitor_tag WHERE tag_id = ? AND monitor_id = ? AND value = ?" , [
2021-08-26 12:55:19 +02:00
tagID ,
monitorID ,
2021-09-10 08:22:34 +02:00
value ,
2021-09-21 15:22:35 +02:00
] ) ;
2021-08-26 12:55:19 +02:00
// Cleanup unused Tags
await R . exec ( "delete from tag where ( select count(*) from monitor_tag mt where tag.id = mt.tag_id ) = 0" ) ;
callback ( {
ok : true ,
msg : "Deleted Successfully." ,
} ) ;
} catch ( e ) {
2021-06-25 15:55:49 +02:00
callback ( {
ok : false ,
2021-07-27 19:47:13 +02:00
msg : e . message ,
2021-06-25 15:55:49 +02:00
} ) ;
}
} ) ;
socket . on ( "changePassword" , async ( password , callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-06-25 15:55:49 +02:00
2021-10-21 16:54:04 +02:00
if ( ! password . newPassword ) {
2021-09-21 15:22:35 +02:00
throw new Error ( "Invalid new password" ) ;
2021-06-25 15:55:49 +02:00
}
2021-10-21 16:54:04 +02:00
if ( passwordStrength ( password . newPassword ) . value === "Too weak" ) {
throw new Error ( "Password is too weak. It should contain alphabetic and numeric characters. It must be at least 6 characters in length." ) ;
}
2021-06-25 15:55:49 +02:00
let user = await R . findOne ( "user" , " id = ? AND active = 1 " , [
2021-07-27 19:47:13 +02:00
socket . userID ,
2021-09-21 15:22:35 +02:00
] ) ;
2021-06-25 15:55:49 +02:00
if ( user && passwordHash . verify ( password . currentPassword , user . password ) ) {
2021-08-09 07:34:44 +02:00
user . resetPassword ( password . newPassword ) ;
2021-06-25 15:55:49 +02:00
callback ( {
ok : true ,
2021-07-27 19:47:13 +02:00
msg : "Password has been updated successfully." ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-06-25 15:55:49 +02:00
} else {
2021-09-21 15:22:35 +02:00
throw new Error ( "Incorrect current password" ) ;
2021-06-25 15:55:49 +02:00
}
} catch ( e ) {
callback ( {
ok : false ,
2021-07-27 19:47:13 +02:00
msg : e . message ,
2021-06-25 15:55:49 +02:00
} ) ;
}
} ) ;
2021-07-06 08:30:10 +02:00
2021-07-31 15:57:58 +02:00
socket . on ( "getSettings" , async ( callback ) => {
2021-07-06 08:30:10 +02:00
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-07-06 08:30:10 +02:00
callback ( {
ok : true ,
2021-07-31 15:57:58 +02:00
data : await getSettings ( "general" ) ,
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
msg : e . message ,
} ) ;
}
} ) ;
socket . on ( "setSettings" , async ( data , callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-07-31 15:57:58 +02:00
2021-09-15 14:40:26 +02:00
await setSettings ( "general" , data ) ;
exports . entryPage = data . entryPage ;
2021-07-31 15:57:58 +02:00
callback ( {
ok : true ,
msg : "Saved"
2021-07-06 08:30:10 +02:00
} ) ;
2021-10-08 14:03:52 +02:00
sendInfo ( socket ) ;
2021-07-06 08:30:10 +02:00
} catch ( e ) {
callback ( {
ok : false ,
2021-07-27 19:47:13 +02:00
msg : e . message ,
2021-07-06 08:30:10 +02:00
} ) ;
}
} ) ;
2021-07-09 08:14:03 +02:00
// Add or Edit
socket . on ( "addNotification" , async ( notification , notificationID , callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-07-09 08:14:03 +02:00
2021-09-21 15:22:35 +02:00
let notificationBean = await Notification . save ( notification , notificationID , socket . userID ) ;
await sendNotificationList ( socket ) ;
2021-07-09 08:14:03 +02:00
callback ( {
ok : true ,
msg : "Saved" ,
2021-09-09 15:24:29 +02:00
id : notificationBean . id ,
2021-07-09 08:14:03 +02:00
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
2021-07-27 19:47:13 +02:00
msg : e . message ,
2021-07-09 08:14:03 +02:00
} ) ;
}
} ) ;
socket . on ( "deleteNotification" , async ( notificationID , callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-07-09 08:14:03 +02:00
2021-09-21 15:22:35 +02:00
await Notification . delete ( notificationID , socket . userID ) ;
await sendNotificationList ( socket ) ;
2021-07-09 08:14:03 +02:00
callback ( {
ok : true ,
msg : "Deleted" ,
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
2021-07-27 19:47:13 +02:00
msg : e . message ,
2021-07-09 08:14:03 +02:00
} ) ;
}
} ) ;
socket . on ( "testNotification" , async ( notification , callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-07-09 08:14:03 +02:00
2021-09-21 15:22:35 +02:00
let msg = await Notification . send ( notification , notification . name + " Testing" ) ;
2021-07-09 08:14:03 +02:00
callback ( {
ok : true ,
2021-07-27 19:47:13 +02:00
msg ,
2021-07-09 08:14:03 +02:00
} ) ;
} catch ( e ) {
2021-09-21 15:22:35 +02:00
console . error ( e ) ;
2021-07-18 14:49:46 +02:00
2021-07-09 08:14:03 +02:00
callback ( {
ok : false ,
2021-07-27 19:47:13 +02:00
msg : e . message ,
2021-07-09 08:14:03 +02:00
} ) ;
}
} ) ;
2021-07-18 12:51:58 +02:00
socket . on ( "checkApprise" , async ( callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-07-18 12:51:58 +02:00
callback ( Notification . checkApprise ( ) ) ;
} catch ( e ) {
callback ( false ) ;
}
} ) ;
2021-08-03 19:03:40 +02:00
2021-09-11 21:53:17 +02:00
socket . on ( "uploadBackup" , async ( uploadedJSON , importHandle , callback ) => {
2021-09-01 17:09:32 +02:00
try {
2021-09-24 08:34:53 +02:00
checkLogin ( socket ) ;
2021-09-01 17:09:32 +02:00
2021-09-02 16:13:31 +02:00
let backupData = JSON . parse ( uploadedJSON ) ;
2021-09-01 17:09:32 +02:00
2021-09-24 08:34:53 +02:00
console . log ( ` Importing Backup, User ID: ${ socket . userID } , Version: ${ backupData . version } ` ) ;
2021-09-23 17:31:01 +02:00
2021-09-11 21:53:17 +02:00
let notificationListData = backupData . notificationList ;
let monitorListData = backupData . monitorList ;
2021-09-01 17:09:32 +02:00
2021-09-24 08:34:53 +02:00
let version17x = compareVersions . compare ( backupData . version , "1.7.0" , ">=" ) ;
2022-01-23 16:22:57 +02:00
let version1114 = compareVersions . compare ( backupData . version , "1.11.4" , ">=" ) ;
2021-09-24 08:34:53 +02:00
2021-09-17 03:25:18 +02:00
// If the import option is "overwrite" it'll clear most of the tables, except "settings" and "user"
2021-09-11 21:53:17 +02:00
if ( importHandle == "overwrite" ) {
2021-09-17 03:25:18 +02:00
// Stops every monitor first, so it doesn't execute any heartbeat while importing
2021-09-11 21:53:17 +02:00
for ( let id in monitorList ) {
2021-09-21 15:22:35 +02:00
let monitor = monitorList [ id ] ;
await monitor . stop ( ) ;
2021-09-01 17:09:32 +02:00
}
2021-09-11 21:53:17 +02:00
await R . exec ( "DELETE FROM heartbeat" ) ;
await R . exec ( "DELETE FROM monitor_notification" ) ;
await R . exec ( "DELETE FROM monitor_tls_info" ) ;
await R . exec ( "DELETE FROM notification" ) ;
2021-09-15 22:07:28 +02:00
await R . exec ( "DELETE FROM monitor_tag" ) ;
await R . exec ( "DELETE FROM tag" ) ;
2021-09-11 21:53:17 +02:00
await R . exec ( "DELETE FROM monitor" ) ;
2021-09-01 17:09:32 +02:00
}
2021-09-17 03:25:18 +02:00
// Only starts importing if the backup file contains at least one notification
2021-09-11 21:53:17 +02:00
if ( notificationListData . length >= 1 ) {
2021-09-17 03:25:18 +02:00
// Get every existing notification name and puts them in one simple string
2021-09-11 21:53:17 +02:00
let notificationNameList = await R . getAll ( "SELECT name FROM notification" ) ;
let notificationNameListString = JSON . stringify ( notificationNameList ) ;
2021-09-01 17:09:32 +02:00
2021-09-11 21:53:17 +02:00
for ( let i = 0 ; i < notificationListData . length ; i ++ ) {
2021-09-17 03:25:18 +02:00
// Only starts importing the notification if the import option is "overwrite", "keep" or "skip" but the notification doesn't exists
2021-09-11 21:53:17 +02:00
if ( ( importHandle == "skip" && notificationNameListString . includes ( notificationListData [ i ] . name ) == false ) || importHandle == "keep" || importHandle == "overwrite" ) {
2021-09-01 17:09:32 +02:00
2021-09-11 21:53:17 +02:00
let notification = JSON . parse ( notificationListData [ i ] . config ) ;
2021-09-21 15:22:35 +02:00
await Notification . save ( notification , null , socket . userID ) ;
2021-09-01 17:09:32 +02:00
2021-09-11 21:53:17 +02:00
}
}
}
2021-09-01 17:09:32 +02:00
2021-09-17 03:25:18 +02:00
// Only starts importing if the backup file contains at least one monitor
2021-09-11 21:53:17 +02:00
if ( monitorListData . length >= 1 ) {
2021-09-17 03:25:18 +02:00
// Get every existing monitor name and puts them in one simple string
2021-09-11 21:53:17 +02:00
let monitorNameList = await R . getAll ( "SELECT name FROM monitor" ) ;
let monitorNameListString = JSON . stringify ( monitorNameList ) ;
for ( let i = 0 ; i < monitorListData . length ; i ++ ) {
2021-09-17 03:25:18 +02:00
// Only starts importing the monitor if the import option is "overwrite", "keep" or "skip" but the notification doesn't exists
2021-09-11 21:53:17 +02:00
if ( ( importHandle == "skip" && monitorNameListString . includes ( monitorListData [ i ] . name ) == false ) || importHandle == "keep" || importHandle == "overwrite" ) {
2021-09-17 03:25:18 +02:00
// Define in here every new variable for monitors which where implemented after the first version of the Import/Export function (1.6.0)
// --- Start ---
// Define default values
2021-09-16 12:29:33 +02:00
let retryInterval = 0 ;
2022-01-23 16:22:57 +02:00
let resendInterval = 0 ;
2021-09-16 12:29:33 +02:00
2021-09-17 03:25:18 +02:00
/ *
Only replace the default value with the backup file data for the specific version , where it appears the first time
More information about that where "let version" will be defined
* /
2021-09-23 17:15:11 +02:00
if ( version17x ) {
2021-09-16 12:29:33 +02:00
retryInterval = monitorListData [ i ] . retryInterval ;
2021-09-15 22:07:28 +02:00
}
2022-01-23 18:35:53 +02:00
2022-01-23 16:22:57 +02:00
if ( version1114 ) {
resendInterval = monitorListData [ i ] . resendInterval ;
}
2021-09-15 22:07:28 +02:00
2021-09-17 03:25:18 +02:00
// --- End ---
2021-09-11 21:53:17 +02:00
let monitor = {
2021-09-17 03:25:18 +02:00
// Define the new variable from earlier here
2021-09-11 21:53:17 +02:00
name : monitorListData [ i ] . name ,
type : monitorListData [ i ] . type ,
url : monitorListData [ i ] . url ,
2021-10-02 16:48:27 +02:00
method : monitorListData [ i ] . method || "GET" ,
body : monitorListData [ i ] . body ,
headers : monitorListData [ i ] . headers ,
2021-11-04 11:12:06 +02:00
basic _auth _user : monitorListData [ i ] . basic _auth _user ,
basic _auth _pass : monitorListData [ i ] . basic _auth _pass ,
2021-09-11 21:53:17 +02:00
interval : monitorListData [ i ] . interval ,
2021-09-15 22:07:28 +02:00
retryInterval : retryInterval ,
2022-01-23 16:22:57 +02:00
resendInterval : resendInterval ,
2021-09-11 21:53:17 +02:00
hostname : monitorListData [ i ] . hostname ,
maxretries : monitorListData [ i ] . maxretries ,
port : monitorListData [ i ] . port ,
keyword : monitorListData [ i ] . keyword ,
ignoreTls : monitorListData [ i ] . ignoreTls ,
upsideDown : monitorListData [ i ] . upsideDown ,
maxredirects : monitorListData [ i ] . maxredirects ,
accepted _statuscodes : monitorListData [ i ] . accepted _statuscodes ,
dns _resolve _type : monitorListData [ i ] . dns _resolve _type ,
dns _resolve _server : monitorListData [ i ] . dns _resolve _server ,
notificationIDList : { } ,
2021-09-21 15:22:35 +02:00
} ;
2021-09-11 21:53:17 +02:00
2021-10-09 11:45:05 +02:00
if ( monitorListData [ i ] . pushToken ) {
monitor . pushToken = monitorListData [ i ] . pushToken ;
}
2021-09-21 15:22:35 +02:00
let bean = R . dispense ( "monitor" ) ;
2021-09-11 21:53:17 +02:00
let notificationIDList = monitor . notificationIDList ;
delete monitor . notificationIDList ;
monitor . accepted _statuscodes _json = JSON . stringify ( monitor . accepted _statuscodes ) ;
delete monitor . accepted _statuscodes ;
2021-09-21 15:22:35 +02:00
bean . import ( monitor ) ;
bean . user _id = socket . userID ;
await R . store ( bean ) ;
2021-09-11 21:53:17 +02:00
2021-09-17 03:25:18 +02:00
// Only for backup files with the version 1.7.0 or higher, since there was the tag feature implemented
2021-09-23 17:15:11 +02:00
if ( version17x ) {
2021-09-17 03:25:18 +02:00
// Only import if the specific monitor has tags assigned
2021-09-24 08:34:53 +02:00
for ( const oldTag of monitorListData [ i ] . tags ) {
// Check if tag already exists and get data ->
let tag = await R . findOne ( "tag" , " name = ?" , [
oldTag . name ,
] ) ;
let tagId ;
if ( ! tag ) {
// -> If it doesn't exist, create new tag from backup file
let beanTag = R . dispense ( "tag" ) ;
beanTag . name = oldTag . name ;
beanTag . color = oldTag . color ;
await R . store ( beanTag ) ;
tagId = beanTag . id ;
} else {
// -> If it already exist, set tagId to value from database
tagId = tag . id ;
2021-09-15 22:07:28 +02:00
}
2021-09-24 08:34:53 +02:00
// Assign the new created tag to the monitor
await R . exec ( "INSERT INTO monitor_tag (tag_id, monitor_id, value) VALUES (?, ?, ?)" , [
tagId ,
bean . id ,
oldTag . value ,
] ) ;
2021-09-15 22:07:28 +02:00
}
}
2021-09-24 08:34:53 +02:00
await updateMonitorNotification ( bean . id , notificationIDList ) ;
2021-09-11 21:53:17 +02:00
2021-09-17 03:25:18 +02:00
// If monitor was active start it immediately, otherwise pause it
2021-09-11 21:53:17 +02:00
if ( monitorListData [ i ] . active == 1 ) {
await startMonitor ( socket . userID , bean . id ) ;
} else {
await pauseMonitor ( socket . userID , bean . id ) ;
}
2021-09-01 17:09:32 +02:00
2021-09-07 23:32:25 +02:00
}
2021-09-01 17:09:32 +02:00
}
2021-09-21 15:22:35 +02:00
await sendNotificationList ( socket ) ;
2021-09-01 17:09:32 +02:00
await sendMonitorList ( socket ) ;
}
callback ( {
ok : true ,
msg : "Backup successfully restored." ,
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
msg : e . message ,
} ) ;
}
} ) ;
2021-08-29 18:47:01 +02:00
socket . on ( "clearEvents" , async ( monitorID , callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-08-29 18:47:01 +02:00
2021-09-21 15:22:35 +02:00
console . log ( ` Clear Events Monitor: ${ monitorID } User ID: ${ socket . userID } ` ) ;
2021-08-29 18:47:01 +02:00
await R . exec ( "UPDATE heartbeat SET msg = ?, important = ? WHERE monitor_id = ? " , [
"" ,
"0" ,
monitorID ,
] ) ;
2021-09-04 20:03:40 +02:00
await sendImportantHeartbeatList ( socket , monitorID , true , true ) ;
2021-08-29 18:47:01 +02:00
callback ( {
ok : true ,
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
msg : e . message ,
} ) ;
}
} ) ;
socket . on ( "clearHeartbeats" , async ( monitorID , callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-08-29 18:47:01 +02:00
2021-09-21 15:22:35 +02:00
console . log ( ` Clear Heartbeats Monitor: ${ monitorID } User ID: ${ socket . userID } ` ) ;
2021-08-29 18:47:01 +02:00
await R . exec ( "DELETE FROM heartbeat WHERE monitor_id = ?" , [
monitorID
] ) ;
2021-09-04 20:03:40 +02:00
await sendHeartbeatList ( socket , monitorID , true , true ) ;
2021-08-29 18:47:01 +02:00
callback ( {
ok : true ,
2021-09-01 00:36:24 +02:00
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
msg : e . message ,
} ) ;
}
} ) ;
socket . on ( "clearStatistics" , async ( callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-09-01 00:36:24 +02:00
2021-09-21 15:22:35 +02:00
console . log ( ` Clear Statistics User ID: ${ socket . userID } ` ) ;
2021-09-01 00:36:24 +02:00
await R . exec ( "DELETE FROM heartbeat" ) ;
callback ( {
ok : true ,
2021-08-29 18:47:01 +02:00
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
msg : e . message ,
} ) ;
}
} ) ;
2021-09-16 16:48:28 +02:00
// Status Page Socket Handler for admin only
statusPageSocketHandler ( socket ) ;
2021-10-26 17:02:32 +02:00
databaseSocketHandler ( socket ) ;
2021-09-16 16:48:28 +02:00
debug ( "added all socket handlers" ) ;
2021-08-03 19:03:40 +02:00
2021-08-04 07:31:17 +02:00
// ***************************
// Better do anything after added all socket handlers here
// ***************************
2021-09-21 15:22:35 +02:00
debug ( "check auto login" ) ;
2021-08-03 19:03:40 +02:00
if ( await setting ( "disableAuth" ) ) {
2021-09-21 15:22:35 +02:00
console . log ( "Disabled Auth: auto login to admin" ) ;
afterLogin ( socket , await R . findOne ( "user" ) ) ;
socket . emit ( "autoLogin" ) ;
2021-08-03 19:03:40 +02:00
} else {
2021-09-21 15:22:35 +02:00
debug ( "need auth" ) ;
2021-08-03 19:03:40 +02:00
}
2021-06-25 15:55:49 +02:00
} ) ;
2021-09-21 15:22:35 +02:00
console . log ( "Init the server" ) ;
2021-08-10 15:28:54 +02:00
server . once ( "error" , async ( err ) => {
console . error ( "Cannot listen: " + err . message ) ;
await Database . close ( ) ;
} ) ;
2021-08-10 10:36:21 +02:00
2021-08-10 15:28:54 +02:00
server . listen ( port , hostname , ( ) => {
2021-08-10 10:36:21 +02:00
if ( hostname ) {
console . log ( ` Listening on ${ hostname } : ${ port } ` ) ;
} else {
2021-08-10 16:00:29 +02:00
console . log ( ` Listening on ${ port } ` ) ;
2021-08-10 10:36:21 +02:00
}
2021-06-25 15:55:49 +02:00
startMonitors ( ) ;
2021-08-21 13:50:22 +02:00
checkVersion . startInterval ( ) ;
2021-10-05 13:13:57 +02:00
if ( testMode ) {
startUnitTest ( ) ;
}
2021-06-25 15:55:49 +02:00
} ) ;
2021-09-27 17:40:38 +02:00
initBackgroundJobs ( args ) ;
2021-06-25 15:55:49 +02:00
} ) ( ) ;
2021-07-09 11:55:48 +02:00
async function updateMonitorNotification ( monitorID , notificationIDList ) {
2021-08-10 15:37:51 +02:00
await R . exec ( "DELETE FROM monitor_notification WHERE monitor_id = ? " , [
2021-07-27 19:47:13 +02:00
monitorID ,
2021-09-21 15:22:35 +02:00
] ) ;
2021-07-09 11:55:48 +02:00
for ( let notificationID in notificationIDList ) {
if ( notificationIDList [ notificationID ] ) {
let relation = R . dispense ( "monitor_notification" ) ;
relation . monitor _id = monitorID ;
relation . notification _id = notificationID ;
2021-09-21 15:22:35 +02:00
await R . store ( relation ) ;
2021-07-09 11:55:48 +02:00
}
}
}
2021-06-25 15:55:49 +02:00
async function checkOwner ( userID , monitorID ) {
let row = await R . getRow ( "SELECT id FROM monitor WHERE id = ? AND user_id = ? " , [
monitorID ,
userID ,
2021-09-21 15:22:35 +02:00
] ) ;
2021-06-25 15:55:49 +02:00
if ( ! row ) {
throw new Error ( "You do not own this monitor." ) ;
}
}
async function sendMonitorList ( socket ) {
2021-06-29 10:06:20 +02:00
let list = await getMonitorJSONList ( socket . userID ) ;
2021-09-21 15:22:35 +02:00
io . to ( socket . userID ) . emit ( "monitorList" , list ) ;
2021-06-29 10:06:20 +02:00
return list ;
2021-06-25 15:55:49 +02:00
}
async function afterLogin ( socket , user ) {
socket . userID = user . id ;
2021-09-21 15:22:35 +02:00
socket . join ( user . id ) ;
2021-06-29 10:06:20 +02:00
2021-09-21 15:22:35 +02:00
let monitorList = await sendMonitorList ( socket ) ;
sendNotificationList ( socket ) ;
2021-08-08 19:58:56 +02:00
2021-08-23 12:52:55 +02:00
await sleep ( 500 ) ;
2022-03-21 09:28:59 +02:00
await StatusPage . sendStatusPageList ( io , socket ) ;
2021-08-23 12:52:55 +02:00
for ( let monitorID in monitorList ) {
await sendHeartbeatList ( socket , monitorID ) ;
}
for ( let monitorID in monitorList ) {
await sendImportantHeartbeatList ( socket , monitorID ) ;
}
for ( let monitorID in monitorList ) {
2021-09-21 15:22:35 +02:00
await Monitor . sendStats ( io , monitorID , user . id ) ;
2021-08-23 12:52:55 +02:00
}
2021-06-25 15:55:49 +02:00
}
async function getMonitorJSONList ( userID ) {
2021-06-27 10:10:55 +02:00
let result = { } ;
2021-06-25 15:55:49 +02:00
2021-08-23 12:52:55 +02:00
let monitorList = await R . find ( "monitor" , " user_id = ? ORDER BY weight DESC, name" , [
2021-07-27 19:47:13 +02:00
userID ,
2021-09-21 15:22:35 +02:00
] ) ;
2021-06-25 15:55:49 +02:00
for ( let monitor of monitorList ) {
2021-07-09 11:55:48 +02:00
result [ monitor . id ] = await monitor . toJSON ( ) ;
2021-06-25 15:55:49 +02:00
}
return result ;
}
2021-11-04 17:19:31 +02:00
async function initDatabase ( testMode = false ) {
2021-07-21 20:02:35 +02:00
if ( ! fs . existsSync ( Database . path ) ) {
2021-09-21 15:22:35 +02:00
console . log ( "Copying Database" ) ;
2021-07-21 20:02:35 +02:00
fs . copyFileSync ( Database . templatePath , Database . path ) ;
2021-07-11 07:47:57 +02:00
}
2021-10-18 22:35:47 +02:00
console . log ( "Connecting to the Database" ) ;
2021-11-04 17:19:31 +02:00
await Database . connect ( testMode ) ;
2021-09-21 15:22:35 +02:00
console . log ( "Connected" ) ;
2021-07-18 12:51:58 +02:00
2021-07-21 20:02:35 +02:00
// Patch the database
2021-09-21 15:22:35 +02:00
await Database . patch ( ) ;
2021-07-21 20:02:35 +02:00
2021-06-25 15:55:49 +02:00
let jwtSecretBean = await R . findOne ( "setting" , " `key` = ? " , [
2021-07-27 19:47:13 +02:00
"jwtSecret" ,
2021-06-25 15:55:49 +02:00
] ) ;
if ( ! jwtSecretBean ) {
2021-08-09 07:34:44 +02:00
console . log ( "JWT secret is not found, generate one." ) ;
2021-08-09 14:09:01 +02:00
jwtSecretBean = await initJWTSecret ( ) ;
2021-08-09 07:34:44 +02:00
console . log ( "Stored JWT secret into database" ) ;
2021-06-25 15:55:49 +02:00
} else {
2021-08-09 07:34:44 +02:00
console . log ( "Load JWT secret from database." ) ;
2021-06-25 15:55:49 +02:00
}
2021-07-21 20:02:35 +02:00
// If there is no record in user table, it is a new Uptime Kuma instance, need to setup
2021-07-11 07:47:57 +02:00
if ( ( await R . count ( "user" ) ) === 0 ) {
2021-09-21 15:22:35 +02:00
console . log ( "No user, need setup" ) ;
2021-07-11 07:47:57 +02:00
needSetup = true ;
}
2021-06-25 15:55:49 +02:00
jwtSecret = jwtSecretBean . value ;
}
async function startMonitor ( userID , monitorID ) {
2021-09-21 15:22:35 +02:00
await checkOwner ( userID , monitorID ) ;
2021-06-25 15:55:49 +02:00
2021-09-21 15:22:35 +02:00
console . log ( ` Resume Monitor: ${ monitorID } User ID: ${ userID } ` ) ;
2021-06-25 15:55:49 +02:00
await R . exec ( "UPDATE monitor SET active = 1 WHERE id = ? AND user_id = ? " , [
monitorID ,
2021-07-27 19:47:13 +02:00
userID ,
2021-06-25 15:55:49 +02:00
] ) ;
let monitor = await R . findOne ( "monitor" , " id = ? " , [
2021-07-27 19:47:13 +02:00
monitorID ,
2021-09-21 15:22:35 +02:00
] ) ;
2021-06-25 15:55:49 +02:00
2021-06-27 10:10:55 +02:00
if ( monitor . id in monitorList ) {
monitorList [ monitor . id ] . stop ( ) ;
}
2021-06-25 15:55:49 +02:00
monitorList [ monitor . id ] = monitor ;
2021-09-21 15:22:35 +02:00
monitor . start ( io ) ;
2021-06-25 15:55:49 +02:00
}
2021-06-27 10:10:55 +02:00
async function restartMonitor ( userID , monitorID ) {
2021-09-21 15:22:35 +02:00
return await startMonitor ( userID , monitorID ) ;
2021-06-27 10:10:55 +02:00
}
2021-06-25 15:55:49 +02:00
async function pauseMonitor ( userID , monitorID ) {
2021-09-21 15:22:35 +02:00
await checkOwner ( userID , monitorID ) ;
2021-06-25 15:55:49 +02:00
2021-09-21 15:22:35 +02:00
console . log ( ` Pause Monitor: ${ monitorID } User ID: ${ userID } ` ) ;
2021-06-25 15:55:49 +02:00
await R . exec ( "UPDATE monitor SET active = 0 WHERE id = ? AND user_id = ? " , [
monitorID ,
2021-07-27 19:47:13 +02:00
userID ,
2021-06-25 15:55:49 +02:00
] ) ;
if ( monitorID in monitorList ) {
monitorList [ monitorID ] . stop ( ) ;
}
}
/ * *
* Resume active monitors
* /
async function startMonitors ( ) {
2021-09-21 15:22:35 +02:00
let list = await R . find ( "monitor" , " active = 1 " ) ;
2021-06-25 15:55:49 +02:00
for ( let monitor of list ) {
monitorList [ monitor . id ] = monitor ;
2021-08-19 12:41:31 +02:00
}
2021-08-19 12:33:52 +02:00
2021-08-19 12:41:31 +02:00
for ( let monitor of list ) {
monitor . start ( io ) ;
2021-08-19 12:33:52 +02:00
// Give some delays, so all monitors won't make request at the same moment when just start the server.
await sleep ( getRandomInt ( 300 , 1000 ) ) ;
2021-06-25 15:55:49 +02:00
}
}
2021-07-15 19:44:51 +02:00
async function shutdownFunction ( signal ) {
2021-07-28 14:35:55 +02:00
console . log ( "Shutdown requested" ) ;
2021-07-27 19:47:13 +02:00
console . log ( "Called signal: " + signal ) ;
2021-07-15 19:44:51 +02:00
2021-09-21 15:22:35 +02:00
console . log ( "Stopping all monitors" ) ;
2021-07-15 19:44:51 +02:00
for ( let id in monitorList ) {
2021-09-21 15:22:35 +02:00
let monitor = monitorList [ id ] ;
monitor . stop ( ) ;
2021-07-15 19:44:51 +02:00
}
2021-07-21 20:02:35 +02:00
await sleep ( 2000 ) ;
await Database . close ( ) ;
2021-07-15 19:44:51 +02:00
}
function finalFunction ( ) {
2021-10-18 22:35:47 +02:00
console . log ( "Graceful shutdown successful!" ) ;
2021-07-15 19:44:51 +02:00
}
gracefulShutdown ( server , {
2021-07-27 19:47:13 +02:00
signals : "SIGINT SIGTERM" ,
2021-07-15 19:44:51 +02:00
timeout : 30000 , // timeout: 30 secs
development : false , // not in dev mode
forceExit : true , // triggers process.exit() at the end of shutdown process
onShutdown : shutdownFunction , // shutdown function (async) - e.g. for cleanup DB, ...
2021-07-27 19:47:13 +02:00
finally : finalFunction , // finally function (sync) - e.g. for logging
2021-07-15 19:44:51 +02:00
} ) ;
2021-08-17 09:32:34 +02:00
// Catch unexpected errors here
process . addListener ( "unhandledRejection" , ( error , promise ) => {
console . trace ( error ) ;
2021-10-29 12:24:47 +02:00
errorLog ( error , false ) ;
2021-08-17 09:32:34 +02:00
console . error ( "If you keep encountering errors, please report to https://github.com/louislam/uptime-kuma/issues" ) ;
} ) ;