@@ -217,6 +217,125 @@ export class StatusService implements IStatusService {
217217 return { nextStatus, transitioned, breaches, nextCounters } ;
218218 } ;
219219
220+ private updateStatusWindowAndRecentChecks = async ( monitor : Monitor , check : Check ) => {
221+ const checkSnapshot = this . toCheckSnapshot ( check ) ;
222+ return await this . monitorsRepository . updateStatusWindowAndChecks (
223+ monitor . id ,
224+ monitor . teamId ,
225+ check . status ,
226+ checkSnapshot ,
227+ monitor . statusWindowSize ,
228+ MAX_RECENT_CHECKS
229+ ) ;
230+ } ;
231+
232+ newUpdateMonitorStatus = async (
233+ statusResponse : MonitorStatusResponse <
234+ | PingStatusPayload
235+ | HttpStatusPayload
236+ | PageSpeedStatusPayload
237+ | HardwareStatusPayload
238+ | DockerStatusPayload
239+ | PortStatusPayload
240+ | GameStatusPayload
241+ | GrpcStatusPayload
242+ | undefined
243+ > ,
244+ check : Check
245+ ) : Promise < StatusChangeResult > => {
246+ try {
247+ const { monitorId, teamId, status, code } = statusResponse ;
248+ const monitor = await this . monitorsRepository . findById ( monitorId , teamId ) ;
249+
250+ // Update running stats
251+ await this . tryUpdateRunningStats ( monitor , statusResponse ) ;
252+ const updatedMonitor = await this . updateStatusWindowAndRecentChecks ( monitor , check ) ;
253+ const prevStatus = updatedMonitor . status ;
254+
255+ // Return early if not enough data points
256+ if ( updatedMonitor . statusWindow . length < updatedMonitor . statusWindowSize ) {
257+ updatedMonitor . status = status === true ? "up" : "down" ;
258+ const updated = await this . monitorsRepository . updateById ( updatedMonitor . id , updatedMonitor . teamId , { status : updatedMonitor . status } ) ;
259+ return {
260+ monitor : updated ,
261+ statusChanged : false ,
262+ prevStatus,
263+ code,
264+ timestamp : Date . now ( ) ,
265+ } ;
266+ }
267+
268+ // With a full window, a single raw check must not change UNLESS we are initializing. Otherwise, only the sliding-window threshold can trigger a transition.
269+ let newStatus : MonitorStatus ;
270+ if ( updatedMonitor . status === "initializing" ) {
271+ newStatus = status === true ? "up" : "down" ;
272+ } else {
273+ newStatus = updatedMonitor . status ;
274+ }
275+
276+ let statusChanged = false ;
277+
278+ // First evaluate reachability-based status changes, which apply to all monitor types and take precedence over hardware breaches.
279+ const reachabilityResult = this . computeReachability ( newStatus , updatedMonitor . statusWindow , updatedMonitor . statusWindowThreshold ) ;
280+ if ( reachabilityResult . transitioned ) {
281+ newStatus = reachabilityResult . nextStatus ;
282+ statusChanged = true ;
283+ }
284+
285+ // Evaluate hardware threshold breaches (only for hardware monitors with metrics payload)
286+ let thresholdBreaches : HardwareBreaches | undefined ;
287+ const hardwarePayload = statusResponse . payload as HardwareStatusPayload | undefined ;
288+ if ( updatedMonitor . type === "hardware" && hardwarePayload ?. data ) {
289+ const hardware = this . computeHardwareStatus ( {
290+ currentStatus : newStatus ,
291+ reachabilityDown : newStatus === "down" ,
292+ metrics : hardwarePayload . data ,
293+ thresholds : {
294+ cpu : updatedMonitor . cpuAlertThreshold ,
295+ memory : updatedMonitor . memoryAlertThreshold ,
296+ disk : updatedMonitor . diskAlertThreshold ,
297+ temp : updatedMonitor . tempAlertThreshold ,
298+ } ,
299+ counters : {
300+ cpu : updatedMonitor . cpuAlertCounter ,
301+ memory : updatedMonitor . memoryAlertCounter ,
302+ disk : updatedMonitor . diskAlertCounter ,
303+ temp : updatedMonitor . tempAlertCounter ,
304+ } ,
305+ } ) ;
306+
307+ updatedMonitor . cpuAlertCounter = hardware . nextCounters . cpu ;
308+ updatedMonitor . memoryAlertCounter = hardware . nextCounters . memory ;
309+ updatedMonitor . diskAlertCounter = hardware . nextCounters . disk ;
310+ updatedMonitor . tempAlertCounter = hardware . nextCounters . temp ;
311+ thresholdBreaches = hardware . breaches ;
312+ if ( hardware . transitioned ) {
313+ newStatus = hardware . nextStatus ;
314+ statusChanged = true ;
315+ }
316+ }
317+
318+ // Apply the final status
319+ updatedMonitor . status = newStatus ;
320+
321+ const updated = await this . monitorsRepository . updateById ( updatedMonitor . id , updatedMonitor . teamId , updatedMonitor ) ;
322+
323+ return {
324+ monitor : updated ,
325+ statusChanged,
326+ prevStatus,
327+ code,
328+ timestamp : new Date ( ) . getTime ( ) ,
329+ thresholdBreaches,
330+ } ;
331+ } catch ( error : unknown ) {
332+ throw new AppError ( {
333+ message : `Failed to update monitor with id ${ check . metadata . monitorId } with status: ${ error instanceof Error ? error . message : "Unknown error" } ` ,
334+ service : SERVICE_NAME ,
335+ method : "updateMonitorStatus" ,
336+ } ) ;
337+ }
338+ } ;
220339 updateMonitorStatus = async (
221340 statusResponse : MonitorStatusResponse <
222341 | PingStatusPayload
0 commit comments