Merge pull request #3270 from bluewave-labs/feat/monitor-status

feat: monitor status
This commit is contained in:
Alexander Holliday
2026-02-11 12:30:59 -08:00
committed by GitHub
26 changed files with 171 additions and 201 deletions
@@ -105,6 +105,29 @@ export const PausedStatusBox = ({ n }: { n: number }) => {
/>
);
};
export const MaintenanceStatusBox = ({ n }: { n: number }) => {
const theme = useTheme();
const { t } = useTranslation();
return (
<StatusBox
label={t("pages.common.monitors.status.maintenance")}
n={n}
color={theme.palette.warning.light}
/>
);
};
export const InitializingStatusBox = ({ n }: { n: number }) => {
const theme = useTheme();
const { t } = useTranslation();
return (
<StatusBox
label={t("pages.common.monitors.status.initializing")}
n={n}
color={theme.palette.warning.light}
/>
);
};
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 (
<StatusBox
label={t("pages.common.monitors.status.initializing")}
n={n}
color={theme.palette.warning.light}
/>
);
};
@@ -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"
/>
<Typography textTransform={"capitalize"}>
{determineStatus(isActive, status)}
</Typography>
<Typography textTransform={"capitalize"}>{determineStatus(status)}</Typography>
</BaseBox>
);
};
@@ -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 (
<Stack
direction={{ xs: "column", md: "row" }}
gap={theme.spacing(8)}
>
<UpStatusBox n={summary?.upMonitors || 0} />
<DownStatusBox n={summary?.downMonitors || 0} />
<PausedStatusBox n={summary?.pausedMonitors || 0} />
<InitializingStatusBox n={summary?.initializingMonitors || 0} />
</Stack>
);
};
@@ -11,3 +11,4 @@ export * from "./charts/PiePageSpeedLegend";
export * from "./charts/HistogramPageSpeedDetails";
export * from "./charts/HistogramPageSpeedDetailsTooltip";
export * from "./charts/HistogramInfrastructure";
export * from "./HeaderMonitorsSummary";
@@ -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 <StatusLabel status={row.status as MonitorStatus} />;
return <StatusLabel status={row.status === true ? "up" : "down"} />;
},
},
{
@@ -195,12 +195,7 @@ export const InfraMonitorsTable = ({
</Typography>
),
render: (row) => {
return (
<StatusLabel
status={row.status}
isActive={row.isActive}
/>
);
return <StatusLabel status={row.status} />;
},
},
{
@@ -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}
/>
<Stack
direction={isSmall ? "column" : "row"}
gap={theme.spacing(8)}
>
<UpStatusBox n={summary?.upMonitors || 0} />
<DownStatusBox n={summary?.downMonitors || 0} />
<PausedStatusBox n={summary?.pausedMonitors || 0} />
</Stack>
<HeaderMonitorsSummary summary={summary} />
<Stack
direction={isSmall ? "column" : "row"}
justifyContent={isSmall ? "flex-start" : "space-between"}
+4 -16
View File
@@ -1,27 +1,22 @@
import {
MonitorBasePageWithStates,
UpStatusBox,
DownStatusBox,
PausedStatusBox,
PageSpeedKeyPriorityFallback,
} from "@/Components/v2/design-elements";
import { Dialog } from "@/Components/v2/inputs";
import { HeaderCreate } from "@/Components/v2/common";
import Stack from "@mui/material/Stack";
import { PageSpeedMonitorsTable } from "@/Pages/PageSpeed/Monitors/Components/PageSpeedMonitorsTable";
import type { Monitor } from "@/Types/Monitor";
import { useTheme } from "@mui/material";
import { useTranslation } from "react-i18next";
import { useState } from "react";
import { useIsAdmin } from "@/Hooks/useIsAdmin";
import { useGet, useDelete } from "@/Hooks/UseApi";
import type { MonitorsWithChecksResponse } from "@/Types/Monitor";
import type { AppSettingsResponse } from "@/Types/Settings";
import { HeaderMonitorsSummary } from "@/Components/v2/monitors";
const PageSpeedMonitorsPage = () => {
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}
/>
<Stack
direction={{ xs: "column", md: "row" }}
gap={theme.spacing(8)}
>
<UpStatusBox n={summary?.upMonitors || 0} />
<DownStatusBox n={summary?.downMonitors || 0} />
<PausedStatusBox n={summary?.pausedMonitors || 0} />
</Stack>
<HeaderMonitorsSummary summary={summary} />
<PageSpeedMonitorsTable
monitors={monitors || []}
refetch={refetch}
@@ -7,7 +7,6 @@ import { StatusLabel, BaseBox } from "@/Components/v2/design-elements";
import { SwitchComponent } from "@/Components/v2/inputs";
import { useTheme } from "@mui/material/styles";
import { determineState } from "@/Utils/MonitorUtils";
import { useSelector } from "react-redux";
import { useState } from "react";
import type { Monitor } from "@/Types/Monitor";
@@ -54,7 +53,6 @@ export const MonitorsList = ({ statusPage, monitors }: MonitorsListProps) => {
)}
{monitors?.map((monitor) => {
const status = determineState(monitor);
return (
<BaseBox
key={monitor.id}
@@ -87,10 +85,7 @@ export const MonitorsList = ({ statusPage, monitors }: MonitorsListProps) => {
</Typography>
)}
</Box>
<StatusLabel
status={status === "up"}
isActive={monitor.isActive}
/>
<StatusLabel status={monitor.status} />
</Stack>
{statusPage.showCharts !== false && (
<Box sx={{ overflow: "hidden", minWidth: 0, flex: 1 }}>
@@ -12,16 +12,16 @@ const getMonitorStatus = (monitors: Monitor[], theme: Theme, t: Function) => {
icon: <AlertTriangle size={24} />,
};
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 = <CircleCheck size={24} />;
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;
@@ -15,7 +15,7 @@ const getHeaders = (t: Function, uiTimezone: string) => {
id: "status",
content: t("common.table.headers.status"),
render: (row) => {
return <StatusLabel status={row.status} />;
return <StatusLabel status={row.status === true ? "up" : "down"} />;
},
},
{
@@ -186,12 +186,7 @@ export const MonitorTable = ({
</Stack>
),
render: (row) => {
return (
<StatusLabel
status={row.status}
isActive={row.isActive}
/>
);
return <StatusLabel status={row.status} />;
},
},
{
+9 -16
View File
@@ -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}
/>
<Stack
direction={isSmall ? "column" : "row"}
gap={theme.spacing(8)}
>
<UpStatusBox n={summary?.upMonitors || 0} />
<DownStatusBox n={summary?.downMonitors || 0} />
<PausedStatusBox n={summary?.pausedMonitors || 0} />
</Stack>
<HeaderMonitorsSummary summary={summary} />
<Stack
direction={isSmall ? "column" : "row"}
+12 -2
View File
@@ -1,5 +1,4 @@
import type { GroupedCheck, CheckSnapshot } from "@/Types/Check";
export type MonitorStatus = boolean | undefined;
export const MonitorTypes = [
"http",
@@ -13,6 +12,15 @@ export const MonitorTypes = [
] 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;
@@ -28,7 +36,7 @@ export interface Monitor {
teamId: string;
name: string;
description?: string;
status?: boolean;
status: MonitorStatus;
statusWindow: boolean[];
statusWindowSize: number;
statusWindowThreshold: number;
@@ -66,6 +74,8 @@ export interface MonitorsSummary {
upMonitors: number;
downMonitors: number;
pausedMonitors: number;
initializingMonitors: number;
maintenanceMonitors: number;
}
export interface MonitorsWithChecksResponse {
+7 -14
View File
@@ -2,13 +2,6 @@ import type { Monitor, MonitorStatus, MonitorType } from "@/Types/Monitor";
import type { PaletteKey } from "@/Utils/Theme/v2Theme";
import type { ValueType } from "@/Components/v2/design-elements/StatusLabel";
export const determineState = (monitor: Monitor) => {
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<MonitorType, string> = {
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 {
+1
View File
@@ -775,6 +775,7 @@
"status": {
"down": "down",
"initializing": "initializing",
"maintenance": "maintenance",
"paused": "paused",
"total": "total",
"up": "up"
+1
View File
@@ -206,6 +206,7 @@ export const initializeServices = async ({
buffer: bufferService,
incidentService,
maintenanceWindowsRepository,
monitorsRepository,
});
const superSimpleQueue = await SuperSimpleQueue.create({
+4 -6
View File
@@ -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<CheckSnapshot, "createdAt"> & { createdAt: Date };
@@ -68,8 +65,9 @@ const MonitorSchema = new Schema<MonitorDocument>(
type: String,
},
status: {
type: Boolean,
default: undefined,
type: String,
enum: MonitorStatuses,
default: "initializing",
},
statusWindow: {
type: [Boolean],
@@ -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<string[]> => {
@@ -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,
@@ -46,7 +46,7 @@ class IncidentService {
handleIncident = async (monitor: Monitor, code: number): Promise<Incident | null> => {
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 {
@@ -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;
}
@@ -163,7 +163,7 @@ export const buildHardwareEmail = async (emailService: any, monitor: Monitor, al
};
export const buildEmail = async (emailService: any, monitor: Monitor): Promise<string> => {
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;
@@ -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<Notification>) => Promise<Notification>;
findById: (id: string, teamId: string) => Promise<Notification>;
@@ -11,7 +12,7 @@ export interface INotificationsService {
handleNotifications: (
monitor: Monitor,
monitorStatusResponse: MonitorStatusResponse,
prevStatus: boolean | undefined,
prevStatus: MonitorStatus,
statusChanged: boolean
) => Promise<boolean>;
@@ -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
@@ -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<boolean>;
getStatusString(status: boolean | undefined): string;
handleIncidentForCheck(check: any, monitor: Monitor, action: any, errorContext?: string): Promise<void>;
updateMonitorStatus(
statusResponse: MonitorStatusResponse<PageSpeedStatusPayload | HardwareStatusPayload | undefined>,
@@ -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;
}
+6 -2
View File
@@ -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 {
+2 -1
View File
@@ -11,6 +11,7 @@ import type {
Monitor,
MonitorMatchMethod,
MonitorType,
MonitorStatus,
} from "@/types/index.js";
export interface MonitorStatusResponse<T = any> {
@@ -106,7 +107,7 @@ export interface MonitorPayloadMap {
export type StatusChangeResult = {
monitor: Monitor;
statusChanged: boolean;
prevStatus: boolean | undefined;
prevStatus: MonitorStatus;
code: number;
timestamp: number;
};