@ -5,6 +5,7 @@ var timezone = require('dayjs/plugin/timezone')
dayjs . extend ( utc )
dayjs . extend ( utc )
dayjs . extend ( timezone )
dayjs . extend ( timezone )
const axios = require ( "axios" ) ;
const axios = require ( "axios" ) ;
const { UP , DOWN , PENDING } = require ( "../util" ) ;
const { tcping , ping } = require ( "../util-server" ) ;
const { tcping , ping } = require ( "../util-server" ) ;
const { R } = require ( "redbean-node" ) ;
const { R } = require ( "redbean-node" ) ;
const { BeanModel } = require ( "redbean-node/dist/bean-model" ) ;
const { BeanModel } = require ( "redbean-node/dist/bean-model" ) ;
@ -49,19 +50,22 @@ class Monitor extends BeanModel {
let retries = 0 ;
let retries = 0 ;
const beat = async ( ) => {
const beat = async ( ) => {
if ( ! previousBeat ) {
if ( ! previousBeat ) {
previousBeat = await R . findOne ( "heartbeat" , " monitor_id = ? ORDER BY time DESC" , [
previousBeat = await R . findOne ( "heartbeat" , " monitor_id = ? ORDER BY time DESC" , [
this . id
this . id
] )
] )
}
}
const isFirstBeat = ! previousBeat ;
let bean = R . dispense ( "heartbeat" )
let bean = R . dispense ( "heartbeat" )
bean . monitor _id = this . id ;
bean . monitor _id = this . id ;
bean . time = R . isoDateTime ( dayjs . utc ( ) ) ;
bean . time = R . isoDateTime ( dayjs . utc ( ) ) ;
bean . status = 0 ;
bean . status = DOWN ;
// Duration
// Duration
if ( previous Beat) {
if ( ! isFirst Beat) {
bean . duration = dayjs ( bean . time ) . diff ( dayjs ( previousBeat . time ) , 'second' ) ;
bean . duration = dayjs ( bean . time ) . diff ( dayjs ( previousBeat . time ) , 'second' ) ;
} else {
} else {
bean . duration = 0 ;
bean . duration = 0 ;
@ -77,7 +81,7 @@ class Monitor extends BeanModel {
bean . ping = dayjs ( ) . valueOf ( ) - startTime ;
bean . ping = dayjs ( ) . valueOf ( ) - startTime ;
if ( this . type === "http" ) {
if ( this . type === "http" ) {
bean . status = 1 ;
bean . status = UP ;
} else {
} else {
let data = res . data ;
let data = res . data ;
@ -89,7 +93,7 @@ class Monitor extends BeanModel {
if ( data . includes ( this . keyword ) ) {
if ( data . includes ( this . keyword ) ) {
bean . msg += ", keyword is found"
bean . msg += ", keyword is found"
bean . status = 1 ;
bean . status = UP ;
} else {
} else {
throw new Error ( bean . msg + ", but keyword is not found" )
throw new Error ( bean . msg + ", but keyword is not found" )
}
}
@ -100,12 +104,12 @@ class Monitor extends BeanModel {
} else if ( this . type === "port" ) {
} else if ( this . type === "port" ) {
bean . ping = await tcping ( this . hostname , this . port ) ;
bean . ping = await tcping ( this . hostname , this . port ) ;
bean . msg = ""
bean . msg = ""
bean . status = 1 ;
bean . status = UP ;
} else if ( this . type === "ping" ) {
} else if ( this . type === "ping" ) {
bean . ping = await ping ( this . hostname ) ;
bean . ping = await ping ( this . hostname ) ;
bean . msg = ""
bean . msg = ""
bean . status = 1 ;
bean . status = UP ;
}
}
retries = 0 ;
retries = 0 ;
@ -113,24 +117,39 @@ class Monitor extends BeanModel {
} catch ( error ) {
} catch ( error ) {
if ( ( this . maxretries > 0 ) && ( retries < this . maxretries ) ) {
if ( ( this . maxretries > 0 ) && ( retries < this . maxretries ) ) {
retries ++ ;
retries ++ ;
bean . status = 2 ;
bean . status = PENDING ;
}
}
bean . msg = error . message ;
bean . msg = error . message ;
}
}
// * ? -> ANY STATUS = important [isFirstBeat]
// UP -> PENDING = not important
// * UP -> DOWN = important
// UP -> UP = not important
// PENDING -> PENDING = not important
// * PENDING -> DOWN = important
// PENDING -> UP = not important
// DOWN -> PENDING = this case not exists
// DOWN -> DOWN = not important
// * DOWN -> UP = important
let isImportant = isFirstBeat ||
( previousBeat . status === UP && bean . status === DOWN ) ||
( previousBeat . status === DOWN && bean . status === UP ) ||
( previousBeat . status === PENDING && bean . status === DOWN ) ;
// Mark as important if status changed, ignore pending pings,
// Mark as important if status changed, ignore pending pings,
// Don't notify if disrupted changes to up
// Don't notify if disrupted changes to up
if ( ( ! previousBeat ) || ( ( previousBeat . status !== bean . status ) && bean . status !== 2 && ! ( previousBeat . status === 2 && bean . status !== 0 ) ) ) {
if ( isImportant ) {
bean . important = true ;
bean . important = true ;
// Do not send if first beat is UP
// Send only if the first beat is DOWN
if ( previousBeat || bean . status !== 1 ) {
if ( ! isFirstBeat || bean . status === DOWN ) {
let notificationList = await R . getAll ( ` SELECT notification.* FROM notification, monitor_notification WHERE monitor_id = ? AND monitor_notification.notification_id = notification.id ` , [
let notificationList = await R . getAll ( ` SELECT notification.* FROM notification, monitor_notification WHERE monitor_id = ? AND monitor_notification.notification_id = notification.id ` , [
this . id
this . id
] )
] )
let text ;
let text ;
if ( bean . status === 1 ) {
if ( bean . status === UP ) {
text = "✅ Up"
text = "✅ Up"
} else {
} else {
text = "🔴 Down"
text = "🔴 Down"
@ -151,8 +170,10 @@ class Monitor extends BeanModel {
bean . important = false ;
bean . important = false ;
}
}
if ( bean . status === 1 ) {
if ( bean . status === UP ) {
console . info ( ` Monitor # ${ this . id } ' ${ this . name } ': Successful Response: ${ bean . ping } ms | Interval: ${ this . interval } seconds | Type: ${ this . type } ` )
console . info ( ` Monitor # ${ this . id } ' ${ this . name } ': Successful Response: ${ bean . ping } ms | Interval: ${ this . interval } seconds | Type: ${ this . type } ` )
} else if ( bean . status === PENDING ) {
console . warn ( ` Monitor # ${ this . id } ' ${ this . name } ': Pending: ${ bean . msg } | Type: ${ this . type } ` )
} else {
} else {
console . warn ( ` Monitor # ${ this . id } ' ${ this . name } ': Failing: ${ bean . msg } | Type: ${ this . type } ` )
console . warn ( ` Monitor # ${ this . id } ' ${ this . name } ': Failing: ${ bean . msg } | Type: ${ this . type } ` )
}
}