diff --git a/client/src/Components/v2/design-elements/StatusBox.tsx b/client/src/Components/v2/design-elements/StatusBox.tsx index bf7979cca..a83ebcf5d 100644 --- a/client/src/Components/v2/design-elements/StatusBox.tsx +++ b/client/src/Components/v2/design-elements/StatusBox.tsx @@ -105,6 +105,29 @@ export const PausedStatusBox = ({ n }: { n: number }) => { /> ); }; +export const MaintenanceStatusBox = ({ n }: { n: number }) => { + const theme = useTheme(); + const { t } = useTranslation(); + return ( + + ); +}; +export const InitializingStatusBox = ({ n }: { n: number }) => { + const theme = useTheme(); + const { t } = useTranslation(); + return ( + + ); +}; + export const TotalChecksBox = ({ n }: { n: number }) => { const theme = useTheme(); const { t } = useTranslation(); @@ -138,15 +161,3 @@ export const UpChecksBox = ({ n }: { n: number }) => { /> ); }; - -export const InitializingStatusBox = ({ n }: { n: number }) => { - const theme = useTheme(); - const { t } = useTranslation(); - return ( - - ); -}; diff --git a/client/src/Components/v2/design-elements/StatusLabel.tsx b/client/src/Components/v2/design-elements/StatusLabel.tsx index d7212ddff..cf404421e 100644 --- a/client/src/Components/v2/design-elements/StatusLabel.tsx +++ b/client/src/Components/v2/design-elements/StatusLabel.tsx @@ -11,33 +11,24 @@ import { useTranslation } from "react-i18next"; export const ValueTypes = ["positive", "negative", "neutral"] as const; export type ValueType = (typeof ValueTypes)[number]; -export const StatusLabel = ({ - status, - isActive, - sx, -}: { - status: MonitorStatus; - isActive?: boolean; - sx?: SxProps; -}) => { +export const StatusLabel = ({ status, sx }: { status: MonitorStatus; sx?: SxProps }) => { const { t } = useTranslation(); const theme = useTheme(); const palette = getStatusPalette(status); - const determineStatus = ( - isActive: boolean | undefined, - status: MonitorStatus - ): string => { - if (isActive === false) { + const determineStatus = (status: MonitorStatus): string => { + if (status === "up") { + return t("pages.common.monitors.status.up"); + } else if (status === "down") { + return t("pages.common.monitors.status.down"); + } else if (status === "maintenance") { + return t("pages.common.monitors.status.maintenance"); + } else if (status === "paused") { return t("pages.common.monitors.status.paused"); + } else if (status === "initializing") { + return t("pages.common.monitors.status.initializing"); } - if (status === true) { - return t("pages.common.monitors.status.up"); - } - if (status === false) { - return t("pages.common.monitors.status.down"); - } return t("pages.common.monitors.status.initializing"); }; @@ -64,9 +55,7 @@ export const StatusLabel = ({ borderRadius="50%" marginRight="5px" /> - - {determineStatus(isActive, status)} - + {determineStatus(status)} ); }; diff --git a/client/src/Components/v2/monitors/HeaderMonitorsSummary.tsx b/client/src/Components/v2/monitors/HeaderMonitorsSummary.tsx new file mode 100644 index 000000000..b2a93a807 --- /dev/null +++ b/client/src/Components/v2/monitors/HeaderMonitorsSummary.tsx @@ -0,0 +1,29 @@ +import { + UpStatusBox, + DownStatusBox, + PausedStatusBox, + InitializingStatusBox, +} from "@/Components/v2/design-elements"; +import Stack from "@mui/material/Stack"; + +import type { MonitorsSummary } from "@/Types/Monitor"; +import { useTheme } from "@mui/material"; + +interface MonitorsSummaryProps { + summary: MonitorsSummary | null; +} + +export const HeaderMonitorsSummary = ({ summary }: MonitorsSummaryProps) => { + const theme = useTheme(); + return ( + + + + + + + ); +}; diff --git a/client/src/Components/v2/monitors/index.tsx b/client/src/Components/v2/monitors/index.tsx index e2856d6a9..9496395f6 100644 --- a/client/src/Components/v2/monitors/index.tsx +++ b/client/src/Components/v2/monitors/index.tsx @@ -11,3 +11,4 @@ export * from "./charts/PiePageSpeedLegend"; export * from "./charts/HistogramPageSpeedDetails"; export * from "./charts/HistogramPageSpeedDetailsTooltip"; export * from "./charts/HistogramInfrastructure"; +export * from "./HeaderMonitorsSummary"; diff --git a/client/src/Pages/Checks/Components/ChecksTable.tsx b/client/src/Pages/Checks/Components/ChecksTable.tsx index 6464be25c..bb1920ab2 100644 --- a/client/src/Pages/Checks/Components/ChecksTable.tsx +++ b/client/src/Pages/Checks/Components/ChecksTable.tsx @@ -6,7 +6,7 @@ import { } from "@/Components/v2/design-elements"; import Box from "@mui/material/Box"; import type { Header } from "@/Components/v2/design-elements/Table"; -import type { Monitor, MonitorStatus } from "@/Types/Monitor"; +import type { Monitor } from "@/Types/Monitor"; import { useTranslation } from "react-i18next"; import { formatDateWithTz } from "@/Utils/TimeUtils"; @@ -52,7 +52,7 @@ export const ChecksTable = ({ id: "status", content: "Status", render: (row) => { - return ; + return ; }, }, { diff --git a/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable.tsx b/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable.tsx index ca236f45f..b2bcadc1a 100644 --- a/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable.tsx +++ b/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable.tsx @@ -195,12 +195,7 @@ export const InfraMonitorsTable = ({ ), render: (row) => { - return ( - - ); + return ; }, }, { diff --git a/client/src/Pages/Infrastructure/Monitors/index.tsx b/client/src/Pages/Infrastructure/Monitors/index.tsx index 1d38dbdd3..01de1c09c 100644 --- a/client/src/Pages/Infrastructure/Monitors/index.tsx +++ b/client/src/Pages/Infrastructure/Monitors/index.tsx @@ -1,13 +1,8 @@ import Stack from "@mui/material/Stack"; import useMediaQuery from "@mui/material/useMediaQuery"; -import { - MonitorBasePageWithStates, - UpStatusBox, - DownStatusBox, - PausedStatusBox, -} from "@/Components/v2/design-elements"; +import { MonitorBasePageWithStates } from "@/Components/v2/design-elements"; import { HeaderCreate } from "@/Components/v2/common"; -import { ControlsFilter } from "@/Components/v2/monitors"; +import { ControlsFilter, HeaderMonitorsSummary } from "@/Components/v2/monitors"; import { TextField, Dialog } from "@/Components/v2/inputs"; import { useGet, useDelete } from "@/Hooks/UseApi"; @@ -99,7 +94,7 @@ const InfrastructureMonitors = () => { { refreshInterval: 5000, keepPreviousData: true } ); - const { summary, count } = monitorsWithChecksData ?? {}; + const { summary, count } = monitorsWithChecksData ?? { summary: null, count: 0 }; const isLoading = monitorsWithChecksLoading; // Check if any filters are active @@ -138,14 +133,7 @@ const InfrastructureMonitors = () => { isLoading={isLoading} isAdmin={isAdmin} /> - - - - - + { const { t } = useTranslation(); - const theme = useTheme(); const isAdmin = useIsAdmin(); const { deleteFn, loading: isDeleting } = useDelete(); @@ -48,8 +43,8 @@ const PageSpeedMonitorsPage = () => { const [rowsPerPage, setRowsPerPage] = useState(10); const monitors = monitorsData?.monitors; - const monitorsCount = monitorsData?.count; - const summary = monitorsData?.summary; + const monitorsCount = monitorsData?.count ?? 0; + const summary = monitorsData?.summary ?? null; const isLoading = monitorsIsLoading || settingsIsLoading; @@ -80,14 +75,7 @@ const PageSpeedMonitorsPage = () => { isLoading={isLoading} isAdmin={isAdmin} /> - - - - - + { )} {monitors?.map((monitor) => { - const status = determineState(monitor); return ( { )} - + {statusPage.showCharts !== false && ( diff --git a/client/src/Pages/StatusPage/Status/Components/StatusBar.tsx b/client/src/Pages/StatusPage/Status/Components/StatusBar.tsx index 8b3343249..b5c52b32b 100644 --- a/client/src/Pages/StatusPage/Status/Components/StatusBar.tsx +++ b/client/src/Pages/StatusPage/Status/Components/StatusBar.tsx @@ -12,16 +12,16 @@ const getMonitorStatus = (monitors: Monitor[], theme: Theme, t: Function) => { icon: , }; - if (monitors.every((monitor) => monitor.status === true)) { + if (monitors.every((monitor) => monitor.status === "up")) { monitorsStatus.msg = t("pages.statusPages.statusBar.allUp"); monitorsStatus.color = theme.palette.success.main; monitorsStatus.icon = ; return monitorsStatus; - } else if (monitors.every((monitor) => monitor.status === false)) { + } else if (monitors.every((monitor) => monitor.status === "down")) { monitorsStatus.msg = t("pages.statusPages.statusBar.allDown"); monitorsStatus.color = theme.palette.error.main; return monitorsStatus; - } else if (monitors.some((monitor) => monitor.status === false)) { + } else if (monitors.some((monitor) => monitor.status === "down")) { monitorsStatus.msg = t("pages.statusPages.statusBar.degraded"); monitorsStatus.color = theme.palette.warning.main; return monitorsStatus; diff --git a/client/src/Pages/Uptime/Details/Components/ChecksTable.tsx b/client/src/Pages/Uptime/Details/Components/ChecksTable.tsx index 2cfdc1f15..ceb8e1a28 100644 --- a/client/src/Pages/Uptime/Details/Components/ChecksTable.tsx +++ b/client/src/Pages/Uptime/Details/Components/ChecksTable.tsx @@ -15,7 +15,7 @@ const getHeaders = (t: Function, uiTimezone: string) => { id: "status", content: t("common.table.headers.status"), render: (row) => { - return ; + return ; }, }, { diff --git a/client/src/Pages/Uptime/Monitors/Components/UptimeMonitorsTable.tsx b/client/src/Pages/Uptime/Monitors/Components/UptimeMonitorsTable.tsx index 94c619672..75738e6ad 100644 --- a/client/src/Pages/Uptime/Monitors/Components/UptimeMonitorsTable.tsx +++ b/client/src/Pages/Uptime/Monitors/Components/UptimeMonitorsTable.tsx @@ -186,12 +186,7 @@ export const MonitorTable = ({ ), render: (row) => { - return ( - - ); + return ; }, }, { diff --git a/client/src/Pages/Uptime/Monitors/index.tsx b/client/src/Pages/Uptime/Monitors/index.tsx index 702bbd928..9e36ccc86 100644 --- a/client/src/Pages/Uptime/Monitors/index.tsx +++ b/client/src/Pages/Uptime/Monitors/index.tsx @@ -1,10 +1,5 @@ -import { ControlsFilter } from "@/Components/v2/monitors"; -import { - MonitorBasePageWithStates, - UpStatusBox, - DownStatusBox, - PausedStatusBox, -} from "@/Components/v2/design-elements"; +import { ControlsFilter, HeaderMonitorsSummary } from "@/Components/v2/monitors"; +import { MonitorBasePageWithStates } from "@/Components/v2/design-elements"; import { TextField, Dialog } from "@/Components/v2/inputs"; import Stack from "@mui/material/Stack"; import { MonitorTable } from "@/Pages/Uptime/Monitors/Components/UptimeMonitorsTable"; @@ -99,7 +94,11 @@ const UptimeMonitorsPage = () => { { refreshInterval: 5000, keepPreviousData: true } ); - const { monitors: monitorsWithChecks, summary, count } = monitorsWithChecksData ?? {}; + const { + monitors: monitorsWithChecks, + summary, + count, + } = monitorsWithChecksData ?? { monitors: null, summary: null, count: 0 }; // Delete hook const { deleteFn, loading: isDeleting } = useDelete(); @@ -150,14 +149,8 @@ const UptimeMonitorsPage = () => { isLoading={isLoading} isAdmin={isAdmin} /> - - - - - + + { - if (typeof monitor === "undefined") return "pending"; - if (monitor?.isActive === false) return "paused"; - if (monitor?.status === undefined) return "pending"; - return monitor?.status == true ? "up" : "down"; -}; - export const getMonitorPath = (type: MonitorType): string => { const pathMap: Record = { http: "uptime", @@ -24,10 +17,10 @@ export const getMonitorPath = (type: MonitorType): string => { }; export const getStatusPalette = (status: MonitorStatus): PaletteKey => { - if (status === true) { + if (status === "up") { return "success"; } - if (status === false) { + if (status === "down") { return "error"; } return "warning"; @@ -43,11 +36,11 @@ export const getValuePalette = (value: ValueType): PaletteKey => { }; export const getStatusColor = (status: MonitorStatus, theme: any): string => { - if (status === true) { + if (status === "up") { return theme.palette.success.light; } - if (status === false) { + if (status === "down") { return theme.palette.error.light; } @@ -101,9 +94,9 @@ export const getStatusPageHeaderConfig = ( return { paletteKey: "error", message: "No monitors available" }; } - const allUp = monitors.every((monitor) => monitor.status === true); - const anyDown = monitors.some((monitor) => monitor.status === false); - const allDown = monitors.every((monitor) => monitor.status === false); + const allUp = monitors.every((monitor) => monitor.status === "up"); + const anyDown = monitors.some((monitor) => monitor.status === "down"); + const allDown = monitors.every((monitor) => monitor.status === "down"); if (allUp) return { diff --git a/client/src/locales/en.json b/client/src/locales/en.json index d2a066c11..7eb833308 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -775,6 +775,7 @@ "status": { "down": "down", "initializing": "initializing", + "maintenance": "maintenance", "paused": "paused", "total": "total", "up": "up" diff --git a/server/src/config/services.ts b/server/src/config/services.ts index 4437e9176..9bf18ff1d 100644 --- a/server/src/config/services.ts +++ b/server/src/config/services.ts @@ -206,6 +206,7 @@ export const initializeServices = async ({ buffer: bufferService, incidentService, maintenanceWindowsRepository, + monitorsRepository, }); const superSimpleQueue = await SuperSimpleQueue.create({ diff --git a/server/src/db/models/Monitor.ts b/server/src/db/models/Monitor.ts index d4cc9a872..9111ce47f 100644 --- a/server/src/db/models/Monitor.ts +++ b/server/src/db/models/Monitor.ts @@ -1,9 +1,6 @@ import { Schema, model, Types, type UpdateQuery } from "mongoose"; import type { Monitor, MonitorMatchMethod, MonitorThresholds, CheckSnapshot } from "@/types/monitor.js"; -import { MonitorTypes } from "@/types/monitor.js"; -import Check from "./Check.js"; -import MonitorStats from "./MonitorStats.js"; -import StatusPage from "./StatusPage.js"; +import { MonitorTypes, MonitorStatuses } from "@/types/monitor.js"; type CheckSnapshotDocument = Omit & { createdAt: Date }; @@ -68,8 +65,9 @@ const MonitorSchema = new Schema( type: String, }, status: { - type: Boolean, - default: undefined, + type: String, + enum: MonitorStatuses, + default: "initializing", }, statusWindow: { type: [Boolean], diff --git a/server/src/repositories/monitors/MongoMonitorsRepository.ts b/server/src/repositories/monitors/MongoMonitorsRepository.ts index 9b30e5ac2..65d93421e 100644 --- a/server/src/repositories/monitors/MongoMonitorsRepository.ts +++ b/server/src/repositories/monitors/MongoMonitorsRepository.ts @@ -55,7 +55,7 @@ class MongoMonitorsRepository implements IMonitorsRepository { query.isActive = filter === "true"; break; case "status": - query.status = filter === "true"; + query.status = filter; break; case "type": query.type = filter; @@ -181,7 +181,13 @@ class MongoMonitorsRepository implements IMonitorsRepository { { $set: { isActive: { $not: "$isActive" }, - status: "$$REMOVE", + status: { + $cond: { + if: { $eq: ["$status", "paused"] }, + then: "initializing", + else: "paused", + }, + }, }, }, ], @@ -223,17 +229,27 @@ class MongoMonitorsRepository implements IMonitorsRepository { totalMonitors: { $sum: 1 }, upMonitors: { $sum: { - $cond: [{ $eq: ["$status", true] }, 1, 0], + $cond: [{ $eq: ["$status", "up"] }, 1, 0], }, }, downMonitors: { $sum: { - $cond: [{ $eq: ["$status", false] }, 1, 0], + $cond: [{ $eq: ["$status", "down"] }, 1, 0], }, }, pausedMonitors: { $sum: { - $cond: [{ $eq: ["$isActive", false] }, 1, 0], + $cond: [{ $eq: ["$status", "paused"] }, 1, 0], + }, + }, + initializingMonitors: { + $sum: { + $cond: [{ $eq: ["$status", "initializing"] }, 1, 0], + }, + }, + maintenanceMonitors: { + $sum: { + $cond: [{ $eq: ["$status", "maintenance"] }, 1, 0], }, }, }, @@ -242,7 +258,7 @@ class MongoMonitorsRepository implements IMonitorsRepository { ]; const [summary] = await MonitorModel.aggregate(pipeline); - return summary ?? { totalMonitors: 0, upMonitors: 0, downMonitors: 0, pausedMonitors: 0 }; + return summary ?? { totalMonitors: 0, upMonitors: 0, downMonitors: 0, pausedMonitors: 0, initializingMonitors: 0, maintenanceMonitors: 0 }; }; findGroupsByTeamId = async (teamId: string): Promise => { @@ -284,7 +300,7 @@ class MongoMonitorsRepository implements IMonitorsRepository { teamId: toStringId(doc.teamId), name: doc.name, description: doc.description ?? undefined, - status: doc.status ?? undefined, + status: doc.status ?? "initializing", statusWindow: doc.statusWindow ?? [], statusWindowSize: doc.statusWindowSize, statusWindowThreshold: doc.statusWindowThreshold, @@ -331,45 +347,13 @@ class MongoMonitorsRepository implements IMonitorsRepository { const notificationIds = (doc.notifications ?? []).map((notification: unknown) => toStringId(notification)); - const checks: Check[] = (doc.checks ?? []).map((check: any) => ({ - id: toStringId(check._id), - metadata: { - monitorId: toStringId(check.metadata?.monitorId), - teamId: toStringId(check.metadata?.teamId), - type: check.metadata?.type, - }, - status: check.status, - responseTime: check.responseTime, - timings: check.timings, - statusCode: check.statusCode, - message: check.message, - ack: check.ack, - ackAt: check.ackAt ?? null, - expiry: toDateString(check.expiry), - cpu: check.cpu, - memory: check.memory, - disk: check.disk, - host: check.host, - errors: check.errors, - capture: check.capture, - net: check.net, - accessibility: check.accessibility, - bestPractices: check.bestPractices, - seo: check.seo, - performance: check.performance, - audits: check.audits, - __v: check.__v, - createdAt: toDateString(check.createdAt), - updatedAt: toDateString(check.updatedAt), - })); - return { id: toStringId(doc._id), userId: toStringId(doc.userId), teamId: toStringId(doc.teamId), name: doc.name, description: doc.description ?? undefined, - status: doc.status ?? undefined, + status: doc.status ?? "initializing", statusWindow: doc.statusWindow ?? [], statusWindowSize: doc.statusWindowSize, statusWindowThreshold: doc.statusWindowThreshold, diff --git a/server/src/service/business/incidentService.ts b/server/src/service/business/incidentService.ts index 8116c893a..881f98f56 100644 --- a/server/src/service/business/incidentService.ts +++ b/server/src/service/business/incidentService.ts @@ -46,7 +46,7 @@ class IncidentService { handleIncident = async (monitor: Monitor, code: number): Promise => { const activeIncident = await this.incidentsRepository.findActiveByMonitorId(monitor.id, monitor.teamId); // Monitor is down, create an incident - if (monitor.status === false) { + if (monitor.status === "down") { if (activeIncident) { return activeIncident; } else { diff --git a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.ts b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.ts index cf3824d60..d5d275e59 100644 --- a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.ts +++ b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.ts @@ -3,7 +3,7 @@ import type { Monitor } from "@/types/monitor.js"; import { AppError } from "@/utils/AppError.js"; import { INetworkService, INotificationsService, IStatusService } from "@/service/index.js"; import IncidentService from "@/service/business/incidentService.js"; -import { IMaintenanceWindowsRepository } from "@/repositories/index.js"; +import { IMaintenanceWindowsRepository, IMonitorsRepository } from "@/repositories/index.js"; class SuperSimpleQueueHelper { static SERVICE_NAME = SERVICE_NAME; @@ -16,6 +16,7 @@ class SuperSimpleQueueHelper { private buffer: any; private incidentService: IncidentService; private maintenanceWindowsRepository: IMaintenanceWindowsRepository; + private monitorsRepository: IMonitorsRepository; constructor({ logger, @@ -26,6 +27,7 @@ class SuperSimpleQueueHelper { buffer, incidentService, maintenanceWindowsRepository, + monitorsRepository, }: { logger: any; networkService: INetworkService; @@ -35,6 +37,7 @@ class SuperSimpleQueueHelper { buffer: any; incidentService: IncidentService; maintenanceWindowsRepository: IMaintenanceWindowsRepository; + monitorsRepository: IMonitorsRepository; }) { this.logger = logger; this.networkService = networkService; @@ -44,6 +47,7 @@ class SuperSimpleQueueHelper { this.notificationsService = notificationsService; this.incidentService = incidentService; this.maintenanceWindowsRepository = maintenanceWindowsRepository; + this.monitorsRepository = monitorsRepository; } get serviceName() { @@ -59,7 +63,8 @@ class SuperSimpleQueueHelper { throw new AppError({ message: "No monitor id", service: SERVICE_NAME, method: "getMonitorJob" }); } - // Step 1. Check for maintenacne window, if found, skip the check + // Step 1. Check for maintenance window, if found, skip the check + const maintenanceWindowActive = await this.isInMaintenanceWindow(monitorId, teamId); if (maintenanceWindowActive) { this.logger.debug({ @@ -67,6 +72,9 @@ class SuperSimpleQueueHelper { service: SERVICE_NAME, method: "getMonitorJob", }); + if (monitor.status !== "maintenance") { + await this.monitorsRepository.updateById(monitorId, teamId, { status: "maintenance" }); + } return; } diff --git a/server/src/service/infrastructure/notificationProviders/utils.ts b/server/src/service/infrastructure/notificationProviders/utils.ts index f71933425..2fe22e834 100644 --- a/server/src/service/infrastructure/notificationProviders/utils.ts +++ b/server/src/service/infrastructure/notificationProviders/utils.ts @@ -163,7 +163,7 @@ export const buildHardwareEmail = async (emailService: any, monitor: Monitor, al }; export const buildEmail = async (emailService: any, monitor: Monitor): Promise => { - const template = monitor.status === true ? "serverIsUpTemplate" : "serverIsDownTemplate"; + const template = monitor.status === "up" ? "serverIsUpTemplate" : "serverIsDownTemplate"; const context = { monitor: monitor.name, url: monitor.url }; const html = await emailService.buildEmail(template, context); return html; diff --git a/server/src/service/infrastructure/notificationsService.ts b/server/src/service/infrastructure/notificationsService.ts index 78b1dc8ab..531014a7a 100644 --- a/server/src/service/infrastructure/notificationsService.ts +++ b/server/src/service/infrastructure/notificationsService.ts @@ -1,7 +1,8 @@ -import type { HardwareStatusPayload, Monitor, MonitorStatusResponse, Notification } from "@/types/index.js"; +import type { HardwareStatusPayload, Monitor, MonitorStatusResponse, Notification, MonitorStatus } from "@/types/index.js"; import { shouldSendHardwareAlert } from "@/service/infrastructure/notificationProviders/utils.js"; import { IMonitorsRepository, INotificationsRepository } from "@/repositories/index.js"; import { INotificationProvider } from "./notificationProviders/INotificationProvider.js"; + export interface INotificationsService { createNotification: (notificationData: Partial) => Promise; findById: (id: string, teamId: string) => Promise; @@ -11,7 +12,7 @@ export interface INotificationsService { handleNotifications: ( monitor: Monitor, monitorStatusResponse: MonitorStatusResponse, - prevStatus: boolean | undefined, + prevStatus: MonitorStatus, statusChanged: boolean ) => Promise; @@ -228,12 +229,7 @@ export class NotificationsService implements INotificationsService { return await this.emailProvider.sendAlert(notification, syntheticMonitor, baseStatus); }; - handleNotifications = async ( - monitor: Monitor, - monitorStatusResponse: MonitorStatusResponse, - prevStatus: boolean | undefined, - statusChanged: boolean - ) => { + handleNotifications = async (monitor: Monitor, monitorStatusResponse: MonitorStatusResponse, prevStatus: MonitorStatus, statusChanged: boolean) => { const { type } = monitor; const payload = monitorStatusResponse.payload as HardwareStatusPayload; // If this is a non-hardeware type monitor and status did not change, we're done diff --git a/server/src/service/infrastructure/statusService.ts b/server/src/service/infrastructure/statusService.ts index 0e1011278..506ad5396 100755 --- a/server/src/service/infrastructure/statusService.ts +++ b/server/src/service/infrastructure/statusService.ts @@ -2,7 +2,6 @@ import { IMonitorsRepository } from "@/repositories/index.js"; import MonitorStats from "../../db/models/MonitorStats.js"; import { CheckModel } from "@/db/models/index.js"; import type { - CheckErrorInfo, Monitor, MonitorStatusResponse, StatusChangeResult, @@ -15,7 +14,6 @@ const SERVICE_NAME = "StatusService"; export interface IStatusService { updateRunningStats({ monitor, networkResponse }: { monitor: Monitor; networkResponse: any }): Promise; - getStatusString(status: boolean | undefined): string; handleIncidentForCheck(check: any, monitor: Monitor, action: any, errorContext?: string): Promise; updateMonitorStatus( statusResponse: MonitorStatusResponse, @@ -39,7 +37,7 @@ export class StatusService implements IStatusService { return StatusService.SERVICE_NAME; } - async updateRunningStats({ monitor, networkResponse }: { monitor: Monitor; networkResponse: any }) { + async updateRunningStats({ monitor, networkResponse }: { monitor: Monitor; networkResponse: MonitorStatusResponse }) { try { const monitorId = monitor.id; const { responseTime, status } = networkResponse; @@ -60,7 +58,7 @@ export class StatusService implements IStatusService { // Update stats // Last response time - stats.lastResponseTime = responseTime; + stats.lastResponseTime = responseTime ?? 0; // Avg response time: let avgResponseTime = stats.avgResponseTime; @@ -111,12 +109,6 @@ export class StatusService implements IStatusService { } } - getStatusString = (status: boolean | undefined) => { - if (status === true) return "up"; - if (status === false) return "down"; - return "unknown"; - }; - handleIncidentForCheck = async (check: Check, monitor: Monitor, action: any, errorContext = "incident handling") => { try { let savedCheck; @@ -208,9 +200,7 @@ export class StatusService implements IStatusService { monitor.recentChecks.shift(); } - if (monitor.status === undefined || monitor.status === null) { - monitor.status = status; - } + monitor.status = status === true ? "up" : "down"; const prevStatus = monitor.status; let newStatus = monitor.status; @@ -233,13 +223,13 @@ export class StatusService implements IStatusService { const failureRate = (failures / monitor.statusWindow.length) * 100; // If threshold has been met and the monitor is not already down, mark down: - if (failureRate >= monitor.statusWindowThreshold && monitor.status !== false) { - newStatus = false; + if (failureRate >= monitor.statusWindowThreshold && monitor.status !== "down") { + newStatus = "down"; statusChanged = true; } // If the failure rate is below the threshold and the monitor is down, recover: - else if (failureRate < monitor.statusWindowThreshold && monitor.status === false) { - newStatus = true; + else if (failureRate < monitor.statusWindowThreshold && monitor.status === "down") { + newStatus = "up"; statusChanged = true; } diff --git a/server/src/types/monitor.ts b/server/src/types/monitor.ts index f6c0f83a7..384e14fe0 100644 --- a/server/src/types/monitor.ts +++ b/server/src/types/monitor.ts @@ -1,10 +1,12 @@ -import type { Check } from "@/types/check.js"; import type { CheckSnapshot } from "@/types/check.js"; export type { CheckSnapshot } from "@/types/check.js"; export const MonitorTypes = ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "unknown"] as const; export type MonitorType = (typeof MonitorTypes)[number]; +export const MonitorStatuses = ["up", "down", "paused", "initializing", "maintenance"] as const; +export type MonitorStatus = (typeof MonitorStatuses)[number]; + export interface MonitorThresholds { usage_cpu?: number; usage_memory?: number; @@ -20,7 +22,7 @@ export interface Monitor { teamId: string; name: string; description?: string; - status?: boolean; + status: MonitorStatus; statusWindow: boolean[]; statusWindowSize: number; statusWindowThreshold: number; @@ -56,6 +58,8 @@ export interface MonitorsSummary { upMonitors: number; downMonitors: number; pausedMonitors: number; + initializingMonitors: number; + maintenanceMonitors: number; } export interface MonitorsWithChecksByTeamIdResult { diff --git a/server/src/types/network.ts b/server/src/types/network.ts index 6bed40316..7fbfb79fe 100644 --- a/server/src/types/network.ts +++ b/server/src/types/network.ts @@ -11,6 +11,7 @@ import type { Monitor, MonitorMatchMethod, MonitorType, + MonitorStatus, } from "@/types/index.js"; export interface MonitorStatusResponse { @@ -106,7 +107,7 @@ export interface MonitorPayloadMap { export type StatusChangeResult = { monitor: Monitor; statusChanged: boolean; - prevStatus: boolean | undefined; + prevStatus: MonitorStatus; code: number; timestamp: number; };