2021-09-14 08:55:45 +02:00
let express = require ( "express" ) ;
2021-09-15 08:34:30 +02:00
const { allowDevAllOrigin , getSettings , setting } = require ( "../util-server" ) ;
2021-09-14 08:55:45 +02:00
const { R } = require ( "redbean-node" ) ;
2021-09-15 14:40:26 +02:00
const server = require ( "../server" ) ;
2021-09-21 18:58:22 +02:00
const apicache = require ( "../modules/apicache" ) ;
2021-09-22 09:10:08 +02:00
const Monitor = require ( "../model/monitor" ) ;
2021-09-30 18:09:43 +02:00
const dayjs = require ( "dayjs" ) ;
2022-01-23 21:33:39 +02:00
const { UP , MAINTENANCE , flipStatus , debug } = require ( "../../src/util" ) ;
2021-09-14 08:55:45 +02:00
let router = express . Router ( ) ;
2021-09-21 18:58:22 +02:00
let cache = apicache . middleware ;
2021-09-30 18:09:43 +02:00
let io = server . io ;
2021-09-21 18:58:22 +02:00
2021-09-15 14:40:26 +02:00
router . get ( "/api/entry-page" , async ( _ , response ) => {
allowDevAllOrigin ( response ) ;
response . json ( server . entryPage ) ;
} ) ;
2021-09-30 18:09:43 +02:00
router . get ( "/api/push/:pushToken" , async ( request , response ) => {
try {
2021-10-14 08:42:34 +02:00
2021-09-30 18:09:43 +02:00
let pushToken = request . params . pushToken ;
let msg = request . query . msg || "OK" ;
2021-10-14 16:32:15 +02:00
let ping = request . query . ping || null ;
2021-09-30 18:09:43 +02:00
let monitor = await R . findOne ( "monitor" , " push_token = ? AND active = 1 " , [
pushToken
] ) ;
if ( ! monitor ) {
throw new Error ( "Monitor not found or not active." ) ;
}
2021-12-08 08:59:59 +02:00
const previousHeartbeat = await Monitor . getPreviousHeartbeat ( monitor . id ) ;
2021-10-14 08:42:34 +02:00
2021-10-14 16:32:15 +02:00
let status = UP ;
if ( monitor . isUpsideDown ( ) ) {
status = flipStatus ( status ) ;
}
let isFirstBeat = true ;
let previousStatus = status ;
let duration = 0 ;
2021-09-30 18:09:43 +02:00
let bean = R . dispense ( "heartbeat" ) ;
bean . time = R . isoDateTime ( dayjs . utc ( ) ) ;
2021-10-14 16:32:15 +02:00
if ( previousHeartbeat ) {
isFirstBeat = false ;
previousStatus = previousHeartbeat . status ;
duration = dayjs ( bean . time ) . diff ( dayjs ( previousHeartbeat . time ) , "second" ) ;
}
2022-01-23 16:22:00 +02:00
const maintenance = await R . getAll ( "SELECT mm.*, maintenance.start_date, maintenance.end_date FROM monitor_maintenance mm JOIN maintenance ON mm.maintenance_id = maintenance.id WHERE mm.monitor_id = ? AND datetime(maintenance.start_date) <= datetime('now', 'localtime') AND datetime(maintenance.end_date) >= datetime('now', 'localtime')" , [ monitor . id ] ) ;
if ( maintenance . length !== 0 ) {
msg = "Monitor under maintenance" ;
status = MAINTENANCE ;
}
2021-10-14 16:32:15 +02:00
debug ( "PreviousStatus: " + previousStatus ) ;
debug ( "Current Status: " + status ) ;
bean . important = Monitor . isImportantBeat ( isFirstBeat , previousStatus , status ) ;
bean . monitor _id = monitor . id ;
bean . status = status ;
2021-09-30 18:09:43 +02:00
bean . msg = msg ;
2021-10-01 10:43:11 +02:00
bean . ping = ping ;
2021-10-14 16:32:15 +02:00
bean . duration = duration ;
2021-09-30 18:26:27 +02:00
2021-09-30 18:09:43 +02:00
await R . store ( bean ) ;
io . to ( monitor . user _id ) . emit ( "heartbeat" , bean . toJSON ( ) ) ;
Monitor . sendStats ( io , monitor . id , monitor . user _id ) ;
response . json ( {
ok : true ,
} ) ;
2021-10-14 16:32:15 +02:00
2022-01-23 16:22:00 +02:00
if ( Monitor . isImportantForNotification ( isFirstBeat , previousStatus , status ) ) {
2021-10-14 16:32:15 +02:00
await Monitor . sendNotification ( isFirstBeat , monitor , bean ) ;
}
2021-09-30 18:09:43 +02:00
} catch ( e ) {
response . json ( {
ok : false ,
msg : e . message
} ) ;
}
} ) ;
2021-09-14 08:55:45 +02:00
// Status Page Config
router . get ( "/api/status-page/config" , async ( _request , response ) => {
allowDevAllOrigin ( response ) ;
2021-09-15 08:34:30 +02:00
2021-09-20 14:44:07 +02:00
let config = await getSettings ( "statusPage" ) ;
2021-09-14 08:55:45 +02:00
if ( ! config . statusPageTheme ) {
config . statusPageTheme = "light" ;
}
2021-09-15 08:34:30 +02:00
if ( ! config . statusPagePublished ) {
config . statusPagePublished = true ;
}
2021-10-29 07:19:24 +02:00
if ( ! config . statusPageTags ) {
2021-11-01 02:23:46 +02:00
config . statusPageTags = false ;
2021-10-29 07:19:24 +02:00
}
2021-09-14 17:27:11 +02:00
if ( ! config . title ) {
config . title = "Uptime Kuma" ;
}
2021-09-14 08:55:45 +02:00
response . json ( config ) ;
} ) ;
2021-09-15 08:34:30 +02:00
// Status Page - Get the current Incident
// Can fetch only if published
router . get ( "/api/status-page/incident" , async ( _ , response ) => {
allowDevAllOrigin ( response ) ;
try {
await checkPublished ( ) ;
2021-09-19 17:24:51 +02:00
let incident = await R . findOne ( "incident" , " pin = 1 AND active = 1" ) ;
if ( incident ) {
incident = incident . toPublicJSON ( ) ;
}
2021-09-16 16:48:28 +02:00
response . json ( {
ok : true ,
2021-09-19 17:24:51 +02:00
incident ,
2021-09-19 13:04:51 +02:00
} ) ;
2021-09-15 08:34:30 +02:00
} catch ( error ) {
send403 ( response , error . message ) ;
}
} ) ;
2022-01-23 16:22:00 +02:00
// Status Page - Maintenance List
// Can fetch only if published
router . get ( "/api/status-page/maintenance-list" , async ( _request , response ) => {
allowDevAllOrigin ( response ) ;
try {
await checkPublished ( ) ;
const publicMaintenanceList = [ ] ;
let maintenanceBeanList = R . convertToBeans ( "maintenance" , await R . getAll ( `
SELECT maintenance . *
FROM maintenance
WHERE datetime ( maintenance . start _date ) <= datetime ( 'now' , 'localtime' )
AND datetime ( maintenance . end _date ) >= datetime ( 'now' , 'localtime' )
ORDER BY maintenance . end _date
` ));
for ( const bean of maintenanceBeanList ) {
publicMaintenanceList . push ( await bean . toPublicJSON ( ) ) ;
}
response . json ( publicMaintenanceList ) ;
} catch ( error ) {
send403 ( response , error . message ) ;
}
} ) ;
2021-09-14 08:55:45 +02:00
// Status Page - Monitor List
2021-09-15 08:34:30 +02:00
// Can fetch only if published
2021-09-21 18:58:22 +02:00
router . get ( "/api/status-page/monitor-list" , cache ( "5 minutes" ) , async ( _request , response ) => {
2021-09-14 08:55:45 +02:00
allowDevAllOrigin ( response ) ;
2021-09-15 08:34:30 +02:00
try {
await checkPublished ( ) ;
2021-09-19 13:04:51 +02:00
const publicGroupList = [ ] ;
2021-11-01 02:23:46 +02:00
const tagsVisible = ( await getSettings ( "statusPage" ) ) . statusPageTags ;
const list = await R . find ( "group" , " public = 1 ORDER BY weight " ) ;
2021-09-19 13:04:51 +02:00
for ( let groupBean of list ) {
2021-11-01 02:23:46 +02:00
let monitorGroup = await groupBean . toPublicJSON ( ) ;
if ( tagsVisible ) {
2021-11-03 00:46:53 +02:00
monitorGroup . monitorList = await Promise . all ( monitorGroup . monitorList . map ( async ( monitor ) => {
2021-10-29 07:19:24 +02:00
// Includes tags as an array in response, allows for tags to be displayed on public status page
2021-11-01 02:23:46 +02:00
const tags = await R . getAll (
` SELECT monitor_tag.monitor_id, monitor_tag.value, tag.name, tag.color
FROM monitor _tag
JOIN tag
ON monitor _tag . tag _id = tag . id
WHERE monitor _tag . monitor _id = ? ` , [monitor.id]
2021-12-08 08:59:59 +02:00
) ;
2021-12-08 09:04:18 +02:00
return {
... monitor ,
tags : tags
} ;
2021-11-01 02:23:46 +02:00
} ) ) ;
2021-10-29 07:19:24 +02:00
}
2021-10-28 04:07:19 +02:00
publicGroupList . push ( monitorGroup ) ;
2021-09-15 08:34:30 +02:00
}
2021-09-19 13:04:51 +02:00
response . json ( publicGroupList ) ;
2021-09-14 08:55:45 +02:00
2021-09-15 08:34:30 +02:00
} catch ( error ) {
send403 ( response , error . message ) ;
}
2021-09-14 08:55:45 +02:00
} ) ;
// Status Page Polling Data
2021-09-15 08:34:30 +02:00
// Can fetch only if published
2021-09-21 18:58:22 +02:00
router . get ( "/api/status-page/heartbeat" , cache ( "5 minutes" ) , async ( _request , response ) => {
2021-09-14 08:55:45 +02:00
allowDevAllOrigin ( response ) ;
2021-09-23 12:45:30 +02:00
2021-09-15 08:34:30 +02:00
try {
await checkPublished ( ) ;
2021-09-14 08:55:45 +02:00
2021-09-19 17:24:51 +02:00
let heartbeatList = { } ;
2021-09-22 09:10:08 +02:00
let uptimeList = { } ;
2021-09-19 17:24:51 +02:00
let monitorIDList = await R . getCol ( `
SELECT monitor _group . monitor _id FROM monitor _group , \ ` group \`
WHERE monitor _group . group _id = \ ` group \` .id
AND public = 1
` );
for ( let monitorID of monitorIDList ) {
let list = await R . getAll ( `
SELECT * FROM heartbeat
WHERE monitor _id = ?
ORDER BY time DESC
2021-09-22 09:10:08 +02:00
LIMIT 50
2021-09-19 17:24:51 +02:00
` , [
monitorID ,
] ) ;
list = R . convertToBeans ( "heartbeat" , list ) ;
heartbeatList [ monitorID ] = list . reverse ( ) . map ( row => row . toPublicJSON ( ) ) ;
2021-09-22 09:10:08 +02:00
const type = 24 ;
uptimeList [ ` ${ monitorID } _ ${ type } ` ] = await Monitor . calcUptime ( type , monitorID ) ;
2021-09-15 08:34:30 +02:00
}
response . json ( {
2021-09-19 17:24:51 +02:00
heartbeatList ,
2021-09-22 09:10:08 +02:00
uptimeList
2021-09-15 08:34:30 +02:00
} ) ;
2021-09-14 08:55:45 +02:00
2021-09-15 08:34:30 +02:00
} catch ( error ) {
send403 ( response , error . message ) ;
}
2021-09-14 08:55:45 +02:00
} ) ;
2021-09-15 08:34:30 +02:00
async function checkPublished ( ) {
if ( ! await isPublished ( ) ) {
throw new Error ( "The status page is not published" ) ;
}
}
2021-09-15 12:28:48 +02:00
/ * *
* Default is published
* @ returns { Promise < boolean > }
* /
2021-09-15 08:34:30 +02:00
async function isPublished ( ) {
2021-09-15 12:28:48 +02:00
const value = await setting ( "statusPagePublished" ) ;
if ( value === null ) {
return true ;
}
return value ;
2021-09-15 08:34:30 +02:00
}
function send403 ( res , msg = "" ) {
res . status ( 403 ) . json ( {
"status" : "fail" ,
"msg" : msg ,
2021-09-19 13:04:51 +02:00
} ) ;
2021-09-15 08:34:30 +02:00
}
2021-09-14 08:55:45 +02:00
module . exports = router ;