38 files changed
+330
-184
No files matched your search
@@ -1,6 +1,6 @@
|
||||
import Box from "@mui/material/Box";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import type { Check } from "@/Types/Check";
|
||||
import type { CheckSnapshot } from "@/Types/Check";
|
||||
import { getResponseColor } from "@/Utils/DataUtils";
|
||||
import { HeatmapResponseTimeTooltip } from "@/Components/v2/common/charts/HeatmapResponseTimeTooltip";
|
||||
import type { SxProps } from "@mui/material/styles";
|
||||
@@ -13,7 +13,7 @@ interface PlaceholderCheck {
|
||||
}
|
||||
|
||||
interface HeatmapResponseTimeProps {
|
||||
checks: Check[];
|
||||
checks: CheckSnapshot[];
|
||||
gap?: ResponsiveStyleValue<number | string>;
|
||||
availabilityCellSx?: SxProps;
|
||||
responseCellSx?: SxProps;
|
||||
@@ -34,7 +34,7 @@ export const HeatmapResponseTime = ({
|
||||
|
||||
const latestChecks = checks.slice(-25).reverse();
|
||||
|
||||
let data: Array<Check | PlaceholderCheck>;
|
||||
let data: Array<CheckSnapshot | PlaceholderCheck>;
|
||||
if (latestChecks.length !== 25) {
|
||||
const placeholders = Array(25 - latestChecks.length).fill({
|
||||
status: "placeholder" as const,
|
||||
|
||||
@@ -2,12 +2,14 @@ import Stack from "@mui/material/Stack";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { formatDateWithTz } from "@/Utils/TimeUtils";
|
||||
import type { Check } from "@/Types/Check";
|
||||
import type { CheckSnapshot } from "@/Types/Check";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useSelector } from "react-redux";
|
||||
import type { RootState } from "@/Types/state";
|
||||
import { useTranslation } from "react-i18next";
|
||||
type HeatmapCheck = Check | { status: "placeholder"; responseTime: 0; createdAt: "" };
|
||||
type HeatmapCheck =
|
||||
| CheckSnapshot
|
||||
| { status: "placeholder"; responseTime: 0; createdAt: "" };
|
||||
|
||||
export const HeatmapResponseTimeTooltip = ({
|
||||
children,
|
||||
@@ -53,9 +55,9 @@ export const HeatmapResponseTimeTooltip = ({
|
||||
<Typography>
|
||||
{formatDateWithTz(check?.createdAt, "ddd, MMMM D, YYYY, HH:mm A", uiTimezone)}
|
||||
</Typography>
|
||||
{check?.originalResponseTime && (
|
||||
{check?.responseTime && (
|
||||
<Typography>
|
||||
{t("common.labels.responseTime")}: {check.originalResponseTime.toFixed()} ms
|
||||
{t("common.labels.responseTime")}: {check.responseTime.toFixed()} ms
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import Box from "@mui/material/Box";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import type { ResponsiveStyleValue } from "@mui/system";
|
||||
import type { Check } from "@/Types/Check";
|
||||
import type { CheckSnapshot } from "@/Types/Check";
|
||||
import { HeatmapResponseTimeTooltip } from "./HeatmapResponseTimeTooltip";
|
||||
|
||||
interface HistogramResponseTimeProps {
|
||||
checks: Check[];
|
||||
checks: CheckSnapshot[];
|
||||
height?: ResponsiveStyleValue<number | string>;
|
||||
gap?: ResponsiveStyleValue<number | string>;
|
||||
}
|
||||
|
||||
@@ -2,16 +2,16 @@ import Stack from "@mui/material/Stack";
|
||||
import { Area, AreaChart, CartesianGrid, ResponsiveContainer, Tooltip } from "recharts";
|
||||
import { HistogramPageSpeedTooltip } from "@/Components/v2/monitors/charts/HistogramPageSpeedTooltip";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import type { Check } from "@/Types/Check";
|
||||
import type { CheckSnapshot } from "@/Types/Check";
|
||||
import type { MonitorStatus } from "@/Types/Monitor";
|
||||
|
||||
import { getStatusColor } from "@/Utils/MonitorUtils";
|
||||
|
||||
const processData = (checks: Check[]) => {
|
||||
const processData = (checks: CheckSnapshot[]) => {
|
||||
if (checks.length === 0) return [];
|
||||
const formattedData: { score: number; date: string }[] = [];
|
||||
|
||||
const calculateScore = (entry: Check) => {
|
||||
const calculateScore = (entry: CheckSnapshot) => {
|
||||
const accessibility = entry?.accessibility || 0;
|
||||
const bestPractices = entry?.bestPractices || 0;
|
||||
const performance = entry?.performance || 0;
|
||||
@@ -32,7 +32,7 @@ export const HistogramPageSpeed = ({
|
||||
checks,
|
||||
status,
|
||||
}: {
|
||||
checks: Check[];
|
||||
checks: CheckSnapshot[];
|
||||
status: MonitorStatus;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
import { HistogramPageSpeedScoresTooltip } from "@/Components/v2/monitors";
|
||||
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import type { Check } from "@/Types/Check";
|
||||
import type { CheckSnapshot } from "@/Types/Check";
|
||||
import type { Palette } from "@mui/material/styles";
|
||||
type PaletteColorKey = Extract<
|
||||
keyof Palette,
|
||||
@@ -52,7 +52,7 @@ export const HistogramPageSpeedDetails = ({
|
||||
checks,
|
||||
range,
|
||||
}: {
|
||||
checks: Check[];
|
||||
checks: CheckSnapshot[];
|
||||
range: string;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { BaseChart } from "@/Components/v2/design-elements";
|
||||
import { FileText } from "lucide-react";
|
||||
import type { Check, CheckAudits } from "@/Types/Check";
|
||||
import type { CheckAudits, CheckSnapshot } from "@/Types/Check";
|
||||
import { Pie, PieChart, ResponsiveContainer, Label } from "recharts";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Box from "@mui/material/Box";
|
||||
@@ -30,7 +30,7 @@ const CenterLabel = ({ viewBox, value }: any) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const PiePageSpeed = ({ latestCheck }: { latestCheck?: Check }) => {
|
||||
export const PiePageSpeed = ({ latestCheck }: { latestCheck?: CheckSnapshot }) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const [hoverTitle, setHoverTitle] = useState<string | null>(null);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { BarChart3 } from "lucide-react";
|
||||
import { BaseChart } from "@/Components/v2/design-elements";
|
||||
import Typography from "@mui/material/Typography";
|
||||
|
||||
import type { Check } from "@/Types/Check";
|
||||
import type { CheckSnapshot } from "@/Types/Check";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getPageSpeedPalette } from "@/Utils/MonitorUtils";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
@@ -56,7 +56,7 @@ const MetricBox = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const PiePageSpeedLegend = ({ latestCheck }: { latestCheck?: Check }) => {
|
||||
export const PiePageSpeedLegend = ({ latestCheck }: { latestCheck?: CheckSnapshot }) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
||||
@@ -137,7 +137,7 @@ const useFetchChecksByMonitor = ({
|
||||
return [checks, checksCount, isLoading, networkError];
|
||||
};
|
||||
|
||||
const useFetchChecksSummaryByTeamId = ({ updateTrigger } = {}) => {
|
||||
const useFetchChecksSummaryByTeamId = ({ dateRange, updateTrigger } = {}) => {
|
||||
const [summary, setSummary] = useState(undefined);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [networkError, setNetworkError] = useState(false);
|
||||
@@ -147,7 +147,7 @@ const useFetchChecksSummaryByTeamId = ({ updateTrigger } = {}) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
const res = await networkService.getChecksAndSummaryByTeamId();
|
||||
const res = await networkService.getChecksAndSummaryByTeamId({ dateRange });
|
||||
setSummary(res.data.data);
|
||||
} catch (error) {
|
||||
setNetworkError(true);
|
||||
@@ -158,7 +158,7 @@ const useFetchChecksSummaryByTeamId = ({ updateTrigger } = {}) => {
|
||||
};
|
||||
|
||||
fetchSummary();
|
||||
}, [updateTrigger]);
|
||||
}, [dateRange, updateTrigger]);
|
||||
|
||||
return [summary, isLoading, networkError];
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@ import { useState } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useFetchChecksTeam, useFetchChecksByMonitor } from "@/Hooks/checkHooks.js";
|
||||
import { Button, Typography, useTheme } from "@mui/material";
|
||||
import { Typography, useTheme } from "@mui/material";
|
||||
import { lighten } from "@mui/material/styles";
|
||||
|
||||
const GetTooltip = (row) => {
|
||||
|
||||
@@ -5,7 +5,6 @@ import GenericFallback from "@/Components/v1/GenericFallback/index.jsx";
|
||||
import IncidentTable from "./Components/IncidentTable/index.jsx";
|
||||
import OptionsHeader from "./Components/OptionsHeader/index.jsx";
|
||||
import StatusBoxes from "./Components/StatusBoxes/index.jsx";
|
||||
import { Box, Button } from "@mui/material";
|
||||
|
||||
//Utils
|
||||
import { useTheme } from "@emotion/react";
|
||||
@@ -26,7 +25,7 @@ const Checks = () => {
|
||||
// Local state
|
||||
const [selectedMonitor, setSelectedMonitor] = useState("0");
|
||||
const [filter, setFilter] = useState(undefined);
|
||||
const [dateRange, setDateRange] = useState(undefined);
|
||||
const [dateRange, setDateRange] = useState("hour");
|
||||
const [monitorLookup, setMonitorLookup] = useState(undefined);
|
||||
const [updateTrigger, setUpdateTrigger] = useState(false);
|
||||
|
||||
@@ -36,6 +35,7 @@ const Checks = () => {
|
||||
const theme = useTheme();
|
||||
const [monitors, , isLoading, networkError] = useFetchMonitorsByTeamId({});
|
||||
const [summary, isLoadingSummary, networkErrorSummary] = useFetchChecksSummaryByTeamId({
|
||||
dateRange,
|
||||
updateTrigger,
|
||||
});
|
||||
const { monitorId } = useParams();
|
||||
|
||||
@@ -92,9 +92,7 @@ const CreateInfrastructureMonitor = () => {
|
||||
initializeInfrastructureMonitorForUpdate(monitor);
|
||||
const fetchLastCheck = async () => {
|
||||
try {
|
||||
const { stats } = monitor ?? {};
|
||||
let latestCheck = stats?.aggregateData?.latestCheck;
|
||||
let disks = latestCheck?.disk || [];
|
||||
let disks = monitor?.recentChecks?.[0]?.disk || [];
|
||||
|
||||
if (disks.length > 0) {
|
||||
setAvailableDisks(disks);
|
||||
|
||||
@@ -19,8 +19,7 @@ const Gauges = ({ isLoading = false, monitor }) => {
|
||||
return <SkeletonLayout />;
|
||||
}
|
||||
|
||||
const { stats } = monitor ?? {};
|
||||
let latestCheck = stats?.aggregateData?.latestCheck;
|
||||
const latestCheck = monitor?.recentChecks?.[0];
|
||||
const memoryUsagePercent = latestCheck?.memory?.usage_percent ?? 0;
|
||||
const memoryUsedBytes = latestCheck?.memory?.used_bytes ?? 0;
|
||||
const memoryTotalBytes = latestCheck?.memory?.total_bytes ?? 0;
|
||||
|
||||
@@ -14,8 +14,7 @@ const InfraStatBoxes = ({ shouldRender, monitor }) => {
|
||||
const { determineState } = useMonitorUtils();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { stats } = monitor ?? {};
|
||||
const latestCheck = stats?.aggregateData?.latestCheck;
|
||||
const latestCheck = monitor?.recentChecks?.[0];
|
||||
|
||||
// Get data from latest check
|
||||
const physicalCores = latestCheck?.cpu?.physical_core ?? 0;
|
||||
|
||||
@@ -127,7 +127,7 @@ const InfrastructureDetails = () => {
|
||||
)}
|
||||
{tab === "network" && (
|
||||
<NetworkStats
|
||||
net={monitor?.stats?.aggregateData?.latestCheck?.net || []}
|
||||
net={monitor?.recentChecks?.[0]?.net || []}
|
||||
isLoading={isLoading}
|
||||
checks={monitor?.stats?.checks}
|
||||
dateRange={dateRange}
|
||||
|
||||
@@ -102,10 +102,10 @@ const MonitorsTable = ({
|
||||
|
||||
const data = monitors?.map((monitor) => {
|
||||
const processor =
|
||||
((monitor.checks[0]?.cpu?.frequency ?? 0) / 1000).toFixed(2) + " GHz";
|
||||
const cpu = (monitor?.checks[0]?.cpu.usage_percent ?? 0) * 100;
|
||||
const mem = (monitor?.checks[0]?.memory.usage_percent ?? 0) * 100;
|
||||
const disk = (monitor?.checks[0]?.disk[0]?.usage_percent ?? 0) * 100;
|
||||
((monitor.recentChecks?.[0]?.cpu?.frequency ?? 0) / 1000).toFixed(2) + " GHz";
|
||||
const cpu = (monitor?.recentChecks?.[0]?.cpu?.usage_percent ?? 0) * 100;
|
||||
const mem = (monitor?.recentChecks?.[0]?.memory?.usage_percent ?? 0) * 100;
|
||||
const disk = (monitor?.recentChecks?.[0]?.disk?.[0]?.usage_percent ?? 0) * 100;
|
||||
const status = determineState(monitor);
|
||||
const percentageColor =
|
||||
monitor.uptimePercentage < 0.25
|
||||
|
||||
@@ -48,15 +48,15 @@ const PageSpeedDetails = () => {
|
||||
monitorStats={monitorStats}
|
||||
/>
|
||||
<HistogramPageSpeedDetails
|
||||
checks={monitor?.checks || []}
|
||||
checks={monitor?.recentChecks || []}
|
||||
range="day"
|
||||
/>
|
||||
<Stack
|
||||
direction={{ xs: "column", md: "row" }}
|
||||
gap={theme.spacing(10)}
|
||||
>
|
||||
<PiePageSpeed latestCheck={monitor?.checks?.[0]} />
|
||||
<PiePageSpeedLegend latestCheck={monitor?.checks?.[0]} />
|
||||
<PiePageSpeed latestCheck={monitor?.recentChecks?.[0]} />
|
||||
<PiePageSpeedLegend latestCheck={monitor?.recentChecks?.[0]} />
|
||||
</Stack>
|
||||
</BasePage>
|
||||
);
|
||||
|
||||
@@ -208,7 +208,7 @@ export const PageSpeedMonitorsTable = ({
|
||||
return (
|
||||
<Stack alignItems={"center"}>
|
||||
<HistogramPageSpeed
|
||||
checks={row.checks}
|
||||
checks={row.recentChecks}
|
||||
status={row.status}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
@@ -2,31 +2,23 @@ import Box from "@mui/material/Box";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { HeatmapResponseTime, HistogramResponseTime } from "@/Components/v2/common";
|
||||
import type { Check } from "@/Types/Check";
|
||||
import type { CheckSnapshot } from "@/Types/Check";
|
||||
|
||||
interface DummyChartProps {
|
||||
chartType: string;
|
||||
}
|
||||
|
||||
const generateDummyChecks = (): Check[] => {
|
||||
const checks: Check[] = [];
|
||||
const generateDummyChecks = (): CheckSnapshot[] => {
|
||||
const checks: CheckSnapshot[] = [];
|
||||
for (let i = 0; i < 25; i++) {
|
||||
const isUp = Math.random() > 0.1;
|
||||
checks.push({
|
||||
id: `dummy-${i}`,
|
||||
metadata: {
|
||||
monitorId: "dummy-monitor",
|
||||
teamId: "dummy-team",
|
||||
type: "http",
|
||||
},
|
||||
status: isUp,
|
||||
statusCode: isUp ? 200 : 500,
|
||||
responseTime: Math.floor(Math.random() * 80) + 20,
|
||||
message: "",
|
||||
ack: false,
|
||||
expiry: new Date(Date.now() + 86400000).toISOString(),
|
||||
createdAt: new Date(Date.now() - i * 60000).toISOString(),
|
||||
updatedAt: new Date(Date.now() - i * 60000).toISOString(),
|
||||
});
|
||||
}
|
||||
return checks;
|
||||
|
||||
@@ -199,9 +199,9 @@ export const MonitorTable = ({
|
||||
content: t("pages.uptime.table.headers.responseTime"),
|
||||
render: (row) => {
|
||||
if (chartType === "histogram") {
|
||||
return <HistogramResponseTime checks={row.checks} />;
|
||||
return <HistogramResponseTime checks={row.recentChecks} />;
|
||||
} else {
|
||||
return <HeatmapResponseTime checks={row.checks} />;
|
||||
return <HeatmapResponseTime checks={row.recentChecks} />;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
+101
-2
@@ -1,7 +1,15 @@
|
||||
export interface CheckMetadata {
|
||||
monitorId: string;
|
||||
teamId: string;
|
||||
type: string;
|
||||
type:
|
||||
| "http"
|
||||
| "ping"
|
||||
| "pagespeed"
|
||||
| "hardware"
|
||||
| "docker"
|
||||
| "port"
|
||||
| "game"
|
||||
| "unknown";
|
||||
}
|
||||
|
||||
export interface CheckCpuInfo {
|
||||
@@ -83,6 +91,7 @@ export interface CheckTimings {
|
||||
lookup?: number;
|
||||
connect?: number;
|
||||
secureConnect?: number;
|
||||
upload?: number;
|
||||
response?: number;
|
||||
end?: number;
|
||||
abort?: number;
|
||||
@@ -103,7 +112,6 @@ export interface Check {
|
||||
id: string;
|
||||
metadata: CheckMetadata;
|
||||
status: boolean;
|
||||
originalResponseTime?: number;
|
||||
responseTime: number;
|
||||
timings?: CheckTimings;
|
||||
statusCode: number;
|
||||
@@ -123,6 +131,7 @@ export interface Check {
|
||||
seo?: number;
|
||||
performance?: number;
|
||||
audits?: CheckAudits;
|
||||
__v: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
@@ -144,3 +153,93 @@ export interface ChecksResponse {
|
||||
checks: Check[];
|
||||
checksCount: number;
|
||||
}
|
||||
|
||||
export type MonitorType =
|
||||
| "http"
|
||||
| "ping"
|
||||
| "pagespeed"
|
||||
| "hardware"
|
||||
| "docker"
|
||||
| "port"
|
||||
| "game"
|
||||
| "unknown";
|
||||
|
||||
export interface ChecksQueryResult {
|
||||
checksCount: number;
|
||||
checks: Check[];
|
||||
}
|
||||
|
||||
export interface PageSpeedChecksResult {
|
||||
monitorType: "pagespeed";
|
||||
checks: Check[];
|
||||
}
|
||||
|
||||
export interface HardwareChecksResult {
|
||||
monitorType: "hardware";
|
||||
aggregateData: {
|
||||
totalChecks: number;
|
||||
};
|
||||
upChecks: {
|
||||
totalChecks: number;
|
||||
};
|
||||
checks: Array<{
|
||||
_id: string;
|
||||
avgCpuUsage: number;
|
||||
avgMemoryUsage: number;
|
||||
avgTemperature: number[];
|
||||
disks: Array<{
|
||||
name: string;
|
||||
readSpeed: number;
|
||||
writeSpeed: number;
|
||||
totalBytes: number;
|
||||
freeBytes: number;
|
||||
usagePercent: number;
|
||||
}>;
|
||||
net: Array<{
|
||||
name: string;
|
||||
bytesSentPerSecond: number;
|
||||
deltaBytesRecv: number;
|
||||
deltaPacketsSent: number;
|
||||
deltaPacketsRecv: number;
|
||||
deltaErrIn: number;
|
||||
deltaErrOut: number;
|
||||
deltaDropIn: number;
|
||||
deltaDropOut: number;
|
||||
deltaFifoIn: number;
|
||||
deltaFifoOut: number;
|
||||
}>;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface UptimeChecksResult {
|
||||
monitorType: Exclude<MonitorType, "hardware" | "pagespeed">;
|
||||
groupedChecks: GroupedCheck[];
|
||||
groupedUpChecks: GroupedCheck[];
|
||||
groupedDownChecks: GroupedCheck[];
|
||||
uptimePercentage: number;
|
||||
avgResponseTime: number;
|
||||
}
|
||||
|
||||
export interface ChecksSummary {
|
||||
totalChecks: number;
|
||||
resolvedChecks: number;
|
||||
downChecks: number;
|
||||
cannotResolveChecks: number;
|
||||
}
|
||||
|
||||
export type CheckSnapshot = Omit<
|
||||
Check,
|
||||
"metadata" | "ack" | "ackAt" | "expiry" | "__v" | "updatedAt"
|
||||
>;
|
||||
|
||||
export interface HasResponseTime {
|
||||
responseTime: number;
|
||||
}
|
||||
|
||||
export type NormalizedCheck<T extends HasResponseTime = Check> = T & {
|
||||
originalResponseTime: number;
|
||||
};
|
||||
|
||||
export type NormalizedUptimeCheck<T extends GroupedCheck = GroupedCheck> = T & {
|
||||
originalAvgResponseTime: number;
|
||||
};
|
||||
+45
-19
@@ -1,4 +1,5 @@
|
||||
import type { Check, GroupedCheck } from "@/Types/Check";
|
||||
import type { GroupedCheck } from "@/Types/Check";
|
||||
import type { CheckSnapshot } from "@/Types/Check";
|
||||
export type MonitorStatus = boolean | undefined;
|
||||
|
||||
export const MonitorTypes = [
|
||||
@@ -13,28 +14,53 @@ export const MonitorTypes = [
|
||||
] as const;
|
||||
export type MonitorType = (typeof MonitorTypes)[number];
|
||||
|
||||
export interface Monitor {
|
||||
checks: Check[];
|
||||
createdAt: string;
|
||||
createdBy: string;
|
||||
interval: number;
|
||||
isActive: boolean;
|
||||
latestChecks: Check[];
|
||||
n: number;
|
||||
name: string;
|
||||
status: MonitorStatus;
|
||||
type: string;
|
||||
updatedAt: string;
|
||||
updatedBy: string;
|
||||
url: string;
|
||||
id: string;
|
||||
export interface MonitorThresholds {
|
||||
usage_cpu?: number;
|
||||
usage_memory?: number;
|
||||
usage_disk?: number;
|
||||
usage_temperature?: number;
|
||||
}
|
||||
|
||||
export interface MonitorWithChecks extends Monitor {
|
||||
checks: Check[];
|
||||
export type MonitorMatchMethod = "equal" | "include" | "regex" | "";
|
||||
|
||||
export interface Monitor {
|
||||
id: string;
|
||||
userId: string;
|
||||
teamId: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
status?: boolean;
|
||||
statusWindow: boolean[];
|
||||
statusWindowSize: number;
|
||||
statusWindowThreshold: number;
|
||||
type: MonitorType;
|
||||
ignoreTlsErrors: boolean;
|
||||
jsonPath?: string;
|
||||
expectedValue?: string;
|
||||
matchMethod?: MonitorMatchMethod;
|
||||
url: string;
|
||||
port?: number;
|
||||
isActive: boolean;
|
||||
interval: number;
|
||||
uptimePercentage?: number;
|
||||
notifications: string[];
|
||||
secret?: string;
|
||||
thresholds?: MonitorThresholds;
|
||||
alertThreshold: number;
|
||||
cpuAlertThreshold: number;
|
||||
memoryAlertThreshold: number;
|
||||
diskAlertThreshold: number;
|
||||
tempAlertThreshold: number;
|
||||
selectedDisks: string[];
|
||||
gameId?: string;
|
||||
group: string | null;
|
||||
recentChecks: CheckSnapshot[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export type MonitorWithChecks = Monitor;
|
||||
|
||||
export interface MonitorsSummary {
|
||||
totalMonitors: number;
|
||||
upMonitors: number;
|
||||
@@ -45,7 +71,7 @@ export interface MonitorsSummary {
|
||||
export interface MonitorsWithChecksResponse {
|
||||
count: number;
|
||||
monitors: MonitorWithChecks[];
|
||||
summary: MonitorsSummary;
|
||||
summary: MonitorsSummary | null;
|
||||
}
|
||||
|
||||
export interface MonitorStats {
|
||||
|
||||
@@ -594,8 +594,8 @@ class NetworkService {
|
||||
* @async
|
||||
* @returns {Promise<AxiosResponse>} The response from the axios GET request.
|
||||
*/
|
||||
getChecksAndSummaryByTeamId = async () => {
|
||||
return this.axiosInstance.get(`/checks/team/summary`);
|
||||
getChecksAndSummaryByTeamId = async ({ dateRange }) => {
|
||||
return this.axiosInstance.get(`/checks/team/summary?dateRange=${dateRange}`);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -54,8 +54,8 @@ async function ensureMonitor({ monitorId, teamId, userId, type }) {
|
||||
async function run() {
|
||||
const mongoUri = process.env.MONGO_URI ?? "mongodb://localhost:27017/uptime_db";
|
||||
const monitorId = parseObjectId(process.env.MONITOR_ID ?? DEFAULT_MONITOR_ID, DEFAULT_MONITOR_ID);
|
||||
const teamId = parseObjectId("69648b0578209af45f9ffe30");
|
||||
const userId = parseObjectId("69648b0678209af45f9ffe32");
|
||||
const teamId = parseObjectId("6971546de1b2bc3de6498e6e");
|
||||
const userId = parseObjectId("6971546de1b2bc3de6498e70");
|
||||
const monitorType = process.env.MONITOR_TYPE ?? DEFAULT_MONITOR_TYPE;
|
||||
const total = Number(process.env.CHECK_TOTAL ?? DEFAULT_TOTAL);
|
||||
const batchSize = Number(process.env.CHECK_BATCH_SIZE ?? DEFAULT_BATCH_SIZE);
|
||||
|
||||
@@ -176,7 +176,7 @@ export const initializeServices = async ({
|
||||
|
||||
const bufferService = new BufferService({ logger, checkService, settingsService });
|
||||
|
||||
const statusService = new StatusService({ db, logger, buffer: bufferService, monitorsRepository });
|
||||
const statusService = new StatusService({ logger, buffer: bufferService, monitorsRepository });
|
||||
|
||||
const webhookProvider = new WebhookProvider(logger);
|
||||
const slackProvider = new SlackProvider(logger);
|
||||
|
||||
@@ -66,7 +66,8 @@ class CheckController {
|
||||
|
||||
getChecksSummaryByTeamId = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const summary = await this.checkService.getChecksSummaryByTeamId({ teamId: req?.user?.teamId });
|
||||
const dateRange = req.query?.dateRange || "hour";
|
||||
const summary = await this.checkService.getChecksSummaryByTeamId({ teamId: req?.user?.teamId, dateRange });
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Checks summary retrieved successfully",
|
||||
|
||||
@@ -324,7 +324,6 @@ class MonitorController {
|
||||
try {
|
||||
await getMonitorsByTeamIdParamValidation.validateAsync(req.params);
|
||||
await getMonitorsWithChecksQueryValidation.validateAsync(req.query);
|
||||
|
||||
const explain = optionalBoolean(req?.query?.explain, "explain");
|
||||
const limit = optionalNumber(req?.query?.limit, "limit");
|
||||
const page = optionalNumber(req?.query?.page, "page");
|
||||
|
||||
@@ -7,7 +7,6 @@ import { IStatusPageService } from "@/service/business/statusPageService.js";
|
||||
import { IMonitorsRepository } from "@/repositories/index.js";
|
||||
import { ISettingsService } from "@/service/system/settingsService.js";
|
||||
import { ParseBoolean } from "@/utils/utils.js";
|
||||
import { MonitorWithChecks } from "@/types/index.js";
|
||||
import { NormalizeData } from "@/utils/dataUtils.js";
|
||||
|
||||
const SERVICE_NAME = "statusPageController";
|
||||
@@ -84,7 +83,7 @@ class StatusPageController {
|
||||
|
||||
const monitors = await this.monitorsRepository.findByIdsWithChecks(statusPage.monitors);
|
||||
const normalizedMonitors = monitors.map((monitor) => {
|
||||
const normalizedChecks = NormalizeData(monitor.checks, 10, 100);
|
||||
const normalizedChecks = NormalizeData(monitor.recentChecks, 10, 100);
|
||||
if (!showURL) {
|
||||
const { url, port, secret, notifications, ...rest } = monitor;
|
||||
return { ...rest, checks: normalizedChecks };
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { Schema, model, Types, type UpdateQuery } from "mongoose";
|
||||
import type { Monitor, MonitorMatchMethod, MonitorThresholds } from "@/types/monitor.js";
|
||||
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";
|
||||
|
||||
type CheckSnapshotDocument = Omit<CheckSnapshot, "createdAt"> & { createdAt: Date };
|
||||
|
||||
type MonitorDocumentBase = Omit<
|
||||
Monitor,
|
||||
"id" | "userId" | "teamId" | "notifications" | "selectedDisks" | "statusWindow" | "createdAt" | "updatedAt"
|
||||
"id" | "userId" | "teamId" | "notifications" | "selectedDisks" | "statusWindow" | "recentChecks" | "createdAt" | "updatedAt"
|
||||
> & {
|
||||
statusWindow: boolean[];
|
||||
recentChecks: CheckSnapshotDocument[];
|
||||
notifications: Types.ObjectId[];
|
||||
selectedDisks: string[];
|
||||
matchMethod?: MonitorMatchMethod;
|
||||
@@ -34,6 +37,15 @@ const thresholdsSchema = new Schema<MonitorThresholds>(
|
||||
{ _id: false }
|
||||
);
|
||||
|
||||
const checkSnapshotSchema = new Schema<CheckSnapshotDocument>(
|
||||
{
|
||||
id: { type: String, required: true },
|
||||
status: { type: Boolean, required: true },
|
||||
createdAt: { type: Date, required: true },
|
||||
},
|
||||
{ _id: false, strict: false }
|
||||
);
|
||||
|
||||
const MonitorSchema = new Schema<MonitorDocument>(
|
||||
{
|
||||
userId: {
|
||||
@@ -165,6 +177,10 @@ const MonitorSchema = new Schema<MonitorDocument>(
|
||||
return value && value.trim() ? value.trim() : null;
|
||||
},
|
||||
},
|
||||
recentChecks: {
|
||||
type: [checkSnapshotSchema],
|
||||
default: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
@@ -202,6 +218,6 @@ MonitorSchema.index({ teamId: 1, type: 1 });
|
||||
|
||||
const MonitorModel = model<MonitorDocument>("Monitor", MonitorSchema);
|
||||
|
||||
export type { MonitorDocument };
|
||||
export type { MonitorDocument, CheckSnapshotDocument };
|
||||
export { MonitorModel };
|
||||
export default MonitorModel;
|
||||
@@ -33,7 +33,7 @@ export interface IChecksRepository {
|
||||
dateString: string,
|
||||
options?: { type?: MonitorType }
|
||||
): Promise<UptimeChecksResult | HardwareChecksResult | PageSpeedChecksResult>;
|
||||
findSummaryByTeamId(teamId: string): Promise<ChecksSummary>;
|
||||
findSummaryByTeamId(teamId: string, dateRange: string): Promise<ChecksSummary>;
|
||||
// update
|
||||
//delete
|
||||
deleteByMonitorId(monitorId: string): Promise<number>;
|
||||
|
||||
@@ -29,7 +29,6 @@ const dateRangeLookup: Record<string, Date | undefined> = {
|
||||
|
||||
export type LatestChecksMap = Record<string, Check[]>;
|
||||
type DateRange = { start: Date; end: Date };
|
||||
type HardwareAggregateData = { latestCheck: CheckDocument | null; totalChecks: number };
|
||||
type HardwareUpChecks = { totalChecks: number };
|
||||
|
||||
class MongoChecksRepository implements IChecksRepository {
|
||||
@@ -366,51 +365,30 @@ class MongoChecksRepository implements IChecksRepository {
|
||||
return this.findUptimeDateRangeChecks(options?.type ?? "http", monitorObjectId, startDate, endDate, dateString);
|
||||
};
|
||||
|
||||
findSummaryByTeamId = async (teamId: string) => {
|
||||
findSummaryByTeamId = async (teamId: string, dateRange: string) => {
|
||||
const matchStage = {
|
||||
"metadata.teamId": new mongoose.Types.ObjectId(teamId),
|
||||
status: false,
|
||||
...(dateRangeLookup[dateRange] && {
|
||||
createdAt: {
|
||||
$gte: dateRangeLookup[dateRange],
|
||||
},
|
||||
}),
|
||||
};
|
||||
const checks = await CheckModel.aggregate([
|
||||
{ $match: matchStage },
|
||||
{
|
||||
$facet: {
|
||||
summary: [
|
||||
{
|
||||
$group: {
|
||||
_id: null,
|
||||
totalChecks: { $sum: { $cond: [{ $eq: ["$status", false] }, 1, 0] } },
|
||||
resolvedChecks: {
|
||||
$sum: {
|
||||
$cond: [{ $and: [{ $eq: ["$ack", true] }, { $eq: ["$status", false] }] }, 1, 0],
|
||||
},
|
||||
},
|
||||
downChecks: {
|
||||
$sum: {
|
||||
$cond: [{ $and: [{ $eq: ["$ack", false] }, { $eq: ["$status", false] }] }, 1, 0],
|
||||
},
|
||||
},
|
||||
cannotResolveChecks: {
|
||||
$sum: {
|
||||
$cond: [{ $eq: ["$statusCode", 5000] }, 1, 0],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
summary: { $arrayElemAt: ["$summary", 0] },
|
||||
$group: {
|
||||
_id: null,
|
||||
totalChecks: { $sum: 1 },
|
||||
resolvedChecks: { $sum: { $cond: [{ $eq: ["$ack", true] }, 1, 0] } },
|
||||
downChecks: { $sum: { $cond: [{ $eq: ["$ack", false] }, 1, 0] } },
|
||||
cannotResolveChecks: { $sum: { $cond: [{ $eq: ["$statusCode", 5000] }, 1, 0] } },
|
||||
},
|
||||
},
|
||||
{ $project: { _id: 0 } },
|
||||
]);
|
||||
return checks[0].summary;
|
||||
return checks[0] ?? { totalChecks: 0, resolvedChecks: 0, downChecks: 0, cannotResolveChecks: 0 };
|
||||
};
|
||||
|
||||
deleteByMonitorId = async (monitorId: string): Promise<number> => {
|
||||
@@ -526,14 +504,13 @@ class MongoChecksRepository implements IChecksRepository {
|
||||
const monitorId = monitorObjectId.toHexString();
|
||||
const dates = { start: startDate, end: endDate };
|
||||
const [aggregateDataDoc, upChecksDoc, hardwareMetrics] = await Promise.all([
|
||||
this.getHardwareAggregateData(monitorId, dates),
|
||||
this.getHardwareTotalChecks(monitorId, dates),
|
||||
this.getHardwareUpChecks(monitorId, dates),
|
||||
this.getHardwareStats(monitorId, dates, dateString),
|
||||
]);
|
||||
|
||||
const aggregateData = {
|
||||
latestCheck: aggregateDataDoc?.latestCheck ? this.toEntity(aggregateDataDoc.latestCheck as CheckDocument) : null,
|
||||
totalChecks: aggregateDataDoc?.totalChecks ?? 0,
|
||||
totalChecks: aggregateDataDoc ?? 0,
|
||||
};
|
||||
|
||||
const upChecks = {
|
||||
@@ -589,25 +566,12 @@ class MongoChecksRepository implements IChecksRepository {
|
||||
};
|
||||
};
|
||||
|
||||
private getHardwareAggregateData = async (monitorId: string, dates: DateRange): Promise<HardwareAggregateData> => {
|
||||
const result = await CheckModel.aggregate([
|
||||
{
|
||||
$match: {
|
||||
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
|
||||
"metadata.type": "hardware",
|
||||
createdAt: { $gte: dates.start, $lte: dates.end },
|
||||
},
|
||||
},
|
||||
{ $sort: { createdAt: -1 } },
|
||||
{
|
||||
$group: {
|
||||
_id: null,
|
||||
latestCheck: { $first: "$$ROOT" },
|
||||
totalChecks: { $sum: 1 },
|
||||
},
|
||||
},
|
||||
]);
|
||||
return result[0] || { totalChecks: 0, latestCheck: null };
|
||||
private getHardwareTotalChecks = async (monitorId: string, dates: DateRange): Promise<number> => {
|
||||
return await CheckModel.countDocuments({
|
||||
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
|
||||
"metadata.type": "hardware",
|
||||
createdAt: { $gte: dates.start, $lte: dates.end },
|
||||
});
|
||||
};
|
||||
|
||||
private getHardwareUpChecks = async (monitorId: string, dates: DateRange): Promise<HardwareUpChecks> => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type MonitorType, type Monitor, type MonitorsSummary, type MonitorWithChecks } from "@/types/index.js";
|
||||
import { type MonitorType, type Monitor, type MonitorsSummary } from "@/types/index.js";
|
||||
|
||||
export interface TeamQueryConfig {
|
||||
limit?: number;
|
||||
@@ -25,7 +25,7 @@ export interface IMonitorsRepository {
|
||||
findAll(): Promise<Monitor[] | null>;
|
||||
findByTeamId(teamId: string, config: TeamQueryConfig): Promise<Monitor[] | null>;
|
||||
findByIds(monitorIds: string[]): Promise<Monitor[]>;
|
||||
findByIdsWithChecks(monitorIds: string[], checksCount?: number): Promise<MonitorWithChecks[]>;
|
||||
findByIdsWithChecks(monitorIds: string[], checksCount?: number): Promise<Monitor[]>;
|
||||
|
||||
// update
|
||||
updateById(monitorId: string, teamId: string, updates: Partial<Monitor>): Promise<Monitor>;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MonitorModel } from "@/db/models/index.js";
|
||||
import type { MonitorDocument } from "@/db/models/index.js";
|
||||
import type { Monitor, MonitorsSummary, MonitorWithChecks, Check } from "@/types/index.js";
|
||||
import type { MonitorDocument, CheckSnapshotDocument } from "@/db/models/index.js";
|
||||
import type { Monitor, MonitorsSummary, CheckSnapshot, Check } from "@/types/index.js";
|
||||
import mongoose, { type FilterQuery, type PipelineStage } from "mongoose";
|
||||
import type { IMonitorsRepository, TeamQueryConfig, SummaryConfig } from "./IMonitorsRepository.js";
|
||||
import { AppError } from "@/utils/AppError.js";
|
||||
@@ -79,7 +79,7 @@ class MongoMonitorsRepository implements IMonitorsRepository {
|
||||
return this.mapDocuments(monitors);
|
||||
};
|
||||
|
||||
findByIdsWithChecks = async (monitorIds: string[], checksCount: number = 25): Promise<MonitorWithChecks[]> => {
|
||||
findByIdsWithChecks = async (monitorIds: string[], checksCount: number = 25): Promise<Monitor[]> => {
|
||||
if (!monitorIds.length) {
|
||||
return [];
|
||||
}
|
||||
@@ -309,12 +309,13 @@ class MongoMonitorsRepository implements IMonitorsRepository {
|
||||
selectedDisks: doc.selectedDisks ?? [],
|
||||
gameId: doc.gameId ?? undefined,
|
||||
group: doc.group ?? null,
|
||||
recentChecks: (doc.recentChecks ?? []).map((check: any) => this.toCheckSnapshot(check)),
|
||||
createdAt: toDateString(doc.createdAt),
|
||||
updatedAt: toDateString(doc.updatedAt),
|
||||
};
|
||||
};
|
||||
|
||||
private toEntityWithChecks = (doc: any): MonitorWithChecks => {
|
||||
private toEntityWithChecks = (doc: any): Monitor => {
|
||||
const toStringId = (value: unknown): string => {
|
||||
if (value instanceof mongoose.Types.ObjectId) {
|
||||
return value.toString();
|
||||
@@ -392,9 +393,37 @@ class MongoMonitorsRepository implements IMonitorsRepository {
|
||||
selectedDisks: doc.selectedDisks ?? [],
|
||||
gameId: doc.gameId ?? undefined,
|
||||
group: doc.group ?? null,
|
||||
recentChecks: (doc.recentChecks ?? []).map((check: any) => this.toCheckSnapshot(check)),
|
||||
createdAt: toDateString(doc.createdAt),
|
||||
updatedAt: toDateString(doc.updatedAt),
|
||||
checks,
|
||||
};
|
||||
};
|
||||
|
||||
private toCheckSnapshot = (doc: CheckSnapshotDocument): CheckSnapshot => {
|
||||
const toDateString = (value: Date | string): string => {
|
||||
return value instanceof Date ? value.toISOString() : value;
|
||||
};
|
||||
|
||||
return {
|
||||
id: doc.id,
|
||||
status: doc.status,
|
||||
responseTime: doc.responseTime,
|
||||
timings: doc.timings,
|
||||
statusCode: doc.statusCode,
|
||||
message: doc.message,
|
||||
cpu: doc.cpu,
|
||||
memory: doc.memory,
|
||||
disk: doc.disk,
|
||||
host: doc.host,
|
||||
errors: doc.errors,
|
||||
capture: doc.capture,
|
||||
net: doc.net,
|
||||
accessibility: doc.accessibility,
|
||||
bestPractices: doc.bestPractices,
|
||||
seo: doc.seo,
|
||||
performance: doc.performance,
|
||||
audits: doc.audits,
|
||||
createdAt: toDateString(doc.createdAt),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { requireTeamId } from "@/controllers/controllerUtils.js";
|
||||
import { Types } from "mongoose";
|
||||
import { IChecksRepository, IMonitorsRepository } from "@/repositories/index.js";
|
||||
import type { MonitorType, MonitorStatusResponse, CheckErrorInfo, Check } from "@/types/index.js";
|
||||
import type { HardwareStatusPayload, PageSpeedStatusPayload } from "@/types/network.js";
|
||||
@@ -39,6 +40,8 @@ class CheckService {
|
||||
const { monitorId, teamId, type, status, responseTime, code, message, payload, timings } = statusResponse;
|
||||
|
||||
const check: Partial<Check> = {
|
||||
id: new Types.ObjectId().toString(),
|
||||
createdAt: new Date().toISOString(),
|
||||
metadata: {
|
||||
monitorId,
|
||||
teamId,
|
||||
@@ -142,11 +145,11 @@ class CheckService {
|
||||
return checkData;
|
||||
};
|
||||
|
||||
getChecksSummaryByTeamId = async ({ teamId }: { teamId: string }) => {
|
||||
getChecksSummaryByTeamId = async ({ teamId, dateRange }: { teamId: string; dateRange: string }) => {
|
||||
if (!teamId) {
|
||||
throw new AppError({ message: "No team ID in request", service: SERVICE_NAME, method: "getChecksSummaryByTeamId", status: 400 });
|
||||
}
|
||||
const summary = await this.checksRepository.findSummaryByTeamId(teamId);
|
||||
const summary = await this.checksRepository.findSummaryByTeamId(teamId, dateRange);
|
||||
return summary;
|
||||
};
|
||||
|
||||
|
||||
@@ -360,22 +360,13 @@ export class MonitorService implements IMonitorService {
|
||||
const snapshotOnlyRequest =
|
||||
requestedTypes.length > 0 && requestedTypes.every((requestedType) => snapshotTypes.includes(requestedType as MonitorType));
|
||||
|
||||
const limitPerMonitor = snapshotOnlyRequest ? 1 : 25;
|
||||
const checksMap = await this.checksRepository.findLatestByMonitorIds(
|
||||
monitorsList.map((monitor) => monitor.id),
|
||||
{ limitPerMonitor }
|
||||
);
|
||||
|
||||
const monitorsWithChecks = monitorsList.map((monitor: Monitor) => {
|
||||
const rawChecks = checksMap[monitor.id] ?? [];
|
||||
const rawChecks = monitor.recentChecks ?? [];
|
||||
const isSnapshotType = snapshotOnlyRequest || snapshotTypes.includes(monitor.type);
|
||||
const checks = isSnapshotType ? rawChecks.slice(0, 1) : NormalizeData(rawChecks, 10, 100);
|
||||
return {
|
||||
...monitor,
|
||||
checks,
|
||||
};
|
||||
monitor.recentChecks = checks;
|
||||
return monitor;
|
||||
});
|
||||
|
||||
return { summary: summary ?? null, count, monitors: monitorsWithChecks };
|
||||
};
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import type {
|
||||
Check,
|
||||
HardwareStatusPayload,
|
||||
PageSpeedStatusPayload,
|
||||
CheckSnapshot,
|
||||
} from "@/types/index.js";
|
||||
const SERVICE_NAME = "StatusService";
|
||||
|
||||
@@ -24,13 +25,11 @@ export interface IStatusService {
|
||||
|
||||
export class StatusService implements IStatusService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
private db: any;
|
||||
private logger: any;
|
||||
private buffer: any;
|
||||
private monitorsRepository: IMonitorsRepository;
|
||||
|
||||
constructor({ db, logger, buffer, monitorsRepository }: { db: any; logger: any; buffer: any; monitorsRepository: IMonitorsRepository }) {
|
||||
this.db = db;
|
||||
constructor({ logger, buffer, monitorsRepository }: { logger: any; buffer: any; monitorsRepository: IMonitorsRepository }) {
|
||||
this.logger = logger;
|
||||
this.buffer = buffer;
|
||||
this.monitorsRepository = monitorsRepository;
|
||||
@@ -175,14 +174,40 @@ export class StatusService implements IStatusService {
|
||||
// Update running stats
|
||||
this.updateRunningStats({ monitor, networkResponse: statusResponse });
|
||||
|
||||
// If the status window size has changed, empty
|
||||
|
||||
monitor.statusWindow = monitor.statusWindow || [];
|
||||
monitor.statusWindow.push(status);
|
||||
while (monitor.statusWindow.length > monitor.statusWindowSize) {
|
||||
monitor.statusWindow.shift();
|
||||
}
|
||||
|
||||
const checkSnapshot: CheckSnapshot = {
|
||||
id: check.id,
|
||||
status: check.status,
|
||||
responseTime: check.responseTime,
|
||||
timings: check.timings,
|
||||
statusCode: check.statusCode,
|
||||
message: check.message,
|
||||
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,
|
||||
createdAt: check.createdAt,
|
||||
};
|
||||
monitor.recentChecks = monitor.recentChecks || [];
|
||||
monitor.recentChecks.push(checkSnapshot);
|
||||
const maxRecentChecks = 25;
|
||||
while (monitor.recentChecks.length > maxRecentChecks) {
|
||||
monitor.recentChecks.shift();
|
||||
}
|
||||
|
||||
if (monitor.status === undefined || monitor.status === null) {
|
||||
monitor.status = status;
|
||||
}
|
||||
|
||||
@@ -121,7 +121,6 @@ export interface PageSpeedChecksResult {
|
||||
export interface HardwareChecksResult {
|
||||
monitorType: "hardware";
|
||||
aggregateData: {
|
||||
latestCheck: Check | null;
|
||||
totalChecks: number;
|
||||
};
|
||||
upChecks: {
|
||||
@@ -178,10 +177,16 @@ export interface ChecksSummary {
|
||||
cannotResolveChecks: number;
|
||||
}
|
||||
|
||||
export type NormalizedCheck<T extends Check = Check> = T & {
|
||||
export interface HasResponseTime {
|
||||
responseTime: number;
|
||||
}
|
||||
|
||||
export type NormalizedCheck<T extends HasResponseTime = Check> = T & {
|
||||
originalResponseTime: number;
|
||||
};
|
||||
|
||||
export type NormalizedUptimeCheck<T extends GroupedCheck = GroupedCheck> = T & {
|
||||
originalAvgResponseTime: number;
|
||||
};
|
||||
|
||||
export type CheckSnapshot = Omit<Check, "metadata" | "ack" | "ackAt" | "expiry" | "__v" | "updatedAt">;
|
||||
@@ -1,4 +1,7 @@
|
||||
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];
|
||||
|
||||
@@ -42,6 +45,7 @@ export interface Monitor {
|
||||
selectedDisks: string[];
|
||||
gameId?: string;
|
||||
group: string | null;
|
||||
recentChecks: CheckSnapshot[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
@@ -53,14 +57,10 @@ export interface MonitorsSummary {
|
||||
pausedMonitors: number;
|
||||
}
|
||||
|
||||
export interface MonitorWithChecks extends Monitor {
|
||||
checks: Check[];
|
||||
}
|
||||
|
||||
export interface MonitorsWithChecksByTeamIdResult {
|
||||
summary: MonitorsSummary | null;
|
||||
count: number;
|
||||
monitors: MonitorWithChecks[];
|
||||
monitors: Monitor[];
|
||||
}
|
||||
|
||||
export interface UptimeDetailsResult {
|
||||
@@ -78,7 +78,6 @@ export interface UptimeDetailsResult {
|
||||
export interface HardwareDetailsResult extends Monitor {
|
||||
stats: {
|
||||
aggregateData: {
|
||||
latestCheck: import("./check.js").Check | null;
|
||||
totalChecks: number;
|
||||
};
|
||||
upChecks: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Check, GroupedCheck, NormalizedCheck, NormalizedUptimeCheck } from "@/types/index.js";
|
||||
import type { GroupedCheck, NormalizedCheck, NormalizedUptimeCheck, HasResponseTime } from "@/types/index.js";
|
||||
|
||||
const calculatePercentile = (arr: Check[], percentile: number): number => {
|
||||
const calculatePercentile = <T extends HasResponseTime>(arr: T[], percentile: number): number => {
|
||||
const sorted = arr.slice().sort((a, b) => a.responseTime - b.responseTime);
|
||||
const index = (percentile / 100) * (sorted.length - 1);
|
||||
const lower = Math.floor(index);
|
||||
@@ -20,7 +20,7 @@ const calculatePercentileUptimeDetails = (arr: GroupedCheck[], percentile: numbe
|
||||
return sorted[lower]!.avgResponseTime * (1 - weight) + sorted[upper]!.avgResponseTime * weight;
|
||||
};
|
||||
|
||||
export const NormalizeData = <T extends Check>(checks: T[], rangeMin: number, rangeMax: number): NormalizedCheck<T>[] => {
|
||||
export const NormalizeData = <T extends HasResponseTime>(checks: T[], rangeMin: number, rangeMax: number): NormalizedCheck<T>[] => {
|
||||
if (checks.length > 1) {
|
||||
const min = calculatePercentile(checks, 0);
|
||||
const max = calculatePercentile(checks, 95);
|
||||
|
||||
Reference in new issue
Block a user