diff --git a/client/src/pages/uptime/UptimeMonitors.tsx b/client/src/pages/uptime/UptimeMonitors.tsx index 93335fd35..429b3c323 100644 --- a/client/src/pages/uptime/UptimeMonitors.tsx +++ b/client/src/pages/uptime/UptimeMonitors.tsx @@ -88,6 +88,7 @@ const UptimeMonitors = () => { ); const monitors: IMonitor[] = response?.data?.monitors ?? ([] as IMonitor[]); + const checksMap = response?.data?.checksMap || {}; const count = response?.data?.count || 0; const upCount = response?.data?.upCount || 0; const downCount = response?.data?.downCount || 0; @@ -156,6 +157,7 @@ const UptimeMonitors = () => { /> ; refetch: Function; setSelectedMonitor: Function; sortField: string; @@ -206,9 +209,9 @@ export const MonitorTable = ({ content: t("monitors.uptime.table.headers.responseTime"), render: (row) => { if (chartType === "histogram") { - return ; + return ; } else { - return ; + return ; } }, }, diff --git a/client/src/types/monitor.ts b/client/src/types/monitor.ts index 489cef221..7a1cad935 100644 --- a/client/src/types/monitor.ts +++ b/client/src/types/monitor.ts @@ -29,7 +29,7 @@ export interface IMonitor { createdBy: string; interval: number; rejectUnauthorized: boolean; - latestChecks: ICheck[]; + latestStatuses: MonitorStatus[]; n: number; name: string; port: number; @@ -56,6 +56,7 @@ export interface IMonitorWithStats { upCount: number; pausedCount: number; monitors: IMonitor[]; + checksMap: Record; } export interface IMonitorWithMonitorStats { diff --git a/server/src/db/models/monitors/Monitor.ts b/server/src/db/models/monitors/Monitor.ts index 7ea98de26..0037f63ce 100644 --- a/server/src/db/models/monitors/Monitor.ts +++ b/server/src/db/models/monitors/Monitor.ts @@ -24,6 +24,7 @@ export const MonitorStatuses = [ "down", "paused", "initializing", + "resuming", ] as const; export type MonitorStatus = (typeof MonitorStatuses)[number]; @@ -56,7 +57,7 @@ export interface IMonitor extends Document { n: number; // Number of consecutive successes required to change status thresholds?: IMonitorThresholds; lastCheckedAt?: Date; - latestChecks: LatestCheck[]; + latestStatuses?: MonitorStatus[]; notificationChannels?: Types.ObjectId[]; createdBy: Types.ObjectId; updatedBy: Types.ObjectId; @@ -98,46 +99,7 @@ const MonitorSchema = new Schema( n: { type: Number, required: true, default: 1 }, thresholds: { type: ThresholdsSchema, required: false, default: undefined }, lastCheckedAt: { type: Date }, - latestChecks: { - type: [ - { - status: { - type: String, - required: true, - enum: MonitorStatuses, - }, - responseTime: { type: Number, required: true }, - checkedAt: { type: Date, required: true }, - dockerContainers: { - type: [ - { - container_id: { type: String }, - container_name: { type: String }, - status: { type: String }, - health: { - healthy: { type: Boolean }, - source: { type: String }, - message: { type: String }, - }, - running: { type: Boolean }, - base_image: { type: String }, - exposed_ports: [ - { - port: { type: String }, - protocol: { type: String }, - }, - ], - started_at: { type: Number }, - finished_at: { type: Number }, - }, - ], - required: false, - default: undefined, - }, - }, - ], - default: [], - }, + latestStatuses: { type: [String], enum: MonitorStatuses, default: [] }, notificationChannels: { type: [{ type: Schema.Types.ObjectId, ref: "NotificationChannel" }], default: [], diff --git a/server/src/repositories/checks/MongoChecksRepository.ts b/server/src/repositories/checks/MongoChecksRepository.ts new file mode 100644 index 000000000..9265c0b1e --- /dev/null +++ b/server/src/repositories/checks/MongoChecksRepository.ts @@ -0,0 +1,58 @@ +import { Check } from "@/db/models/index.js"; +import type { ICheck } from "@/db/models/index.js"; +import type { IChecksRepository } from "@/repositories/checks/IChecksRepository.js"; +import { CheckEntity } from "@/types/domain/check.js"; +import mongoose from "mongoose"; + +export class MongoChecksRepository implements IChecksRepository { + private toEntity = (check: ICheck): CheckEntity => { + return { + id: check._id.toString(), + metadata: { + monitorId: check.metadata.monitorId.toString(), + teamId: check.metadata.teamId.toString(), + type: check.metadata.type, + }, + status: check.status, + httpStatusCode: check.httpStatusCode, + message: check.message, + timings: check.timings, + errorMessage: check.errorMessage, + ackAt: check.ackAt, + ackBy: check.ackBy?.toString(), + system: check.system, + capture: check.capture, + lighthouse: check.lighthouse, + dockerContainers: check.dockerContainers, + createdAt: check.createdAt, + updatedAt: check.updatedAt, + expiry: check.expiry, + }; + }; + + findLatestByMonitorIds = async (monitorIds: string[]) => { + const checks = await Check.aggregate([ + { + $match: { + "metadata.monitorId": { + $in: monitorIds.map((id) => new mongoose.Types.ObjectId(id)), + }, + }, + }, + { $sort: { createdAt: -1 } }, + { + $group: { + _id: "$metadata.monitorId", + latestChecks: { $push: "$$ROOT" }, + }, + }, + { + $project: { + latestChecks: { $slice: [{ $ifNull: ["$latestChecks", []] }, 25] }, + }, + }, + ]); + console.log(checks); + return checks; + }; +} diff --git a/server/src/services/business/MonitorService.ts b/server/src/services/business/MonitorService.ts index d52cce1d1..3b7aee38c 100644 --- a/server/src/services/business/MonitorService.ts +++ b/server/src/services/business/MonitorService.ts @@ -189,48 +189,39 @@ class MonitorService implements IMonitorService { .skip(skip) .limit(rowsPerPage); - if ( - type.length === 1 && - (type[0] === "pagespeed" || - type[0] === "infrastructure" || - type[0] === "docker") - ) { - const monitorIds = monitors.map((m) => m._id); + const monitorIds = monitors.map((m) => m._id); - const checks = await Check.aggregate([ - { - $match: { - "metadata.monitorId": { $in: monitorIds }, - }, + const checks = await Check.aggregate([ + { + $match: { + "metadata.monitorId": { $in: monitorIds }, }, - { $sort: { createdAt: -1 } }, - { - $group: { - _id: "$metadata.monitorId", - latestChecks: { $push: "$$ROOT" }, - }, + }, + { $sort: { createdAt: -1 } }, + { + $group: { + _id: "$metadata.monitorId", + latestChecks: { $push: "$$ROOT" }, }, - { - $project: { - latestChecks: { $slice: [{ $ifNull: ["$latestChecks", []] }, 25] }, - }, + }, + { + $project: { + latestChecks: { $slice: [{ $ifNull: ["$latestChecks", []] }, 25] }, }, - ]); + }, + ]); - const checksMap = new Map( - checks.map((c: any) => [c._id.toString(), c.latestChecks]) - ); + const checksMap = new Map( + checks.map((c: any) => [c._id.toString(), c.latestChecks]) + ); - monitors.forEach((monitor) => { - monitor.latestChecks = checksMap.get(monitor._id.toString()) || []; - }); - } return { count: counts.total, upCount: counts.upCount, downCount: counts.downCount, pausedCount: counts.pausedCount, monitors, + checksMap: Object.fromEntries(checksMap), }; }; @@ -786,7 +777,7 @@ class MonitorService implements IMonitorService { status: { $cond: { if: { $eq: ["$status", "paused"] }, - then: "initializing", + then: "resuming", else: "paused", }, }, diff --git a/server/src/services/infrastructure/StatusService.ts b/server/src/services/infrastructure/StatusService.ts index a61101a1d..1bebefe3f 100644 --- a/server/src/services/infrastructure/StatusService.ts +++ b/server/src/services/infrastructure/StatusService.ts @@ -5,7 +5,7 @@ import { ICheck, } from "@/db/models/index.js"; import { StatusResponse } from "./NetworkService.js"; -import { LatestCheck } from "@/db/models/monitors/Monitor.js"; +// Using compact latestStatuses on Monitor for transition checks const emptyMetrics = (): ThresholdEvaluationResult["metrics"] => ({ cpu: { breached: false }, @@ -73,49 +73,31 @@ class StatusService implements IStatusService { const newStatus = statusResponse.status; monitor.lastCheckedAt = new Date(); - // Store latest checks for display - monitor.latestChecks = monitor.latestChecks || []; - const nextCheck: LatestCheck = { - status: newStatus, - responseTime: statusResponse.responseTime, - checkedAt: monitor.lastCheckedAt, - }; - // If this is a docker type, add the snapshot - if (monitor.type === "docker") { - const payload: any = statusResponse.payload; - nextCheck.dockerContainers = payload.data; - } - monitor.latestChecks.push(nextCheck); - - if (monitor.type === "docker") { + monitor.latestStatuses = monitor.latestStatuses || []; + const prevLen = monitor.latestStatuses.length; + monitor.latestStatuses.push(newStatus); + while (monitor.latestStatuses.length > MAX_LATEST_CHECKS) { + monitor.latestStatuses.shift(); } - while (monitor.latestChecks.length > MAX_LATEST_CHECKS) { - monitor.latestChecks.shift(); - } - - // Update monitor status if (monitor.status === "initializing") { monitor.status = newStatus; - return [await monitor.save(), monitor.latestChecks.length === 0]; - } else { - const { n } = monitor; - const latestChecks = monitor.latestChecks.slice(-n); - // Return early if not enough statuses to evaluate - if (latestChecks.length < n) { - return [await monitor.save(), false]; - } - - // If all different than current status, update status - const allDifferent = latestChecks.every( - (check) => check.status !== monitor.status - ); - if (allDifferent && monitor.status !== newStatus) { - monitor.status = newStatus; - } - - return [await monitor.save(), allDifferent]; + const lenAfter = monitor.latestStatuses.length; + const thresholdReachedNow = lenAfter >= monitor.n && prevLen < monitor.n; + return [await monitor.save(), thresholdReachedNow]; } + + const { n } = monitor; + const latestN = monitor.latestStatuses.slice(-n); + if (latestN.length < n) { + return [await monitor.save(), false]; + } + + const allDifferent = latestN.every((s) => s !== monitor.status); + if (allDifferent && monitor.status !== newStatus) { + monitor.status = newStatus; + } + return [await monitor.save(), allDifferent]; }; calculateAvgResponseTime = (