@ -3,9 +3,15 @@ const { parseTimeObject, parseTimeFromTimeObject, utcToLocal, localToUTC, log }
const { timeObjectToUTC , timeObjectToLocal } = require ( "../util-server" ) ;
const { R } = require ( "redbean-node" ) ;
const dayjs = require ( "dayjs" ) ;
const Cron = require ( "croner" ) ;
const { UptimeKumaServer } = require ( "../uptime-kuma-server" ) ;
const apicache = require ( "../modules/apicache" ) ;
class Maintenance extends BeanModel {
static statusList = { } ;
static jobList = { } ;
/ * *
* Return an object that ready to parse to JSON for public
* Only show necessary data to public
@ -15,16 +21,16 @@ class Maintenance extends BeanModel {
let dateRange = [ ] ;
if ( this . start _date ) {
dateRange . push ( utcToLocal ( this . start _date ) ) ;
dateRange . push ( this . start _date ) ;
if ( this . end _date ) {
dateRange . push ( utcToLocal ( this . end _date ) ) ;
dateRange . push ( this . end _date ) ;
}
}
let timeRange = [ ] ;
let startTime = timeObjectToLocal( parseTimeObject( this . start _time ) ) ;
let startTime = parseTimeObject( this . start _time ) ;
timeRange . push ( startTime ) ;
let endTime = timeObjectToLocal( parseTimeObject( this . end _time ) ) ;
let endTime = parseTimeObject( this . end _time ) ;
timeRange . push ( endTime ) ;
let obj = {
@ -39,12 +45,18 @@ class Maintenance extends BeanModel {
weekdays : ( this . weekdays ) ? JSON . parse ( this . weekdays ) : [ ] ,
daysOfMonth : ( this . days _of _month ) ? JSON . parse ( this . days _of _month ) : [ ] ,
timeslotList : [ ] ,
cron : this . cron ,
duration : this . duration ,
timezone : await this . getTimezone ( ) ,
timezoneOffset : await this . getTimezoneOffset ( ) ,
status : await this . getStatus ( ) ,
} ;
const timeslotList = await this . getTimeslotList ( ) ;
for ( let timeslot of timeslotList ) {
obj . timeslotList . push ( await timeslot . toPublicJSON ( ) ) ;
if ( this . strategy === "single" ) {
obj . timeslotList . push ( {
startDate : this . start _date ,
endDate : this . end _date ,
} ) ;
}
if ( ! Array . isArray ( obj . weekdays ) ) {
@ -55,54 +67,9 @@ class Maintenance extends BeanModel {
obj . daysOfMonth = [ ] ;
}
// Maintenance Status
if ( ! obj . active ) {
obj . status = "inactive" ;
} else if ( obj . strategy === "manual" ) {
obj . status = "under-maintenance" ;
} else if ( obj . timeslotList . length > 0 ) {
let currentTimestamp = dayjs ( ) . unix ( ) ;
for ( let timeslot of obj . timeslotList ) {
if ( dayjs . utc ( timeslot . startDate ) . unix ( ) <= currentTimestamp && dayjs . utc ( timeslot . endDate ) . unix ( ) >= currentTimestamp ) {
log . debug ( "timeslot" , "Timeslot ID: " + timeslot . id ) ;
log . debug ( "timeslot" , "currentTimestamp:" + currentTimestamp ) ;
log . debug ( "timeslot" , "timeslot.start_date:" + dayjs . utc ( timeslot . startDate ) . unix ( ) ) ;
log . debug ( "timeslot" , "timeslot.end_date:" + dayjs . utc ( timeslot . endDate ) . unix ( ) ) ;
obj . status = "under-maintenance" ;
break ;
}
}
if ( ! obj . status ) {
obj . status = "scheduled" ;
}
} else if ( obj . timeslotList . length === 0 ) {
obj . status = "ended" ;
} else {
obj . status = "unknown" ;
}
return obj ;
}
/ * *
* Only get future or current timeslots only
* @ returns { Promise < [ ] > }
* /
async getTimeslotList ( ) {
return R . convertToBeans ( "maintenance_timeslot" , await R . getAll ( `
SELECT maintenance _timeslot . *
FROM maintenance _timeslot , maintenance
WHERE maintenance _timeslot . maintenance _id = maintenance . id
AND maintenance . id = ?
AND $ { Maintenance . getActiveAndFutureMaintenanceSQLCondition ( ) }
` , [
this . id
] ) ) ;
}
/ * *
* Return an object that ready to parse to JSON
* @ param { string } timezone If not specified , the timeRange will be in UTC
@ -135,26 +102,10 @@ class Maintenance extends BeanModel {
}
/ * *
* Get the start date and time for maintenance
* @ returns { dayjs . Dayjs } Start date and time
* /
getStartDateTime ( ) {
let startOfTheDay = dayjs . utc ( this . start _date ) . format ( "HH:mm" ) ;
log . debug ( "timeslot" , "startOfTheDay: " + startOfTheDay ) ;
// Start Time
let startTimeSecond = dayjs . utc ( this . start _time , "HH:mm" ) . diff ( dayjs . utc ( startOfTheDay , "HH:mm" ) , "second" ) ;
log . debug ( "timeslot" , "startTime: " + startTimeSecond ) ;
// Bake StartDate + StartTime = Start DateTime
return dayjs . utc ( this . start _date ) . add ( startTimeSecond , "second" ) ;
}
/ * *
* Get the duraction of maintenance in seconds
* Get the duration of maintenance in seconds
* @ returns { number } Duration of maintenance
* /
get Duration( ) {
calcDuration ( ) {
let duration = dayjs . utc ( this . end _time , "HH:mm" ) . diff ( dayjs . utc ( this . start _time , "HH:mm" ) , "second" ) ;
// Add 24hours if it is across day
if ( duration < 0 ) {
@ -169,30 +120,24 @@ class Maintenance extends BeanModel {
* @ param { Object } obj Data to fill bean with
* @ returns { Bean } Filled bean
* /
static jsonToBean ( bean , obj ) {
static async jsonToBean ( bean , obj ) {
if ( obj . id ) {
bean . id = obj . id ;
}
// Apply timezone offset to timeRange, as it cannot apply automatically.
if ( obj . timeRange [ 0 ] ) {
timeObjectToUTC ( obj . timeRange [ 0 ] ) ;
if ( obj . timeRange [ 1 ] ) {
timeObjectToUTC ( obj . timeRange [ 1 ] ) ;
}
}
bean . title = obj . title ;
bean . description = obj . description ;
bean . strategy = obj . strategy ;
bean . interval _day = obj . intervalDay ;
bean . timezone = obj . timezone ;
bean . duration = obj . duration ;
bean . active = obj . active ;
if ( obj . dateRange [ 0 ] ) {
bean . start _date = localToUTC( obj. dateRange [ 0 ] ) ;
bean . start _date = obj. dateRange [ 0 ] ;
if ( obj . dateRange [ 1 ] ) {
bean . end _date = localToUTC( obj. dateRange [ 1 ] ) ;
bean . end _date = obj. dateRange [ 1 ] ;
}
}
@ -202,38 +147,111 @@ class Maintenance extends BeanModel {
bean . weekdays = JSON . stringify ( obj . weekdays ) ;
bean . days _of _month = JSON . stringify ( obj . daysOfMonth ) ;
await bean . generateCron ( ) ;
return bean ;
}
/ * *
* SQL conditions for active maintenance
* @ returns { string }
* Run the cron
* /
static getActiveMaintenanceSQLCondition ( ) {
return `
(
( maintenance _timeslot . start _date <= DATETIME ( 'now' )
AND maintenance _timeslot . end _date >= DATETIME ( 'now' )
AND maintenance . active = 1 )
OR
( maintenance . strategy = 'manual' AND active = 1 )
)
` ;
async run ( ) {
if ( Maintenance . jobList [ this . id ] ) {
log . debug ( "maintenance" , "Maintenance is already running, stop it first. id: " + this . id ) ;
this . stop ( ) ;
}
log . debug ( "maintenance" , "Run maintenance id: " + this . id ) ;
// 1.21.2 migration
if ( ! this . cron ) {
//this.generateCron();
//this.timezone = "UTC";
// this.duration =
if ( this . cron ) {
//await R.store(this);
}
}
if ( this . strategy === "single" ) {
Maintenance . jobList [ this . id ] = new Cron ( this . start _date , { timezone : await this . getTimezone ( ) } , ( ) => {
log . info ( "maintenance" , "Maintenance id: " + this . id + " is under maintenance now" ) ;
UptimeKumaServer . getInstance ( ) . sendMaintenanceListByUserID ( this . user _id ) ;
apicache . clear ( ) ;
} ) ;
}
}
/ * *
* SQL conditions for active and future maintenance
* @ returns { string }
* /
static getActiveAndFutureMaintenanceSQLCondition ( ) {
return `
(
( ( maintenance _timeslot . end _date >= DATETIME ( 'now' )
AND maintenance . active = 1 )
OR
( maintenance . strategy = 'manual' AND active = 1 ) )
)
` ;
stop ( ) {
if ( Maintenance . jobList [ this . id ] ) {
Maintenance . jobList [ this . id ] . stop ( ) ;
delete Maintenance . jobList [ this . id ] ;
}
}
async isUnderMaintenance ( ) {
return ( await this . getStatus ( ) ) === "under-maintenance" ;
}
async getTimezone ( ) {
if ( ! this . timezone ) {
return await UptimeKumaServer . getInstance ( ) . getTimezone ( ) ;
}
return this . timezone ;
}
async getTimezoneOffset ( ) {
return dayjs . tz ( dayjs ( ) , await this . getTimezone ( ) ) . format ( "Z" ) ;
}
async getStatus ( ) {
if ( ! this . active ) {
return "inactive" ;
}
if ( this . strategy === "manual" ) {
return "under-maintenance" ;
}
// Check if the maintenance is started
if ( this . start _date && dayjs ( ) . isBefore ( dayjs . tz ( this . start _date , await this . getTimezone ( ) ) ) ) {
return "scheduled" ;
}
// Check if the maintenance is ended
if ( this . end _date && dayjs ( ) . isAfter ( dayjs . tz ( this . end _date , await this . getTimezone ( ) ) ) ) {
return "ended" ;
}
if ( this . strategy === "single" ) {
return "under-maintenance" ;
}
if ( ! Maintenance . statusList [ this . id ] ) {
Maintenance . statusList [ this . id ] = "unknown" ;
}
return Maintenance . statusList [ this . id ] ;
}
setStatus ( status ) {
Maintenance . statusList [ this . id ] = status ;
}
async generateCron ( ) {
log . info ( "maintenance" , "Generate cron for maintenance id: " + this . id ) ;
if ( this . strategy === "recurring-interval" ) {
let array = this . start _time . split ( ":" ) ;
let hour = parseInt ( array [ 0 ] ) ;
let minute = parseInt ( array [ 1 ] ) ;
this . cron = minute + " " + hour + " */" + this . interval _day + " * *" ;
this . duration = this . calcDuration ( ) ;
log . debug ( "maintenance" , "Cron: " + this . cron ) ;
log . debug ( "maintenance" , "Duration: " + this . duration ) ;
}
}
}