) => {
+ e.stopPropagation();
+ setAnchorEl(null);
+ };
+
+ return (
+
+
+
+
+
+ {items.map((item) => (
+ ) => {
+ e.stopPropagation();
+ if (item.closeMenu) handleClose(e);
+ item.action();
+ }}
+ >
+ {item.label}
+
+ ))}
+
+
+ );
+};
diff --git a/client/src/Components/v2/common/charts/HeatmapResponseTime.tsx b/client/src/Components/v2/common/charts/HeatmapResponseTime.tsx
new file mode 100644
index 000000000..97289ba43
--- /dev/null
+++ b/client/src/Components/v2/common/charts/HeatmapResponseTime.tsx
@@ -0,0 +1,140 @@
+import Box from "@mui/material/Box";
+import { useTheme } from "@mui/material/styles";
+import type { Check } from "@/Types/Check";
+import { getResponseColor } from "@/Utils/DataUtils";
+import { HeatmapResponseTimeTooltip } from "@/Components/v2/common/charts/HeatmapResponseTimeTooltip";
+import type { SxProps } from "@mui/material/styles";
+import type { ResponsiveStyleValue } from "@mui/system";
+
+interface PlaceholderCheck {
+ status: "placeholder";
+ responseTime: 0;
+ createdAt: "";
+}
+
+interface HeatmapResponseTimeProps {
+ checks: Check[];
+ gap?: ResponsiveStyleValue;
+ availabilityCellSx?: SxProps;
+ responseCellSx?: SxProps;
+}
+
+export const HeatmapResponseTime = ({
+ checks,
+ gap,
+ availabilityCellSx,
+ responseCellSx,
+}: HeatmapResponseTimeProps) => {
+ const theme = useTheme();
+ if (!gap) {
+ gap = theme.spacing(0.5);
+ }
+
+ if (!checks || checks.length === 0) return null;
+
+ const latestChecks = checks.slice(-25).reverse();
+
+ let data: Array;
+ if (latestChecks.length !== 25) {
+ const placeholders = Array(25 - latestChecks.length).fill({
+ status: "placeholder" as const,
+ });
+ data = [...latestChecks, ...placeholders];
+ } else {
+ data = latestChecks;
+ }
+ return (
+
+
+ {data.map((check, index) => {
+ const statusBg =
+ check.status === "placeholder"
+ ? theme.palette.action.hover
+ : check.status === true
+ ? theme.palette.success.light
+ : theme.palette.error.light;
+ const respBg =
+ check.status === "placeholder"
+ ? theme.palette.action.hover
+ : getResponseColor(check.responseTime || 0, {
+ start: theme.palette.success.main,
+ mid: theme.palette.warning.main,
+ end: theme.palette.error.main,
+ });
+ const statusBorder =
+ check.status === "placeholder"
+ ? theme.palette.divider
+ : check.status === true
+ ? theme.palette.success.main
+ : theme.palette.error.main;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+ })}
+
+
+ );
+};
diff --git a/client/src/Components/v2/common/charts/HeatmapResponseTimeTooltip.tsx b/client/src/Components/v2/common/charts/HeatmapResponseTimeTooltip.tsx
new file mode 100644
index 000000000..7e9ae4470
--- /dev/null
+++ b/client/src/Components/v2/common/charts/HeatmapResponseTimeTooltip.tsx
@@ -0,0 +1,76 @@
+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 { 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: "" };
+
+export const HeatmapResponseTimeTooltip = ({
+ children,
+ check,
+}: {
+ children: React.ReactElement;
+ check: HeatmapCheck;
+}) => {
+ const { t } = useTranslation();
+ const uiTimezone = useSelector((state: RootState) => state.ui.timezone);
+ const theme = useTheme();
+
+ const getColor = (status: boolean) => {
+ if (status === true) return theme.palette.success.light;
+ if (status === false) return theme.palette.error.light;
+ };
+
+ if (check.status === "placeholder") {
+ return children;
+ }
+
+ return (
+
+
+ {formatDateWithTz(check?.createdAt, "ddd, MMMM D, YYYY, HH:mm A", uiTimezone)}
+
+ {check?.originalResponseTime && (
+
+ {t("common.labels.responseTime")}: {check.originalResponseTime.toFixed()} ms
+
+ )}
+
+
+ Status:{" "}
+
+ {check?.status === true
+ ? t("pages.common.monitors.status.up")
+ : t("pages.common.monitors.status.down")}
+
+
+
+ }
+ >
+ {children}
+
+ );
+};
diff --git a/client/src/Components/v2/common/charts/HistogramResponseTime.tsx b/client/src/Components/v2/common/charts/HistogramResponseTime.tsx
new file mode 100644
index 000000000..bbe3bd9d4
--- /dev/null
+++ b/client/src/Components/v2/common/charts/HistogramResponseTime.tsx
@@ -0,0 +1,102 @@
+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 { HeatmapResponseTimeTooltip } from "./HeatmapResponseTimeTooltip";
+
+interface HistogramResponseTimeProps {
+ checks: Check[];
+ height?: ResponsiveStyleValue;
+ gap?: ResponsiveStyleValue;
+}
+
+const DEFAULT_HEIGHT = 50;
+
+export const HistogramResponseTime = ({
+ checks,
+ height = DEFAULT_HEIGHT,
+ gap,
+}: HistogramResponseTimeProps) => {
+ const theme = useTheme();
+
+ if (!Array.isArray(checks) || checks.length === 0) return null;
+
+ let data = Array();
+
+ if (!checks || checks.length === 0) {
+ data = [];
+ }
+ if (checks.length !== 25) {
+ const placeholders = Array(25 - checks.length).fill({
+ status: "placeholder",
+ });
+ data = [...checks, ...placeholders];
+ } else {
+ data = checks;
+ }
+ const chartHeight = typeof height === "number" ? `${height}px` : height;
+ const gridGap = gap ?? theme.spacing(0.5);
+ return (
+
+
+ {data.map((check, index) => {
+ const isPlaceholder = (check as any).status === "placeholder";
+ const heightPct = `${Math.max(0, Math.min(100, (check as any).responseTime ?? 0))}%`;
+ const barColor =
+ check.status === true
+ ? theme.palette.success.light
+ : theme.palette.error.light;
+ const bar = (
+
+
+
+ );
+
+ return isPlaceholder ? (
+
+ {bar}
+
+ ) : (
+
+ {bar}
+
+ );
+ })}
+
+
+ );
+};
diff --git a/client/src/Components/v2/common/controls/HeaderCreate.tsx b/client/src/Components/v2/common/controls/HeaderCreate.tsx
new file mode 100644
index 000000000..8234cc8ed
--- /dev/null
+++ b/client/src/Components/v2/common/controls/HeaderCreate.tsx
@@ -0,0 +1,47 @@
+import { Stack } from "@mui/material";
+import { useNavigate } from "react-router-dom";
+import { ButtonInput } from "../../inputs/Button";
+import { useTranslation } from "react-i18next";
+
+interface HeaderCreateProps {
+ label?: string;
+ path: string;
+ isLoading?: boolean;
+ isAdmin?: boolean;
+}
+
+export const HeaderCreate = ({
+ label,
+ path,
+ isLoading = false,
+ isAdmin = false,
+}: HeaderCreateProps) => {
+ const navigate = useNavigate();
+ const { t } = useTranslation();
+
+ if (!isAdmin) {
+ return null;
+ }
+
+ const handleClick = () => {
+ navigate(path);
+ };
+
+ return (
+
+
+ {label || t("common.buttons.create")}
+
+
+ );
+};
diff --git a/client/src/Components/v2/common/controls/HeaderMonitorControls.tsx b/client/src/Components/v2/common/controls/HeaderMonitorControls.tsx
new file mode 100644
index 000000000..17576a8b2
--- /dev/null
+++ b/client/src/Components/v2/common/controls/HeaderMonitorControls.tsx
@@ -0,0 +1,102 @@
+import Stack from "@mui/material/Stack";
+import { Icon, MonitorStatus } from "@/Components/v2/design-elements";
+import { Button } from "@/Components/v2/inputs";
+import { Settings, Pause, Play, Mail, Bug } from "lucide-react";
+
+import { useTranslation } from "react-i18next";
+import { useNavigate } from "react-router-dom";
+import { useTheme } from "@mui/material/styles";
+import { usePost } from "@/Hooks/UseApi";
+
+import type { Monitor } from "@/Types/Monitor.js";
+
+interface HeaderMonitorControlsProps {
+ path: string;
+ monitor?: Monitor;
+ isAdmin: boolean;
+ refetch: Function;
+}
+
+export const HeaderMonitorControls = ({
+ path,
+ monitor,
+ isAdmin,
+ refetch,
+}: HeaderMonitorControlsProps) => {
+ const { t } = useTranslation();
+ const navigate = useNavigate();
+ const theme = useTheme();
+ const {
+ post,
+ loading: isPosting,
+ // error: postError,
+ } = usePost();
+
+ if (!monitor) {
+ return null;
+ }
+ return (
+
+
+
+ }
+ onClick={async () => {
+ await post(`/notifications/test/all`, { monitorId: monitor.id });
+ }}
+ sx={{
+ whiteSpace: "nowrap",
+ }}
+ >
+ {t("common.buttons.testNotifications")}
+
+ }
+ onClick={() => {
+ navigate(`/incidents/${monitor?.id}`);
+ }}
+ >
+ {t("common.buttons.incidents")}
+
+ {isAdmin && (
+ : }
+ onClick={async () => {
+ await post(`/monitors/pause/${monitor.id}`, {});
+ await refetch();
+ }}
+ >
+ {monitor?.isActive ? t("pause") : t("resume")}
+
+ )}
+ {isAdmin && (
+ }
+ onClick={() => navigate(`/${path}/configure/${monitor.id}`)}
+ >
+ Configure
+
+ )}
+
+
+ );
+};
diff --git a/client/src/Components/v2/common/controls/HeaderTimeRange.tsx b/client/src/Components/v2/common/controls/HeaderTimeRange.tsx
new file mode 100644
index 000000000..57b61473a
--- /dev/null
+++ b/client/src/Components/v2/common/controls/HeaderTimeRange.tsx
@@ -0,0 +1,89 @@
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import { ToggleButtonGroup, ToggleButton } from "@/Components/v2/inputs";
+import { useTheme } from "@mui/material/styles";
+
+interface MonitorTimeFrameHeaderProps {
+ isLoading?: boolean;
+ hasDateRange?: boolean;
+ dateRange: string;
+ setDateRange: (dateRange: string) => void;
+}
+
+export const HeaderTimeRange = ({
+ isLoading = false,
+ hasDateRange = true,
+ dateRange,
+ setDateRange,
+}: MonitorTimeFrameHeaderProps) => {
+ const theme = useTheme();
+
+ const handleChange = (
+ _event: React.MouseEvent,
+ newValue: string | null
+ ) => {
+ if (newValue !== null) {
+ setDateRange(newValue);
+ }
+ };
+
+ let timeFramePicker = null;
+
+ if (hasDateRange) {
+ timeFramePicker = (
+
+
+ Recent
+
+
+ Day
+
+
+ Week
+
+
+ Month
+
+
+ );
+ }
+
+ return (
+
+
+ Showing statistics for past{" "}
+ {dateRange === "recent"
+ ? "2 hours"
+ : dateRange === "day"
+ ? "24 hours"
+ : dateRange === "week"
+ ? "7 days"
+ : "30 days"}
+ .
+
+ {timeFramePicker}
+
+ );
+};
diff --git a/client/src/Components/v2/common/index.ts b/client/src/Components/v2/common/index.ts
new file mode 100644
index 000000000..d0d1262b7
--- /dev/null
+++ b/client/src/Components/v2/common/index.ts
@@ -0,0 +1,6 @@
+export * from "../common/charts/HistogramResponseTime";
+export * from "../common/charts/HeatmapResponseTime";
+export * from "../common/charts/HeatmapResponseTimeTooltip";
+export * from "../common/controls/HeaderCreate";
+export * from "../common/controls/HeaderMonitorControls";
+export * from "../common/controls/HeaderTimeRange";
diff --git a/client/src/Components/v2/design-elements/BaseBox.tsx b/client/src/Components/v2/design-elements/BaseBox.tsx
new file mode 100644
index 000000000..580d15dcf
--- /dev/null
+++ b/client/src/Components/v2/design-elements/BaseBox.tsx
@@ -0,0 +1,19 @@
+import Box, { type BoxProps } from "@mui/material/Box";
+import { useTheme } from "@mui/material/styles";
+
+export const BaseBox = ({ sx, ...rest }: BoxProps) => {
+ const theme = useTheme();
+ return (
+
+ );
+};
diff --git a/client/src/Components/v2/design-elements/BaseChart.tsx b/client/src/Components/v2/design-elements/BaseChart.tsx
new file mode 100644
index 000000000..fd9251915
--- /dev/null
+++ b/client/src/Components/v2/design-elements/BaseChart.tsx
@@ -0,0 +1,72 @@
+import { BaseBox } from "../design-elements";
+import Stack from "@mui/material/Stack";
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import type { ResponsiveStyleValue } from "@mui/system";
+
+import { useTheme } from "@mui/material/styles";
+
+type BaseChartProps = React.PropsWithChildren<{
+ icon: React.ReactNode;
+ title: string;
+ width?: number | string;
+ maxWidth?: number | string;
+ padding?: number | string | ResponsiveStyleValue;
+}>;
+
+export const BaseChart = ({
+ children,
+ icon,
+ title,
+ width = "100%",
+ maxWidth = "100%",
+ padding,
+}: BaseChartProps) => {
+ const theme = useTheme();
+ return (
+
+
+
+ {icon && (
+
+ {icon}
+
+ )}
+ {title}
+
+ {children}
+
+
+ );
+};
diff --git a/client/src/Components/v2/design-elements/BasePage.tsx b/client/src/Components/v2/design-elements/BasePage.tsx
new file mode 100644
index 000000000..de20fc005
--- /dev/null
+++ b/client/src/Components/v2/design-elements/BasePage.tsx
@@ -0,0 +1,209 @@
+import Stack from "@mui/material/Stack";
+import Alert from "@mui/material/Alert";
+import Link from "@mui/material/Link";
+import {
+ ErrorFallback,
+ EmptyFallback,
+ EmptyMonitorFallback,
+ BaseFallback,
+} from "@/Components/v2/design-elements/Fallback";
+import { Breadcrumb } from "@/Components/v2/design-elements/Breadcrumb";
+import CircularProgress from "@mui/material/CircularProgress";
+
+import type { StackProps } from "@mui/material/Stack";
+import { useTheme } from "@mui/material/styles";
+import { Trans, useTranslation } from "react-i18next";
+import { Link as RouterLink } from "react-router-dom";
+import { Typography } from "@mui/material";
+
+export const PageSpeedKeyPriorityFallback = () => {
+ return (
+
+
+
+
+ ),
+ }}
+ />
+
+
+
+ );
+};
+
+interface BasePageProps extends StackProps {
+ loading?: boolean;
+ error?: boolean;
+ children: React.ReactNode;
+ breadcrumbOverride?: string[];
+}
+
+export const BasePage = ({
+ loading,
+ error,
+ children,
+ breadcrumbOverride,
+ ...props
+}: BasePageProps) => {
+ const theme = useTheme();
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+
+ if (error) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+ {children}
+
+ );
+};
+
+interface BasePageWithStatesProps extends StackProps {
+ loading: boolean;
+ error: any;
+ items: any[];
+ bullets: string[] | unknown;
+ page: string;
+ actionButtonText: string;
+ actionLink: string;
+ children: React.ReactNode;
+}
+
+export const BasePageWithStates = ({
+ loading,
+ error,
+ items,
+ page,
+ bullets,
+ actionButtonText,
+ actionLink,
+ children,
+ ...props
+}: BasePageWithStatesProps) => {
+ const showLoading = loading && (!items || items.length === 0);
+
+ if (isEmpty(items)) {
+ return (
+
+ );
+ }
+
+ return (
+
+ {children}
+
+ );
+};
+
+interface MonitorBasePageWithStatesProps extends StackProps {
+ loading: boolean;
+ error: any;
+ items: any[];
+ page: string;
+ actionLink?: string;
+ children: React.ReactNode;
+ priorityFallback?: React.ReactNode;
+}
+
+const isEmpty = (items: any[]) => {
+ if (!items) return true;
+ if (Array.isArray(items) && items.length === 0) return true;
+ return false;
+};
+
+export const MonitorBasePageWithStates = ({
+ loading,
+ error,
+ items,
+ page,
+ actionLink,
+ children,
+ priorityFallback,
+ ...props
+}: MonitorBasePageWithStatesProps) => {
+ const { t } = useTranslation();
+
+ const showLoading = loading && (!items || items.length === 0);
+
+ if (priorityFallback) {
+ return (
+
+ {priorityFallback}
+
+ );
+ }
+
+ if (!loading && isEmpty(items)) {
+ return (
+
+ );
+ }
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/client/src/Components/v2/design-elements/Breadcrumb.tsx b/client/src/Components/v2/design-elements/Breadcrumb.tsx
new file mode 100644
index 000000000..492ed0ec9
--- /dev/null
+++ b/client/src/Components/v2/design-elements/Breadcrumb.tsx
@@ -0,0 +1,78 @@
+import MuiBreadcrumbs from "@mui/material/Breadcrumbs";
+import Typography from "@mui/material/Typography";
+import { Link, useLocation } from "react-router-dom";
+import { useTheme } from "@mui/material/styles";
+import { ChevronRight } from "lucide-react";
+
+const isId = (segment: string): boolean => {
+ return segment.length === 24 || /^[a-f0-9-]{36}$/.test(segment);
+};
+
+export const Breadcrumb = () => {
+ const theme = useTheme();
+ const location = useLocation();
+
+ const segments = location.pathname.split("/").filter((x) => x);
+
+ // Build simplified breadcrumb: "uptime" or "uptime / details"
+ const basePage = segments[0] || "home";
+ const hasDetailsPage = segments.length > 1 && isId(segments[1]);
+
+ return (
+
+ }
+ sx={{
+ fontSize: "14px",
+ marginBottom: theme.spacing(6),
+ "& .MuiBreadcrumbs-separator": {
+ color: theme.palette.text.secondary,
+ },
+ }}
+ >
+ {hasDetailsPage ? (
+
+
+ {basePage.charAt(0).toUpperCase() + basePage.slice(1)}
+
+
+ ) : (
+
+ {basePage.charAt(0).toUpperCase() + basePage.slice(1)}
+
+ )}
+ {hasDetailsPage && (
+
+ Details
+
+ )}
+
+ );
+};
diff --git a/client/src/Components/v2/design-elements/BulletPointCheck.tsx b/client/src/Components/v2/design-elements/BulletPointCheck.tsx
new file mode 100644
index 000000000..2893ee4c2
--- /dev/null
+++ b/client/src/Components/v2/design-elements/BulletPointCheck.tsx
@@ -0,0 +1,46 @@
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import { Check } from "lucide-react";
+import { useTheme } from "@mui/material/styles";
+
+export const BulletPointCheck = ({
+ text,
+ variant = "info",
+}: {
+ text: string;
+ noHighlightText?: string;
+ variant?: "success" | "error" | "info";
+}) => {
+ const theme = useTheme();
+ const colors: Record = {
+ success: theme.palette.success.main,
+ error: theme.palette.error.main,
+ info: theme.palette.text.secondary,
+ };
+
+ return (
+
+
+
+ {text}
+
+
+ );
+};
diff --git a/client/src/Components/v2/design-elements/Dot.tsx b/client/src/Components/v2/design-elements/Dot.tsx
new file mode 100644
index 000000000..541e90812
--- /dev/null
+++ b/client/src/Components/v2/design-elements/Dot.tsx
@@ -0,0 +1,23 @@
+import type { CSSProperties } from "react";
+
+interface DotProps {
+ color?: string;
+ size?: string;
+ style?: CSSProperties;
+}
+
+export const Dot = ({ color = "gray", size = "4px", style }: DotProps) => {
+ return (
+
+ );
+};
diff --git a/client/src/Components/v2/design-elements/Fallback.tsx b/client/src/Components/v2/design-elements/Fallback.tsx
new file mode 100644
index 000000000..16eece25a
--- /dev/null
+++ b/client/src/Components/v2/design-elements/Fallback.tsx
@@ -0,0 +1,180 @@
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import { BulletPointCheck, SkeletonCard } from "@/Components/v2/design-elements";
+import { Button } from "@/Components/v2/inputs";
+
+import { useNavigate } from "react-router";
+import useMediaQuery from "@mui/material/useMediaQuery";
+import { useTheme } from "@mui/material/styles";
+import type { StackProps } from "@mui/material/Stack";
+
+interface BaseFallbackProps extends StackProps {
+ children: React.ReactNode;
+}
+
+export const BaseFallback = ({ children, ...props }: BaseFallbackProps) => {
+ const theme = useTheme();
+ const isSmall = useMediaQuery(theme.breakpoints.down("md"));
+
+ return (
+
+
+ {children}
+
+ );
+};
+
+export const ErrorFallback = ({
+ title,
+ subtitle,
+}: {
+ title: string;
+ subtitle: string;
+}) => {
+ const theme = useTheme();
+ return (
+
+
+ {title}
+
+ {subtitle}
+
+ );
+};
+
+export const EmptyFallback = ({
+ title,
+ bullets,
+ actionButtonText,
+ actionLink,
+}: {
+ title: string;
+ bullets: any;
+ actionButtonText: string;
+ actionLink: string;
+}) => {
+ const theme = useTheme();
+ const navigate = useNavigate();
+ return (
+
+
+
+ {title}
+
+
+ {Array.isArray(bullets) &&
+ bullets?.map((bullet: string) => (
+
+ ))}
+
+
+ navigate(actionLink)}
+ >
+ {actionButtonText}
+
+
+
+
+ );
+};
+
+export const EmptyMonitorFallback = ({
+ page,
+ title,
+ bullets,
+ actionButtonText,
+ actionLink,
+}: {
+ page: string;
+ title: string;
+ bullets: any;
+ actionButtonText: string;
+ actionLink: string;
+}) => {
+ const theme = useTheme();
+ const navigate = useNavigate();
+ return (
+
+
+
+ {title}
+
+
+ {Array.isArray(bullets) &&
+ bullets?.map((bullet: string, index: number) => (
+
+ ))}
+
+
+ navigate(actionLink)}
+ >
+ {actionButtonText}
+
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/design-elements/Icon.tsx b/client/src/Components/v2/design-elements/Icon.tsx
new file mode 100644
index 000000000..b32cd84b2
--- /dev/null
+++ b/client/src/Components/v2/design-elements/Icon.tsx
@@ -0,0 +1,18 @@
+import type { LucideIcon } from "lucide-react";
+
+interface IconProps {
+ icon: LucideIcon;
+ size?: number;
+ strokeWidth?: number;
+}
+
+const Icon = ({ icon: Icon, size = 20, strokeWidth = 1.5 }: IconProps) => {
+ return (
+
+ );
+};
+
+export default Icon;
diff --git a/client/src/Components/v2/design-elements/MonitorStatus.tsx b/client/src/Components/v2/design-elements/MonitorStatus.tsx
new file mode 100644
index 000000000..3d5e81845
--- /dev/null
+++ b/client/src/Components/v2/design-elements/MonitorStatus.tsx
@@ -0,0 +1,58 @@
+import type { Monitor } from "@/Types/Monitor";
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import { PulseDot, Dot } from "@/Components/v2/design-elements";
+import { getStatusColor, formatUrl } from "@/Utils/MonitorUtils";
+import { useTheme } from "@mui/material/styles";
+import prettyMilliseconds from "pretty-ms";
+import { typographyLevels } from "@/Utils/Theme/v2Palette";
+import useMediaQuery from "@mui/material/useMediaQuery";
+export const MonitorStatus = ({ monitor }: { monitor: Monitor }) => {
+ const theme = useTheme();
+ const isSmall = useMediaQuery(theme.breakpoints.down("md"));
+
+ if (!monitor) {
+ return null;
+ }
+ return (
+
+
+ {monitor.name}
+
+
+
+
+ {formatUrl(monitor?.url)}
+
+ {!isSmall && (
+ <>
+
+
+ Checking every {prettyMilliseconds(monitor?.interval, { verbose: true })}
+
+ >
+ )}
+
+
+ );
+};
diff --git a/client/src/Components/v2/design-elements/PulseDot.tsx b/client/src/Components/v2/design-elements/PulseDot.tsx
new file mode 100644
index 000000000..ab792b628
--- /dev/null
+++ b/client/src/Components/v2/design-elements/PulseDot.tsx
@@ -0,0 +1,48 @@
+import { Box, Stack, useTheme } from "@mui/material";
+
+interface PulseDotProps {
+ color: string;
+}
+
+export const PulseDot = ({ color }: PulseDotProps) => {
+ const theme = useTheme();
+
+ return (
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/design-elements/SkeletonCard.tsx b/client/src/Components/v2/design-elements/SkeletonCard.tsx
new file mode 100644
index 000000000..05f0c462a
--- /dev/null
+++ b/client/src/Components/v2/design-elements/SkeletonCard.tsx
@@ -0,0 +1,147 @@
+import { Box, Stack, useTheme } from "@mui/material";
+
+interface SkeletonCardProps {
+ width?: number | string | { xs?: number | string; md?: number | string };
+ showHalo?: boolean;
+}
+
+export const SkeletonCard = ({
+ width = { xs: 130, md: 216 },
+ showHalo = true,
+}: SkeletonCardProps) => {
+ const theme = useTheme();
+ const pulseAnimation = {
+ "@keyframes pulse": {
+ "0%, 100%": { opacity: 1 },
+ "50%": { opacity: 0.72 },
+ },
+ };
+
+ const floatAnimation = {
+ "@keyframes float": {
+ "0%, 100%": { transform: "rotate(5deg) translate(12px, -20px)" },
+ "50%": { transform: "rotate(5deg) translate(12px, -24px)" },
+ },
+ };
+
+ const cardStyle = {
+ width: width,
+ height: 57,
+ borderRadius: theme.shape.borderRadius,
+ background:
+ theme.palette.mode === "dark"
+ ? theme.palette.secondary.dark
+ : theme.palette.background.paper,
+ boxShadow:
+ theme.palette.mode === "dark"
+ ? "0 8px 30px rgba(0,0,0,.3), 0 0 0 1px rgba(255,255,255,.05) inset"
+ : "0 8px 30px rgba(0,0,0,.08), 0 0 0 1px rgba(0,0,0,.05) inset",
+ display: "flex",
+ gap: theme.spacing(5),
+ alignItems: "center",
+ padding: theme.spacing(5),
+ };
+
+ const blockStyle = {
+ width: 55,
+ height: 34,
+ borderRadius: theme.shape.borderRadius,
+ background: theme.palette.secondary.main,
+ border: `1px solid ${theme.palette.divider}`,
+ animation: "pulse 1.6s ease-in-out infinite",
+ ...pulseAnimation,
+ };
+
+ const lineStyle = {
+ height: 7,
+ borderRadius: theme.shape.borderRadius,
+ background: theme.palette.secondary.main,
+ border: `1px solid ${theme.palette.divider}`,
+ animation: "pulse 1.6s ease-in-out infinite",
+ ...pulseAnimation,
+ };
+
+ return (
+
+ {showHalo && (
+
+ )}
+
+ {/* Card stack */}
+
+ {/* Front card (now at bottom) */}
+
+
+
+ {/* Empty space to maintain card height */}
+
+
+
+ {/* Back card A */}
+
+
+
+
+
+
+
+
+
+ {/* Back card B */}
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/design-elements/StatBox.tsx b/client/src/Components/v2/design-elements/StatBox.tsx
new file mode 100644
index 000000000..b78a003dc
--- /dev/null
+++ b/client/src/Components/v2/design-elements/StatBox.tsx
@@ -0,0 +1,86 @@
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import Box from "@mui/material/Box";
+import { useTheme } from "@mui/material/styles";
+import { lighten } from "@mui/material/styles";
+import useMediaQuery from "@mui/material/useMediaQuery";
+import type { PaletteKey } from "@/Utils/Theme/v2Theme";
+import { BaseBox, TooltipWithInfo } from "@/Components/v2/design-elements";
+import type { SxProps } from "@mui/material";
+
+type GradientBox = React.PropsWithChildren<{
+ palette?: PaletteKey;
+ sx?: SxProps;
+}>;
+
+export const GradientBox = ({ children, palette, sx }: GradientBox) => {
+ const theme = useTheme();
+ const isSmall = useMediaQuery(theme.breakpoints.down("md"));
+ const isLight = theme.palette.mode === "light";
+ const paper = theme.palette.background.paper;
+ const paperStart = lighten(paper, isLight ? 0.06 : 0.08);
+ const paperEnd = paper;
+ const bg = palette
+ ? `linear-gradient(135deg, ${theme.palette[palette].light} 0%, ${theme.palette[palette].main} 100%)`
+ : `linear-gradient(135deg, ${paperStart} 0%, ${paperEnd} 100%)`;
+
+ return (
+
+ {children}
+
+ );
+};
+
+type StatBoxProps = React.PropsWithChildren<{
+ title: string;
+ subtitle: string;
+ palette?: PaletteKey;
+ sx?: SxProps;
+ tooltip?: string;
+ onClick?: () => void;
+}>;
+
+export const StatBox = ({
+ title,
+ subtitle,
+ palette,
+ children,
+ sx,
+ tooltip,
+ onClick,
+}: StatBoxProps) => {
+ const theme = useTheme();
+ const textColor = palette ? theme.palette[palette].contrastText : "inherit";
+
+ return (
+
+
+
+ {title}
+ {tooltip && (
+
+ )}
+
+ {subtitle}
+ {children}
+
+
+ );
+};
diff --git a/client/src/Components/v2/design-elements/StatusBox.tsx b/client/src/Components/v2/design-elements/StatusBox.tsx
new file mode 100644
index 000000000..6b1c1920c
--- /dev/null
+++ b/client/src/Components/v2/design-elements/StatusBox.tsx
@@ -0,0 +1,112 @@
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import Box from "@mui/material/Box";
+import { BaseBox } from "@/Components/v2/design-elements";
+import Background from "@/assets/Images/background-grid.svg?react";
+import { useTranslation } from "react-i18next";
+
+import { useTheme } from "@mui/material/styles";
+
+type StatusBoxProps = React.PropsWithChildren<{ children: React.ReactNode }>;
+
+export const BGBox = ({ children }: StatusBoxProps) => {
+ const theme = useTheme();
+ return (
+
+
+
+
+ {children}
+
+ );
+};
+
+const StatusBox = ({
+ label,
+ n,
+ color,
+}: {
+ label: string;
+ n: number;
+ color: string | undefined;
+}) => {
+ const theme = useTheme();
+ return (
+
+
+
+ {label}
+
+
+ {n}
+
+
+
+ );
+};
+
+export const UpStatusBox = ({ n }: { n: number }) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+ return (
+
+ );
+};
+
+export const DownStatusBox = ({ n }: { n: number }) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+ return (
+
+ );
+};
+
+export const PausedStatusBox = ({ n }: { n: number }) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+ return (
+
+ );
+};
+
+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
new file mode 100644
index 000000000..c9991f0d4
--- /dev/null
+++ b/client/src/Components/v2/design-elements/StatusLabel.tsx
@@ -0,0 +1,104 @@
+import Box from "@mui/material/Box";
+import { BaseBox } from "@/Components/v2/design-elements";
+import Typography from "@mui/material/Typography";
+
+import type { MonitorStatus } from "@/Types/Monitor";
+import type { SxProps } from "@mui/material/styles";
+import { getStatusPalette, getValuePalette } from "@/Utils/MonitorUtils";
+import { useTheme } from "@mui/material/styles";
+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;
+}) => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+ const palette = getStatusPalette(status);
+
+ const determineStatus = (
+ isActive: boolean | undefined,
+ status: MonitorStatus
+ ): string => {
+ if (isActive === false) {
+ return t("pages.common.monitors.status.paused");
+ }
+
+ 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");
+ };
+
+ return (
+
+
+
+ {determineStatus(isActive, status)}
+
+
+ );
+};
+
+export const ValueLabel = ({ value, text }: { value: ValueType; text: string }) => {
+ const theme = useTheme();
+ const palette = getValuePalette(value);
+ const transformedText = text.charAt(0).toUpperCase() + text.slice(1).toLowerCase();
+
+ return (
+
+
+ {transformedText}
+
+ );
+};
diff --git a/client/src/Components/v2/design-elements/Table.tsx b/client/src/Components/v2/design-elements/Table.tsx
new file mode 100644
index 000000000..0516f1ddd
--- /dev/null
+++ b/client/src/Components/v2/design-elements/Table.tsx
@@ -0,0 +1,577 @@
+import Stack from "@mui/material/Stack";
+import Grid2 from "@mui/material/Grid2";
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import Paper from "@mui/material/Paper";
+import Table from "@mui/material/Table";
+import TableBody from "@mui/material/TableBody";
+import TableCell from "@mui/material/TableCell";
+import TableContainer from "@mui/material/TableContainer";
+import TableHead from "@mui/material/TableHead";
+import TableRow from "@mui/material/TableRow";
+import Collapse from "@mui/material/Collapse";
+import Tooltip from "@mui/material/Tooltip";
+
+import IconButton from "@mui/material/IconButton";
+import {
+ ChevronsLeft,
+ ChevronsRight,
+ ChevronLeft,
+ ChevronRight,
+ Coffee,
+ Ellipsis,
+ PartyPopper,
+} from "lucide-react";
+
+import TablePagination from "@mui/material/TablePagination";
+import type { TablePaginationOwnProps } from "@mui/material/TablePagination";
+
+import { useTranslation } from "react-i18next";
+import { useState, Fragment } from "react";
+import { useTheme } from "@mui/material/styles";
+import useMediaQuery from "@mui/material/useMediaQuery";
+
+export type Header = {
+ id: number | string;
+ content: React.ReactNode;
+ onClick?: (event: React.MouseEvent, row: T) => void;
+ render: (row: T) => React.ReactNode;
+};
+
+type DataTableProps = {
+ headers: Header[];
+ data: T[];
+ onRowClick?: (row: T) => void;
+ cardsOnSmallScreens?: boolean;
+ expandableRows?: boolean;
+ renderExpandedContent?: (row: T) => React.ReactNode;
+ emptyViewText?: string;
+ emptyViewPositive?: boolean;
+};
+
+export function DataTable<
+ T extends {
+ id?: string | number;
+ _id?: string | number;
+ onRowClick?: (row: T) => void;
+ },
+>({
+ headers,
+ data,
+ onRowClick,
+ cardsOnSmallScreens = true,
+ expandableRows = false,
+ renderExpandedContent,
+ emptyViewText,
+ emptyViewPositive,
+}: DataTableProps) {
+ const theme = useTheme();
+ const [expanded, setExpanded] = useState<(string | number) | null>(null);
+ const handleExpand = (row: T) => {
+ const key = row.id || row._id || null;
+ setExpanded(expanded === key ? null : key);
+ };
+
+ const isSmall = useMediaQuery(theme.breakpoints.down("md"));
+
+ if (data.length === 0 || headers.length === 0) {
+ return (
+
+ );
+ }
+
+ const keys = [];
+ // Return stack of cards for small screens
+ if (isSmall && cardsOnSmallScreens) {
+ return (
+
+ {data.map((row) => {
+ const key = row.id || row._id || Math.random();
+ keys.push(key);
+ return (
+ (onRowClick ? onRowClick(row) : null)}
+ spacing={theme.spacing(4)}
+ sx={{
+ borderStyle: "solid",
+ borderWidth: 1,
+ borderColor: theme.palette.divider,
+ borderRadius: theme.shape.borderRadius,
+ padding: theme.spacing(4),
+ cursor: onRowClick ? "pointer" : "default",
+ }}
+ key={key}
+ >
+ {headers.map((header) => {
+ return (
+
+
+
+ {header.content}
+
+
+
+ {header.render(row)}{" "}
+
+
+ );
+ })}
+ {expandableRows && renderExpandedContent && renderExpandedContent(row) && (
+ {renderExpandedContent(row)}
+ )}
+
+ );
+ })}
+
+ );
+ }
+
+ return (
+
+
+
+
+ {headers.map((header, idx) => {
+ return (
+
+ {header.content}
+
+ );
+ })}
+
+
+
+ {data.map((row) => {
+ const key = row.id || row._id || Math.random();
+ const isExpanded = expanded === key;
+
+ return (
+
+ {
+ if (expandableRows) handleExpand(row);
+ else if (onRowClick) onRowClick(row);
+ }}
+ >
+ {headers.map((header, index) => {
+ return (
+ header.onClick!(e, row) : undefined
+ }
+ >
+ {header.render(row)}
+
+ );
+ })}
+
+ {expandableRows &&
+ renderExpandedContent &&
+ renderExpandedContent(row) && (
+
+
+
+ {renderExpandedContent(row)}
+
+
+
+ )}
+
+ );
+ })}
+
+
+
+ );
+}
+
+interface TablePaginationActionsProps {
+ count: number;
+ page: number;
+ rowsPerPage: number;
+ onPageChange: (event: React.MouseEvent, newPage: number) => void;
+}
+
+function TablePaginationActions(props: TablePaginationActionsProps) {
+ const theme = useTheme();
+ const { count, page, rowsPerPage, onPageChange } = props;
+
+ const handleFirstPageButtonClick = (event: React.MouseEvent) => {
+ onPageChange(event, 0);
+ };
+
+ const handleBackButtonClick = (event: React.MouseEvent) => {
+ onPageChange(event, page - 1);
+ };
+
+ const handleNextButtonClick = (event: React.MouseEvent) => {
+ onPageChange(event, page + 1);
+ };
+
+ const handleLastPageButtonClick = (event: React.MouseEvent) => {
+ onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1));
+ };
+
+ return (
+
+
+ {theme.direction === "rtl" ? (
+
+ ) : (
+
+ )}
+
+
+ {theme.direction === "rtl" ? (
+
+ ) : (
+
+ )}
+
+ = Math.ceil(count / rowsPerPage) - 1}
+ aria-label="next page"
+ >
+ {theme.direction === "rtl" ? (
+
+ ) : (
+
+ )}
+
+ = Math.ceil(count / rowsPerPage) - 1}
+ aria-label="last page"
+ >
+ {theme.direction === "rtl" ? (
+
+ ) : (
+
+ )}
+
+
+ );
+}
+
+interface HasMoreTablePaginationActionsProps {
+ hasMore?: boolean;
+ page: number;
+ onPageChange: (event: React.MouseEvent, newPage: number) => void;
+}
+function HasMoreTablePaginationActions(props: HasMoreTablePaginationActionsProps) {
+ const theme = useTheme();
+ const { hasMore, page, onPageChange } = props;
+
+ const handleFirstPageButtonClick = (event: React.MouseEvent) => {
+ onPageChange(event, 0);
+ };
+
+ const handleBackButtonClick = (event: React.MouseEvent) => {
+ onPageChange(event, page - 1);
+ };
+
+ const handleNextButtonClick = (event: React.MouseEvent) => {
+ onPageChange(event, page + 1);
+ };
+
+ return (
+
+
+ {theme.direction === "rtl" ? (
+
+ ) : (
+
+ )}
+
+
+ {theme.direction === "rtl" ? (
+
+ ) : (
+
+ )}
+
+
+ {theme.direction === "rtl" ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+ );
+}
+
+interface PaginationProps extends TablePaginationOwnProps {
+ component?: React.ElementType;
+ hasMore?: boolean;
+}
+
+export const Pagination = ({ ...props }: PaginationProps) => {
+ const { hasMore, ...rest } = props;
+ const isSmall = useMediaQuery((theme: any) => theme.breakpoints.down("sm"));
+ const theme = useTheme();
+ const labelDisplayedRows = ({
+ from,
+ to,
+ count,
+ }: {
+ from: number;
+ to: number;
+ count: number;
+ }) => {
+ if (hasMore) {
+ return to === 0 ? "" : `${from}–${to}`;
+ }
+ return `${from}–${to} of ${count}`;
+ };
+
+ return (
+
+ typeof hasMore === "boolean" ? (
+
+ ) : (
+
+ )
+ }
+ rowsPerPageOptions={[5, 10, 25]}
+ labelDisplayedRows={labelDisplayedRows}
+ {...rest}
+ sx={{
+ "& .MuiTablePagination-toolbar": isSmall
+ ? {
+ display: "grid",
+ gridTemplateColumns: "1fr 1fr",
+ gridAutoRows: "auto",
+ rowGap: theme.spacing(1),
+ alignItems: "center",
+ justifyItems: "center",
+ paddingLeft: 0,
+ paddingRight: 0,
+ }
+ : { display: "flex", alignItems: "center" },
+ "& .MuiTablePagination-selectLabel": {
+ gridColumn: "1",
+ gridRow: "1",
+ justifySelf: "center",
+ },
+ "& .MuiTablePagination-select": {
+ gridColumn: "2",
+ gridRow: "1",
+ justifySelf: "center",
+ },
+ "& .MuiTablePagination-displayedRows": isSmall
+ ? { gridColumn: "1 / span 2", gridRow: "2", justifySelf: "center" }
+ : {},
+ "& .table-pagination-actions": isSmall
+ ? { gridColumn: "1 / span 2", gridRow: "3", justifySelf: "center" }
+ : {},
+ "& .MuiSelect-select": {
+ border: 1,
+ borderColor: theme.palette.divider,
+ borderRadius: theme.shape.borderRadius,
+ },
+ }}
+ />
+ );
+};
+
+const EmptyView = ({ text, positive }: { text?: string; positive?: boolean }) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+ const Icon = positive ? PartyPopper : Coffee;
+ return (
+
+
+
+
+
+ {text ?? t("common.table.empty")}
+
+
+ );
+};
diff --git a/client/src/Components/v2/design-elements/Tooltip.tsx b/client/src/Components/v2/design-elements/Tooltip.tsx
new file mode 100644
index 000000000..f83b1331c
--- /dev/null
+++ b/client/src/Components/v2/design-elements/Tooltip.tsx
@@ -0,0 +1,79 @@
+import MuiTooltip from "@mui/material/Tooltip";
+import { useTheme } from "@mui/material/styles";
+import { Info } from "lucide-react";
+
+import type { TooltipProps } from "@mui/material/Tooltip";
+
+type StyledTooltipProps = TooltipProps;
+
+export const Tooltip = ({ placement = "top", ...props }: StyledTooltipProps) => {
+ const theme = useTheme();
+
+ return (
+
+ {props.children}
+
+ );
+};
+
+export interface TooltipWithInfoProps extends Omit {
+ iconSize?: number;
+ iconColor?: string;
+}
+
+export const TooltipWithInfo = ({
+ iconSize = 14,
+ iconColor,
+ ...props
+}: TooltipWithInfoProps) => {
+ const theme = useTheme();
+
+ const defaultColor = theme.palette.text.secondary;
+
+ return (
+
+
+ {
+ e.currentTarget.style.opacity = "1";
+ }}
+ onMouseLeave={(e) => {
+ e.currentTarget.style.opacity = "0.7";
+ }}
+ />
+
+
+ );
+};
diff --git a/client/src/Components/v2/design-elements/index.tsx b/client/src/Components/v2/design-elements/index.tsx
new file mode 100644
index 000000000..6ebffc624
--- /dev/null
+++ b/client/src/Components/v2/design-elements/index.tsx
@@ -0,0 +1,17 @@
+export * from "./Breadcrumb";
+export * from "./BasePage";
+export * from "./Fallback";
+export * from "./SkeletonCard";
+export * from "./BulletPointCheck";
+export * from "./StatusBox";
+export * from "./BaseBox";
+export * from "./Table";
+export { DataTable as Table } from "./Table";
+export * from "./StatusLabel";
+export * from "./PulseDot";
+export * from "./Dot";
+export * from "./MonitorStatus";
+export { default as Icon } from "./Icon";
+export * from "./Tooltip";
+export * from "./StatBox";
+export * from "./BaseChart";
diff --git a/client/src/Components/v2/inputs/AutoComplete.tsx b/client/src/Components/v2/inputs/AutoComplete.tsx
new file mode 100644
index 000000000..af0e8cbfd
--- /dev/null
+++ b/client/src/Components/v2/inputs/AutoComplete.tsx
@@ -0,0 +1,88 @@
+import Autocomplete from "@mui/material/Autocomplete";
+import type { AutocompleteProps } from "@mui/material/Autocomplete";
+import { TextField, Checkbox } from "@/Components/v2/inputs";
+import ListItem from "@mui/material/ListItem";
+import Stack from "@mui/material/Stack";
+import { useTheme } from "@mui/material/styles";
+import { FieldLabel } from "./FieldLabel";
+import { ChevronDown } from "lucide-react";
+
+type AutoCompleteInputProps = Omit<
+ AutocompleteProps,
+ "renderInput"
+> & {
+ renderInput?: AutocompleteProps["renderInput"];
+ fieldLabel?: string;
+ required?: boolean;
+};
+
+export const AutoCompleteInput = ({
+ fieldLabel,
+ required,
+ renderInput,
+ ...props
+}: AutoCompleteInputProps) => {
+ const theme = useTheme();
+ const multiple = props.multiple;
+
+ const defaultRenderInput = (params: any) => (
+
+ );
+
+ const autocomplete = (
+
+ }
+ renderInput={renderInput || defaultRenderInput}
+ getOptionKey={(option) => option.id}
+ renderTags={() => null}
+ renderOption={(props, option, { selected }) => {
+ const { key, ...optionProps } = props;
+ return (
+
+
+ {multiple && }
+ {option.name}
+
+
+ );
+ }}
+ sx={{
+ "&.MuiAutocomplete-root .MuiAutocomplete-input": {
+ padding: `0 ${theme.spacing(5)}`,
+ },
+ "& .MuiInputBase-root .MuiAutocomplete-endAdornment": {
+ right: theme.spacing(3),
+ },
+ }}
+ />
+ );
+
+ if (fieldLabel) {
+ return (
+
+ {fieldLabel}
+ {autocomplete}
+
+ );
+ }
+
+ return autocomplete;
+};
diff --git a/client/src/Components/v2/inputs/Button.tsx b/client/src/Components/v2/inputs/Button.tsx
new file mode 100644
index 000000000..54f776075
--- /dev/null
+++ b/client/src/Components/v2/inputs/Button.tsx
@@ -0,0 +1,37 @@
+import Button from "@mui/material/Button";
+import type { ButtonProps } from "@mui/material/Button";
+import { useTheme } from "@mui/material/styles";
+
+export const ButtonInput = ({ sx, ...props }: ButtonProps) => {
+ const theme = useTheme();
+ const outlinedSx =
+ props.variant === "outlined"
+ ? {
+ color: theme.palette.text.primary,
+ borderColor: theme.palette.divider,
+ "&:hover": {
+ borderColor: theme.palette.text.secondary,
+ },
+ }
+ : {};
+ return (
+
+ );
+};
diff --git a/client/src/Components/v2/inputs/Checkbox.tsx b/client/src/Components/v2/inputs/Checkbox.tsx
new file mode 100644
index 000000000..195a796dd
--- /dev/null
+++ b/client/src/Components/v2/inputs/Checkbox.tsx
@@ -0,0 +1,56 @@
+import { forwardRef } from "react";
+import Checkbox from "@mui/material/Checkbox";
+import type { CheckboxProps } from "@mui/material/Checkbox";
+import { Square, SquareCheck } from "lucide-react";
+import { useTheme } from "@mui/material/styles";
+
+type CheckboxInputProps = CheckboxProps & {
+ label?: string;
+};
+export const CheckboxInput = forwardRef(
+ function CheckboxInput(
+ { sx, ...props }: CheckboxInputProps,
+ ref: React.Ref
+ ) {
+ const theme = useTheme();
+ return (
+
+ }
+ checkedIcon={
+
+ }
+ sx={{
+ color: theme.palette.text.secondary,
+ "&.Mui-checked": {
+ color: theme.palette.primary.main,
+ },
+ "&:hover": { backgroundColor: "transparent" },
+ "& svg": {
+ stroke: "currentColor",
+ },
+ "& svg path, & svg line, & svg polyline, & svg rect": {
+ stroke: "currentColor",
+ fill: "none",
+ },
+
+ ...sx,
+ }}
+ />
+ );
+ }
+);
diff --git a/client/src/Components/v2/inputs/Dialog.tsx b/client/src/Components/v2/inputs/Dialog.tsx
new file mode 100644
index 000000000..373d95023
--- /dev/null
+++ b/client/src/Components/v2/inputs/Dialog.tsx
@@ -0,0 +1,56 @@
+import Dialog from "@mui/material/Dialog";
+import DialogActions from "@mui/material/DialogActions";
+import DialogContent from "@mui/material/DialogContent";
+import DialogContentText from "@mui/material/DialogContentText";
+import DialogTitle from "@mui/material/DialogTitle";
+import { Button } from "@/Components/v2/inputs";
+import { typographyLevels } from "@/Utils/Theme/v2Palette";
+import { useTranslation } from "react-i18next";
+
+export const DialogInput = ({
+ open,
+ title,
+ content,
+ onConfirm,
+ onCancel,
+ confirmColor = "primary",
+ cancelColor = "error",
+ loading = false,
+}: {
+ open: boolean;
+ title?: string;
+ content?: string;
+ onConfirm?(item: any): any;
+ onCancel?(item: any): any;
+ confirmColor?: "error" | "primary";
+ cancelColor?: "error" | "primary";
+ loading?: boolean;
+}) => {
+ const { t } = useTranslation();
+ return (
+
+ {title}
+
+ {content}
+
+
+
+ {t("common.buttons.cancel")}
+
+
+ {t("common.buttons.confirm")}
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/inputs/FieldLabel.tsx b/client/src/Components/v2/inputs/FieldLabel.tsx
new file mode 100644
index 000000000..552991538
--- /dev/null
+++ b/client/src/Components/v2/inputs/FieldLabel.tsx
@@ -0,0 +1,38 @@
+import Typography from "@mui/material/Typography";
+import { useTheme } from "@mui/material/styles";
+
+interface FieldLabelProps {
+ children: React.ReactNode;
+ required?: boolean;
+ htmlFor?: string;
+}
+
+export const FieldLabel = ({ children, required = false, htmlFor }: FieldLabelProps) => {
+ const theme = useTheme();
+
+ return (
+
+ {children}
+ {required && (
+
+ *
+
+ )}
+
+ );
+};
diff --git a/client/src/Components/v2/inputs/Select.tsx b/client/src/Components/v2/inputs/Select.tsx
new file mode 100644
index 000000000..057b0e72c
--- /dev/null
+++ b/client/src/Components/v2/inputs/Select.tsx
@@ -0,0 +1,102 @@
+import Select from "@mui/material/Select";
+import React from "react";
+import { useTheme } from "@mui/material/styles";
+import type { SelectProps } from "@mui/material/Select";
+import Stack from "@mui/material/Stack";
+import { FieldLabel } from "./FieldLabel";
+import { ChevronDown } from "lucide-react";
+import Typography from "@mui/material/Typography";
+
+interface SelectInputProps extends Omit, "label"> {
+ fieldLabel?: string;
+ required?: boolean;
+ placeholder?: string;
+ placeholderColor?: string;
+}
+
+export const SelectInput = ({
+ fieldLabel,
+ required,
+ placeholder,
+ placeholderColor,
+ ...props
+}: SelectInputProps) => {
+ const theme = useTheme();
+ const emptyPlaceholderColor = placeholderColor || theme.palette.text.disabled;
+
+ const renderValue = (selected: any) => {
+ const isMultiple = Boolean((props as any).multiple);
+ const isEmpty = isMultiple
+ ? !Array.isArray(selected) || selected.length === 0
+ : selected === undefined || selected === null || selected === "";
+
+ if (isEmpty && placeholder) {
+ return {placeholder} ;
+ }
+
+ if (isMultiple) {
+ const items: string[] = Array.isArray(selected) ? selected : [];
+ const capitalized = items.map(
+ (item) => item.charAt(0).toUpperCase() + item.slice(1)
+ );
+ return ({capitalized.join(" | ")} ) as any;
+ }
+
+ const nodes = React.Children.toArray(props.children as React.ReactNode);
+ for (const node of nodes) {
+ if (!React.isValidElement(node)) continue;
+ const el = node as React.ReactElement<{
+ value?: unknown;
+ children?: React.ReactNode;
+ }>;
+ if (el.props?.value === selected) {
+ return (el.props.children ?? selected) as React.ReactNode;
+ }
+ }
+ return selected as React.ReactNode;
+ };
+
+ const select = (
+
+ {...props}
+ displayEmpty
+ renderValue={renderValue}
+ inputProps={{
+ ...(props.inputProps || {}),
+ "aria-placeholder": placeholder,
+ }}
+ IconComponent={() => (
+
+ )}
+ sx={{
+ height: "34px",
+ "& .MuiSelect-icon": {
+ right: theme.spacing(3),
+ },
+ "& .MuiOutlinedInput-notchedOutline": {
+ borderRadius: theme.shape.borderRadius,
+ borderColor: theme.palette.divider,
+ },
+ "&:hover .MuiOutlinedInput-notchedOutline": {
+ borderColor: theme.palette.divider,
+ },
+ ...props.sx,
+ }}
+ />
+ );
+
+ if (fieldLabel) {
+ return (
+
+ {fieldLabel}
+ {select}
+
+ );
+ }
+
+ return select;
+};
diff --git a/client/src/Components/v2/inputs/TextInput.tsx b/client/src/Components/v2/inputs/TextInput.tsx
new file mode 100644
index 000000000..9a16a43c7
--- /dev/null
+++ b/client/src/Components/v2/inputs/TextInput.tsx
@@ -0,0 +1,56 @@
+import { forwardRef } from "react";
+import TextField from "@mui/material/TextField";
+import type { TextFieldProps } from "@mui/material";
+import { typographyLevels } from "@/Utils/Theme/v2Palette";
+import { useTheme } from "@mui/material/styles";
+import Stack from "@mui/material/Stack";
+import { FieldLabel } from "./FieldLabel";
+
+interface TextInputProps extends Omit {
+ fieldLabel?: string;
+ required?: boolean;
+}
+
+export const TextInput = forwardRef(function TextInput(
+ { fieldLabel, required, ...props },
+ ref
+) {
+ const theme = useTheme();
+
+ const input = (
+
+ );
+
+ if (fieldLabel) {
+ return (
+
+ {fieldLabel}
+ {input}
+
+ );
+ }
+
+ return input;
+});
+TextInput.displayName = "TextInput";
diff --git a/client/src/Components/v2/inputs/ToggleButton.tsx b/client/src/Components/v2/inputs/ToggleButton.tsx
new file mode 100644
index 000000000..40b4309b8
--- /dev/null
+++ b/client/src/Components/v2/inputs/ToggleButton.tsx
@@ -0,0 +1,52 @@
+import ToggleButton from "@mui/material/ToggleButton";
+import ToggleButtonGroup from "@mui/material/ToggleButtonGroup";
+import type { ToggleButtonProps } from "@mui/material/ToggleButton";
+import type { ToggleButtonGroupProps } from "@mui/material/ToggleButtonGroup";
+import { useTheme } from "@mui/material/styles";
+
+export const ToggleButtonInput = ({ sx, ...props }: ToggleButtonProps) => {
+ return (
+
+ );
+};
+
+export const ToggleButtonGroupInput = ({ sx, ...props }: ToggleButtonGroupProps) => {
+ const theme = useTheme();
+ return (
+
+ );
+};
diff --git a/client/src/Components/v2/inputs/index.tsx b/client/src/Components/v2/inputs/index.tsx
new file mode 100644
index 000000000..70e740dbd
--- /dev/null
+++ b/client/src/Components/v2/inputs/index.tsx
@@ -0,0 +1,11 @@
+export { ButtonInput as Button } from "./Button";
+export * from "./FieldLabel";
+export { SelectInput as Select } from "./Select";
+export { CheckboxInput as Checkbox } from "./Checkbox";
+export { AutoCompleteInput as Autocomplete } from "./AutoComplete";
+export { TextInput as TextField } from "./TextInput";
+export {
+ ToggleButtonInput as ToggleButton,
+ ToggleButtonGroupInput as ToggleButtonGroup,
+} from "./ToggleButton";
+export { DialogInput as Dialog } from "./Dialog";
diff --git a/client/src/Components/v2/monitors/ControlsFilter.tsx b/client/src/Components/v2/monitors/ControlsFilter.tsx
new file mode 100644
index 000000000..d7eecefb5
--- /dev/null
+++ b/client/src/Components/v2/monitors/ControlsFilter.tsx
@@ -0,0 +1,92 @@
+import Stack from "@mui/material/Stack";
+import { Select, Button } from "@/Components/v2/inputs";
+import MenuItem from "@mui/material/MenuItem";
+import useMediaQuery from "@mui/material/useMediaQuery";
+
+import type { MonitorType } from "@/Types/Monitor";
+import { Typography, useTheme } from "@mui/material";
+
+const types = ["http", "ping", "port", "docker"];
+const statuses = ["up", "down"];
+const states = ["active", "paused"];
+
+export const ControlsFilter = ({
+ selectedTypes,
+ setSelectedTypes,
+ selectedStatus,
+ setSelectedStatus,
+ selectedState,
+ setSelectedState,
+ onClearFilters,
+}: {
+ selectedTypes: MonitorType[];
+ setSelectedTypes: React.Dispatch>;
+ selectedStatus: string;
+ setSelectedStatus: React.Dispatch>;
+ selectedState: string;
+ setSelectedState: React.Dispatch>;
+ onClearFilters: () => void;
+}) => {
+ const theme = useTheme();
+ const isSmall = useMediaQuery(theme.breakpoints.down("md"));
+ const isFilterActive =
+ selectedTypes.length > 0 || selectedStatus !== "" || selectedState !== "";
+ return (
+
+ setSelectedTypes(e.target.value as MonitorType[])}
+ >
+ {types.map((type) => (
+
+ {type}
+
+ ))}
+
+ setSelectedStatus(e.target.value)}
+ >
+ {statuses.map((status) => (
+
+ {status}
+
+ ))}
+
+ setSelectedState(e.target.value)}
+ >
+ {states.map((state) => (
+
+ {state}
+
+ ))}
+
+ {isFilterActive && (
+
+ Clear Filters
+
+ )}
+
+ );
+};
diff --git a/client/src/Components/v2/monitors/MonitorStatBoxes.tsx b/client/src/Components/v2/monitors/MonitorStatBoxes.tsx
new file mode 100644
index 000000000..65be06f3b
--- /dev/null
+++ b/client/src/Components/v2/monitors/MonitorStatBoxes.tsx
@@ -0,0 +1,69 @@
+import Stack from "@mui/material/Stack";
+import { StatBox } from "@/Components/v2/design-elements";
+
+import { useTheme } from "@mui/material/styles";
+import type { MonitorStats, Monitor } from "@/Types/Monitor";
+import { getHumanReadableDuration } from "@/Utils/timeUtilsLegacy.js";
+import { getStatusPalette } from "@/Utils/MonitorUtils";
+import { useTranslation } from "react-i18next";
+
+interface MonitorStatBoxesProps {
+ monitor?: Monitor;
+ monitorStats: MonitorStats | null;
+ certificateExpiry?: string;
+}
+
+export const MonitorStatBoxes = ({
+ monitor,
+ monitorStats,
+ certificateExpiry,
+}: MonitorStatBoxesProps) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+ if (!monitorStats || !monitor) {
+ return null;
+ }
+
+ const timeOfLastFailure = monitorStats?.timeOfLastFailure || 0;
+ const timeSinceLastFailure =
+ timeOfLastFailure > 0
+ ? Date.now() - timeOfLastFailure
+ : Date.now() - new Date(monitorStats?.createdAt || 0).getTime();
+
+ // Determine time since last check
+ const timeOfLastCheck = monitorStats?.lastCheckTimestamp || 0;
+ const timeSinceLastCheck = Date.now() - timeOfLastCheck;
+
+ const streakTime = getHumanReadableDuration(timeSinceLastFailure);
+
+ const lastCheckTime = getHumanReadableDuration(timeSinceLastCheck);
+ const palette = getStatusPalette(monitor?.status);
+
+ return (
+
+
+
+
+
+ {monitor?.type === "http" && (
+
+ )}
+
+ );
+};
diff --git a/client/src/Components/v2/monitors/charts/HistogramDetails.tsx b/client/src/Components/v2/monitors/charts/HistogramDetails.tsx
new file mode 100644
index 000000000..8858a9784
--- /dev/null
+++ b/client/src/Components/v2/monitors/charts/HistogramDetails.tsx
@@ -0,0 +1,163 @@
+import { BaseChart, BaseBox } from "@/Components/v2/design-elements";
+import { Clock } from "lucide-react";
+import {
+ AreaChart,
+ Area,
+ XAxis,
+ Tooltip,
+ CartesianGrid,
+ ResponsiveContainer,
+ Text,
+} from "recharts";
+import Typography from "@mui/material/Typography";
+
+import { useSelector } from "react-redux";
+import {
+ formatDateWithTz,
+ tickDateFormatLookup,
+ tooltipDateFormatLookup,
+} from "@/Utils/TimeUtils";
+import { useTheme } from "@mui/material/styles";
+import type { GroupedCheck } from "@/Types/Check";
+import type { RootState } from "@/Types/state";
+
+type XTickProps = {
+ x: number;
+ y: number;
+ payload: { value: any };
+ range: string;
+};
+
+export const XTick = ({ x, y, payload, range }: XTickProps) => {
+ const format = tickDateFormatLookup(range);
+ const theme = useTheme();
+ const uiTimezone = useSelector((state: RootState) => state.ui.timezone);
+ return (
+
+ {formatDateWithTz(payload?.value, format, uiTimezone)}
+
+ );
+};
+
+type ResponseTimeToolTipProps = {
+ active?: boolean | undefined;
+ payload?: any[];
+ label?: string | number;
+ range: string;
+ theme: any;
+ uiTimezone: string;
+};
+
+const ResponseTimeToolTip = ({
+ active,
+ payload,
+ label,
+ range,
+ theme,
+ uiTimezone,
+}: ResponseTimeToolTipProps) => {
+ if (!label) return null;
+ if (!payload) return null;
+ if (!active) return null;
+
+ const format = tooltipDateFormatLookup(range);
+ const responseTime = Math.floor(payload?.[0]?.payload?.avgResponseTime || 0);
+ return (
+
+ {formatDateWithTz(String(label), format, uiTimezone)}
+ Response time: {responseTime} ms
+
+ );
+};
+
+export const HistogramDetails = ({
+ checks,
+ range,
+}: {
+ checks: GroupedCheck[];
+ range: string;
+}) => {
+ const theme = useTheme();
+ const uiTimezone = useSelector((state: RootState) => state.ui.timezone);
+ return (
+
+ }
+ title="Response times"
+ >
+
+
+
+
+
+
+
+
+
+ (
+
+ )}
+ />
+
+ (
+
+ )}
+ />
+
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/monitors/charts/HistogramPageSpeed.tsx b/client/src/Components/v2/monitors/charts/HistogramPageSpeed.tsx
new file mode 100644
index 000000000..1bcceebd1
--- /dev/null
+++ b/client/src/Components/v2/monitors/charts/HistogramPageSpeed.tsx
@@ -0,0 +1,106 @@
+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 { MonitorStatus } from "@/Types/Monitor";
+
+import { getStatusColor } from "@/Utils/MonitorUtils";
+
+const processData = (checks: Check[]) => {
+ if (checks.length === 0) return [];
+ const formattedData: { score: number; date: string }[] = [];
+
+ const calculateScore = (entry: Check) => {
+ const accessibility = entry?.accessibility || 0;
+ const bestPractices = entry?.bestPractices || 0;
+ const performance = entry?.performance || 0;
+ const seo = entry?.seo || 0;
+ return Math.floor((accessibility + bestPractices + performance + seo) / 4);
+ };
+
+ checks.forEach((entry) => {
+ const score = calculateScore(entry);
+ const date = entry.createdAt;
+ formattedData.push({ score, date });
+ });
+
+ return formattedData;
+};
+
+export const HistogramPageSpeed = ({
+ checks,
+ status,
+}: {
+ checks: Check[];
+ status: MonitorStatus;
+}) => {
+ const theme = useTheme();
+ const scores = processData(checks ?? []);
+ const color = getStatusColor(status, theme);
+ return (
+ event.stopPropagation()}
+ overflow={"hidden"}
+ sx={{
+ cursor: "default",
+ position: "relative",
+ }}
+ >
+
+
+
+ }
+ />
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/monitors/charts/HistogramPageSpeedDetails.tsx b/client/src/Components/v2/monitors/charts/HistogramPageSpeedDetails.tsx
new file mode 100644
index 000000000..8fc39e16c
--- /dev/null
+++ b/client/src/Components/v2/monitors/charts/HistogramPageSpeedDetails.tsx
@@ -0,0 +1,144 @@
+import { BaseChart } from "@/Components/v2/design-elements";
+import { TrendingUp } from "lucide-react";
+import { XTick } from "@/Components/v2/monitors/";
+import {
+ XAxis,
+ AreaChart,
+ Area,
+ Tooltip,
+ CartesianGrid,
+ ResponsiveContainer,
+} from "recharts";
+import { HistogramPageSpeedScoresTooltip } from "@/Components/v2/monitors";
+
+import { useTheme } from "@mui/material/styles";
+import type { Check } from "@/Types/Check";
+import type { Palette } from "@mui/material/styles";
+type PaletteColorKey = Extract<
+ keyof Palette,
+ "primary" | "error" | "success" | "warning"
+>;
+
+export interface ConfigItem {
+ id: string;
+ text: string;
+ palette: PaletteColorKey;
+}
+
+const config: Record = {
+ seo: {
+ id: "seo",
+ text: "SEO",
+ palette: "primary",
+ },
+ performance: {
+ id: "performance",
+ text: "performance",
+ palette: "success",
+ },
+ bestPractices: {
+ id: "bestPractices",
+ text: "best practices",
+ palette: "warning",
+ },
+ accessibility: {
+ id: "accessibility",
+ text: "accessibility",
+ palette: "error",
+ },
+};
+
+export const HistogramPageSpeedDetails = ({
+ checks,
+ range,
+}: {
+ checks: Check[];
+ range: string;
+}) => {
+ const theme = useTheme();
+ return (
+
+ }
+ title="Score history"
+ >
+
+
+ (
+
+ )}
+ />
+
+ }
+ />
+
+ {Object.values(config).map(({ id, palette }) => {
+ const startColor = theme.palette[palette].main;
+ const endColor = theme.palette[palette].light;
+
+ return (
+
+
+
+
+ );
+ })}
+
+ {Object.keys(config).map((key) => {
+ const { palette } = config[key];
+ const strokeColor = theme.palette[palette].main;
+ const bgColor = theme.palette.primary.main;
+
+ return (
+
+ );
+ })}
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/monitors/charts/HistogramPageSpeedDetailsTooltip.tsx b/client/src/Components/v2/monitors/charts/HistogramPageSpeedDetailsTooltip.tsx
new file mode 100644
index 000000000..7219ba821
--- /dev/null
+++ b/client/src/Components/v2/monitors/charts/HistogramPageSpeedDetailsTooltip.tsx
@@ -0,0 +1,78 @@
+import Box from "@mui/material/Box";
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import { useTheme } from "@mui/material/styles";
+import { formatDateWithTz } from "@/Utils/TimeUtils";
+import type { ConfigItem } from "@/Components/v2/monitors";
+import type { TooltipProps } from "recharts";
+import { useSelector } from "react-redux";
+import type { RootState } from "@/Types/state";
+
+interface HistogramPageSpeedScoresTooltipProps
+ extends Partial> {
+ config: Record;
+}
+export const HistogramPageSpeedScoresTooltip = ({
+ active,
+ payload,
+ label,
+ config,
+}: HistogramPageSpeedScoresTooltipProps) => {
+ const theme = useTheme();
+ const uiTimezone = useSelector((state: RootState) => state.ui.timezone);
+
+ if (active && payload && payload.length) {
+ return (
+
+
+ {formatDateWithTz(label, "ddd, MMMM D, YYYY, h:mm A", uiTimezone)}
+
+ {Object.keys(config)
+ .reverse()
+ .map((key) => {
+ const { palette } = config[key];
+ const dotColor = theme.palette[palette].main;
+
+ return (
+
+
+
+ {config[key].text}
+
+ {Math.floor(payload[0].payload[key])}
+
+ );
+ })}
+
+ );
+ }
+ return null;
+};
diff --git a/client/src/Components/v2/monitors/charts/HistogramPageSpeedTooltip.tsx b/client/src/Components/v2/monitors/charts/HistogramPageSpeedTooltip.tsx
new file mode 100644
index 000000000..28d43a66e
--- /dev/null
+++ b/client/src/Components/v2/monitors/charts/HistogramPageSpeedTooltip.tsx
@@ -0,0 +1,39 @@
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import { formatDateWithTz } from "@/Utils/TimeUtils";
+import type { TooltipProps } from "recharts";
+import { useTheme } from "@mui/material/styles";
+import { useSelector } from "react-redux";
+import type { RootState } from "@/Types/state";
+
+export const HistogramPageSpeedTooltip = ({
+ active,
+ payload,
+}: TooltipProps) => {
+ const uiTimezone = useSelector((state: RootState) => state.ui.timezone);
+ const theme = useTheme();
+
+ if (!active || !payload || payload.length === 0) {
+ return null;
+ }
+
+ const data = payload[0]?.payload;
+
+ return (
+
+
+ {formatDateWithTz(data?.date, "ddd, MMMM D, YYYY, HH:mm A", uiTimezone)}
+
+ Score: {data?.score}
+
+ );
+};
diff --git a/client/src/Components/v2/monitors/charts/HistogramStatus.tsx b/client/src/Components/v2/monitors/charts/HistogramStatus.tsx
new file mode 100644
index 000000000..1c25c339e
--- /dev/null
+++ b/client/src/Components/v2/monitors/charts/HistogramStatus.tsx
@@ -0,0 +1,166 @@
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import { BaseChart } from "@/Components/v2/design-elements";
+import { useTheme } from "@mui/material/styles";
+import { useSelector } from "react-redux";
+import { formatDateWithTz } from "@/Utils/timeUtilsLegacy";
+import { useTranslation } from "react-i18next";
+import { ResponsiveContainer, BarChart, XAxis, Bar, Cell, Tooltip } from "recharts";
+import { getResponseTimeColor } from "@/Utils/MonitorUtils";
+import type { GroupedCheck } from "@/Types/Check";
+import type { RootState } from "@/Types/state";
+
+interface XLabelProps {
+ p1: GroupedCheck;
+ p2: GroupedCheck;
+ range: string;
+}
+
+const XLabel = ({ p1, p2, range }: XLabelProps) => {
+ const theme = useTheme();
+ const uiTimezone = useSelector((state: RootState) => state.ui.timezone);
+ const dateFormat = range === "day" ? "MMM D, h:mm A" : "MMM D";
+ return (
+ <>
+
+ {formatDateWithTz(p1.bucketDate, dateFormat, uiTimezone)}
+
+
+ {formatDateWithTz(p2.bucketDate, dateFormat, uiTimezone)}
+
+ >
+ );
+};
+
+interface HistogramStatusProps {
+ title: string;
+ icon: React.ReactNode;
+ checks?: GroupedCheck[];
+ range: string;
+}
+
+export const HistogramStatus = ({
+ title,
+ icon,
+ checks = [],
+ range,
+}: HistogramStatusProps) => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+ const CustomTooltip = ({
+ active,
+ payload,
+ }: {
+ active?: boolean;
+ payload?: Array<{ payload: GroupedCheck }>;
+ }) => {
+ const uiTimezone = useSelector((state: RootState) => state.ui.timezone);
+ if (!active || !payload?.length) return null;
+ const d = payload[0]?.payload;
+ const avg = d?.avgResponseTime ?? 0;
+ const titleText = t("common.charts.labels.averageResponseTime");
+ const fmt = range === "30d" ? "MMM D, YYYY" : "ddd, MMM D, YYYY, h:mm A";
+ let dateLabel = "";
+ if (d?.bucketDate) {
+ const base = new Date(d.bucketDate);
+ const midBucket =
+ range === "30d" ? new Date(base.getTime() + 12 * 60 * 60 * 1000) : base;
+ dateLabel = formatDateWithTz(midBucket.toISOString(), fmt, uiTimezone);
+ }
+ return (
+
+ {dateLabel ? (
+
+ {dateLabel}
+
+ ) : null}
+ {titleText}
+ {Math.floor(avg)} ms
+
+ );
+ };
+
+ const totalChecks = checks.reduce((acc, check) => acc + (check.totalChecks || 0), 0);
+
+ return (
+
+
+
+
+ Total checks: {totalChecks}
+
+
+
+
+
+ }
+ />
+ }
+ />
+
+ {checks?.map((groupedCheck, index) => {
+ const fillColor = getResponseTimeColor(groupedCheck.avgResponseTime);
+ return (
+ |
+ );
+ })}
+
+
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/monitors/charts/PiePageSpeed.tsx b/client/src/Components/v2/monitors/charts/PiePageSpeed.tsx
new file mode 100644
index 000000000..1c58bb3f7
--- /dev/null
+++ b/client/src/Components/v2/monitors/charts/PiePageSpeed.tsx
@@ -0,0 +1,161 @@
+import { BaseChart } from "@/Components/v2/design-elements";
+import { FileText } from "lucide-react";
+import type { Check, CheckAudits } from "@/Types/Check";
+import { Pie, PieChart, ResponsiveContainer, Label } from "recharts";
+import Typography from "@mui/material/Typography";
+import Box from "@mui/material/Box";
+import { getPageSpeedPalette } from "@/Utils/MonitorUtils";
+import { useTheme } from "@mui/material/styles";
+import { alpha } from "@mui/material/styles";
+import { useState } from "react";
+import Tooltip from "@mui/material/Tooltip";
+import { useTranslation } from "react-i18next";
+
+const CenterLabel = ({ viewBox, value }: any) => {
+ const { cx, cy } = viewBox;
+ return (
+
+
+ {value}
+
+
+ );
+};
+
+export const PiePageSpeed = ({ latestCheck }: { latestCheck?: Check }) => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+ const [hoverTitle, setHoverTitle] = useState(null);
+
+ if (!latestCheck) return null;
+ const LABELS: Record = {
+ FCP: t("pages.pageSpeed.charts.common.fcp"),
+ SI: t("pages.pageSpeed.charts.common.si"),
+ LCP: t("pages.pageSpeed.charts.common.lcp"),
+ TBT: t("pages.pageSpeed.charts.common.tbt"),
+ CLS: t("pages.pageSpeed.charts.common.cls"),
+ };
+ const metrics: { key: keyof CheckAudits; color: string; weight: number }[] = [
+ { key: "fcp", color: alpha("#1DE9B6", 0.6), weight: 0.1 },
+ { key: "si", color: alpha("#7C4DFF", 0.6), weight: 0.1 },
+ { key: "lcp", color: alpha("#FFB200", 0.6), weight: 0.25 },
+ { key: "tbt", color: alpha("#00AFFE", 0.6), weight: 0.3 },
+ { key: "cls", color: alpha("#FF4181", 0.6), weight: 0.25 },
+ ];
+
+ const scores = metrics.flatMap(({ key, color, weight }) => {
+ const audit = latestCheck?.audits?.[key];
+ const val = Math.floor((audit?.score ?? 0) * 100);
+ const inverse = 100 - val;
+ return [
+ {
+ name: `${key.toUpperCase()} Inverse`,
+ value: inverse * weight,
+ fill: "transparent",
+ stroke: color,
+ weight,
+ },
+ {
+ name: key.toUpperCase(),
+ value: val * weight,
+ fill: color,
+ stroke: color,
+ weight,
+ },
+ ];
+ });
+
+ const totalScore =
+ (latestCheck.audits?.fcp?.score || 0) * 0.1 +
+ (latestCheck.audits?.si?.score || 0) * 0.1 +
+ (latestCheck.audits?.lcp?.score || 0) * 0.25 +
+ (latestCheck.audits?.tbt?.score || 0) * 0.3 +
+ (latestCheck.audits?.cls?.score || 0) * 0.25;
+ const pageSpeedPalette = getPageSpeedPalette(Math.floor(totalScore * 100));
+
+ const score = [
+ {
+ value: 100,
+ fill: alpha(theme.palette[pageSpeedPalette].light || "#ffffff", 0.6),
+ stroke: "none",
+ },
+ ];
+
+ return (
+
+ }
+ title={t("pages.pageSpeed.charts.pie.title")}
+ >
+
+
+
+
+
+ }
+ />
+
+ `${name}: ${Math.round(value)}`}
+ dataKey="value"
+ stroke="none"
+ onMouseEnter={(_, index: number) => {
+ const d = scores[index];
+ if (!d) return;
+ const isInverse = String(d.name).includes("Inverse");
+ const pair = isInverse && scores[index + 1] ? scores[index + 1] : d;
+ const base = String(pair.name).replace(" Inverse", "");
+ const full = LABELS[base] ?? base;
+ const displayVal = Math.round(Number(pair.value) || 0);
+ setHoverTitle(`${full}: ${displayVal}`);
+ }}
+ onMouseLeave={() => setHoverTitle(null)}
+ />
+
+
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/monitors/charts/PiePageSpeedLegend.tsx b/client/src/Components/v2/monitors/charts/PiePageSpeedLegend.tsx
new file mode 100644
index 000000000..5d220ae4e
--- /dev/null
+++ b/client/src/Components/v2/monitors/charts/PiePageSpeedLegend.tsx
@@ -0,0 +1,105 @@
+import Stack from "@mui/material/Stack";
+import Box from "@mui/material/Box";
+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 { useTranslation } from "react-i18next";
+import { getPageSpeedPalette } from "@/Utils/MonitorUtils";
+import { useTheme } from "@mui/material/styles";
+
+const MetricBox = ({
+ label,
+ value,
+ weight,
+}: {
+ label: string;
+ value: number;
+ weight: number;
+}) => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+ const palette = getPageSpeedPalette(value);
+ return (
+
+
+ {label}
+
+ {`${value}%`}
+ {`${t("pages.pageSpeed.charts.legend.weight")}: ${weight}%`}
+
+
+
+
+ );
+};
+
+export const PiePageSpeedLegend = ({ latestCheck }: { latestCheck?: Check }) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+
+ if (!latestCheck) {
+ return null;
+ }
+ return (
+
+ }
+ title={t("pages.pageSpeed.charts.legend.title")}
+ >
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/monitors/charts/RadialAvgResponse.tsx b/client/src/Components/v2/monitors/charts/RadialAvgResponse.tsx
new file mode 100644
index 000000000..b54bfc764
--- /dev/null
+++ b/client/src/Components/v2/monitors/charts/RadialAvgResponse.tsx
@@ -0,0 +1,91 @@
+import { BaseChart } from "@/Components/v2/design-elements";
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import { Gauge } from "lucide-react";
+import { Cell, RadialBarChart, RadialBar, ResponsiveContainer } from "recharts";
+
+import { useTranslation } from "react-i18next";
+import { getResponseTimeColor } from "@/Utils/MonitorUtils";
+import { useTheme } from "@mui/material/styles";
+
+export const RadialAvgResponse = ({ avg, max }: { avg: number; max: number }) => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+ const chartData = [
+ { name: "max", value: max - avg, color: "transparent" },
+ { name: "avg", value: avg, color: "red" },
+ ];
+
+ const palette = getResponseTimeColor(avg);
+ const msg: Record = {
+ success: "Excellent",
+ warning: "Average",
+ danger: "Poor",
+ };
+
+ return (
+
+ }
+ title={t("common.charts.labels.averageResponseTime")}
+ >
+
+
+
+
+ |
+ |
+
+
+
+
+ {t("common.charts.labels.low")}
+ {t("common.charts.labels.high")}
+
+
+
+ {msg[palette]}
+
+ {`${avg?.toFixed()}ms`}
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/monitors/index.tsx b/client/src/Components/v2/monitors/index.tsx
new file mode 100644
index 000000000..4efaae65c
--- /dev/null
+++ b/client/src/Components/v2/monitors/index.tsx
@@ -0,0 +1,11 @@
+export * from "./ControlsFilter";
+export * from "./MonitorStatBoxes";
+export * from "./charts/HistogramStatus";
+export * from "./charts/RadialAvgResponse";
+export * from "./charts/HistogramDetails";
+export * from "./charts/HistogramPageSpeed";
+export * from "./charts/HistogramPageSpeedTooltip";
+export * from "./charts/PiePageSpeed";
+export * from "./charts/PiePageSpeedLegend";
+export * from "./charts/HistogramPageSpeedDetails";
+export * from "./charts/HistogramPageSpeedDetailsTooltip";
diff --git a/client/src/Features/Auth/authSlice.js b/client/src/Features/Auth/authSlice.js
index 5ee31944a..b78a8b47e 100644
--- a/client/src/Features/Auth/authSlice.js
+++ b/client/src/Features/Auth/authSlice.js
@@ -10,21 +10,24 @@ const initialState = {
msg: null,
};
-export const register = createAsyncThunk("auth/register", async (form, thunkApi) => {
- try {
- const res = await networkService.registerUser(form);
- return res.data;
- } catch (error) {
- if (error.response.data) {
- return thunkApi.rejectWithValue(error.response.data);
+export const register = createAsyncThunk(
+ "auth/register",
+ async ({ user, token }, thunkApi) => {
+ try {
+ const res = await networkService.registerUser(user, token);
+ return res.data;
+ } catch (error) {
+ if (error.response.data) {
+ return thunkApi.rejectWithValue(error.response.data);
+ }
+ const payload = {
+ status: false,
+ msg: error.message ? error.message : "Unknown error",
+ };
+ return thunkApi.rejectWithValue(payload);
}
- const payload = {
- status: false,
- msg: error.message ? error.message : "Unknown error",
- };
- return thunkApi.rejectWithValue(payload);
}
-});
+);
export const login = createAsyncThunk("auth/login", async (form, thunkApi) => {
try {
diff --git a/client/src/Features/UI/uiSlice.js b/client/src/Features/UI/uiSlice.js
index 45ba267b0..65aff99a8 100644
--- a/client/src/Features/UI/uiSlice.js
+++ b/client/src/Features/UI/uiSlice.js
@@ -29,6 +29,7 @@ const initialState = {
distributedUptimeEnabled: false,
language: "en",
starPromptOpen: true,
+ chartType: "histogram",
};
const uiSlice = createSlice({
@@ -47,6 +48,10 @@ const uiSlice = createSlice({
toggleSidebar: (state) => {
state.sidebar.collapsed = !state.sidebar.collapsed;
},
+ setCollapsed: (state, action) => {
+ const { collapsed } = action.payload;
+ state.sidebar.collapsed = collapsed;
+ },
setMode: (state, action) => {
state.mode = action.payload;
},
@@ -54,6 +59,9 @@ const uiSlice = createSlice({
state.showURL = action.payload;
},
setGreeting(state, action) {
+ if (!state.greeting) {
+ state.greeting = { index: 0, lastUpdate: null };
+ }
state.greeting.index = action.payload.index;
state.greeting.lastUpdate = action.payload.lastUpdate;
},
@@ -66,6 +74,9 @@ const uiSlice = createSlice({
setStarPromptOpen: (state, action) => {
state.starPromptOpen = action.payload;
},
+ setChartType: (state, action) => {
+ state.chartType = action.payload;
+ },
},
});
@@ -73,6 +84,7 @@ export default uiSlice.reducer;
export const {
setRowsPerPage,
toggleSidebar,
+ setCollapsed,
setMode,
setShowURL,
setGreeting,
@@ -80,4 +92,5 @@ export const {
setDistributedUptimeEnabled,
setLanguage,
setStarPromptOpen,
+ setChartType,
} = uiSlice.actions;
diff --git a/client/src/Hooks/UseApi.ts b/client/src/Hooks/UseApi.ts
new file mode 100644
index 000000000..aa71f01af
--- /dev/null
+++ b/client/src/Hooks/UseApi.ts
@@ -0,0 +1,144 @@
+import useSWR from "swr";
+import type { SWRConfiguration } from "swr";
+import type { AxiosRequestConfig } from "axios";
+import { get, patch, post, deleteOp } from "@/Utils/ApiClient";
+import { useState } from "react";
+import { useToast } from "@/Hooks/UseToast";
+
+interface ApiResponse {
+ success: boolean;
+ msg: string;
+ data: T;
+}
+
+const fetcher = async (url: string, config?: AxiosRequestConfig) => {
+ const res = await get>(url, config);
+ return res.data;
+};
+
+export const useGet = (
+ url: string | null,
+ axiosConfig?: AxiosRequestConfig,
+ swrConfig?: SWRConfiguration
+) => {
+ const { data, error, isLoading, mutate } = useSWR>(
+ url,
+ (url: string) => fetcher(url, axiosConfig),
+ swrConfig
+ );
+
+ return {
+ data: data?.data ?? null,
+ isLoading,
+ error,
+ refetch: mutate,
+ };
+};
+
+export const usePost = () => {
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+ const { toastError, toastSuccess } = useToast();
+
+ const postFn = async (
+ endpoint: string,
+ body: B,
+ config?: AxiosRequestConfig
+ ): Promise | null> => {
+ setLoading(true);
+ setError(null);
+
+ try {
+ const res = await post>(endpoint, body, {
+ ...config,
+ headers: {
+ ...config?.headers,
+ },
+ });
+ toastSuccess(res.data?.msg || "Operation successful");
+
+ return res.data;
+ } catch (err: any) {
+ const errMsg = err?.response?.data?.msg || err.message || "An error occurred";
+ console.error(err);
+ toastError(errMsg);
+ setError(errMsg);
+ return null;
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return { post: postFn, loading, error };
+};
+
+export const usePatch = () => {
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+ const { toastError, toastSuccess } = useToast();
+
+ const patchFn = async (
+ endpoint: string,
+ body?: B,
+ config?: AxiosRequestConfig
+ ): Promise | null> => {
+ setLoading(true);
+ setError(null);
+
+ try {
+ const res = await patch>(endpoint, body, {
+ ...config,
+ headers: {
+ ...config?.headers,
+ },
+ });
+ toastSuccess(res.data?.msg || "Operation successful");
+ return res.data;
+ } catch (err: any) {
+ console.error(err);
+ const errMsg = err?.response?.data?.msg || err.message || "An error occurred";
+ toastError(errMsg);
+ setError(errMsg);
+ return null;
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return { patch: patchFn, loading, error };
+};
+
+export const useDelete = () => {
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+ const { toastError, toastSuccess } = useToast();
+
+ const deleteFn = async (
+ endpoint: string,
+ config?: AxiosRequestConfig
+ ): Promise | null> => {
+ setLoading(true);
+ setError(null);
+
+ try {
+ const res = await deleteOp>(endpoint, {
+ ...config,
+ headers: {
+ ...config?.headers,
+ },
+ });
+ toastSuccess(res.data?.msg || "Operation successful");
+ return res.data;
+ } catch (err: any) {
+ console.error(err);
+ const errMsg = err?.response?.data?.message || err.message || "An error occurred";
+ toastError(errMsg);
+ setError(errMsg);
+ return null;
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return { deleteFn, loading, error };
+};
diff --git a/client/src/Hooks/UseToast.ts b/client/src/Hooks/UseToast.ts
new file mode 100644
index 000000000..8823aa675
--- /dev/null
+++ b/client/src/Hooks/UseToast.ts
@@ -0,0 +1,24 @@
+import { toast, type ToastOptions } from "react-toastify";
+
+export const useToast = () => {
+ const showToast = (message: string, options?: ToastOptions) => {
+ toast.dismiss();
+ const baseStyle: React.CSSProperties = {
+ whiteSpace: "pre-line",
+ wordBreak: "break-word",
+ };
+ toast(message, {
+ ...options,
+ style: { ...baseStyle, ...(options?.style || {}) },
+ });
+ };
+
+ const toastSuccess = (msg: string, opts?: ToastOptions) =>
+ showToast(msg, { ...opts, type: "success" });
+ const toastError = (msg: string, opts?: ToastOptions) =>
+ showToast(msg, { ...opts, type: "error" });
+ const toastInfo = (msg: string, opts?: ToastOptions) =>
+ showToast(msg, { ...opts, type: "info" });
+
+ return { showToast, toastSuccess, toastError, toastInfo };
+};
diff --git a/client/src/Hooks/checkHooks.js b/client/src/Hooks/checkHooks.js
index bdb1a73b7..49a9b6ba3 100644
--- a/client/src/Hooks/checkHooks.js
+++ b/client/src/Hooks/checkHooks.js
@@ -1,6 +1,6 @@
import { useState, useEffect } from "react";
-import { networkService } from "../main";
-import { createToast } from "../Utils/toastUtils";
+import { networkService } from "../main.jsx";
+import { createToast } from "../Utils/toastUtils.jsx";
import { useTranslation } from "react-i18next";
const useFetchChecksTeam = ({
@@ -185,36 +185,9 @@ const useResolveIncident = () => {
return [resolveIncident, isLoading];
};
-const useAcknowledgeChecks = () => {
- const [isLoading, setIsLoading] = useState(false);
- const { t } = useTranslation();
-
- const acknowledge = async (setUpdateTrigger, monitorId = null) => {
- setIsLoading(true);
- try {
- if (monitorId) {
- await networkService.updateMonitorChecksStatus({ monitorId, ack: true });
- } else {
- await networkService.updateAllChecksStatus({ ack: true });
- }
- setUpdateTrigger((prev) => !prev);
- } catch (error) {
- const toastMessage = monitorId
- ? t("checkHooks.failureResolveMonitor")
- : t("checkHooks.failureResolveAll");
- createToast({ body: toastMessage });
- } finally {
- setIsLoading(false);
- }
- };
-
- return { acknowledge, isLoading };
-};
-
export {
useFetchChecksByMonitor,
useFetchChecksTeam,
useFetchChecksSummaryByTeamId,
useResolveIncident,
- useAcknowledgeChecks,
};
diff --git a/client/src/Hooks/inviteHooks.js b/client/src/Hooks/inviteHooks.js
index fb577272a..c862eec31 100644
--- a/client/src/Hooks/inviteHooks.js
+++ b/client/src/Hooks/inviteHooks.js
@@ -1,5 +1,5 @@
import { useState } from "react";
-import { networkService } from "../main";
+import { networkService } from "../main.jsx";
import { useTranslation } from "react-i18next";
const CLIENT_HOST = import.meta.env.VITE_APP_CLIENT_HOST;
diff --git a/client/src/Hooks/logHooks.js b/client/src/Hooks/logHooks.js
index 989870eea..d5162de5a 100644
--- a/client/src/Hooks/logHooks.js
+++ b/client/src/Hooks/logHooks.js
@@ -1,6 +1,6 @@
import { useState, useEffect } from "react";
-import { networkService } from "../main";
-import { createToast } from "../Utils/toastUtils";
+import { networkService } from "../main.jsx";
+import { createToast } from "../Utils/toastUtils.jsx";
import { useTranslation } from "react-i18next";
const useFetchLogs = () => {
diff --git a/client/src/Hooks/monitorHooks.js b/client/src/Hooks/monitorHooks.js
index eb1e7acdc..9d46e9365 100644
--- a/client/src/Hooks/monitorHooks.js
+++ b/client/src/Hooks/monitorHooks.js
@@ -1,12 +1,12 @@
import { useEffect, useState } from "react";
-import { networkService } from "../main";
-import { createToast } from "../Utils/toastUtils";
+import { networkService } from "../main.jsx";
+import { createToast } from "../Utils/toastUtils.jsx";
import { useTheme } from "@emotion/react";
-import { useMonitorUtils } from "./useMonitorUtils";
+import { useMonitorUtils } from "./useMonitorUtils.js";
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
-const useFetchMonitorsWithSummary = ({ types, monitorUpdateTrigger }) => {
+export const useFetchMonitorsWithSummary = ({ types, monitorUpdateTrigger }) => {
const [isLoading, setIsLoading] = useState(false);
const [monitors, setMonitors] = useState(undefined);
const [monitorsSummary, setMonitorsSummary] = useState(undefined);
@@ -37,7 +37,7 @@ const useFetchMonitorsWithSummary = ({ types, monitorUpdateTrigger }) => {
return [monitors, monitorsSummary, isLoading, networkError];
};
-const useFetchMonitorsWithChecks = ({
+export const useFetchMonitorsWithChecks = ({
types,
limit,
page,
@@ -50,6 +50,7 @@ const useFetchMonitorsWithChecks = ({
const [isLoading, setIsLoading] = useState(false);
const [count, setCount] = useState(undefined);
const [monitors, setMonitors] = useState(undefined);
+ const [summary, setSummary] = useState(undefined);
const [networkError, setNetworkError] = useState(false);
const theme = useTheme();
@@ -67,12 +68,14 @@ const useFetchMonitorsWithChecks = ({
field,
order,
});
- const { count, monitors } = res?.data?.data ?? {};
+
+ const { count, monitors, summary } = res?.data?.data ?? {};
const mappedMonitors = monitors.map((monitor) =>
getMonitorWithPercentage(monitor, theme)
);
+ setSummary(summary);
setMonitors(mappedMonitors);
- setCount(count?.monitorsCount ?? 0);
+ setCount(count || 0);
} catch (error) {
console.error(error);
setNetworkError(true);
@@ -96,25 +99,12 @@ const useFetchMonitorsWithChecks = ({
types,
monitorUpdateTrigger,
]);
- return [monitors, count, isLoading, networkError];
+ return [summary, monitors, count, isLoading, networkError];
};
-const useFetchMonitorsByTeamId = ({
- types,
- limit,
- page,
- rowsPerPage,
- filter,
- field,
- order,
- checkOrder,
- normalize,
- status,
- updateTrigger,
-}) => {
+export const useFetchMonitorsByTeamId = ({ types, filter, updateTrigger }) => {
const [isLoading, setIsLoading] = useState(false);
const [monitors, setMonitors] = useState(undefined);
- const [summary, setSummary] = useState(undefined);
const [networkError, setNetworkError] = useState(false);
useEffect(() => {
@@ -122,20 +112,11 @@ const useFetchMonitorsByTeamId = ({
try {
setIsLoading(true);
const res = await networkService.getMonitorsByTeamId({
- limit,
types,
- page,
- rowsPerPage,
filter,
- field,
- order,
- checkOrder,
- status,
- normalize,
});
- if (res?.data?.data?.filteredMonitors) {
- setMonitors(res.data.data.filteredMonitors);
- setSummary(res.data.data.summary);
+ if (res?.data?.data) {
+ setMonitors(res.data.data);
}
} catch (error) {
setNetworkError(true);
@@ -147,23 +128,11 @@ const useFetchMonitorsByTeamId = ({
}
};
fetchMonitors();
- }, [
- types,
- limit,
- page,
- rowsPerPage,
- filter,
- field,
- order,
- updateTrigger,
- checkOrder,
- normalize,
- status,
- ]);
- return [monitors, summary, isLoading, networkError];
+ }, [types, filter, updateTrigger]);
+ return [monitors, isLoading, networkError];
};
-const useFetchStatsByMonitorId = ({
+export const useFetchStatsByMonitorId = ({
monitorId,
sortOrder,
limit,
@@ -202,7 +171,7 @@ const useFetchStatsByMonitorId = ({
return [monitor, audits, isLoading, networkError];
};
-const useFetchMonitorGames = ({ setGames, updateTrigger }) => {
+export const useFetchMonitorGames = ({ setGames, updateTrigger }) => {
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const fetchGames = async () => {
@@ -221,7 +190,7 @@ const useFetchMonitorGames = ({ setGames, updateTrigger }) => {
return [isLoading];
};
-const useFetchMonitorById = ({ monitorId, setMonitor, updateTrigger }) => {
+export const useFetchMonitorById = ({ monitorId, setMonitor, updateTrigger }) => {
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
if (typeof monitorId === "undefined") {
@@ -244,7 +213,7 @@ const useFetchMonitorById = ({ monitorId, setMonitor, updateTrigger }) => {
return [isLoading];
};
-const useFetchHardwareMonitorById = ({ monitorId, dateRange, updateTrigger }) => {
+export const useFetchHardwareMonitorById = ({ monitorId, dateRange, updateTrigger }) => {
const [isLoading, setIsLoading] = useState(true);
const [networkError, setNetworkError] = useState(false);
const [monitor, setMonitor] = useState(undefined);
@@ -270,8 +239,36 @@ const useFetchHardwareMonitorById = ({ monitorId, dateRange, updateTrigger }) =>
}, [monitorId, dateRange, updateTrigger]);
return [monitor, isLoading, networkError];
};
+export const useFetchPageSpeedMonitorById = ({ monitorId, dateRange, updateTrigger }) => {
+ const [isLoading, setIsLoading] = useState(true);
+ const [networkError, setNetworkError] = useState(false);
+ const [monitor, setMonitor] = useState(undefined);
+ const [monitorStats, setMonitorStats] = useState(undefined);
-const useFetchUptimeMonitorById = ({ monitorId, dateRange, trigger }) => {
+ useEffect(() => {
+ const fetchMonitor = async () => {
+ try {
+ if (!monitorId) {
+ return { monitor: undefined, isLoading: false, networkError: undefined };
+ }
+ const response = await networkService.getPageSpeedDetailsByMonitorId({
+ monitorId: monitorId,
+ dateRange: dateRange,
+ });
+ setMonitor(response.data.data.monitor);
+ setMonitorStats(response.data.data.monitorStats);
+ } catch (error) {
+ setNetworkError(true);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+ fetchMonitor();
+ }, [monitorId, dateRange, updateTrigger]);
+ return [monitor, monitorStats, isLoading, networkError];
+};
+
+export const useFetchUptimeMonitorById = ({ monitorId, dateRange, trigger }) => {
const [networkError, setNetworkError] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [monitor, setMonitor] = useState(undefined);
@@ -299,7 +296,7 @@ const useFetchUptimeMonitorById = ({ monitorId, dateRange, trigger }) => {
return [monitor, monitorStats, isLoading, networkError];
};
-const useCreateMonitor = () => {
+export const useCreateMonitor = () => {
const [isLoading, setIsLoading] = useState(false);
const navigate = useNavigate();
const createMonitor = async ({ monitor, redirect }) => {
@@ -319,7 +316,7 @@ const useCreateMonitor = () => {
return [createMonitor, isLoading];
};
-const useFetchGlobalSettings = () => {
+export const useFetchGlobalSettings = () => {
const [isLoading, setIsLoading] = useState(true);
const [globalSettings, setGlobalSettings] = useState(undefined);
useEffect(() => {
@@ -341,13 +338,13 @@ const useFetchGlobalSettings = () => {
return [globalSettings, isLoading];
};
-const useDeleteMonitor = () => {
+export const useDeleteMonitor = () => {
const [isLoading, setIsLoading] = useState(false);
const navigate = useNavigate();
const deleteMonitor = async ({ monitor, redirect }) => {
try {
setIsLoading(true);
- await networkService.deleteMonitorById({ monitorId: monitor._id });
+ await networkService.deleteMonitorById({ monitorId: monitor.id });
createToast({ body: "Monitor deleted successfully!" });
if (redirect) {
navigate(redirect);
@@ -361,7 +358,7 @@ const useDeleteMonitor = () => {
return [deleteMonitor, isLoading];
};
-const useUpdateMonitor = () => {
+export const useUpdateMonitor = () => {
const [isLoading, setIsLoading] = useState(false);
const navigate = useNavigate();
const updateMonitor = async ({ monitor, redirect }) => {
@@ -387,10 +384,11 @@ const useUpdateMonitor = () => {
...(monitor.type === "hardware" && {
thresholds: monitor.thresholds,
secret: monitor.secret,
+ selectedDisks: monitor.selectedDisks,
}),
};
await networkService.updateMonitor({
- monitorId: monitor._id,
+ monitorId: monitor.id,
updatedFields,
});
@@ -407,7 +405,7 @@ const useUpdateMonitor = () => {
return [updateMonitor, isLoading];
};
-const usePauseMonitor = () => {
+export const usePauseMonitor = () => {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(undefined);
const pauseMonitor = async ({ monitorId, triggerUpdate }) => {
@@ -430,7 +428,7 @@ const usePauseMonitor = () => {
return [pauseMonitor, isLoading, error];
};
-const useAddDemoMonitors = () => {
+export const useAddDemoMonitors = () => {
const [isLoading, setIsLoading] = useState(false);
const { t } = useTranslation();
const addDemoMonitors = async () => {
@@ -447,7 +445,7 @@ const useAddDemoMonitors = () => {
return [addDemoMonitors, isLoading];
};
-const useDeleteAllMonitors = () => {
+export const useDeleteAllMonitors = () => {
const [isLoading, setIsLoading] = useState(false);
const { t } = useTranslation();
const deleteAllMonitors = async () => {
@@ -464,7 +462,7 @@ const useDeleteAllMonitors = () => {
return [deleteAllMonitors, isLoading];
};
-const useDeleteMonitorStats = () => {
+export const useDeleteMonitorStats = () => {
const { t } = useTranslation();
const [isLoading, setIsLoading] = useState(false);
const deleteMonitorStats = async () => {
@@ -482,7 +480,7 @@ const useDeleteMonitorStats = () => {
return [deleteMonitorStats, isLoading];
};
-const useCreateBulkMonitors = () => {
+export const useCreateBulkMonitors = () => {
const [isLoading, setIsLoading] = useState(false);
const createBulkMonitors = async (file, user) => {
@@ -505,7 +503,7 @@ const useCreateBulkMonitors = () => {
return [createBulkMonitors, isLoading];
};
-const useExportMonitors = () => {
+export const useExportMonitors = () => {
const [isLoading, setIsLoading] = useState(false);
const { t } = useTranslation();
@@ -538,23 +536,19 @@ const useExportMonitors = () => {
return [exportMonitors, isLoading];
};
-export {
- useFetchMonitorsWithSummary,
- useFetchMonitorsWithChecks,
- useFetchMonitorsByTeamId,
- useFetchStatsByMonitorId,
- useFetchMonitorById,
- useFetchUptimeMonitorById,
- useFetchHardwareMonitorById,
- useCreateMonitor,
- useFetchGlobalSettings,
- useDeleteMonitor,
- useUpdateMonitor,
- usePauseMonitor,
- useAddDemoMonitors,
- useDeleteAllMonitors,
- useDeleteMonitorStats,
- useCreateBulkMonitors,
- useExportMonitors,
- useFetchMonitorGames,
+export const useFetchJson = () => {
+ const [isLoading, setIsLoading] = useState(false);
+ const fetchJson = async () => {
+ try {
+ setIsLoading(true);
+ const res = await networkService.fetchJson();
+ createToast({ body: "JSON fetched successfully" });
+ return res?.data?.data ?? [];
+ } catch (error) {
+ createToast({ body: "Failed to create monitor." });
+ } finally {
+ setIsLoading(false);
+ }
+ };
+ return [fetchJson, isLoading];
};
diff --git a/client/src/Hooks/settingsHooks.js b/client/src/Hooks/settingsHooks.js
index 98a21c1fa..4693d8daa 100644
--- a/client/src/Hooks/settingsHooks.js
+++ b/client/src/Hooks/settingsHooks.js
@@ -1,6 +1,6 @@
import { useState, useEffect } from "react";
-import { networkService } from "../main";
-import { createToast } from "../Utils/toastUtils";
+import { networkService } from "../main.jsx";
+import { createToast } from "../Utils/toastUtils.jsx";
import { useTranslation } from "react-i18next";
const useFetchSettings = ({ setSettingsData, setIsApiKeySet, setIsEmailPasswordSet }) => {
@@ -42,11 +42,11 @@ const useSaveSettings = ({
setIsLoading(true);
try {
const settingsResponse = await networkService.updateAppSettings({ settings });
- if (settings.checkTTL) {
- await networkService.updateChecksTTL({
- ttl: settings.checkTTL,
- });
- }
+ // if (settings.checkTTL) {
+ // await networkService.updateChecksTTL({
+ // ttl: settings.checkTTL,
+ // });
+ // }
setIsApiKeySet(settingsResponse.data.data.pagespeedKeySet);
setIsEmailPasswordSet(settingsResponse.data.data.emailPasswordSet);
if (settingsResponse.data.data.pagespeedKeySet === true) {
@@ -56,6 +56,7 @@ const useSaveSettings = ({
setEmailPasswordHasBeenReset(false);
}
setSettingsData(settingsResponse.data.data);
+
createToast({ body: t("settingsSuccessSaved") });
} catch (error) {
createToast({ body: t("settingsFailedToSave") });
diff --git a/client/src/Hooks/useNotifications.js b/client/src/Hooks/useNotifications.js
index 539725958..6c5919fac 100644
--- a/client/src/Hooks/useNotifications.js
+++ b/client/src/Hooks/useNotifications.js
@@ -1,9 +1,9 @@
import { useState, useEffect, useCallback } from "react";
-import { createToast } from "../Utils/toastUtils";
-import { networkService } from "../main";
+import { createToast } from "../Utils/toastUtils.jsx";
+import { networkService } from "../main.jsx";
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
-import { NOTIFICATION_TYPES } from "../Pages/Notifications/utils";
+import { NOTIFICATION_TYPES } from "../Pages/Notifications/utils.js";
const useCreateNotification = () => {
const navigate = useNavigate();
@@ -102,7 +102,7 @@ const useGetNotificationById = (id, setNotification) => {
const notificationData = {
address: notification?.address,
notificationName: notification?.notificationName,
- type: NOTIFICATION_TYPES.find((type) => type.value === notification?.type)?._id,
+ type: NOTIFICATION_TYPES.find((type) => type.value === notification?.type)?.id,
};
setNotification(notificationData);
diff --git a/client/src/Hooks/useSendTestEmail.js b/client/src/Hooks/useSendTestEmail.js
index e280b1f99..7223a489a 100644
--- a/client/src/Hooks/useSendTestEmail.js
+++ b/client/src/Hooks/useSendTestEmail.js
@@ -1,7 +1,7 @@
import { useState } from "react";
-import { networkService } from "../main";
+import { networkService } from "../main.jsx";
import { useSelector } from "react-redux";
-import { createToast } from "../Utils/toastUtils";
+import { createToast } from "../Utils/toastUtils.jsx";
import { useTranslation } from "react-i18next";
const useSendTestEmail = () => {
diff --git a/client/src/Hooks/useSidebar.js b/client/src/Hooks/useSidebar.js
new file mode 100644
index 000000000..97724b417
--- /dev/null
+++ b/client/src/Hooks/useSidebar.js
@@ -0,0 +1,29 @@
+import { useSelector } from "react-redux";
+
+// CSS variable names for sidebar widths
+const SIDEBAR_WIDTH_VAR = "var(--env-var-side-bar-width)";
+const SIDEBAR_COLLAPSED_WIDTH_VAR = "var(--env-var-side-bar-collapsed-width)";
+
+// Transition timing for sidebar width changes
+const SIDEBAR_TRANSITION = "width 650ms cubic-bezier(0.36, -0.01, 0, 0.77)";
+
+/**
+ * Hook to get sidebar state and computed width
+ * Centralizes sidebar width logic to avoid duplication between Sidebar and HomeLayout
+ *
+ * @returns {Object} Sidebar state and styles
+ * @returns {boolean} collapsed - Whether the sidebar is collapsed
+ * @returns {string} width - CSS width value based on collapsed state
+ * @returns {string} transition - CSS transition for width changes
+ */
+export const useSidebar = () => {
+ const collapsed = useSelector((state) => state.ui.sidebar?.collapsed ?? false);
+
+ return {
+ collapsed,
+ width: collapsed ? SIDEBAR_COLLAPSED_WIDTH_VAR : SIDEBAR_WIDTH_VAR,
+ transition: SIDEBAR_TRANSITION,
+ };
+};
+
+export default useSidebar;
diff --git a/client/src/Hooks/userHooks.js b/client/src/Hooks/userHooks.js
index 8be32a082..3d822f979 100644
--- a/client/src/Hooks/userHooks.js
+++ b/client/src/Hooks/userHooks.js
@@ -1,13 +1,12 @@
import { useEffect, useState, useCallback } from "react";
-import { networkService } from "../main";
-import { createToast } from "../Utils/toastUtils";
+import { networkService } from "../main.jsx";
+import { createToast } from "../Utils/toastUtils.jsx";
import { useTranslation } from "react-i18next";
export const useGetUser = (userId) => {
const [user, setUser] = useState(undefined);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
-
const fetchUser = useCallback(async () => {
try {
setIsLoading(true);
@@ -54,8 +53,29 @@ export const useEditUser = (userId) => {
setIsLoading(false);
}
},
- [userId]
+ [userId, t]
);
- return [editUser, isLoading, error];
+ const changePassword = useCallback(
+ async (passwordForm) => {
+ try {
+ setIsLoading(true);
+
+ await networkService.changePasswordByAdmin({ userId, passwordForm });
+ createToast({
+ body: t("teamPanel.changeTeamPassword.success"),
+ });
+ } catch (error) {
+ createToast({
+ body: error.message,
+ });
+ setError(error);
+ } finally {
+ setIsLoading(false);
+ }
+ },
+ [userId, t]
+ );
+
+ return [editUser, isLoading, error, changePassword];
};
diff --git a/client/src/Pages/About/index.jsx b/client/src/Pages/About/index.jsx
index 97040b2e3..ff7bb6c6b 100644
--- a/client/src/Pages/About/index.jsx
+++ b/client/src/Pages/About/index.jsx
@@ -1,6 +1,6 @@
import React from "react";
-import AppBar from "../../Components/Common/AppBar";
-import Footer from "../../Components/Common/Footer";
+import AppBar from "@/Components/v1/Common/AppBar.jsx";
+import Footer from "@/Components/v1/Common/Footer.jsx";
import { useTranslation } from "react-i18next";
const AboutUs = () => {
diff --git a/client/src/Pages/Account/EditUser/hooks/editUser.js b/client/src/Pages/Account/EditUser/hooks/editUser.js
index 2ffa914a0..aeb50ffff 100644
--- a/client/src/Pages/Account/EditUser/hooks/editUser.js
+++ b/client/src/Pages/Account/EditUser/hooks/editUser.js
@@ -1,8 +1,8 @@
import { useState, useEffect } from "react";
-import { ROLES } from "../../../../Utils/roleUtils";
-import { editUserValidation } from "../../../../Validation/validation";
+import { ROLES } from "../../../../Utils/roleUtils.js";
+import { editUserValidation } from "../../../../Validation/validation.js";
import Joi from "joi";
-import { createToast } from "../../../../Utils/toastUtils";
+import { createToast } from "../../../../Utils/toastUtils.jsx";
import { useTranslation } from "react-i18next";
export const useEditUserForm = (user) => {
const [searchInput, setSearchInput] = useState("");
diff --git a/client/src/Pages/Account/EditUser/index.jsx b/client/src/Pages/Account/EditUser/index.jsx
index e1eeead9d..2b2ac5128 100644
--- a/client/src/Pages/Account/EditUser/index.jsx
+++ b/client/src/Pages/Account/EditUser/index.jsx
@@ -1,23 +1,25 @@
// Components
import Stack from "@mui/material/Stack";
-import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
-import Breadcrumbs from "../../../Components/Breadcrumbs";
-import TextInput from "../../../Components/Inputs/TextInput";
-import Search from "../../../Components/Inputs/Search";
+import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
+import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
+import Search from "@/Components/v1/Inputs/Search/index.jsx";
import Button from "@mui/material/Button";
-import RoleTable from "../components/RoleTable";
-
+import RoleTable from "../components/RoleTable/index.jsx";
+import ChangePasswordModal from "@/Pages/Account/components/ChangePasswordModal/index.jsx";
// Utils
import { useParams } from "react-router-dom";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
-import { useGetUser, useEditUser } from "../../../Hooks/userHooks";
-import { EDITABLE_ROLES, ROLES } from "../../../Utils/roleUtils";
-import { useEditUserForm, useValidateEditUserForm } from "./hooks/editUser";
+import { useGetUser, useEditUser } from "../../../Hooks/userHooks.js";
+import { EDITABLE_ROLES, ROLES } from "../../../Utils/roleUtils.js";
+import { useEditUserForm, useValidateEditUserForm } from "./hooks/editUser.js";
+import { useSelector } from "react-redux";
const EditUser = () => {
+ const { user } = useSelector((state) => state.auth);
const { userId } = useParams();
+ const isSameUser = user?._id === userId;
const theme = useTheme();
const { t } = useTranslation();
const BREADCRUMBS = [
@@ -25,8 +27,8 @@ const EditUser = () => {
{ name: t("editUserPage.title"), path: "" },
];
- const [user, isLoading, error] = useGetUser(userId);
- const [editUser, isSaving, saveError] = useEditUser(userId);
+ const [userToEdit, isLoading, error] = useGetUser(userId);
+ const [editUser, isSaving, saveError, changePassword] = useEditUser(userId);
const [
form,
setForm,
@@ -34,7 +36,7 @@ const EditUser = () => {
handleDeleteRole,
searchInput,
handleSearchInput,
- ] = useEditUserForm(user);
+ ] = useEditUserForm(userToEdit);
const [errors, validateForm, validateField] = useValidateEditUserForm();
const onChange = (e) => {
@@ -104,7 +106,12 @@ const EditUser = () => {
roles={form?.role}
handleDeleteRole={handleDeleteRole}
/>
-
+
{
>
{t("editUserPage.form.save")}
-
+ {!isSameUser && (
+
+ )}
+
);
diff --git a/client/src/Pages/Account/components/AddMemberMenu/index.jsx b/client/src/Pages/Account/components/AddMemberMenu/index.jsx
index ab1ba599d..96bea3522 100644
--- a/client/src/Pages/Account/components/AddMemberMenu/index.jsx
+++ b/client/src/Pages/Account/components/AddMemberMenu/index.jsx
@@ -3,7 +3,7 @@ import Button from "@mui/material/Button";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import { useTheme } from "@emotion/react";
-import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
+import Icon from "@/Components/v1/Icon";
import { useTranslation } from "react-i18next";
import Proptypes from "prop-types";
@@ -24,7 +24,13 @@ const AddMemberMenu = ({ handleInviteOpen, handleIsRegisterOpen }) => {
}
+ endIcon={
+
+ }
onClick={handleClick}
>
{t("teamPanel.addTeamMember.addMemberMenu")}
diff --git a/client/src/Pages/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx b/client/src/Pages/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx
index e42c504b2..1237777ff 100644
--- a/client/src/Pages/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx
+++ b/client/src/Pages/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx
@@ -1,5 +1,5 @@
import { useState } from "react";
-import { newOrChangedCredentials } from "../../../../../Validation/validation";
+import { newOrChangedCredentials } from "../../../../../Validation/validation.js";
import { useTranslation } from "react-i18next";
const useAddTeamMember = () => {
const { t } = useTranslation();
diff --git a/client/src/Pages/Account/components/AddTeamMember/index.jsx b/client/src/Pages/Account/components/AddTeamMember/index.jsx
index 2df8fb226..e5280af16 100644
--- a/client/src/Pages/Account/components/AddTeamMember/index.jsx
+++ b/client/src/Pages/Account/components/AddTeamMember/index.jsx
@@ -1,16 +1,16 @@
import { Button, Stack } from "@mui/material";
-import { GenericDialog } from "../../../../Components/Dialog/genericDialog";
-import TextInput from "../../../../Components/Inputs/TextInput";
-import Select from "../../../../Components/Inputs/Select";
-import { useGetInviteToken } from "../../../../Hooks/inviteHooks";
+import { GenericDialog } from "@/Components/v1/Dialog/genericDialog.jsx";
+import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
+import Select from "@/Components/v1/Inputs/Select/index.jsx";
+import { useGetInviteToken } from "../../../../Hooks/inviteHooks.js";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
-import { createToast } from "../../../../Utils/toastUtils";
+import { createToast } from "../../../../Utils/toastUtils.jsx";
import { useState } from "react";
-import PasswordTooltip from "../../../Auth/components/PasswordTooltip";
-import useAddTeamMember from "./hooks/useAddTeamMember";
-import usePasswordFeedback from "../../../Auth/hooks/usePasswordFeedback";
-import { PasswordEndAdornment } from "../../../../Components/Inputs/TextInput/Adornments";
+import PasswordTooltip from "../../../Auth/components/PasswordTooltip.jsx";
+import useAddTeamMember from "./hooks/useAddTeamMember.jsx";
+import usePasswordFeedback from "../../../Auth/hooks/usePasswordFeedback.jsx";
+import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx";
import PropTypes from "prop-types";
const INITIAL_FORM_STATE = {
diff --git a/client/src/Pages/Account/components/ChangePasswordModal/index.jsx b/client/src/Pages/Account/components/ChangePasswordModal/index.jsx
new file mode 100644
index 000000000..725a9b00e
--- /dev/null
+++ b/client/src/Pages/Account/components/ChangePasswordModal/index.jsx
@@ -0,0 +1,149 @@
+import { useState } from "react";
+import { Button, Stack } from "@mui/material";
+import { GenericDialog } from "@/Components/v1/Dialog/genericDialog";
+import TextInput from "@/Components/v1/Inputs/TextInput";
+import PasswordTooltip from "@/Pages/Auth/components/PasswordTooltip";
+import { useTheme } from "@emotion/react";
+import { useTranslation } from "react-i18next";
+import { createToast } from "../../../../Utils/toastUtils";
+import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments";
+import usePasswordFeedback from "@/Pages/Auth/hooks/usePasswordFeedback";
+import PropTypes from "prop-types";
+
+const ChangePasswordModal = ({ isSaving, isLoading, changePassword }) => {
+ const INITIAL_FORM_STATE = {
+ password: "",
+ confirm: "",
+ };
+ const theme = useTheme();
+ const { t } = useTranslation();
+ const { feedback, handlePasswordFeedback } = usePasswordFeedback();
+ const [form, setForm] = useState(INITIAL_FORM_STATE);
+ const [isChangePasswordOpen, setIsChangePasswordOpen] = useState(false);
+ const [errors, setErrors] = useState({});
+ const [isLoadingSubmit, setIsLoadingSubmit] = useState(false);
+ const closeChangePasswordModal = () => {
+ setIsChangePasswordOpen(false);
+ setForm(INITIAL_FORM_STATE);
+ };
+
+ const onChange = (e) => {
+ let { name, value } = e.target;
+ const updatedForm = { ...form, [name]: value };
+ setForm(updatedForm);
+
+ handlePasswordFeedback(updatedForm, name, value, form, errors, setErrors);
+ };
+ const isFormValid =
+ form.password.length > 1 &&
+ form.confirm.length > 1 &&
+ !errors.password &&
+ !errors.confirm;
+ const onsubmitChangePassword = async (event) => {
+ event.preventDefault();
+ if (!isFormValid) return;
+ const newPasswordForm = {
+ password: form.password,
+ };
+ try {
+ setIsLoadingSubmit(true);
+ await changePassword(newPasswordForm);
+ closeChangePasswordModal();
+ } catch (error) {
+ const errorMsg = error.response?.data?.msg || error.message || "unknownError";
+ createToast({
+ type: "error",
+ body: t(errorMsg),
+ });
+ } finally {
+ setIsLoadingSubmit(false);
+ }
+ };
+
+ return (
+ <>
+ setIsChangePasswordOpen(true)}
+ disabled={isLoading || isSaving}
+ >
+ {t("teamPanel.changeTeamPassword.changePasswordMenu")}
+
+
+
+ }
+ sx={{ mb: theme.spacing(5) }}
+ />
+
+ }
+ sx={{ mb: theme.spacing(5) }}
+ />
+
+
+
+ {t("teamPanel.cancel")}
+
+
+ {t("save")}
+
+
+
+ >
+ );
+};
+
+ChangePasswordModal.propTypes = {
+ isSaving: PropTypes.bool.isRequired,
+ isLoading: PropTypes.bool.isRequired,
+ changePassword: PropTypes.func.isRequired,
+};
+
+export default ChangePasswordModal;
diff --git a/client/src/Pages/Account/components/PasswordPanel.jsx b/client/src/Pages/Account/components/PasswordPanel.jsx
index dabd67706..e889db96c 100644
--- a/client/src/Pages/Account/components/PasswordPanel.jsx
+++ b/client/src/Pages/Account/components/PasswordPanel.jsx
@@ -2,14 +2,14 @@ import TabPanel from "@mui/lab/TabPanel";
import { useState } from "react";
import { useTheme } from "@emotion/react";
import { Box, Stack, Typography, Button } from "@mui/material";
-import { PasswordEndAdornment } from "../../../Components/Inputs/TextInput/Adornments";
-import TextInput from "../../../Components/Inputs/TextInput";
-import { newOrChangedCredentials } from "../../../Validation/validation";
-import Alert from "../../../Components/Alert";
-import { update } from "../../../Features/Auth/authSlice";
+import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx";
+import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
+import { newOrChangedCredentials } from "../../../Validation/validation.js";
+import Alert from "@/Components/v1/Alert/index.jsx";
+import { update } from "../../../Features/Auth/authSlice.js";
import { useDispatch, useSelector } from "react-redux";
-import { createToast } from "../../../Utils/toastUtils";
-import { getTouchedFieldErrors } from "../../../Validation/error";
+import { createToast } from "../../../Utils/toastUtils.jsx";
+import { getTouchedFieldErrors } from "../../../Validation/error.js";
import { useTranslation } from "react-i18next";
const defaultPasswordsState = {
@@ -167,7 +167,11 @@ const PasswordPanel = () => {
value={localData.password}
onChange={handleChange}
error={errors[idToName["edit-current-password"]] ? true : false}
- helperText={errors[idToName["edit-current-password"]]}
+ helperText={
+ errors[idToName["edit-current-password"]]
+ ? t(errors[idToName["edit-current-password"]])
+ : ""
+ }
endAdornment={ }
flex={1}
/>
@@ -193,7 +197,11 @@ const PasswordPanel = () => {
value={localData.newPassword}
onChange={handleChange}
error={errors[idToName["edit-new-password"]] ? true : false}
- helperText={errors[idToName["edit-new-password"]]}
+ helperText={
+ errors[idToName["edit-new-password"]]
+ ? t(errors[idToName["edit-new-password"]])
+ : ""
+ }
endAdornment={ }
flex={1}
/>
@@ -219,7 +227,11 @@ const PasswordPanel = () => {
value={localData.confirm}
onChange={handleChange}
error={errors[idToName["edit-confirm-password"]] ? true : false}
- helperText={errors[idToName["edit-confirm-password"]]}
+ helperText={
+ errors[idToName["edit-confirm-password"]]
+ ? t(errors[idToName["edit-confirm-password"]])
+ : ""
+ }
endAdornment={ }
flex={1}
/>
diff --git a/client/src/Pages/Account/components/ProfilePanel.jsx b/client/src/Pages/Account/components/ProfilePanel.jsx
index 7c73e7aa3..dfe508879 100644
--- a/client/src/Pages/Account/components/ProfilePanel.jsx
+++ b/client/src/Pages/Account/components/ProfilePanel.jsx
@@ -2,16 +2,16 @@ import { useTheme } from "@emotion/react";
import { useState } from "react";
import TabPanel from "@mui/lab/TabPanel";
import { Box, Button, Divider, Stack, Typography } from "@mui/material";
-import Avatar from "../../../Components/Avatar";
-import TextInput from "../../../Components/Inputs/TextInput";
-import ImageUpload from "../../../Components/Inputs/ImageUpload";
-import { newOrChangedCredentials } from "../../../Validation/validation";
+import Avatar from "@/Components/v1/Avatar/index.jsx";
+import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
+import ImageUpload from "@/Components/v1/Inputs/ImageUpload/index.jsx";
+import { newOrChangedCredentials } from "../../../Validation/validation.js";
import { useDispatch, useSelector } from "react-redux";
-import { clearAuthState, deleteUser, update } from "../../../Features/Auth/authSlice";
-import { createToast } from "../../../Utils/toastUtils";
-import { logger } from "../../../Utils/Logger";
-import { GenericDialog } from "../../../Components/Dialog/genericDialog";
-import Dialog from "../../../Components/Dialog";
+import { clearAuthState, deleteUser, update } from "../../../Features/Auth/authSlice.js";
+import { createToast } from "../../../Utils/toastUtils.jsx";
+import { logger } from "../../../Utils/Logger.js";
+import { GenericDialog } from "@/Components/v1/Dialog/genericDialog.jsx";
+import Dialog from "@/Components/v1/Dialog/index.jsx";
import { useTranslation } from "react-i18next";
/**
@@ -210,7 +210,11 @@ const ProfilePanel = () => {
autoComplete="given-name"
onChange={handleChange}
error={errors[idToName["edit-first-name"]] ? true : false}
- helperText={errors[idToName["edit-first-name"]]}
+ helperText={
+ errors[idToName["edit-first-name"]]
+ ? t(errors[idToName["edit-first-name"]])
+ : ""
+ }
flex={1}
/>
@@ -228,7 +232,11 @@ const ProfilePanel = () => {
value={localData.lastName}
onChange={handleChange}
error={errors[idToName["edit-last-name"]] ? true : false}
- helperText={errors[idToName["edit-last-name"]]}
+ helperText={
+ errors[idToName["edit-last-name"]]
+ ? t(errors[idToName["edit-last-name"]])
+ : ""
+ }
flex={1}
/>
diff --git a/client/src/Pages/Account/components/RoleTable/index.jsx b/client/src/Pages/Account/components/RoleTable/index.jsx
index 9d6ba3a16..90a30dcd4 100644
--- a/client/src/Pages/Account/components/RoleTable/index.jsx
+++ b/client/src/Pages/Account/components/RoleTable/index.jsx
@@ -1,9 +1,9 @@
import Typography from "@mui/material/Typography";
-import DataTable from "../../../../Components/Table";
-import DeleteOutlineRoundedIcon from "@mui/icons-material/DeleteOutlineRounded";
+import DataTable from "@/Components/v1/Table/index.jsx";
+import Icon from "@/Components/v1/Icon";
import { useTranslation } from "react-i18next";
-import { ROLES } from "../../../../Utils/roleUtils";
+import { ROLES } from "../../../../Utils/roleUtils.js";
const RoleTable = ({ roles, handleDeleteRole }) => {
const { t } = useTranslation();
@@ -21,11 +21,13 @@ const RoleTable = ({ roles, handleDeleteRole }) => {
render: (row) => {
if (row === ROLES.SUPERADMIN) return null;
return (
- {
handleDeleteRole(row);
}}
- sx={{ cursor: "pointer" }}
+ style={{ cursor: "pointer" }}
/>
);
},
diff --git a/client/src/Pages/Account/components/TeamPanel.jsx b/client/src/Pages/Account/components/TeamPanel.jsx
index fc1768642..bb40a5e4f 100644
--- a/client/src/Pages/Account/components/TeamPanel.jsx
+++ b/client/src/Pages/Account/components/TeamPanel.jsx
@@ -3,18 +3,18 @@ import TabPanel from "@mui/lab/TabPanel";
import { Button, ButtonGroup, Stack, Typography } from "@mui/material";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
-import TextInput from "../../../Components/Inputs/TextInput";
-import { newOrChangedCredentials } from "../../../Validation/validation";
-import { networkService } from "../../../main";
-import { createToast } from "../../../Utils/toastUtils";
-import Select from "../../../Components/Inputs/Select";
-import { GenericDialog } from "../../../Components/Dialog/genericDialog";
-import AddTeamMember from "../components/AddTeamMember";
-import DataTable from "../../../Components/Table";
-import { useGetInviteToken } from "../../../Hooks/inviteHooks";
+import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
+import { newOrChangedCredentials } from "../../../Validation/validation.js";
+import { networkService } from "../../../main.jsx";
+import { createToast } from "../../../Utils/toastUtils.jsx";
+import Select from "@/Components/v1/Inputs/Select/index.jsx";
+import { GenericDialog } from "@/Components/v1/Dialog/genericDialog.jsx";
+import AddTeamMember from "./AddTeamMember/index.jsx";
+import DataTable from "@/Components/v1/Table/index.jsx";
+import { useGetInviteToken } from "../../../Hooks/inviteHooks.js";
import { useNavigate } from "react-router-dom";
-import { useIsSuperAdmin } from "../../../Hooks/useIsAdmin";
-import AddMemberMenu from "./AddMemberMenu";
+import { useIsSuperAdmin } from "@/Hooks/useIsAdmin.js";
+import AddMemberMenu from "./AddMemberMenu/index.jsx";
/**
* TeamPanel component manages the organization and team members,
* providing functionalities like renaming the organization, managing team members,
@@ -104,7 +104,7 @@ const TeamPanel = () => {
team = team.map((member) => ({
...member,
- id: member._id,
+ id: member.id,
role: member.role.map((role) => ROLE_MAP[role]).join(","),
}));
setData(team);
@@ -268,6 +268,7 @@ const TeamPanel = () => {
cursor: isSuperAdmin ? "pointer" : "default",
},
onRowClick: (row) => {
+ console.log(row);
if (isSuperAdmin) {
navigate(`/account/team/${row.id}`);
}
diff --git a/client/src/Pages/Account/index.jsx b/client/src/Pages/Account/index.jsx
index 37c64aca6..ae16ff9d7 100644
--- a/client/src/Pages/Account/index.jsx
+++ b/client/src/Pages/Account/index.jsx
@@ -3,11 +3,11 @@ import PropTypes from "prop-types";
import { useNavigate } from "react-router-dom";
import { useSelector } from "react-redux";
import { Box, Tab, useTheme } from "@mui/material";
-import CustomTabList from "../../Components/Tab";
+import CustomTabList from "@/Components/v1/Tab/index.jsx";
import TabContext from "@mui/lab/TabContext";
-import ProfilePanel from "./components/ProfilePanel";
-import PasswordPanel from "./components/PasswordPanel";
-import TeamPanel from "./components/TeamPanel";
+import ProfilePanel from "./components/ProfilePanel.jsx";
+import PasswordPanel from "./components/PasswordPanel.jsx";
+import TeamPanel from "./components/TeamPanel.jsx";
import { useTranslation } from "react-i18next";
/**
diff --git a/client/src/Pages/Auth/CheckEmail.jsx b/client/src/Pages/Auth/CheckEmail.jsx
index e1f5e43ed..95261a768 100644
--- a/client/src/Pages/Auth/CheckEmail.jsx
+++ b/client/src/Pages/Auth/CheckEmail.jsx
@@ -3,13 +3,13 @@ import { useEffect, useState } from "react";
import { useTheme } from "@emotion/react";
import { useDispatch } from "react-redux";
import { useNavigate } from "react-router-dom";
-import { createToast } from "../../Utils/toastUtils";
-import { forgotPassword } from "../../Features/Auth/authSlice";
+import { createToast } from "../../Utils/toastUtils.jsx";
+import { forgotPassword } from "../../Features/Auth/authSlice.js";
import { Trans, useTranslation } from "react-i18next";
-import Background from "../../assets/Images/background-grid.svg?react";
-import EmailIcon from "../../assets/icons/email.svg?react";
-import Logo from "../../assets/icons/checkmate-icon.svg?react";
-import IconBox from "../../Components/IconBox";
+import Background from "@/assets/Images/background-grid.svg?react";
+import Logo from "@/assets/icons/checkmate-icon.svg?react";
+import IconBox from "@/Components/v1/IconBox/index.jsx";
+import Icon from "@/Components/v1/Icon";
import "./index.css";
const CheckEmail = () => {
@@ -160,7 +160,7 @@ const CheckEmail = () => {
svgHeight={24}
mb={theme.spacing(4)}
>
-
+
{t("auth.forgotPassword.heading")}
diff --git a/client/src/Pages/Auth/ForgotPassword.jsx b/client/src/Pages/Auth/ForgotPassword.jsx
index 2a887756e..e6ba4c1f7 100644
--- a/client/src/Pages/Auth/ForgotPassword.jsx
+++ b/client/src/Pages/Auth/ForgotPassword.jsx
@@ -1,16 +1,16 @@
import { Box, Stack, Typography, Button } from "@mui/material";
import { useTheme } from "@emotion/react";
-import { createToast } from "../../Utils/toastUtils";
+import { createToast } from "../../Utils/toastUtils.jsx";
import { useDispatch, useSelector } from "react-redux";
-import { forgotPassword } from "../../Features/Auth/authSlice";
+import { forgotPassword } from "../../Features/Auth/authSlice.js";
import { useEffect, useState } from "react";
-import { newOrChangedCredentials } from "../../Validation/validation";
+import { newOrChangedCredentials } from "../../Validation/validation.js";
import { useNavigate } from "react-router-dom";
-import TextInput from "../../Components/Inputs/TextInput";
-import Logo from "../../assets/icons/checkmate-icon.svg?react";
-import Key from "../../assets/icons/key.svg?react";
-import Background from "../../assets/Images/background-grid.svg?react";
-import IconBox from "../../Components/IconBox";
+import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
+import Logo from "@/assets/icons/checkmate-icon.svg?react";
+import Background from "@/assets/Images/background-grid.svg?react";
+import IconBox from "@/Components/v1/IconBox/index.jsx";
+import Icon from "@/Components/v1/Icon";
import { Trans, useTranslation } from "react-i18next";
import "./index.css";
@@ -165,7 +165,7 @@ const ForgotPassword = () => {
svgHeight={24}
mb={theme.spacing(4)}
>
-
+
{t("auth.forgotPassword.heading")}
diff --git a/client/src/Pages/Auth/Login/hooks/useLoginSubmit.jsx b/client/src/Pages/Auth/Login/hooks/useLoginSubmit.jsx
index 6bc5716ff..b009793d5 100644
--- a/client/src/Pages/Auth/Login/hooks/useLoginSubmit.jsx
+++ b/client/src/Pages/Auth/Login/hooks/useLoginSubmit.jsx
@@ -1,7 +1,7 @@
import { useDispatch } from "react-redux";
-import { login } from "../../../../Features/Auth/authSlice";
+import { login } from "../../../../Features/Auth/authSlice.js";
import { useNavigate } from "react-router-dom";
-import { createToast } from "../../../../Utils/toastUtils";
+import { createToast } from "../../../../Utils/toastUtils.jsx";
import { useTranslation } from "react-i18next";
const useLoginSubmit = () => {
diff --git a/client/src/Pages/Auth/Login/hooks/useValidateLoginForm.jsx b/client/src/Pages/Auth/Login/hooks/useValidateLoginForm.jsx
index e5d045be8..0872213ad 100644
--- a/client/src/Pages/Auth/Login/hooks/useValidateLoginForm.jsx
+++ b/client/src/Pages/Auth/Login/hooks/useValidateLoginForm.jsx
@@ -1,5 +1,5 @@
import { useState } from "react";
-import { loginCredentials } from "../../../../Validation/validation";
+import { loginCredentials } from "../../../../Validation/validation.js";
const useValidateLoginForm = () => {
const [errors, setErrors] = useState({
diff --git a/client/src/Pages/Auth/Login/index.jsx b/client/src/Pages/Auth/Login/index.jsx
index d06ecef6d..7f5dcb5e4 100644
--- a/client/src/Pages/Auth/Login/index.jsx
+++ b/client/src/Pages/Auth/Login/index.jsx
@@ -1,17 +1,17 @@
// Components
import Stack from "@mui/material/Stack";
import Button from "@mui/material/Button";
-import TextInput from "../../../Components/Inputs/TextInput";
-import { PasswordEndAdornment } from "../../../Components/Inputs/TextInput/Adornments";
-import TextLink from "../../../Components/TextLink";
-import AuthPageWrapper from "../components/AuthPageWrapper";
+import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
+import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx";
+import TextLink from "@/Components/v1/TextLink/index.jsx";
+import AuthPageWrapper from "../components/AuthPageWrapper.jsx";
// Utils
import { useTheme } from "@mui/material/styles";
import { useTranslation } from "react-i18next";
-import useLoginForm from "./hooks/useLoginForm";
-import useValidateLoginForm from "./hooks/useValidateLoginForm";
-import useLoginSubmit from "./hooks/useLoginSubmit";
-import useLoadingSubmit from "./hooks/useLoadingSubmit";
+import useLoginForm from "./hooks/useLoginForm.jsx";
+import useValidateLoginForm from "./hooks/useValidateLoginForm.jsx";
+import useLoginSubmit from "./hooks/useLoginSubmit.jsx";
+import useLoadingSubmit from "./hooks/useLoadingSubmit.jsx";
const Login = () => {
// Hooks
diff --git a/client/src/Pages/Auth/NewPasswordConfirmed.jsx b/client/src/Pages/Auth/NewPasswordConfirmed.jsx
index 7ace77abc..c92e52552 100644
--- a/client/src/Pages/Auth/NewPasswordConfirmed.jsx
+++ b/client/src/Pages/Auth/NewPasswordConfirmed.jsx
@@ -2,11 +2,11 @@ import { Box, Button, Stack, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
import { useNavigate } from "react-router-dom";
import { useDispatch } from "react-redux";
-import { clearAuthState } from "../../Features/Auth/authSlice";
-import Background from "../../assets/Images/background-grid.svg?react";
-import ConfirmIcon from "../../assets/icons/check-outlined.svg?react";
-import Logo from "../../assets/icons/checkmate-icon.svg?react";
-import IconBox from "../../Components/IconBox";
+import { clearAuthState } from "../../Features/Auth/authSlice.js";
+import Background from "@/assets/Images/background-grid.svg?react";
+import Logo from "@/assets/icons/checkmate-icon.svg?react";
+import IconBox from "@/Components/v1/IconBox/index.jsx";
+import Icon from "@/Components/v1/Icon";
import { Trans, useTranslation } from "react-i18next";
import "./index.css";
@@ -94,7 +94,7 @@ const NewPasswordConfirmed = () => {
svgHeight={24}
mb={theme.spacing(4)}
>
-
+
{t("auth.forgotPassword.heading")}
diff --git a/client/src/Pages/Auth/Register/index.jsx b/client/src/Pages/Auth/Register/index.jsx
index 2b45d2d15..821c56427 100644
--- a/client/src/Pages/Auth/Register/index.jsx
+++ b/client/src/Pages/Auth/Register/index.jsx
@@ -1,10 +1,10 @@
// Components
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
-import TextInput from "../../../Components/Inputs/TextInput";
+import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
import Button from "@mui/material/Button";
import Box from "@mui/material/Box";
-import PasswordTooltip from "../components/PasswordTooltip";
+import PasswordTooltip from "../components/PasswordTooltip.jsx";
// Utils
import { useTheme } from "@emotion/react";
@@ -13,11 +13,11 @@ import { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import { useParams } from "react-router-dom";
-import { networkService } from "../../../main";
-import { newOrChangedCredentials } from "../../../Validation/validation";
-import { register } from "../../../Features/Auth/authSlice";
-import AuthPageWrapper from "../components/AuthPageWrapper";
-import { createToast } from "../../../Utils/toastUtils";
+import { networkService } from "../../../main.jsx";
+import { newOrChangedCredentials } from "../../../Validation/validation.js";
+import { register } from "../../../Features/Auth/authSlice.js";
+import AuthPageWrapper from "../components/AuthPageWrapper.jsx";
+import { createToast } from "../../../Utils/toastUtils.jsx";
import PropTypes from "prop-types";
const getFeedbackStatus = (form, errors, field, criteria) => {
@@ -176,7 +176,7 @@ const Register = ({ superAdminExists }) => {
delete toSubmit.confirm;
- const action = await dispatch(register(toSubmit));
+ const action = await dispatch(register({ user: toSubmit, token }));
if (action.payload.success) {
navigate("/uptime");
createToast({
diff --git a/client/src/Pages/Auth/SetNewPassword.jsx b/client/src/Pages/Auth/SetNewPassword.jsx
index b4899f47c..ada5d6246 100644
--- a/client/src/Pages/Auth/SetNewPassword.jsx
+++ b/client/src/Pages/Auth/SetNewPassword.jsx
@@ -4,18 +4,18 @@ import { useNavigate } from "react-router-dom";
import { useParams } from "react-router-dom";
import { useTheme } from "@emotion/react";
import { Box, Stack, Typography, Button } from "@mui/material";
-import { setNewPassword } from "../../Features/Auth/authSlice";
-import { createToast } from "../../Utils/toastUtils";
-import { newOrChangedCredentials } from "../../Validation/validation";
-import Check from "../../Components/Check/Check";
-import TextInput from "../../Components/Inputs/TextInput";
-import { PasswordEndAdornment } from "../../Components/Inputs/TextInput/Adornments";
-import IconBox from "../../Components/IconBox";
-import LockIcon from "../../assets/icons/lock.svg?react";
-import Logo from "../../assets/icons/checkmate-icon.svg?react";
-import Background from "../../assets/Images/background-grid.svg?react";
+import { setNewPassword } from "../../Features/Auth/authSlice.js";
+import { createToast } from "../../Utils/toastUtils.jsx";
+import { newOrChangedCredentials } from "../../Validation/validation.js";
+import Check from "@/Components/v1/Check/Check.jsx";
+import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
+import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx";
+import IconBox from "@/Components/v1/IconBox/index.jsx";
+import Logo from "@/assets/icons/checkmate-icon.svg?react";
+import Background from "@/assets/Images/background-grid.svg?react";
+import Icon from "@/Components/v1/Icon";
import "./index.css";
-import { useValidatePassword } from "./hooks/useValidatePassword";
+import { useValidatePassword } from "./hooks/useValidatePassword.jsx";
import { Trans, useTranslation } from "react-i18next";
const SetNewPassword = () => {
@@ -140,7 +140,7 @@ const SetNewPassword = () => {
svgHeight={24}
mb={theme.spacing(4)}
>
-
+
{t("auth.forgotPassword.heading")}
diff --git a/client/src/Pages/Auth/components/AuthHeader.jsx b/client/src/Pages/Auth/components/AuthHeader.jsx
index ce454c065..56b69ef14 100644
--- a/client/src/Pages/Auth/components/AuthHeader.jsx
+++ b/client/src/Pages/Auth/components/AuthHeader.jsx
@@ -1,9 +1,9 @@
// Components
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
-import Logo from "../../../assets/icons/checkmate-icon.svg?react";
-import LanguageSelector from "../../../Components/LanguageSelector";
-import ThemeSwitch from "../../../Components/ThemeSwitch";
+import Logo from "@/assets/icons/checkmate-icon.svg?react";
+import LanguageSelector from "@/Components/LanguageSelector.jsx";
+import ThemeSwitch from "@/Components/v1/ThemeSwitch/index.jsx";
// Utils
import { useTheme } from "@mui/material/styles";
diff --git a/client/src/Pages/Auth/components/AuthPageWrapper.jsx b/client/src/Pages/Auth/components/AuthPageWrapper.jsx
index b83be19c5..a5fbe311b 100644
--- a/client/src/Pages/Auth/components/AuthPageWrapper.jsx
+++ b/client/src/Pages/Auth/components/AuthPageWrapper.jsx
@@ -1,10 +1,10 @@
-import Background from "../../../assets/Images/background-grid.svg?react";
+import Background from "@/assets/Images/background-grid.svg?react";
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
-import AuthHeader from "../components/AuthHeader";
+import AuthHeader from "./AuthHeader.jsx";
import { useTheme } from "@mui/material/styles";
-import Logo from "../../../assets/icons/checkmate-icon.svg?react";
+import Logo from "@/assets/icons/checkmate-icon.svg?react";
import PropTypes from "prop-types";
const AuthPageWrapper = ({ children, heading, welcome }) => {
diff --git a/client/src/Pages/Auth/components/PasswordTooltip.jsx b/client/src/Pages/Auth/components/PasswordTooltip.jsx
index bc2f28ec7..361850aca 100644
--- a/client/src/Pages/Auth/components/PasswordTooltip.jsx
+++ b/client/src/Pages/Auth/components/PasswordTooltip.jsx
@@ -1,4 +1,4 @@
-import Check from "../../../Components/Check/Check";
+import Check from "@/Components/v1/Check/Check.jsx";
import Stack from "@mui/material/Stack";
import { Tooltip, useTheme } from "@mui/material";
import { useTranslation } from "react-i18next";
@@ -75,7 +75,7 @@ const PasswordTooltip = ({ feedback, form, children }) => {
slotProps={{
tooltip: {
sx: {
- backgroundColor: theme.palette.tertiary.background,
+ backgroundColor: theme.palette.tertiary.main,
border: `0.5px solid ${theme.palette.primary.lowContrast}90`,
borderRadius: theme.spacing(4),
color: theme.palette.primary.contrastText,
@@ -88,7 +88,7 @@ const PasswordTooltip = ({ feedback, form, children }) => {
},
arrow: {
sx: {
- color: theme.palette.tertiary.background,
+ color: theme.palette.tertiary.main,
},
},
}}
diff --git a/client/src/Pages/Auth/hooks/usePasswordFeedback.jsx b/client/src/Pages/Auth/hooks/usePasswordFeedback.jsx
index 4159e6166..73e8fb738 100644
--- a/client/src/Pages/Auth/hooks/usePasswordFeedback.jsx
+++ b/client/src/Pages/Auth/hooks/usePasswordFeedback.jsx
@@ -1,5 +1,5 @@
import { useState } from "react";
-import { newOrChangedCredentials } from "../../../Validation/validation";
+import { newOrChangedCredentials } from "../../../Validation/validation.js";
const usePasswordFeedback = () => {
const [feedback, setFeedback] = useState({});
diff --git a/client/src/Pages/Auth/hooks/useValidatePassword.jsx b/client/src/Pages/Auth/hooks/useValidatePassword.jsx
index 102d8d73e..014804d35 100644
--- a/client/src/Pages/Auth/hooks/useValidatePassword.jsx
+++ b/client/src/Pages/Auth/hooks/useValidatePassword.jsx
@@ -1,5 +1,5 @@
import { useMemo, useState } from "react";
-import { newOrChangedCredentials } from "../../../Validation/validation";
+import { newOrChangedCredentials } from "../../../Validation/validation.js";
const getFeedbackStatus = (form, errors, field, criteria) => {
const fieldErrors = errors[field];
diff --git a/client/src/Pages/Checks/Components/IncidentTable/index.jsx b/client/src/Pages/Checks/Components/IncidentTable/index.jsx
new file mode 100644
index 000000000..857f31bff
--- /dev/null
+++ b/client/src/Pages/Checks/Components/IncidentTable/index.jsx
@@ -0,0 +1,233 @@
+//Components
+import Stack from "@mui/material/Stack";
+import DataTable from "@/Components/v1/Table/index.jsx";
+import Table from "@mui/material/Table";
+import TableBody from "@mui/material/TableBody";
+import TableRow from "@mui/material/TableRow";
+import TableCell from "@mui/material/TableCell";
+import TableSkeleton from "@/Components/v1/Table/skeleton.jsx";
+import Pagination from "@/Components/v1/Table/TablePagination/index.jsx";
+import { StatusLabel } from "@/Components/v1/Label/index.jsx";
+import { HttpStatusLabel } from "@/Components/v1/HttpStatusLabel/index.jsx";
+import GenericFallback from "@/Components/v1/GenericFallback/index.jsx";
+import NetworkError from "@/Components/v1/GenericFallback/NetworkError.jsx";
+
+//Utils
+import { formatDateWithTz } from "@/Utils/timeUtilsLegacy.js";
+import { useSelector } from "react-redux";
+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 { lighten } from "@mui/material/styles";
+
+const GetTooltip = (row) => {
+ const theme = useTheme();
+ const phases = row?.timings?.phases;
+
+ const phaseKeyFormattingMap = {
+ firstByte: "first byte",
+ };
+ return (
+
+ {`Status code: ${row?.statusCode}`}
+ {`Response time: ${row?.responseTime} ms`}
+ {phases && (
+ <>
+ {`Request timing: `}
+
+
+ {Object.keys(phases)?.map((phaseKey) => (
+
+
+
+ {`${phaseKeyFormattingMap[phaseKey] || phaseKey}:`}
+
+
+
+ {`${phases[phaseKey]} ms`}
+
+
+ ))}
+
+
+ >
+ )}
+
+ );
+};
+
+const IncidentTable = ({
+ isLoading,
+ monitors,
+ selectedMonitor,
+ filter,
+ dateRange,
+ updateTrigger,
+}) => {
+ //Redux state
+ const uiTimezone = useSelector((state) => state.ui.timezone);
+
+ //Local state
+ const [page, setPage] = useState(0);
+ const [rowsPerPage, setRowsPerPage] = useState(10);
+ const selectedMonitorDetails = monitors?.[selectedMonitor];
+ const selectedMonitorType = selectedMonitorDetails?.type;
+
+ //Hooks
+ const [checksMonitor, checksCountMonitor, isLoadingMonitor, networkErrorMonitor] =
+ useFetchChecksByMonitor({
+ monitorId: selectedMonitor === "0" ? undefined : selectedMonitor,
+ type: selectedMonitorType,
+ status: false,
+ sortOrder: "desc",
+ limit: null,
+ dateRange,
+ filter: filter === "resolved" ? "all" : filter,
+ ack: filter === "resolved" ? true : false,
+ page: page,
+ rowsPerPage: rowsPerPage,
+ enabled: selectedMonitor !== "0",
+ updateTrigger,
+ });
+
+ const [checksTeam, checksCountTeam, isLoadingTeam, networkErrorTeam] =
+ useFetchChecksTeam({
+ status: false,
+ sortOrder: "desc",
+ limit: null,
+ dateRange,
+ filter: filter === "resolved" ? "all" : filter,
+ ack: filter === "resolved" ? true : false,
+ page: page,
+ rowsPerPage: rowsPerPage,
+ enabled: selectedMonitor === "0",
+ updateTrigger,
+ });
+
+ const checks = selectedMonitor === "0" ? checksTeam : checksMonitor;
+ const checksCount = selectedMonitor === "0" ? checksCountTeam : checksCountMonitor;
+ isLoading = isLoadingTeam || isLoadingMonitor;
+ const networkError = selectedMonitor === "0" ? networkErrorTeam : networkErrorMonitor;
+
+ const { t } = useTranslation();
+
+ //Handlers
+ const handleChangePage = (_, newPage) => {
+ setPage(newPage);
+ };
+
+ const handleChangeRowsPerPage = (event) => {
+ setRowsPerPage(event.target.value);
+ };
+
+ const headers = [
+ {
+ id: "monitorName",
+ content: t("incidentsTableMonitorName"),
+ render: (row) => {
+ return monitors?.[row.metadata?.monitorId]?.name || "N/A";
+ },
+ },
+ {
+ id: "status",
+ content: t("incidentsTableStatus"),
+ render: (row) => {
+ const status = row.status === true ? "up" : "down";
+ return (
+
+ );
+ },
+ },
+ {
+ id: "dateTime",
+ content: t("incidentsTableDateTime"),
+ render: (row) => {
+ const formattedDate = formatDateWithTz(
+ row.createdAt,
+ "YYYY-MM-DD HH:mm:ss A",
+ uiTimezone
+ );
+ return formattedDate;
+ },
+ },
+ {
+ id: "statusCode",
+ content: t("incidentsTableStatusCode"),
+ render: (row) => ,
+ },
+ { id: "message", content: t("incidentsTableMessage"), render: (row) => row.message },
+ ];
+
+ if (isLoading) return ;
+
+ if (networkError) {
+ return (
+
+
+
+ );
+ }
+
+ if (!isLoading && typeof checksCount === "undefined") {
+ return {t("incidentsTableNoIncidents")} ;
+ }
+
+ return (
+ <>
+
+
+ >
+ );
+};
+
+IncidentTable.propTypes = {
+ isLoading: PropTypes.bool,
+ monitors: PropTypes.object,
+ selectedMonitor: PropTypes.string,
+ filter: PropTypes.string,
+ dateRange: PropTypes.string,
+ updateTrigger: PropTypes.bool,
+ setUpdateTrigger: PropTypes.func,
+};
+export default IncidentTable;
diff --git a/client/src/Pages/Checks/Components/OptionsHeader/index.jsx b/client/src/Pages/Checks/Components/OptionsHeader/index.jsx
new file mode 100644
index 000000000..74bea4711
--- /dev/null
+++ b/client/src/Pages/Checks/Components/OptionsHeader/index.jsx
@@ -0,0 +1,140 @@
+// Components
+import { Stack, Typography, Button, ButtonGroup } from "@mui/material";
+import Select from "@/Components/v1/Inputs/Select/index.jsx";
+import PropTypes from "prop-types";
+
+//Utils
+import { useTheme } from "@emotion/react";
+import SkeletonLayout from "./skeleton.jsx";
+import { useTranslation } from "react-i18next";
+
+const OptionsHeader = ({
+ shouldRender,
+ selectedMonitor = 0,
+ setSelectedMonitor,
+ monitors,
+ filter = "all",
+ setFilter,
+ dateRange = "hour",
+ setDateRange,
+}) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+ console.log(monitors);
+ const monitorNames = typeof monitors !== "undefined" ? Object.values(monitors) : [];
+ const filterOptions = [
+ { id: "all", name: t("incidentsOptionsHeaderFilterAll") },
+ { id: "down", name: t("incidentsOptionsHeaderFilterDown") },
+ { id: "resolve", name: t("incidentsOptionsHeaderFilterCannotResolve") },
+ { id: "resolved", name: t("incidentsOptionsHeaderFilterResolved") },
+ ];
+
+ // The stacks below which are three in number have the same style so
+ const stackStyles = {
+ direction: "row",
+ alignItems: "center",
+ gap: theme.spacing(6),
+ };
+
+ if (!shouldRender) return ;
+
+ return (
+
+
+
+ {t("incidentsOptionsHeader")}
+
+ setSelectedMonitor(e.target.value)}
+ items={monitorNames}
+ sx={{
+ backgroundColor: theme.palette.primary.main,
+ color: theme.palette.primary.contrastTextSecondary,
+ }}
+ maxWidth={250}
+ />
+
+
+
+ {t("incidentsOptionsHeaderFilterBy")}
+
+ setFilter(e.target.value)}
+ items={filterOptions}
+ sx={{
+ backgroundColor: theme.palette.primary.main,
+ color: theme.palette.primary.contrastTextSecondary,
+ }}
+ />
+
+
+
+ {t("incidentsOptionsHeaderShow")}
+
+
+ setDateRange("hour")}
+ >
+ {t("incidentsOptionsHeaderLastHour")}
+
+ setDateRange("day")}
+ >
+ {t("incidentsOptionsHeaderLastDay")}
+
+ setDateRange("week")}
+ >
+ {t("incidentsOptionsHeaderLastWeek")}
+
+ setDateRange("all")}
+ >
+ {t("incidentsOptionsHeaderFilterAll")}
+
+
+
+
+ );
+};
+
+OptionsHeader.propTypes = {
+ shouldRender: PropTypes.bool,
+ selectedMonitor: PropTypes.string,
+ setSelectedMonitor: PropTypes.func,
+ monitors: PropTypes.object,
+ filter: PropTypes.string,
+ setFilter: PropTypes.func,
+ dateRange: PropTypes.string,
+ setDateRange: PropTypes.func,
+};
+
+export default OptionsHeader;
diff --git a/client/src/Pages/Incidents/Components/OptionsHeader/skeleton.jsx b/client/src/Pages/Checks/Components/OptionsHeader/skeleton.jsx
similarity index 100%
rename from client/src/Pages/Incidents/Components/OptionsHeader/skeleton.jsx
rename to client/src/Pages/Checks/Components/OptionsHeader/skeleton.jsx
diff --git a/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx b/client/src/Pages/Checks/Components/StatusBoxes/StatusBox.jsx
similarity index 84%
rename from client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx
rename to client/src/Pages/Checks/Components/StatusBoxes/StatusBox.jsx
index 7b20e3d51..d7f02ada3 100644
--- a/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx
+++ b/client/src/Pages/Checks/Components/StatusBoxes/StatusBox.jsx
@@ -1,11 +1,8 @@
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { Box, Stack, Typography } from "@mui/material";
-import Background from "../../../../assets/Images/background-grid.svg?react";
-import AlertIcon from "../../../../assets/icons/alert-icon.svg?react";
-import CheckIcon from "../../../../assets/icons/check-icon.svg?react";
-import CloseIcon from "../../../../assets/icons/close-icon.svg?react";
-import WarningIcon from "../../../../assets/icons/warning-icon.svg?react";
+import Background from "@/assets/Images/background-grid.svg?react";
+import Icon from "@/Components/v1/Icon";
const StatusBox = ({ title, value, status }) => {
const theme = useTheme();
@@ -26,28 +23,40 @@ const StatusBox = ({ title, value, status }) => {
color = theme.palette.success.lowContrast;
icon = (
-
+
);
} else if (status === "down") {
color = theme.palette.error.lowContrast;
icon = (
-
+
);
} else if (status === "paused") {
color = theme.palette.warning.lowContrast;
icon = (
-
+
);
} else {
color = theme.palette.accent.main;
icon = (
-
+
);
}
diff --git a/client/src/Pages/Incidents/Components/StatusBoxes/index.jsx b/client/src/Pages/Checks/Components/StatusBoxes/index.jsx
similarity index 87%
rename from client/src/Pages/Incidents/Components/StatusBoxes/index.jsx
rename to client/src/Pages/Checks/Components/StatusBoxes/index.jsx
index 6ba2f1820..bb993111a 100644
--- a/client/src/Pages/Incidents/Components/StatusBoxes/index.jsx
+++ b/client/src/Pages/Checks/Components/StatusBoxes/index.jsx
@@ -1,9 +1,9 @@
import PropTypes from "prop-types";
import { Stack } from "@mui/material";
-import StatusBox from "./StatusBox";
+import StatusBox from "./StatusBox.jsx";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
-import SkeletonLayout from "./skeleton";
+import SkeletonLayout from "./skeleton.jsx";
const StatusBoxes = ({ isLoading, summary }) => {
const theme = useTheme();
@@ -17,7 +17,7 @@ const StatusBoxes = ({ isLoading, summary }) => {
>
{
+ // Redux state
+ const { t } = useTranslation();
+
+ const BREADCRUMBS = [{ name: t("checksPageTitle"), path: "/checks" }];
+
+ // Local state
+ const [selectedMonitor, setSelectedMonitor] = useState("0");
+ const [filter, setFilter] = useState(undefined);
+ const [dateRange, setDateRange] = useState(undefined);
+ const [monitorLookup, setMonitorLookup] = useState(undefined);
+ const [updateTrigger, setUpdateTrigger] = useState(false);
+
+ //Hooks
+
+ //Utils
+ const theme = useTheme();
+ const [monitors, , isLoading, networkError] = useFetchMonitorsByTeamId({});
+ const [summary, isLoadingSummary, networkErrorSummary] = useFetchChecksSummaryByTeamId({
+ updateTrigger,
+ });
+ const { monitorId } = useParams();
+
+ useEffect(() => {
+ if (monitorId) {
+ setSelectedMonitor(monitorId);
+ }
+ }, [monitorId]);
+
+ useEffect(() => {
+ const monitorLookup = monitors?.reduce((acc, monitor) => {
+ acc[monitor.id] = {
+ id: monitor.id,
+ name: monitor.name,
+ type: monitor.type,
+ };
+ return acc;
+ }, {});
+ setMonitorLookup(monitorLookup);
+ }, [monitors]);
+
+ if (networkError || networkErrorSummary) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
+ );
+};
+
+export default Checks;
diff --git a/client/src/Pages/Incidents/Components/ActiveIncidentsPanel/index.jsx b/client/src/Pages/Incidents/Components/ActiveIncidentsPanel/index.jsx
new file mode 100644
index 000000000..fda257695
--- /dev/null
+++ b/client/src/Pages/Incidents/Components/ActiveIncidentsPanel/index.jsx
@@ -0,0 +1,134 @@
+import PropTypes from "prop-types";
+import { Box, Stack, Typography } from "@mui/material";
+import { useTheme } from "@emotion/react";
+import { useTranslation } from "react-i18next";
+import PanelSkeleton from "../IncidentsSummaryPanel/skeleton.jsx";
+import Icon from "@/Components/v1/Icon";
+import SummaryCard from "../SummaryCard/index.jsx";
+
+/**
+ * ActiveIncidentsPanel Component
+ *
+ * Displays a quick overview of currently active incidents.
+ * Shows only essential information: monitor name, status badge, and duration.
+ *
+ * @param {Object} props
+ * @param {Array} props.incidents - Array of active incidents
+ * @param {number} props.totalCount - Total count of active incidents (for badge)
+ * @param {boolean} props.isLoading - Loading state
+ * @param {Object} props.error - Error object if any
+ * @param {Function} props.onIncidentClick - Callback when incident is clicked (optional)
+ */
+const ActiveIncidentsPanel = ({ totalCount = 0, isLoading = false, error = null }) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+
+ if (isLoading) {
+ return ;
+ }
+
+ if (error) {
+ return (
+
+
+ {t("incidentsPage.incidentsActivePanelError")}
+
+
+ );
+ }
+
+ if (!totalCount || totalCount === 0) {
+ return (
+
+
+
+
+
+
+
+ {t("incidentsPage.allSystemsAreOperational")}
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
+ {totalCount}
+
+
+
+ );
+};
+
+ActiveIncidentsPanel.propTypes = {
+ totalCount: PropTypes.number,
+ isLoading: PropTypes.bool,
+ error: PropTypes.object,
+ onIncidentClick: PropTypes.func,
+};
+
+export default ActiveIncidentsPanel;
diff --git a/client/src/Pages/Incidents/Components/IncidentDetailsModal/index.jsx b/client/src/Pages/Incidents/Components/IncidentDetailsModal/index.jsx
new file mode 100644
index 000000000..bfca9e93f
--- /dev/null
+++ b/client/src/Pages/Incidents/Components/IncidentDetailsModal/index.jsx
@@ -0,0 +1,415 @@
+// Components
+import { Stack, Typography, Grid, Box, Button, Divider } from "@mui/material";
+import { GenericDialog } from "@/Components/v1/Dialog/genericDialog.jsx";
+import GenericFallback from "@/Components/v1/GenericFallback/index.jsx";
+import SummaryCard from "../SummaryCard/index.jsx";
+import ResolveIncidentDialog from "../ResolveIncidentDialog/index.jsx";
+
+// Utils
+import { useEffect, useState } from "react";
+import { useTheme } from "@emotion/react";
+import { useTranslation } from "react-i18next";
+import { formatDateWithTz } from "@/Utils/timeUtilsLegacy.js";
+import { useSelector } from "react-redux";
+import PropTypes from "prop-types";
+
+// Hooks
+import useFetchIncidents from "../../hooks/useFetchIncidents.js";
+import useGetIncidentsDuration from "../../hooks/useGetIncidentsDuration.js";
+
+const IncidentDetailsModal = ({ open, incidentId, onClose, onResolved }) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+ const { fetchIncidentById, resolveIncident } = useFetchIncidents();
+ const [incidentData, setIncidentData] = useState(null);
+ const [isLoading, setIsLoading] = useState(false);
+ const [isResolveDialogOpen, setIsResolveDialogOpen] = useState(false);
+ const uiTimezone = useSelector((state) => state.ui.timezone);
+ const isActive = incidentData?.incident?.status === true;
+ const duration = useGetIncidentsDuration(incidentData?.incident || null, isActive);
+
+ const statusColor = isActive ? theme.palette.error.main : theme.palette.success.main;
+ const toCapitalLetter = (text) =>
+ typeof text === "string" && text.length > 0
+ ? text.charAt(0).toUpperCase() + text.slice(1)
+ : text;
+
+ const KeyValueRow = ({ label, value, sx = {} }) => (
+
+
+
+ {label}
+
+
+
+
+ {value ?? "-"}
+
+
+
+ );
+
+ useEffect(() => {
+ const loadIncident = async () => {
+ if (!incidentId || !open) {
+ setIncidentData(null);
+ setIsLoading(false);
+ return;
+ }
+
+ setIsLoading(true);
+ try {
+ const incidentData = await fetchIncidentById(incidentId);
+ setIncidentData(incidentData);
+ } catch (error) {
+ console.error("Error fetching incident:", error);
+ setIncidentData(null);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ loadIncident();
+ }, [incidentId, open, fetchIncidentById]);
+
+ const openResolveDialog = () => {
+ setIsResolveDialogOpen(true);
+ };
+
+ const closeResolveDialog = () => {
+ setIsResolveDialogOpen(false);
+ };
+
+ const handleAfterResolve = async () => {
+ if (!incidentId) return;
+ const updatedIncident = await fetchIncidentById(incidentId);
+ if (updatedIncident) {
+ setIncidentData(updatedIncident);
+ }
+ };
+
+ const renderContent = () => {
+ console.log(incidentData);
+
+ if (isLoading) {
+ return (
+
+ );
+ }
+
+ if (!incidentData) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
+ {isActive ? t("incidentsPage.active") : t("incidentsPage.resolved")}
+
+
+
+
+
+ {t("incidentsPage.incidentItemMonitor")}
+
+
+ {incidentData?.monitor?.name || t("incidentsPage.unknownMonitor")}
+
+
+
+
+ {t("incidentsPage.URL")}
+
+
+ {incidentData?.monitor?.url || "-"}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {t("incidentsPage.timeline")}
+
+
+
+
+ {!isActive && (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ {isActive
+ ? t("incidentsPage.errorDetails")
+ : t("incidentsPage.resolutionDetails")}
+
+
+
+
+
+
+
+
+ {!isActive && (
+ <>
+
+ {incidentData?.incident?.resolutionType === "manual" && (
+
+ )}
+ >
+ )}
+
+
+
+
+
+
+
+ );
+ };
+
+ return (
+
+
+ {renderContent()}
+ {!isActive &&
+ incidentData?.incident?.resolutionType === "manual" &&
+ incidentData?.user?.email && (
+
+ {t("incidentsPage.resolvedBy")}: {incidentData.user.email}
+
+ )}
+
+ {isActive && (
+
+ {t("incidentsPage.incidentsTableActionResolveManually")}
+
+ )}
+
+
+ {t("close")}
+
+
+
+
+
+ );
+};
+
+IncidentDetailsModal.propTypes = {
+ open: PropTypes.bool.isRequired,
+ incidentId: PropTypes.string,
+ onClose: PropTypes.func.isRequired,
+ onResolved: PropTypes.func,
+};
+
+export default IncidentDetailsModal;
diff --git a/client/src/Pages/Incidents/Components/IncidentTable/IncidentActionsMenu.jsx b/client/src/Pages/Incidents/Components/IncidentTable/IncidentActionsMenu.jsx
new file mode 100644
index 000000000..d125a9fdb
--- /dev/null
+++ b/client/src/Pages/Incidents/Components/IncidentTable/IncidentActionsMenu.jsx
@@ -0,0 +1,156 @@
+// Components
+import IconButton from "@mui/material/IconButton";
+import Menu from "@mui/material/Menu";
+import MenuItem from "@mui/material/MenuItem";
+import Icon from "@/Components/v1/Icon/index.jsx";
+import ResolveIncidentDialog from "../ResolveIncidentDialog/index.jsx";
+
+// Utils
+import { useState } from "react";
+import { useTheme } from "@emotion/react";
+import { useTranslation } from "react-i18next";
+import { TypeToPathMap } from "@/Utils/monitorUtilsLegacy.js";
+import { useNavigate } from "react-router-dom";
+import PropTypes from "prop-types";
+
+const IncidentActionsMenu = ({ incident, monitor, onResolve, onOpenDetails }) => {
+ const [anchorEl, setAnchorEl] = useState(null);
+ const [isDialogOpen, setIsDialogOpen] = useState(false);
+ const theme = useTheme();
+ const { t } = useTranslation();
+ const navigate = useNavigate();
+ const openMenu = (event) => {
+ event.preventDefault();
+ event.stopPropagation();
+ setAnchorEl(event.currentTarget);
+ };
+
+ const closeMenu = (e) => {
+ if (e) {
+ e.stopPropagation();
+ }
+ setAnchorEl(null);
+ };
+
+ const openResolveDialog = (e) => {
+ closeMenu(e);
+ setIsDialogOpen(true);
+ };
+
+ const closeDialog = (e) => {
+ if (e) {
+ e.stopPropagation();
+ }
+ setIsDialogOpen(false);
+ };
+
+ const isActive = incident.status === true;
+ const handleGoToDetails = (e) => {
+ e.stopPropagation();
+ closeMenu(e);
+ if (onOpenDetails) {
+ onOpenDetails(incident.id);
+ }
+ };
+
+ const handleGoToMonitor = async (e) => {
+ e.stopPropagation();
+ closeMenu(e);
+ try {
+ if (monitor) {
+ const path = TypeToPathMap[monitor.type];
+ if (path && monitor.id) {
+ navigate(`/${path}/${monitor.id}`);
+ } else {
+ console.error(`Monitor type not found`);
+ }
+ } else {
+ console.error("Monitor information not found in incident data");
+ }
+ } catch (error) {
+ console.error("Error fetching incident details:", error);
+ }
+ };
+
+ return (
+ <>
+ {
+ event.stopPropagation();
+ openMenu(event);
+ }}
+ sx={{
+ "&:focus": {
+ outline: "none",
+ },
+ "& svg path": {
+ stroke: theme.palette.primary.contrastTextTertiary,
+ },
+ }}
+ >
+
+
+
+
+
+ {t("incidentsPage.incidentsTableActionDetails", "Details")}
+
+
+ {t("incidentsPage.incidentsTableActionGoToMonitor", "Go to monitor")}
+
+ {isActive && (
+ {
+ e.stopPropagation();
+ openResolveDialog(e);
+ }}
+ >
+ {t("incidentsPage.incidentsTableActionResolveManually")}
+
+ )}
+
+
+ >
+ );
+};
+
+IncidentActionsMenu.propTypes = {
+ incident: PropTypes.shape({
+ id: PropTypes.string.isRequired,
+ status: PropTypes.bool.isRequired,
+ }).isRequired,
+ monitor: PropTypes.shape({
+ id: PropTypes.string.isRequired,
+ type: PropTypes.string.isRequired,
+ }).isRequired,
+ onResolve: PropTypes.func.isRequired,
+ onOpenDetails: PropTypes.func,
+};
+
+export default IncidentActionsMenu;
diff --git a/client/src/Pages/Incidents/Components/IncidentTable/index.jsx b/client/src/Pages/Incidents/Components/IncidentTable/index.jsx
index df9174290..2a03a8822 100644
--- a/client/src/Pages/Incidents/Components/IncidentTable/index.jsx
+++ b/client/src/Pages/Incidents/Components/IncidentTable/index.jsx
@@ -1,223 +1,144 @@
//Components
-import Stack from "@mui/material/Stack";
-import DataTable from "../../../../Components/Table";
-import Table from "@mui/material/Table";
-import TableBody from "@mui/material/TableBody";
-import TableRow from "@mui/material/TableRow";
-import TableCell from "@mui/material/TableCell";
-import TableSkeleton from "../../../../Components/Table/skeleton";
-import Pagination from "../../../../Components/Table/TablePagination";
-import { StatusLabel } from "../../../../Components/Label";
-import { HttpStatusLabel } from "../../../../Components/HttpStatusLabel";
-import GenericFallback from "../../../../Components/GenericFallback";
-import NetworkError from "../../../../Components/GenericFallback/NetworkError";
+import DataTable from "@/Components/v1/Table/index.jsx";
+import TableSkeleton from "@/Components/v1/Table/skeleton.jsx";
+import Pagination from "@/Components/v1/Table/TablePagination/index.jsx";
+import { StatusLabel } from "@/Components/v1/Label/index.jsx";
+import { HttpStatusLabel } from "@/Components/v1/HttpStatusLabel/index.jsx";
+import GenericFallback from "@/Components/v1/GenericFallback/index.jsx";
+import NetworkError from "@/Components/v1/GenericFallback/NetworkError.jsx";
+import IncidentActionsMenu from "./IncidentActionsMenu.jsx";
//Utils
-import { formatDateWithTz } from "../../../../Utils/timeUtils";
+import { formatDateWithTz } from "../../../../Utils/timeUtilsLegacy.js";
import { useSelector } from "react-redux";
-import { useState } from "react";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
-import { useFetchChecksTeam } from "../../../../Hooks/checkHooks";
-import { useFetchChecksByMonitor } from "../../../../Hooks/checkHooks";
-import { useResolveIncident } from "../../../../Hooks/checkHooks";
-import { Button, Typography, useTheme } from "@mui/material";
-import { lighten } from "@mui/material/styles";
-
-const GetTooltip = (row) => {
- const theme = useTheme();
- const phases = row?.timings?.phases;
-
- const phaseKeyFormattingMap = {
- firstByte: "first byte",
- };
- return (
-
- {`Status code: ${row?.statusCode}`}
- {`Response time: ${row?.responseTime} ms`}
- {phases && (
- <>
- {`Request timing: `}
-
-
- {Object.keys(phases)?.map((phaseKey) => (
-
-
-
- {`${phaseKeyFormattingMap[phaseKey] || phaseKey}:`}
-
-
-
- {`${phases[phaseKey]} ms`}
-
-
- ))}
-
-
- >
- )}
-
- );
-};
+import { Typography, useTheme } from "@mui/material";
const IncidentTable = ({
- isLoading,
- monitors,
- selectedMonitor,
- filter,
- dateRange,
- updateTrigger,
- setUpdateTrigger,
+ monitors = [],
+ incidents = [],
+ incidentsCount = 0,
+ isLoading = false,
+ networkError = false,
+ page = 0,
+ rowsPerPage = 10,
+ handleChangePage,
+ handleChangeRowsPerPage,
+ resolveIncident,
+ handleUpdateTrigger,
+ onOpenDetails,
}) => {
- //Redux state
const uiTimezone = useSelector((state) => state.ui.timezone);
- //Local state
- const [page, setPage] = useState(0);
- const [rowsPerPage, setRowsPerPage] = useState(10);
- const selectedMonitorDetails = monitors?.[selectedMonitor];
- const selectedMonitorType = selectedMonitorDetails?.type;
-
- //Hooks
- const [resolveIncident, resolveLoading] = useResolveIncident();
-
- const [checksMonitor, checksCountMonitor, isLoadingMonitor, networkErrorMonitor] =
- useFetchChecksByMonitor({
- monitorId: selectedMonitor === "0" ? undefined : selectedMonitor,
- type: selectedMonitorType,
- status: false,
- sortOrder: "desc",
- limit: null,
- dateRange,
- filter: filter === "resolved" ? "all" : filter,
- ack: filter === "resolved" ? true : false,
- page: page,
- rowsPerPage: rowsPerPage,
- enabled: selectedMonitor !== "0",
- updateTrigger,
- });
-
- const [checksTeam, checksCountTeam, isLoadingTeam, networkErrorTeam] =
- useFetchChecksTeam({
- status: false,
- sortOrder: "desc",
- limit: null,
- dateRange,
- filter: filter === "resolved" ? "all" : filter,
- ack: filter === "resolved" ? true : false,
- page: page,
- rowsPerPage: rowsPerPage,
- enabled: selectedMonitor === "0",
- updateTrigger,
- });
-
- const checks = selectedMonitor === "0" ? checksTeam : checksMonitor;
- const checksCount = selectedMonitor === "0" ? checksCountTeam : checksCountMonitor;
- isLoading = isLoadingTeam || isLoadingMonitor;
- const networkError = selectedMonitor === "0" ? networkErrorTeam : networkErrorMonitor;
-
const { t } = useTranslation();
+ const theme = useTheme();
- //Handlers
- const handleChangePage = (_, newPage) => {
- setPage(newPage);
- };
-
- const handleChangeRowsPerPage = (event) => {
- setRowsPerPage(event.target.value);
- };
-
- const handleResolveIncident = (checkId) => {
- resolveIncident(checkId, setUpdateTrigger);
+ const handleResolveIncident = async (incidentId, options = {}) => {
+ try {
+ await resolveIncident(incidentId, options);
+ handleUpdateTrigger();
+ } catch (error) {
+ console.error(t("incidentsPage.errorResolvingIncident"), error);
+ }
};
const headers = [
{
id: "monitorName",
content: t("incidentsTableMonitorName"),
- render: (row) => monitors[row.monitorId]?.name ?? "N/A",
+ render: (row) => {
+ const monitor = monitors.find((monitor) => monitor.id === row.monitorId);
+ return monitor ? monitor.name : "N/A";
+ },
},
{
id: "status",
content: t("incidentsTableStatus"),
render: (row) => {
- const status = row.status === true ? "up" : "down";
+ const status = row.status === true ? "down" : "up";
+ const statusText =
+ row.status === true ? t("incidentsPage.active") : t("incidentsPage.resolved");
return (
);
},
},
{
- id: "dateTime",
- content: t("incidentsTableDateTime"),
+ id: "startTime",
+ content: t("incidentsPage.startTime"),
render: (row) => {
const formattedDate = formatDateWithTz(
- row.createdAt,
+ row.startTime || row.createdAt,
"YYYY-MM-DD HH:mm:ss A",
uiTimezone
);
return formattedDate;
},
},
+ {
+ id: "endTime",
+ content: t("incidentsPage.endTime"),
+ render: (row) => {
+ if (row.endTime) {
+ return formatDateWithTz(row.endTime, "YYYY-MM-DD HH:mm:ss A", uiTimezone);
+ }
+ return "-";
+ },
+ },
+ {
+ id: "resolutionType",
+ content: t("incidentsPage.resolutionType"),
+ render: (row) => {
+ if (row.resolutionType) {
+ return (
+
+ {row.resolutionType}
+
+ );
+ }
+ return "-";
+ },
+ },
{
id: "statusCode",
content: t("incidentsTableStatusCode"),
render: (row) => ,
},
- { id: "message", content: t("incidentsTableMessage"), render: (row) => row.message },
+ {
+ id: "message",
+ content: t("incidentsTableMessage"),
+ render: (row) => row.message || "-",
+ },
{
id: "action",
content: t("actions"),
render: (row) => {
- return row.ack === false ? (
- {
- handleResolveIncident(row._id);
- }}
- >
- {t("incidentsTableActionResolve")}
-
- ) : (
-
- {t("incidentsTableResolvedAt")}{" "}
- {formatDateWithTz(row.ackAt, "YYYY-MM-DD HH:mm:ss A", uiTimezone)}
-
+ return (
+ monitor.id === row.monitorId)}
+ onResolve={handleResolveIncident}
+ onOpenDetails={onOpenDetails}
+ />
);
},
},
];
- if (isLoading || resolveLoading) return ;
+ if (isLoading) return ;
if (networkError) {
return (
@@ -227,20 +148,25 @@ const IncidentTable = ({
);
}
- if (!isLoading && typeof checksCount === "undefined") {
- return {t("incidentsTableNoIncidents")} ;
+ if (!isLoading && !networkError && incidents?.length === 0) {
+ return (
+
+ {t("incidentsTableNoIncidents", "No incidents found")}
+
+ );
}
+ const incidentsData = Array.isArray(incidents) ? incidents : [];
+
return (
<>
{
+ const { fetchIncidentSummary } = useFetchIncidents();
+ const [summary, setSummary] = useState({
+ totalActive: 0,
+ avgResolutionTimeHours: 0,
+ total: 0,
+ topMonitor: null,
+ totalManualResolutions: 0,
+ totalAutomaticResolutions: 0,
+ latestIncidents: [],
+ });
+
+ const [isLoadingSummary, setIsLoadingSummary] = useState(false);
+ const [summaryError, setSummaryError] = useState(null);
+
+ useEffect(() => {
+ const fetchSummary = async () => {
+ setIsLoadingSummary(true);
+ setSummaryError(null);
+
+ try {
+ const summaryData = await fetchIncidentSummary({
+ limit: 3,
+ });
+
+ if (summaryData) {
+ setSummary(summaryData);
+ } else {
+ setSummaryError(new Error("Failed to fetch summary"));
+ }
+ } catch (error) {
+ console.error("Error fetching incident summary:", error);
+ setSummaryError(error);
+ } finally {
+ setIsLoadingSummary(false);
+ }
+ };
+
+ fetchSummary();
+ }, [fetchIncidentSummary, updateTrigger]);
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+IncidentsSummaryPanel.propTypes = {
+ updateTrigger: PropTypes.bool.isRequired,
+};
+export default IncidentsSummaryPanel;
diff --git a/client/src/Pages/Incidents/Components/IncidentsSummaryPanel/skeleton.jsx b/client/src/Pages/Incidents/Components/IncidentsSummaryPanel/skeleton.jsx
new file mode 100644
index 000000000..153cd7c25
--- /dev/null
+++ b/client/src/Pages/Incidents/Components/IncidentsSummaryPanel/skeleton.jsx
@@ -0,0 +1,20 @@
+import { Skeleton } from "@mui/material";
+import SummaryCard from "../SummaryCard/index.jsx";
+
+/**
+ * Simple skeleton loader for summary panels
+ * Reusable for ActiveIncidentsPanel, LatestIncidentsPanel, and StatisticsPanel
+ */
+const PanelSkeleton = () => {
+ return (
+
+
+
+ );
+};
+
+export default PanelSkeleton;
diff --git a/client/src/Pages/Incidents/Components/LatestIncidentsPanel/IncidentItem.jsx b/client/src/Pages/Incidents/Components/LatestIncidentsPanel/IncidentItem.jsx
new file mode 100644
index 000000000..ca5bb640f
--- /dev/null
+++ b/client/src/Pages/Incidents/Components/LatestIncidentsPanel/IncidentItem.jsx
@@ -0,0 +1,96 @@
+import PropTypes from "prop-types";
+import { Box, Stack, Typography } from "@mui/material";
+import { useTheme } from "@emotion/react";
+import { StatusLabel } from "@/Components/v1/Label/index.jsx";
+import { getHumanReadableDuration } from "@/Utils/timeUtilsLegacy.js";
+import Icon from "@/Components/v1/Icon";
+import { useTranslation } from "react-i18next";
+import useGetIncidentsDuration from "../../hooks/useGetIncidentsDuration";
+
+const IncidentItem = ({ incident }) => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+ const isActive = incident.status === true;
+ const duration = useGetIncidentsDuration(incident, isActive);
+ const iconWrapperStyle = {
+ display: "flex",
+ justifyContent: "center",
+ mx: theme.spacing(2),
+ color: theme.palette.primary.contrastTextTertiary,
+ "& svg": {
+ width: 18,
+ height: 18,
+ },
+ "& svg path": {
+ stroke: "currentColor",
+ strokeWidth: 1.5,
+ },
+ };
+
+ return (
+
+
+
+
+
+
+ {incident.monitorName || t("incidentsPage.unknownMonitor")}
+
+
+
+
+
+
+
+
+ {duration}
+
+
+ );
+};
+
+IncidentItem.propTypes = {
+ incident: PropTypes.shape({
+ _id: PropTypes.string,
+ id: PropTypes.string,
+ monitorId: PropTypes.string,
+ monitorName: PropTypes.string,
+ status: PropTypes.bool,
+ startTime: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Date)]),
+ endTime: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Date)]),
+ resolutionType: PropTypes.oneOf(["automatic", "manual"]),
+ }),
+ onClick: PropTypes.func,
+};
+
+export default IncidentItem;
diff --git a/client/src/Pages/Incidents/Components/LatestIncidentsPanel/index.jsx b/client/src/Pages/Incidents/Components/LatestIncidentsPanel/index.jsx
new file mode 100644
index 000000000..ff1ef9417
--- /dev/null
+++ b/client/src/Pages/Incidents/Components/LatestIncidentsPanel/index.jsx
@@ -0,0 +1,114 @@
+import PropTypes from "prop-types";
+import { Box, Stack, Typography, Divider } from "@mui/material";
+import { useTheme } from "@emotion/react";
+import { useTranslation } from "react-i18next";
+import PanelSkeleton from "../IncidentsSummaryPanel/skeleton.jsx";
+import IncidentItem from "./IncidentItem.jsx";
+import SummaryCard from "../SummaryCard/index.jsx";
+import Icon from "@/Components/v1/Icon";
+
+/**
+ * LatestIncidentsPanel Component
+ *
+ * Displays a quick overview of recent incidents (both active and resolved).
+ * Shows only essential information: monitor name, status, duration, and resolution type.
+ *
+ * @param {Object} props
+ * @param {Array} props.incidents - Array of recent incidents
+ * @param {boolean} props.isLoading - Loading state
+ * @param {Object} props.error - Error object if any
+ */
+const LatestIncidentsPanel = ({ incidents = [], isLoading = false, error = null }) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+
+ if (isLoading) {
+ return ;
+ }
+
+ if (error) {
+ return (
+
+
+
+ {t("incidentsPage.incidentsLatestPanelError")}
+
+
+
+ );
+ }
+
+ return (
+
+ {!incidents || incidents.length === 0 ? (
+
+
+
+
+
+
+ {t("incidentsPage.incidentsLatestPanelEmpty")}
+
+
+ ) : (
+
+ {incidents.map((incident, index) => (
+
+
+ {index < incidents.length - 1 && }
+
+ ))}
+
+ )}
+
+ );
+};
+
+LatestIncidentsPanel.propTypes = {
+ incidents: PropTypes.arrayOf(
+ PropTypes.shape({
+ _id: PropTypes.string,
+ monitorId: PropTypes.string,
+ monitorName: PropTypes.string,
+ status: PropTypes.bool,
+ duration: PropTypes.string,
+ resolutionType: PropTypes.oneOf(["automatic", "manual"]),
+ })
+ ),
+ isLoading: PropTypes.bool,
+ error: PropTypes.object,
+ onIncidentClick: PropTypes.func,
+};
+
+export default LatestIncidentsPanel;
diff --git a/client/src/Pages/Incidents/Components/OptionsHeader/index.jsx b/client/src/Pages/Incidents/Components/OptionsHeader/index.jsx
index 259880223..8d7439576 100644
--- a/client/src/Pages/Incidents/Components/OptionsHeader/index.jsx
+++ b/client/src/Pages/Incidents/Components/OptionsHeader/index.jsx
@@ -1,11 +1,10 @@
// Components
import { Stack, Typography, Button, ButtonGroup } from "@mui/material";
-import Select from "../../../../Components/Inputs/Select";
+import Select from "@/Components/v1/Inputs/Select/index.jsx";
import PropTypes from "prop-types";
//Utils
import { useTheme } from "@emotion/react";
-import SkeletonLayout from "./skeleton";
import { useTranslation } from "react-i18next";
const OptionsHeader = ({
@@ -15,27 +14,28 @@ const OptionsHeader = ({
monitors,
filter = "all",
setFilter,
- dateRange = "hour",
+ dateRange = "all",
setDateRange,
}) => {
const theme = useTheme();
const { t } = useTranslation();
const monitorNames = typeof monitors !== "undefined" ? Object.values(monitors) : [];
+
const filterOptions = [
- { _id: "all", name: t("incidentsOptionsHeaderFilterAll") },
- { _id: "down", name: t("incidentsOptionsHeaderFilterDown") },
- { _id: "resolve", name: t("incidentsOptionsHeaderFilterCannotResolve") },
- { _id: "resolved", name: t("incidentsOptionsHeaderFilterResolved") },
+ { _id: "all", name: t("incidentsPage.incidentsOptionsHeaderFilterAll") },
+ { _id: "active", name: t("incidentsPage.incidentsOptionsHeaderFilterActive") },
+ { _id: "resolved", name: t("incidentsPage.incidentsOptionsHeaderFilterResolved") },
+ { _id: "manual", name: t("incidentsPage.incidentsOptionsHeaderFilterManual") },
+ { _id: "automatic", name: t("incidentsPage.incidentsOptionsHeaderFilterAutomatic") },
];
- // The stacks below which are three in number have the same style so
const stackStyles = {
direction: "row",
alignItems: "center",
gap: theme.spacing(6),
};
- if (!shouldRender) return ;
+ if (!shouldRender) return;
return (
{
+ const { t } = useTranslation();
+ const theme = useTheme();
+ const [comment, setComment] = useState("");
+ const [isSubmitting, setIsSubmitting] = useState(false);
+
+ useEffect(() => {
+ if (open) {
+ setComment("");
+ }
+ }, [open]);
+
+ const handleConfirm = async () => {
+ if (!incidentId) return;
+ const trimmed = comment.trim();
+ try {
+ setIsSubmitting(true);
+ await onResolve(incidentId, trimmed ? { comment: trimmed } : {});
+ if (onAfterResolve) {
+ await onAfterResolve();
+ }
+ onResolved?.();
+ onClose();
+ } catch (error) {
+ console.error("Error resolving incident:", error);
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ return (
+
+
+ setComment(e.target.value)}
+ maxWidth="100%"
+ />
+
+
+ {t("cancel", "Cancel")}
+
+
+ {t("incidentsPage.resolveIncidentDialogConfirm")}
+
+
+
+
+ );
+};
+
+ResolveIncidentDialog.propTypes = {
+ open: PropTypes.bool.isRequired,
+ incidentId: PropTypes.string,
+ onClose: PropTypes.func.isRequired,
+ onResolve: PropTypes.func.isRequired,
+ onAfterResolve: PropTypes.func,
+ isLoading: PropTypes.bool,
+ onResolved: PropTypes.func,
+};
+
+export default ResolveIncidentDialog;
diff --git a/client/src/Pages/Incidents/Components/StatisticsPanel/index.jsx b/client/src/Pages/Incidents/Components/StatisticsPanel/index.jsx
new file mode 100644
index 000000000..7f07f688f
--- /dev/null
+++ b/client/src/Pages/Incidents/Components/StatisticsPanel/index.jsx
@@ -0,0 +1,168 @@
+import PropTypes from "prop-types";
+import { Box, Stack, Typography, Divider } from "@mui/material";
+import { useTheme } from "@emotion/react";
+import PanelSkeleton from "../IncidentsSummaryPanel/skeleton.jsx";
+import { useTranslation } from "react-i18next";
+import Icon from "@/Components/v1/Icon";
+import SummaryCard from "../SummaryCard/index.jsx";
+
+/**
+ * StatisticsPanel Component
+ *
+ * Displays key metrics and statistics about incidents.
+ *
+ * @param {Object} props
+ * @param {Object} props.statistics - Statistics data object
+ * @param {number} props.statistics.total - Total incidents count
+ * @param {number} props.statistics.totalTrend - Trend percentage for total (optional)
+ * @param {string} props.statistics.mttr - Mean Time To Resolve (formatted string, e.g., "2h 15m")
+ * @param {number} props.statistics.mttrTrend - Trend percentage for MTTR (optional)
+ * @param {Object} props.statistics.topMonitor - Top monitor with most incidents
+ * @param {string} props.statistics.topMonitor.name - Monitor name
+ * @param {number} props.statistics.topMonitor.count - Incident count
+ * @param {number} props.statistics.topMonitor.percentage - Percentage of total
+ * @param {string} props.statistics.uptimeAffected - Total downtime (formatted string, e.g., "12h 45m")
+ * @param {number} props.statistics.availability - Availability percentage
+ * @param {boolean} props.isLoading - Loading state
+ * @param {Object} props.error - Error object if any
+ */
+const StatisticsPanel = ({ isLoading = false, error = null, summary = {} }) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+ const totalResolutions =
+ (summary?.totalManualResolutions || 0) + (summary?.totalAutomaticResolutions || 0);
+
+ const iconWrapperStyle = {
+ display: "flex",
+ justifyContent: "center",
+ mx: theme.spacing(2),
+ color: theme.palette.primary.contrastTextTertiary,
+ "& svg": {
+ width: 18,
+ height: 18,
+ },
+ "& svg path": {
+ stroke: "currentColor",
+ strokeWidth: 1.5,
+ },
+ };
+ if (isLoading) {
+ return ;
+ }
+
+ if (error) {
+ return (
+
+
+
+ {t("incidentsPage.incidentsStatisticsPanelError")}
+
+
+
+ );
+ }
+
+ const getMostAffectedMonitor = () => {
+ if (!summary.total || summary.total === 0) {
+ return t("incidentsPage.none");
+ }
+ return summary.topMonitor?.monitorName || t("incidentsPage.none");
+ };
+
+ const rowStyle = {
+ py: theme.spacing(0.5),
+ minHeight: 32,
+ };
+
+ return (
+
+
+
+
+
+
+
+
+ {t("incidentsPage.totalIncidents")}: {summary.total || 0}
+
+
+
+
+
+
+
+
+
+
+ {t("incidentsPage.mostAffectedMonitor")}: {getMostAffectedMonitor()}
+
+
+
+
+
+
+
+
+
+ {t("incidentsPage.avgResolutionTime")}:{" "}
+ {summary.total > 0
+ ? `${summary.avgResolutionTimeHours || 0} ${t("incidentsPage.hours")}`
+ : "N/A"}
+
+
+
+
+ );
+};
+
+StatisticsPanel.propTypes = {
+ summary: PropTypes.object,
+ isLoading: PropTypes.bool,
+ error: PropTypes.object,
+};
+
+export default StatisticsPanel;
diff --git a/client/src/Pages/Incidents/Components/SummaryCard/index.jsx b/client/src/Pages/Incidents/Components/SummaryCard/index.jsx
new file mode 100644
index 000000000..3a8589dc1
--- /dev/null
+++ b/client/src/Pages/Incidents/Components/SummaryCard/index.jsx
@@ -0,0 +1,64 @@
+import { Paper } from "@mui/material";
+import { useTheme } from "@mui/material/styles";
+import PropTypes from "prop-types";
+import { Box, Stack, Typography, Divider } from "@mui/material";
+const SummaryCard = ({ children, isHighPriority = false, sx = {}, title = null }) => {
+ const theme = useTheme();
+ return (
+
+ {title && (
+
+
+
+ {title}
+
+
+
+
+ )}
+
+ {children}
+
+
+ );
+};
+SummaryCard.propTypes = {
+ children: PropTypes.node,
+ isHighPriority: PropTypes.bool,
+ sx: PropTypes.object,
+ title: PropTypes.string,
+};
+export default SummaryCard;
diff --git a/client/src/Pages/Incidents/hooks/useFetchIncidents.js b/client/src/Pages/Incidents/hooks/useFetchIncidents.js
new file mode 100644
index 000000000..e308c9319
--- /dev/null
+++ b/client/src/Pages/Incidents/hooks/useFetchIncidents.js
@@ -0,0 +1,215 @@
+import { useState, useCallback } from "react";
+import { networkService } from "../../../main.jsx";
+import { createToast } from "../../../Utils/toastUtils.jsx";
+import { useTranslation } from "react-i18next";
+/**
+ * Hook to fetch and manage incidents
+ *
+ * Provides multiple functions to fetch incidents with different filters.
+ *
+ * @returns {Object} {
+ * incidents,
+ * incidentsCount,
+ * isLoading,
+ * networkError,
+ * fetchIncidents,
+ * fetchActiveIncidents,
+ * fetchResolvedIncidents,
+ * fetchIncidentsByTeam,
+ * fetchIncidentById,
+ * resolveIncident,
+ * fetchIncidentSummary
+ * }
+ */
+const useFetchIncidents = () => {
+ const [incidents, setIncidents] = useState(null);
+ const [incidentsCount, setIncidentsCount] = useState(null);
+ const [isLoading, setIsLoading] = useState(false);
+ const [networkError, setNetworkError] = useState(false);
+ const { t } = useTranslation();
+ /**
+ * Generic function to fetch incidents by team with custom config
+ *
+ * @param {Object} config - Configuration object
+ * @param {string} [config.monitorId] - Filter by monitor ID
+ * @param {boolean} [config.status] - Filter by status (true=active, false=resolved)
+ * @param {string} [config.resolutionType] - Filter by resolution type (automatic/manual)
+ * @param {string} [config.sortOrder] - Sort order (asc/desc)
+ * @param {string} [config.dateRange] - Date range filter
+ * @param {number} [config.page] - Page number
+ * @param {number} [config.rowsPerPage] - Rows per page
+ */
+ const fetchIncidentsByTeam = useCallback(async (config = {}) => {
+ try {
+ setIsLoading(true);
+ setNetworkError(false);
+
+ const res = await networkService.getIncidentsByTeam(config);
+ setIncidents(res.data?.data?.incidents || []);
+ setIncidentsCount(res.data?.data?.count || 0);
+ } catch (error) {
+ setNetworkError(true);
+ console.error(t("incidentsPage.errorFetchingIncidents"), error);
+ createToast({ body: error.message || t("incidentsPage.errorFetchingIncidents") });
+ } finally {
+ setIsLoading(false);
+ }
+ }, []);
+
+ /**
+ * Fetch active (open) incidents - Default function
+ *
+ * @param {Object} config - Additional configuration
+ */
+ const fetchActiveIncidents = useCallback(
+ async (config = {}) => {
+ await fetchIncidentsByTeam({
+ status: true,
+ sortOrder: "desc",
+ ...config,
+ });
+ },
+ [fetchIncidentsByTeam]
+ );
+
+ const fetchIncidentsByResolutionType = useCallback(
+ async (resolutionType, config = {}) => {
+ await fetchIncidentsByTeam({
+ resolutionType,
+ sortOrder: "desc",
+ ...config,
+ });
+ },
+ [fetchIncidentsByTeam]
+ );
+ /**
+ * Fetch resolved incidents
+ *
+ * @param {Object} config - Additional configuration
+ */
+ const fetchResolvedIncidents = useCallback(
+ async (config = {}) => {
+ await fetchIncidentsByTeam({
+ status: false,
+ sortOrder: "desc",
+ ...config,
+ });
+ },
+ [fetchIncidentsByTeam]
+ );
+
+ /**
+ * Fetch incidents with custom filters (alias for fetchIncidentsByTeam)
+ *
+ * @param {Object} config - Configuration object
+ */
+ const fetchIncidents = useCallback(
+ async (config = {}) => {
+ await fetchIncidentsByTeam(config);
+ },
+ [fetchIncidentsByTeam]
+ );
+
+ /**
+ * Fetch a single incident by ID
+ *
+ * @param {string} incidentId - The ID of the incident to fetch
+ * @returns {Promise} The incident object or null if not found
+ */
+ const fetchIncidentById = useCallback(async (incidentId) => {
+ if (!incidentId) {
+ console.error(t("incidentsPage.noIncidentIdProvided"));
+ return null;
+ }
+
+ try {
+ setIsLoading(true);
+ setNetworkError(false);
+
+ const res = await networkService.getIncidentById(incidentId);
+ return res.data?.data || null;
+ } catch (error) {
+ setNetworkError(true);
+ console.error(t("incidentsPage.errorFetchingIncident"), error);
+ createToast({ body: error.message || t("incidentsPage.errorFetchingIncident") });
+ return null;
+ } finally {
+ setIsLoading(false);
+ }
+ }, []);
+
+ /**
+ * Resolve an incident manually
+ *
+ * @param {string} incidentId - The ID of the incident to resolve
+ * @param {Object} options - Resolution options
+ * @param {string} [options.comment] - Optional comment about the resolution
+ * @param {Function} [onSuccess] - Callback function to call on success
+ * @param {Function} [onError] - Callback function to call on error
+ */
+ const resolveIncident = useCallback(async (incidentId, options = {}) => {
+ if (!incidentId) {
+ console.error(t("incidentsPage.noIncidentIdProvided"));
+ return;
+ }
+
+ try {
+ setIsLoading(true);
+ setNetworkError(false);
+
+ const res = await networkService.resolveIncidentManually(incidentId, options);
+ if (res.data?.success) {
+ createToast({ body: t("incidentsPage.incidentResolvedSuccessfully") });
+ } else {
+ createToast({ body: t("incidentsPage.errorResolvingIncident") });
+ }
+ } catch (error) {
+ setNetworkError(true);
+ console.error(t("incidentsPage.errorResolvingIncident"), error);
+ const errorMessage =
+ error.response?.data?.message ||
+ error.message ||
+ t("incidentsPage.errorResolvingIncident");
+ createToast({ body: errorMessage });
+ } finally {
+ setIsLoading(false);
+ }
+ }, []);
+
+ /**
+ * Fetch incident summary
+ *
+ * @param {Object} config - Configuration object
+ * @param {number} [config.limit=10] - Number of latest incidents to return
+ * @returns {Promise} The summary object or null if error
+ */
+ const fetchIncidentSummary = useCallback(async (config = {}) => {
+ try {
+ const res = await networkService.getIncidentSummary(config);
+ return res.data?.data || null;
+ } catch (error) {
+ console.error(t("incidentsPage.errorFetchingIncidentSummary"), error);
+ createToast({
+ body: error.message || t("incidentsPage.errorFetchingIncidentSummary"),
+ });
+ return null;
+ }
+ }, []);
+
+ return {
+ incidents,
+ incidentsCount,
+ isLoading,
+ networkError,
+ fetchIncidents,
+ fetchActiveIncidents,
+ fetchResolvedIncidents,
+ fetchIncidentsByTeam,
+ fetchIncidentById,
+ resolveIncident,
+ fetchIncidentSummary,
+ fetchIncidentsByResolutionType,
+ };
+};
+
+export default useFetchIncidents;
diff --git a/client/src/Pages/Incidents/hooks/useGetIncidentsDuration.js b/client/src/Pages/Incidents/hooks/useGetIncidentsDuration.js
new file mode 100644
index 000000000..a4f434a80
--- /dev/null
+++ b/client/src/Pages/Incidents/hooks/useGetIncidentsDuration.js
@@ -0,0 +1,25 @@
+import { getHumanReadableDuration } from "@/Utils/timeUtilsLegacy.js";
+const useGetIncidentsDuration = (incident, isActive) => {
+ if (!incident?.startTime) {
+ return "-";
+ }
+ const startTime = new Date(incident?.startTime);
+ const endTime = isActive
+ ? new Date()
+ : incident?.endTime
+ ? new Date(incident.endTime)
+ : null;
+
+ if (!endTime) {
+ return "-";
+ }
+
+ const durationMs = endTime.getTime() - startTime.getTime();
+
+ if (durationMs < 0) {
+ return "-";
+ }
+
+ return getHumanReadableDuration(durationMs);
+};
+export default useGetIncidentsDuration;
diff --git a/client/src/Pages/Incidents/index.jsx b/client/src/Pages/Incidents/index.jsx
index 7696c453b..0377405b7 100644
--- a/client/src/Pages/Incidents/index.jsx
+++ b/client/src/Pages/Incidents/index.jsx
@@ -1,73 +1,113 @@
// Components
import { Stack } from "@mui/material";
-import Breadcrumbs from "../../Components/Breadcrumbs";
-import GenericFallback from "../../Components/GenericFallback";
-import IncidentTable from "./Components/IncidentTable";
-import OptionsHeader from "./Components/OptionsHeader";
-import StatusBoxes from "./Components/StatusBoxes";
-import { Box, Button } from "@mui/material";
-
+import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
+import GenericFallback from "@/Components/v1/GenericFallback/index.jsx";
+import IncidentTable from "./Components/IncidentTable/index.jsx";
+import OptionsHeader from "./Components/OptionsHeader/index.jsx";
+import IncidentsSummaryPanel from "./Components/IncidentsSummaryPanel/index.jsx";
+import IncidentDetailsModal from "./Components/IncidentDetailsModal/index.jsx";
//Utils
import { useTheme } from "@emotion/react";
-import { useFetchMonitorsByTeamId } from "../../Hooks/monitorHooks";
-import { useFetchChecksSummaryByTeamId } from "../../Hooks/checkHooks";
-import { useAcknowledgeChecks } from "../../Hooks/checkHooks";
import { useState, useEffect } from "react";
-import NetworkError from "../../Components/GenericFallback/NetworkError";
+import NetworkError from "@/Components/v1/GenericFallback/NetworkError.jsx";
import { useTranslation } from "react-i18next";
import { useParams } from "react-router-dom";
-//Constants
-const Incidents = () => {
- // Redux state
+// Hooks
+import useFetchIncidents from "./hooks/useFetchIncidents.js";
+import { useFetchMonitorsByTeamId } from "../../Hooks/monitorHooks.js";
+
+const Incidents2 = () => {
+ const { monitorId } = useParams();
const { t } = useTranslation();
const BREADCRUMBS = [
{ name: t("incidentsPageTitle", "Incidents"), path: "/incidents" },
];
- // Local state
- const [selectedMonitor, setSelectedMonitor] = useState("0");
- const [filter, setFilter] = useState(undefined);
- const [dateRange, setDateRange] = useState(undefined);
+ const [selectedMonitor, setSelectedMonitor] = useState(monitorId || "0");
+ const [filter, setFilter] = useState("all");
+ const [dateRange, setDateRange] = useState("all");
+ const [page, setPage] = useState(0);
+ const [rowsPerPage, setRowsPerPage] = useState(10);
const [monitorLookup, setMonitorLookup] = useState(undefined);
const [updateTrigger, setUpdateTrigger] = useState(false);
+ const [isDetailsOpen, setIsDetailsOpen] = useState(false);
+ const [selectedIncidentId, setSelectedIncidentId] = useState(null);
+ const handleUpdateTrigger = () => {
+ setUpdateTrigger((prev) => !prev);
+ };
- //Hooks
- const { acknowledge, isLoadingAcknowledge } = useAcknowledgeChecks();
+ const [monitors, isLoadingMonitors, monitorsNetworkError] = useFetchMonitorsByTeamId(
+ {}
+ );
+
+ const {
+ incidents,
+ incidentsCount,
+ isLoading: isLoadingIncidents,
+ networkError: incidentsNetworkError,
+ fetchIncidents,
+ fetchActiveIncidents,
+ fetchResolvedIncidents,
+ resolveIncident,
+ fetchIncidentsByResolutionType,
+ } = useFetchIncidents();
+
+ const networkError = monitorsNetworkError || incidentsNetworkError;
- //Utils
const theme = useTheme();
- const [monitors, , isLoading, networkError] = useFetchMonitorsByTeamId({});
- const [summary, isLoadingSummary, networkErrorSummary] = useFetchChecksSummaryByTeamId({
- updateTrigger,
- });
- const { monitorId } = useParams();
useEffect(() => {
- if (monitorId) {
- setSelectedMonitor(monitorId);
+ setPage(0);
+ }, [selectedMonitor, filter, dateRange]);
+
+ useEffect(() => {
+ const config = {
+ monitorId: selectedMonitor !== "0" ? selectedMonitor : undefined,
+ sortOrder: "desc",
+ dateRange,
+ page,
+ rowsPerPage,
+ };
+
+ if (filter === "active") {
+ fetchActiveIncidents(config);
+ } else if (filter === "resolved") {
+ fetchResolvedIncidents(config);
+ } else if (filter === "manual") {
+ fetchIncidentsByResolutionType("manual", config);
+ } else if (filter === "automatic") {
+ fetchIncidentsByResolutionType("automatic", config);
+ } else {
+ fetchIncidents(config);
}
- }, [monitorId]);
+ }, [
+ selectedMonitor,
+ filter,
+ dateRange,
+ page,
+ rowsPerPage,
+ updateTrigger,
+ fetchActiveIncidents,
+ fetchResolvedIncidents,
+ fetchIncidents,
+ fetchIncidentsByResolutionType,
+ ]);
useEffect(() => {
- const monitorLookup = monitors?.reduce((acc, monitor) => {
- acc[monitor._id] = {
- _id: monitor._id,
+ const lookup = monitors?.reduce((acc, monitor) => {
+ acc[monitor.id] = {
+ id: monitor.id,
name: monitor.name,
type: monitor.type,
};
return acc;
}, {});
- setMonitorLookup(monitorLookup);
+ setMonitorLookup(lookup);
}, [monitors]);
- const handleAcknowledge = () => {
- const monitorId = selectedMonitor === "0" ? null : selectedMonitor;
- acknowledge(setUpdateTrigger, monitorId);
- };
-
- if (networkError || networkErrorSummary) {
+ if (networkError) {
return (
@@ -75,27 +115,33 @@ const Incidents = () => {
);
}
+ const handleChangePage = (_, newPage) => {
+ setPage(newPage);
+ };
+
+ const handleChangeRowsPerPage = (event) => {
+ setRowsPerPage(parseInt(event.target.value, 10));
+ setPage(0);
+ };
+ const handleOpenDetails = (id) => {
+ if (!id) return;
+ setSelectedIncidentId(id);
+ setIsDetailsOpen(true);
+ };
+
+ const handleCloseDetails = () => {
+ setIsDetailsOpen(false);
+ setSelectedIncidentId(null);
+ };
+
return (
-
-
- {selectedMonitor === "0"
- ? t("incidentsPageActionResolveAll")
- : t("incidentsPageActionResolveMonitor")}
-
-
-
+
+
+
{
dateRange={dateRange}
setDateRange={setDateRange}
/>
+
+
);
};
-export default Incidents;
+export default Incidents2;
diff --git a/client/src/Pages/Infrastructure/Create/Components/CustomAlertsSection.jsx b/client/src/Pages/Infrastructure/Create/Components/CustomAlertsSection.jsx
index 5a6f5e981..64f82cec2 100644
--- a/client/src/Pages/Infrastructure/Create/Components/CustomAlertsSection.jsx
+++ b/client/src/Pages/Infrastructure/Create/Components/CustomAlertsSection.jsx
@@ -1,7 +1,7 @@
-import ConfigBox from "../../../../Components/ConfigBox";
+import ConfigBox from "@/Components/v1/ConfigBox/index.jsx";
import { Box, Stack, Typography } from "@mui/material";
-import { CustomThreshold } from "../Components/CustomThreshold";
-import { capitalizeFirstLetter } from "../../../../Utils/stringUtils";
+import { CustomThreshold } from "./CustomThreshold/index.jsx";
+import { capitalizeFirstLetter } from "../../../../Utils/stringUtils.js";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
import PropTypes from "prop-types";
diff --git a/client/src/Pages/Infrastructure/Create/Components/CustomThreshold/index.jsx b/client/src/Pages/Infrastructure/Create/Components/CustomThreshold/index.jsx
index 865698f44..d783f7985 100644
--- a/client/src/Pages/Infrastructure/Create/Components/CustomThreshold/index.jsx
+++ b/client/src/Pages/Infrastructure/Create/Components/CustomThreshold/index.jsx
@@ -1,6 +1,6 @@
import { Box, Stack, Typography } from "@mui/material";
-import TextInput from "../../../../../Components/Inputs/TextInput";
-import Checkbox from "../../../../../Components/Inputs/Checkbox";
+import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
+import Checkbox from "@/Components/v1/Inputs/Checkbox/index.jsx";
import { useTheme } from "@emotion/react";
import PropTypes from "prop-types";
diff --git a/client/src/Pages/Infrastructure/Create/Components/DiskSelection.jsx b/client/src/Pages/Infrastructure/Create/Components/DiskSelection.jsx
new file mode 100644
index 000000000..50c4ed980
--- /dev/null
+++ b/client/src/Pages/Infrastructure/Create/Components/DiskSelection.jsx
@@ -0,0 +1,90 @@
+import React from "react";
+import ConfigBox from "@/Components/v1/ConfigBox/index.jsx";
+import { Box, Stack, Typography } from "@mui/material";
+import Checkbox from "@/Components/v1/Inputs/Checkbox/index.jsx";
+import { useTheme } from "@emotion/react";
+import { useTranslation } from "react-i18next";
+import PropTypes from "prop-types";
+
+const DiskSelection = ({ availableDisks, selectedDisks, onChange }) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+
+ const handleDiskChange = (event, mountpoint) => {
+ const isChecked = event.target.checked;
+ let newSelectedDisks = [];
+
+ if (isChecked) {
+ newSelectedDisks = [...selectedDisks, mountpoint];
+ } else {
+ newSelectedDisks = selectedDisks.filter((disk) => disk !== mountpoint);
+ }
+
+ onChange(newSelectedDisks);
+ };
+
+ return (
+
+
+
+ {t("v1.infrastructure.disk_selection_title")}
+
+
+ {t("v1.infrastructure.disk_selection_description")}
+
+
+
+
+ {!availableDisks || availableDisks.length === 0 ? (
+
+ {t("v1.infrastructure.disk_selection_info")}
+
+ ) : (
+ availableDisks.map((disk) => {
+ const identifier = disk.mountpoint || disk.device;
+ return (
+
+
+ handleDiskChange(e, identifier)}
+ />
+
+
+ );
+ })
+ )}
+
+
+ );
+};
+
+DiskSelection.propTypes = {
+ availableDisks: PropTypes.arrayOf(
+ PropTypes.shape({
+ mountpoint: PropTypes.string.isRequired,
+ })
+ ),
+ selectedDisks: PropTypes.arrayOf(PropTypes.string).isRequired,
+ onChange: PropTypes.func.isRequired,
+};
+
+export default DiskSelection;
diff --git a/client/src/Pages/Infrastructure/Create/Components/MonitorActionButtons.jsx b/client/src/Pages/Infrastructure/Create/Components/MonitorActionButtons.jsx
index becda3e3a..2bde5f321 100644
--- a/client/src/Pages/Infrastructure/Create/Components/MonitorActionButtons.jsx
+++ b/client/src/Pages/Infrastructure/Create/Components/MonitorActionButtons.jsx
@@ -2,9 +2,8 @@ import { useState } from "react";
import { Box, Button } from "@mui/material";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
-import PauseCircleOutlineIcon from "@mui/icons-material/PauseCircleOutline";
-import PlayCircleOutlineRoundedIcon from "@mui/icons-material/PlayCircleOutlineRounded";
-import Dialog from "../../../../Components/Dialog";
+import Icon from "@/Components/v1/Icon";
+import Dialog from "@/Components/v1/Dialog/index.jsx";
import PropTypes from "prop-types";
const MonitorActionButtons = ({ monitor, isBusy, handlePause, handleRemove }) => {
@@ -36,12 +35,18 @@ const MonitorActionButtons = ({ monitor, isBusy, handlePause, handleRemove }) =>
>
{monitor?.isActive ? (
<>
-
+
{t("pause")}
>
) : (
<>
-
+
{t("resume")}
>
)}
diff --git a/client/src/Pages/Infrastructure/Create/Components/MonitorStatusHeader.jsx b/client/src/Pages/Infrastructure/Create/Components/MonitorStatusHeader.jsx
index 2ed48bf64..8fa6f4ff1 100644
--- a/client/src/Pages/Infrastructure/Create/Components/MonitorStatusHeader.jsx
+++ b/client/src/Pages/Infrastructure/Create/Components/MonitorStatusHeader.jsx
@@ -1,8 +1,8 @@
import { Box, Stack, Tooltip, Typography } from "@mui/material";
-import { useMonitorUtils } from "../../../../Hooks/useMonitorUtils";
+import { useMonitorUtils } from "../../../../Hooks/useMonitorUtils.js";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
-import PulseDot from "../../../../Components/Animated/PulseDot";
+import PulseDot from "@/Components/v1/Animated/PulseDot.jsx";
import PropTypes from "prop-types";
const MonitorStatusHeader = ({ monitor, infrastructureMonitor }) => {
const theme = useTheme();
diff --git a/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx b/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx
index f0a701517..a38ac9fc6 100644
--- a/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx
+++ b/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx
@@ -17,6 +17,7 @@ const useInfrastructureMonitorForm = () => {
temperature: false,
usage_temperature: "",
secret: "",
+ selectedDisks: [],
});
const onChangeForm = (name, value) => {
@@ -48,6 +49,7 @@ const useInfrastructureMonitorForm = () => {
temperature: gt.temperature !== undefined,
usage_temperature: gt.temperature !== undefined ? gt.temperature.toString() : "",
secret: "",
+ selectedDisks: [],
}));
}, []);
@@ -81,6 +83,7 @@ const useInfrastructureMonitorForm = () => {
? (thresholds.usage_temperature * 100).toString()
: "",
secret: monitor.secret || "",
+ selectedDisks: monitor.selectedDisks || [],
}));
}, []);
return {
diff --git a/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx b/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx
index 9b3ee0d04..04279cac4 100644
--- a/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx
+++ b/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx
@@ -1,4 +1,4 @@
-import { useCreateMonitor, useUpdateMonitor } from "../../../../Hooks/monitorHooks";
+import { useCreateMonitor, useUpdateMonitor } from "../../../../Hooks/monitorHooks.js";
const useInfrastructureSubmit = () => {
const [createMonitor, isCreating] = useCreateMonitor();
const [updateMonitor, isUpdating] = useUpdateMonitor();
@@ -26,11 +26,11 @@ const useInfrastructureSubmit = () => {
...(infrastructureMonitor.disk
? { usage_disk: infrastructureMonitor.usage_disk }
: {}),
- temperature: infrastructureMonitor.temperature,
...(infrastructureMonitor.temperature
? { usage_temperature: infrastructureMonitor.usage_temperature }
: {}),
secret: infrastructureMonitor.secret,
+ selectedDisks: infrastructureMonitor.selectedDisks,
};
return form;
};
@@ -49,6 +49,7 @@ const useInfrastructureSubmit = () => {
usage_disk,
temperature,
usage_temperature,
+ selectedDisks,
...rest
} = form;
@@ -60,11 +61,12 @@ const useInfrastructureSubmit = () => {
};
const finalForm = {
- ...(isCreate ? {} : { _id: monitorId }),
+ ...(isCreate ? {} : { id: monitorId }),
...rest,
description: form.name,
type: "hardware",
notifications: infrastructureMonitor.notifications,
+ selectedDisks,
thresholds,
};
// Handle create or update
diff --git a/client/src/Pages/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx b/client/src/Pages/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx
index 080f441c4..f7665e65f 100644
--- a/client/src/Pages/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx
+++ b/client/src/Pages/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx
@@ -1,6 +1,6 @@
import { useState } from "react";
-import { infrastructureMonitorValidation } from "../../../../Validation/validation";
-import { createToast } from "../../../../Utils/toastUtils";
+import { infrastructureMonitorValidation } from "../../../../Validation/validation.js";
+import { createToast } from "../../../../Utils/toastUtils.jsx";
const useValidateInfrastructureForm = () => {
const [errors, setErrors] = useState({});
diff --git a/client/src/Pages/Infrastructure/Create/index.jsx b/client/src/Pages/Infrastructure/Create/index.jsx
index a9d16c926..0a09105b8 100644
--- a/client/src/Pages/Infrastructure/Create/index.jsx
+++ b/client/src/Pages/Infrastructure/Create/index.jsx
@@ -1,18 +1,20 @@
//Components
-import Breadcrumbs from "../../../Components/Breadcrumbs";
-import ConfigBox from "../../../Components/ConfigBox";
-import FieldWrapper from "../../../Components/Inputs/FieldWrapper";
-import Link from "../../../Components/Link";
-import Select from "../../../Components/Inputs/Select";
-import TextInput from "../../../Components/Inputs/TextInput";
+import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
+import ConfigBox from "@/Components/v1/ConfigBox/index.jsx";
+import FieldWrapper from "@/Components/v1/Inputs/FieldWrapper/index.jsx";
+import Link from "@/Components/v1/Link/index.jsx";
+import Select from "@/Components/v1/Inputs/Select/index.jsx";
+import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
import { Box, Stack, Typography, Button, ButtonGroup } from "@mui/material";
-import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments";
-import MonitorStatusHeader from "./Components/MonitorStatusHeader";
-import MonitorActionButtons from "./Components/MonitorActionButtons";
-import CustomAlertsSection from "./Components/CustomAlertsSection";
+import { HttpAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx";
+import MonitorStatusHeader from "./Components/MonitorStatusHeader.jsx";
+import MonitorActionButtons from "./Components/MonitorActionButtons.jsx";
+import CustomAlertsSection from "./Components/CustomAlertsSection.jsx";
+import DiskSelection from "./Components/DiskSelection.jsx";
// Utils
-import NotificationsConfig from "../../../Components/NotificationConfig";
-import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications";
+import NotificationsConfig from "@/Components/v1/NotificationConfig/index.jsx";
+import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications.js";
+import { networkService } from "../../../Utils/NetworkService.js";
import { useParams } from "react-router-dom";
import { useState, useEffect } from "react";
import { useTheme } from "@emotion/react";
@@ -20,12 +22,12 @@ import { useTranslation } from "react-i18next";
import {
useDeleteMonitor,
useFetchGlobalSettings,
- useFetchHardwareMonitorById,
+ useFetchMonitorById,
usePauseMonitor,
-} from "../../../Hooks/monitorHooks";
-import useInfrastructureMonitorForm from "./hooks/useInfrastructureMonitorForm";
-import useValidateInfrastructureForm from "./hooks/useValidateInfrastructureForm";
-import useInfrastructureSubmit from "./hooks/useInfrastructureSubmit";
+} from "@/Hooks/monitorHooks.js";
+import useInfrastructureMonitorForm from "./hooks/useInfrastructureMonitorForm.jsx";
+import useValidateInfrastructureForm from "./hooks/useValidateInfrastructureForm.jsx";
+import useInfrastructureSubmit from "./hooks/useInfrastructureSubmit.jsx";
const CreateInfrastructureMonitor = () => {
const { monitorId } = useParams();
@@ -35,13 +37,16 @@ const CreateInfrastructureMonitor = () => {
const { t } = useTranslation();
// State
+ const [monitor, setMonitor] = useState(null);
const [https, setHttps] = useState(false);
const [updateTrigger, setUpdateTrigger] = useState(false);
+ const [availableDisks, setAvailableDisks] = useState([]);
// Fetch monitor details if editing
- const [monitor, isLoading] = useFetchHardwareMonitorById({
+ const [isLoading] = useFetchMonitorById({
monitorId,
- updateTrigger,
+ setMonitor,
+ updateTrigger: true,
});
const [deleteMonitor, isDeleting] = useDeleteMonitor();
const [globalSettings, globalSettingsLoading] = useFetchGlobalSettings();
@@ -85,6 +90,31 @@ const CreateInfrastructureMonitor = () => {
} else if (monitor) {
setHttps(monitor.url.startsWith("https"));
initializeInfrastructureMonitorForUpdate(monitor);
+ const fetchLastCheck = async () => {
+ try {
+ const { stats } = monitor ?? {};
+ let latestCheck = stats?.aggregateData?.latestCheck;
+ let disks = latestCheck?.disk || [];
+
+ if (disks.length > 0) {
+ setAvailableDisks(disks);
+ return;
+ }
+
+ const response = await networkService.getChecksByMonitor({
+ monitorId,
+ dateRange: "all",
+ rowsPerPage: 1,
+ sortOrder: "desc",
+ });
+ disks = response?.data?.data?.checks?.[0]?.disk || [];
+ setAvailableDisks(disks);
+ } catch (error) {
+ setAvailableDisks([]);
+ }
+ };
+
+ fetchLastCheck();
}
}, [
isCreate,
@@ -99,7 +129,9 @@ const CreateInfrastructureMonitor = () => {
const onSubmit = async (event) => {
event.preventDefault();
const form = buildForm(infrastructureMonitor, https);
- const error = validateForm(form);
+ // When editing, exclude URL from validation since it's disabled and can't be changed
+ const formToValidate = isCreate ? form : { ...form, url: monitor.url };
+ const error = validateForm(formToValidate);
if (error) {
return;
}
@@ -175,14 +207,14 @@ const CreateInfrastructureMonitor = () => {
<>>
)}
- {!isCreate && (
+ {!isCreate && monitor && (
)}
- {!isCreate && (
+ {!isCreate && monitor && (
{
infrastructureMonitor={infrastructureMonitor}
handleCheckboxChange={handleCheckboxChange}
/>
+
+ {monitorId && (
+
+ onChangeForm("selectedDisks", newSelectedDisks)
+ }
+ />
+ )}
+
{
const theme = useTheme();
const { getDimensions } = useHardwareUtils();
diff --git a/client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/index.jsx b/client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/index.jsx
index 0eb3aa36d..3465683d9 100644
--- a/client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/index.jsx
+++ b/client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/index.jsx
@@ -1,7 +1,7 @@
// Components
import { Stack } from "@mui/material";
-import InfraAreaChart from "./InfraAreaChart";
-import SkeletonLayout from "./skeleton";
+import InfraAreaChart from "./InfraAreaChart.jsx";
+import SkeletonLayout from "./skeleton.jsx";
// Utils
import {
@@ -9,9 +9,9 @@ import {
TzTick,
InfrastructureTooltip,
TemperatureTooltip,
-} from "../../../../../Components/Charts/Utils/chartUtils";
+} from "@/Components/v1/Charts/Utils/chartUtils.jsx";
import { useTheme } from "@emotion/react";
-import { useHardwareUtils } from "../../Hooks/useHardwareUtils";
+import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx";
import { useTranslation } from "react-i18next";
const AreaChartBoxes = ({ shouldRender, monitor, dateRange }) => {
const theme = useTheme();
diff --git a/client/src/Pages/Infrastructure/Details/Components/BaseContainer/index.jsx b/client/src/Pages/Infrastructure/Details/Components/BaseContainer/index.jsx
index 246769751..9c0fe1574 100644
--- a/client/src/Pages/Infrastructure/Details/Components/BaseContainer/index.jsx
+++ b/client/src/Pages/Infrastructure/Details/Components/BaseContainer/index.jsx
@@ -11,10 +11,10 @@ import { Box } from "@mui/material";
// Utils
import { useTheme } from "@emotion/react";
-import { useHardwareUtils } from "../../Hooks/useHardwareUtils";
+import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx";
import PropTypes from "prop-types";
-const BaseContainer = ({ children, sx = {} }) => {
+const BaseContainer = ({ children, sx = {}, shouldExpand = false }) => {
const theme = useTheme();
const { getDimensions } = useHardwareUtils();
return (
@@ -22,7 +22,7 @@ const BaseContainer = ({ children, sx = {} }) => {
sx={{
padding: `${theme.spacing(getDimensions().baseBoxPaddingVertical)} ${theme.spacing(getDimensions().baseBoxPaddingHorizontal)}`,
minWidth: 200,
- width: 225,
+ width: shouldExpand ? "100%" : 225,
backgroundColor: theme.palette.primary.main,
border: 1,
borderStyle: "solid",
@@ -38,6 +38,7 @@ const BaseContainer = ({ children, sx = {} }) => {
BaseContainer.propTypes = {
children: PropTypes.node.isRequired,
sx: PropTypes.object,
+ shouldExpand: PropTypes.bool,
};
export default BaseContainer;
diff --git a/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx b/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx
index 70d36bd00..c762eeb32 100644
--- a/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx
+++ b/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx
@@ -1,26 +1,42 @@
// Components
-import CustomGauge from "../../../../../Components/Charts/CustomGauge";
-import BaseContainer from "../BaseContainer";
+import CustomGauge from "@/Components/v1/Charts/CustomGauge/index.jsx";
+import BaseContainer from "../BaseContainer/index.jsx";
import { Stack, Typography, Box } from "@mui/material";
// Utils
import { useTheme } from "@emotion/react";
import PropTypes from "prop-types";
-const Gauge = ({ value, heading, metricOne, valueOne, metricTwo, valueTwo }) => {
+const Gauge = ({
+ value,
+ heading,
+ metricOne,
+ valueOne,
+ metricTwo,
+ valueTwo,
+ metricThree,
+ valueThree,
+ metricFour,
+ valueFour,
+ shouldExpand = false,
+}) => {
const theme = useTheme();
const valueStyle = {
borderRadius: theme.spacing(2),
backgroundColor: theme.palette.tertiary.main,
- width: "40%",
+ minWidth: "40%",
+ maxWidth: "60%",
mb: theme.spacing(2),
mt: theme.spacing(2),
pr: theme.spacing(2),
textAlign: "right",
+ overflow: "hidden",
+ textOverflow: "ellipsis",
+ whiteSpace: "nowrap",
};
return (
-
+
{metricTwo}
{valueTwo}
+
+ {metricThree}
+ {valueThree}
+
+
+ {metricFour}
+ {valueFour}
+
@@ -83,6 +117,11 @@ Gauge.propTypes = {
valueOne: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
metricTwo: PropTypes.string,
valueTwo: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
+ metricThree: PropTypes.string,
+ valueThree: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
+ metricFour: PropTypes.string,
+ valueFour: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
+ shouldExpand: PropTypes.bool,
};
export default Gauge;
diff --git a/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/index.jsx b/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/index.jsx
index 3c1c68f8b..7693fd32e 100644
--- a/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/index.jsx
+++ b/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/index.jsx
@@ -1,16 +1,17 @@
// Components
-import { Stack } from "@mui/material";
-import Gauge from "./Gauge";
-import SkeletonLayout from "./skeleton";
+import { Box } from "@mui/material";
+import Gauge from "./Gauge.jsx";
+import SkeletonLayout from "./skeleton.jsx";
import PropTypes from "prop-types";
// Utils
-import { useHardwareUtils } from "../../Hooks/useHardwareUtils";
+import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
const Gauges = ({ isLoading = false, monitor }) => {
- const { decimalToPercentage, formatBytes } = useHardwareUtils();
+ const { decimalToPercentage, formatBytes, formatDeviceName, formatMountpoint } =
+ useHardwareUtils();
const theme = useTheme();
const { t } = useTranslation();
@@ -46,23 +47,41 @@ const Gauges = ({ isLoading = false, monitor }) => {
metricTwo: t("frequency"),
valueTwo: `${(cpuFrequency / 1000).toFixed(2)} Ghz`,
},
- ...(latestCheck?.disk ?? []).map((disk, idx) => ({
- type: "disk",
- diskIndex: idx,
- value: decimalToPercentage(disk.usage_percent),
- heading: `Disk${idx} usage`,
- metricOne: t("used"),
- valueOne: formatBytes(disk.total_bytes - disk.free_bytes, true),
- metricTwo: t("total"),
- valueTwo: formatBytes(disk.total_bytes, true),
- })),
+ ...(latestCheck?.disk ?? [])
+ .filter((disk) => {
+ if (!monitor?.selectedDisks || monitor.selectedDisks.length === 0) {
+ return true;
+ }
+ return monitor.selectedDisks.includes(disk.mountpoint || disk.device);
+ })
+ .map((disk, idx) => ({
+ type: "disk",
+ diskIndex: idx,
+ value: decimalToPercentage(disk.usage_percent),
+ heading: `Disk${idx} usage`,
+ metricOne: t("used"),
+ valueOne: formatBytes(disk.total_bytes - disk.free_bytes, true),
+ metricTwo: t("total"),
+ valueTwo: formatBytes(disk.total_bytes, true),
+ metricThree: t("device"),
+ valueThree: formatDeviceName(disk.device),
+ metricFour: t("mountedOn"),
+ valueFour: formatMountpoint(disk.mountpoint),
+ })),
];
+ // Only expand gauges to fill row when there are 4 or more
+ const shouldExpand = gauges.length >= 4;
+
return (
-
{gauges.map((gauge) => {
return (
@@ -74,10 +93,15 @@ const Gauges = ({ isLoading = false, monitor }) => {
valueOne={gauge.valueOne}
metricTwo={gauge.metricTwo}
valueTwo={gauge.valueTwo}
+ metricThree={gauge.metricThree}
+ valueThree={gauge.valueThree}
+ metricFour={gauge.metricFour}
+ valueFour={gauge.valueFour}
+ shouldExpand={shouldExpand}
/>
);
})}
-
+
);
};
diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx
index 9157a3432..d9f93311c 100644
--- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx
+++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx
@@ -1,15 +1,15 @@
import PropTypes from "prop-types";
import { Stack, Typography } from "@mui/material";
-import InfraAreaChart from "../../../../../Pages/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart";
+import InfraAreaChart from "../AreaChartBoxes/InfraAreaChart.jsx";
import {
TzTick,
InfrastructureTooltip,
NetworkTick,
-} from "../../../../../Components/Charts/Utils/chartUtils";
+} from "@/Components/v1/Charts/Utils/chartUtils.jsx";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
-import { useHardwareUtils } from "../../Hooks/useHardwareUtils";
+import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx";
const NetworkCharts = ({ ethernetData, dateRange }) => {
const theme = useTheme();
diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx
index 98ba90dae..0747dfcd1 100644
--- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx
+++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx
@@ -1,9 +1,9 @@
import PropTypes from "prop-types";
-import StatusBoxes from "../../../../../Components/StatusBoxes";
-import StatBox from "../../../../../Components/StatBox";
+import StatusBoxes from "@/Components/v1/StatusBoxes/index.jsx";
+import StatBox from "@/Components/v1/StatBox/index.jsx";
import { Typography } from "@mui/material";
import { useTranslation } from "react-i18next";
-import { useHardwareUtils } from "../../Hooks/useHardwareUtils";
+import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx";
function formatNumber(num) {
return num != null ? num.toLocaleString() : "0";
diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx
index 41d2b0a87..78db333e6 100644
--- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx
+++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx
@@ -3,10 +3,10 @@ import { useState, useEffect } from "react";
import { Box } from "@mui/material";
import { useTranslation } from "react-i18next";
import { useTheme } from "@emotion/react";
-import Select from "../../../../../Components/Inputs/Select";
-import NetworkStatBoxes from "./NetworkStatBoxes";
-import NetworkCharts from "./NetworkCharts";
-import MonitorTimeFrameHeader from "../../../../../Components/MonitorTimeFrameHeader";
+import Select from "@/Components/v1/Inputs/Select/index.jsx";
+import NetworkStatBoxes from "./NetworkStatBoxes.jsx";
+import NetworkCharts from "./NetworkCharts.jsx";
+import MonitorTimeFrameHeader from "@/Components/v1/MonitorTimeFrameHeader/index.jsx";
const getAvailableInterfaces = (net) => {
return (net || []).map((iface) => iface.name).filter(Boolean);
diff --git a/client/src/Pages/Infrastructure/Details/Components/StatusBoxes/index.jsx b/client/src/Pages/Infrastructure/Details/Components/StatusBoxes/index.jsx
index 6c11e7c82..ef91709cf 100644
--- a/client/src/Pages/Infrastructure/Details/Components/StatusBoxes/index.jsx
+++ b/client/src/Pages/Infrastructure/Details/Components/StatusBoxes/index.jsx
@@ -1,11 +1,11 @@
// Components
import { Stack, Typography } from "@mui/material";
-import StatusBoxes from "../../../../../Components/StatusBoxes";
-import StatBox from "../../../../../Components/StatBox";
+import StatusBoxes from "@/Components/v1/StatusBoxes/index.jsx";
+import StatBox from "@/Components/v1/StatBox/index.jsx";
//Utils
-import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils";
-import { useHardwareUtils } from "../../Hooks/useHardwareUtils";
+import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils.js";
+import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx";
import { useTranslation } from "react-i18next";
const InfraStatBoxes = ({ shouldRender, monitor }) => {
diff --git a/client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx b/client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx
index 8edb6fb87..8938847a4 100644
--- a/client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx
+++ b/client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx
@@ -1,4 +1,4 @@
-import { Typography } from "@mui/material";
+import { Typography, Tooltip } from "@mui/material";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
@@ -120,6 +120,85 @@ const useHardwareUtils = () => {
}
};
+ const formatDeviceName = (device) => {
+ const deviceStr = String(device || "");
+
+ // Show full device path
+ return (
+
+
+ {deviceStr}
+
+
+ );
+ };
+
+ const formatMountpoint = (mountpoint) => {
+ const mountpointStr = String(mountpoint || "");
+
+ if (!mountpointStr) {
+ return (
+
+
+ N/A
+
+
+ );
+ }
+
+ // Show full mountpoint path
+ return (
+
+
+ {mountpointStr}
+
+
+ );
+ };
+
/**
* Converts a decimal value to a percentage
*
@@ -178,6 +257,8 @@ const useHardwareUtils = () => {
return {
formatBytes,
+ formatDeviceName,
+ formatMountpoint,
decimalToPercentage,
buildTemps,
getDimensions,
diff --git a/client/src/Pages/Infrastructure/Details/index.jsx b/client/src/Pages/Infrastructure/Details/index.jsx
index a16f3906b..62a5c2642 100644
--- a/client/src/Pages/Infrastructure/Details/index.jsx
+++ b/client/src/Pages/Infrastructure/Details/index.jsx
@@ -1,20 +1,20 @@
// Components
import { Stack, Typography, Tab } from "@mui/material";
-import Breadcrumbs from "../../../Components/Breadcrumbs";
-import MonitorDetailsControlHeader from "../../../Components/MonitorDetailsControlHeader";
-import MonitorTimeFrameHeader from "../../../Components/MonitorTimeFrameHeader";
-import StatusBoxes from "./Components/StatusBoxes";
-import GaugeBoxes from "./Components/GaugeBoxes";
-import AreaChartBoxes from "./Components/AreaChartBoxes";
-import GenericFallback from "../../../Components/GenericFallback";
-import NetworkStats from "./Components/NetworkStats";
-import CustomTabList from "../../../Components/Tab";
+import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
+import MonitorDetailsControlHeader from "@/Components/v1/MonitorDetailsControlHeader/index.jsx";
+import MonitorTimeFrameHeader from "@/Components/v1/MonitorTimeFrameHeader/index.jsx";
+import StatusBoxes from "./Components/StatusBoxes/index.jsx";
+import GaugeBoxes from "./Components/GaugeBoxes/index.jsx";
+import AreaChartBoxes from "./Components/AreaChartBoxes/index.jsx";
+import GenericFallback from "@/Components/v1/GenericFallback/index.jsx";
+import NetworkStats from "./Components/NetworkStats/index.jsx";
+import CustomTabList from "@/Components/v1/Tab/index.jsx";
import TabContext from "@mui/lab/TabContext";
// Utils
import { useTheme } from "@emotion/react";
-import { useIsAdmin } from "../../../Hooks/useIsAdmin";
-import { useFetchHardwareMonitorById } from "../../../Hooks/monitorHooks";
+import { useIsAdmin } from "@/Hooks/useIsAdmin.js";
+import { useFetchHardwareMonitorById } from "../../../Hooks/monitorHooks.js";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useParams } from "react-router-dom";
diff --git a/client/src/Pages/Infrastructure/Monitors/Components/Filters/index.jsx b/client/src/Pages/Infrastructure/Monitors/Components/Filters/index.jsx
index 3c2511c2f..01e56c68a 100644
--- a/client/src/Pages/Infrastructure/Monitors/Components/Filters/index.jsx
+++ b/client/src/Pages/Infrastructure/Monitors/Components/Filters/index.jsx
@@ -1,9 +1,9 @@
import { useTheme } from "@emotion/react";
import PropTypes from "prop-types";
-import FilterHeader from "../../../../../Components/FilterHeader";
+import FilterHeader from "@/Components/v1/FilterHeader/index.jsx";
import { useMemo } from "react";
import { Box, Button } from "@mui/material";
-import ClearIcon from "@mui/icons-material/Clear";
+import Icon from "@/Components/v1/Icon";
import { useTranslation } from "react-i18next";
/**
@@ -68,7 +68,12 @@ const Filter = ({
color={theme.palette.primary.contrastText}
onClick={handleReset}
variant="contained"
- endIcon={ }
+ endIcon={
+
+ }
sx={{
visibility: isFilterActive ? "visible" : "hidden",
}}
diff --git a/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx b/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx
index 7ee603ef7..292eb9714 100644
--- a/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx
+++ b/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx
@@ -1,18 +1,18 @@
// Components
import { Box } from "@mui/material";
-import DataTable from "../../../../../Components/Table";
-import Host from "../../../../../Components/Host";
-import { StatusLabel } from "../../../../../Components/Label";
+import DataTable from "@/Components/v1/Table/index.jsx";
+import Host from "@/Components/v1/Host/index.jsx";
+import { StatusLabel } from "@/Components/v1/Label/index.jsx";
import { Stack } from "@mui/material";
-import { InfrastructureMenu } from "../MonitorsTableMenu";
-import LoadingSpinner from "../../../../Uptime/Monitors/Components/LoadingSpinner";
+import { InfrastructureMenu } from "../MonitorsTableMenu/index.jsx";
+import LoadingSpinner from "../../../../Uptime/Monitors/Components/LoadingSpinner/index.jsx";
// Assets
-import CPUChipIcon from "../../../../../assets/icons/cpu-chip.svg?react";
-import CustomGauge from "../../../../../Components/Charts/CustomGauge";
+import Icon from "@/Components/v1/Icon";
+import CustomGauge from "@/Components/v1/Charts/CustomGauge/index.jsx";
// Utils
import { useTheme } from "@emotion/react";
-import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils";
+import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils.js";
import { useNavigate } from "react-router-dom";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
@@ -67,9 +67,9 @@ const MonitorsTable = ({
alignItems={"center"}
gap=".25rem"
>
-
{row.processor}
@@ -115,7 +115,7 @@ const MonitorsTable = ({
: theme.palette.success.main;
return {
- id: monitor._id,
+ id: monitor.id,
name: monitor.name,
url: monitor.url,
processor,
diff --git a/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx b/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx
index 147f1c297..50af0aaa2 100644
--- a/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx
+++ b/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx
@@ -3,13 +3,13 @@
import { useRef, useState } from "react";
import { useTheme } from "@emotion/react";
import { useNavigate } from "react-router-dom";
-import { createToast } from "../../../../../Utils/toastUtils";
+import { createToast } from "@/Utils/toastUtils.jsx";
import { IconButton, Menu, MenuItem } from "@mui/material";
-import Settings from "../../../../../assets/icons/settings-bold.svg?react";
+import Icon from "@/Components/v1/Icon";
import PropTypes from "prop-types";
-import Dialog from "../../../../../Components/Dialog";
-import { networkService } from "../../../../../Utils/NetworkService.js";
-import { usePauseMonitor } from "../../../../../Hooks/monitorHooks";
+import Dialog from "@/Components/v1/Dialog/index.jsx";
+import { networkService } from "@/Utils/NetworkService.js";
+import { usePauseMonitor } from "@/Hooks/monitorHooks.js";
import { useTranslation } from "react-i18next";
/**
@@ -100,7 +100,10 @@ const InfrastructureMenu = ({ monitor, isAdmin, updateCallback }) => {
}}
ref={anchor}
>
-
+
{
const field = toFilterStatus !== undefined ? "status" : undefined;
- const [monitors, summary, isLoading, networkError] = useFetchMonitorsByTeamId({
- limit: 1,
+ const [summary, monitors, count, isLoading, networkError] = useFetchMonitorsWithChecks({
types: TYPES,
- page,
+ limit: 1,
+ page: page,
field: field,
filter: toFilterStatus ?? search,
- rowsPerPage,
- updateTrigger,
+ rowsPerPage: rowsPerPage,
+ monitorUpdateTrigger: updateTrigger,
});
return (
@@ -99,7 +100,7 @@ const InfrastructureMonitors = () => {
{
isSearching={isSearching}
/>
{
const theme = useTheme()
return(
-
{children}
@@ -99,7 +102,7 @@ const Gauges = ({ diagnostics, isLoading }) => {
return (
{
diff --git a/client/src/Pages/Logs/Diagnostics/index.jsx b/client/src/Pages/Logs/Diagnostics/index.jsx
index 72ee47f48..194e13821 100644
--- a/client/src/Pages/Logs/Diagnostics/index.jsx
+++ b/client/src/Pages/Logs/Diagnostics/index.jsx
@@ -1,14 +1,14 @@
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
-import Gauges from "./components/gauges";
+import Gauges from "./components/gauges/index.jsx";
import Button from "@mui/material/Button";
-import StatBox from "../../../Components/StatBox";
-import StatusBoxes from "../../../Components/StatusBoxes";
+import StatBox from "@/Components/v1/StatBox/index.jsx";
+import StatusBoxes from "@/Components/v1/StatusBoxes/index.jsx";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
-import { useFetchDiagnostics } from "../../../Hooks/logHooks";
-import { getHumanReadableDuration } from "../../../Utils/timeUtils";
-import { formatBytes, getPercentage } from "./utils/utils";
+import { useFetchDiagnostics } from "../../../Hooks/logHooks.js";
+import { getHumanReadableDuration } from "../../../Utils/timeUtilsLegacy.js";
+import { formatBytes, getPercentage } from "./utils/utils.js";
const Diagnostics = () => {
// Local state
@@ -26,12 +26,12 @@ const Diagnostics = () => {
status="up"
heading={t("status")}
subHeading={
- error
+ error
? t("logsPage.logLevelSelect.values.error")
- : isLoading
+ : isLoading
? t("commonSaving")
- : diagnostics
- ? t("diagnosticsPage.diagnosticDescription")
+ : diagnostics
+ ? t("diagnosticsPage.diagnosticDescription")
: t("general.noOptionsFound", { unit: "data" })
}
/>
diff --git a/client/src/Pages/Logs/Logs/index.jsx b/client/src/Pages/Logs/Logs/index.jsx
index b44b3fd1a..d0319a387 100644
--- a/client/src/Pages/Logs/Logs/index.jsx
+++ b/client/src/Pages/Logs/Logs/index.jsx
@@ -1,15 +1,18 @@
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
-import Select from "../../../Components/Inputs/Select";
+import Select from "@/Components/v1/Inputs/Select/index.jsx";
import Typography from "@mui/material/Typography";
-import Divider from "@mui/material/Divider";
+import DataTable from "@/Components/v1/Table/index.jsx";
+import Pagination from "@/Components/v1/Table/TablePagination/index.jsx";
-import { useFetchLogs } from "../../../Hooks/logHooks";
+import { useFetchLogs } from "../../../Hooks/logHooks.js";
import { useTheme } from "@emotion/react";
import { useState } from "react";
import { useTranslation } from "react-i18next";
-const formatLog = (theme, log, idx) => {
+const LevelBadge = ({ level }) => {
+ const theme = useTheme();
+
const levelColors = {
info: theme.palette.success.main,
warn: theme.palette.warning.main,
@@ -17,31 +20,38 @@ const formatLog = (theme, log, idx) => {
debug: theme.palette.accent.main,
};
- const color = levelColors[log.level] || theme.palette.primary.contrastText;
+ const color = levelColors[level] || theme.palette.primary.contrastText;
return (
-
- [{log.timestamp}] {" "}
- {log.level.toUpperCase()}
- {": "}
- {`(${log.service})`}
- {`(${log.method})`}
- {": "}
- {log.message}
-
-
+
+ {level}
+
);
};
-const Logs = () => {
- // Local state
- const [logLevel, setLogLevel] = useState("all");
+const formatTimestamp = (timestamp) => {
+ if (!timestamp) return "-";
+ const date = new Date(timestamp);
+ return date.toLocaleString();
+};
+
+const Logs = () => {
+ const [logLevel, setLogLevel] = useState("all");
+ const [page, setPage] = useState(0);
+ const [rowsPerPage, setRowsPerPage] = useState(15);
- // Hooks
const theme = useTheme();
const { t } = useTranslation();
const [logs, isLoading, error] = useFetchLogs();
- // Setup
+
const LOG_LEVELS = [
{ _id: "all", name: t("logsPage.logLevelSelect.values.all") },
{ _id: "info", name: t("logsPage.logLevelSelect.values.info") },
@@ -49,53 +59,116 @@ const Logs = () => {
{ _id: "error", name: t("logsPage.logLevelSelect.values.error") },
{ _id: "debug", name: t("logsPage.logLevelSelect.values.debug") },
];
+
+ const headers = [
+ {
+ id: "timestamp",
+ content: t("logsPage.table.timestamp"),
+ render: (row) => (
+
+ {formatTimestamp(row.timestamp)}
+
+ ),
+ },
+ {
+ id: "level",
+ content: t("logsPage.table.level"),
+ render: (row) => ,
+ },
+ {
+ id: "service",
+ content: t("logsPage.table.service"),
+ render: (row) => (
+ {row.service || "-"}
+ ),
+ },
+ {
+ id: "method",
+ content: t("logsPage.table.method"),
+ render: (row) => (
+ {row.method || "-"}
+ ),
+ },
+ {
+ id: "message",
+ content: t("logsPage.table.message"),
+ render: (row) => (
+
+ {row.message || "-"}
+
+ ),
+ },
+ ];
+
+ const filteredLogs = logs
+ ?.filter((log) => {
+ if (logLevel === "all") return true;
+ return log.level === logLevel;
+ })
+ .reverse()
+ .map((log, idx) => ({ ...log, id: idx }));
+
+ const paginatedLogs = filteredLogs?.slice(
+ page * rowsPerPage,
+ page * rowsPerPage + rowsPerPage
+ );
+
+ const handleChangePage = (event, newPage) => {
+ setPage(newPage);
+ };
+
+ const handleChangeRowsPerPage = (event) => {
+ setRowsPerPage(parseInt(event.target.value, 10));
+ setPage(0);
+ };
+
+ const handleLogLevelChange = (e) => {
+ setLogLevel(e.target.value);
+ setPage(0);
+ };
+
return (
-
{t("logsPage.logLevelSelect.title")}
{
- setLogLevel(e.target.value);
- }}
+ onChange={handleLogLevelChange}
/>
-
-
- {logs
- ?.filter((log) => {
- if (logLevel === "all") return true;
- return log.level === logLevel;
- })
- .reverse()
- .map((log, idx) => formatLog(theme, log, idx))}
-
-
+ />
+
+ {filteredLogs?.length > 0 && (
+
+ )}
);
};
diff --git a/client/src/Pages/Logs/Queue/components/FailedJobTable/index.jsx b/client/src/Pages/Logs/Queue/components/FailedJobTable/index.jsx
index 94fc2f5c3..49f6317bf 100644
--- a/client/src/Pages/Logs/Queue/components/FailedJobTable/index.jsx
+++ b/client/src/Pages/Logs/Queue/components/FailedJobTable/index.jsx
@@ -1,10 +1,10 @@
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
-import DataTable from "../../../../../Components/Table";
+import DataTable from "@/Components/v1/Table/index.jsx";
import { useNavigate } from "react-router-dom";
import { useTheme } from "@emotion/react";
-import { TypeToPathMap } from "../../../../../Utils/monitorUtils";
+import { TypeToPathMap } from "../../../../../Utils/monitorUtilsLegacy.js";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
diff --git a/client/src/Pages/Logs/Queue/components/JobTable/index.jsx b/client/src/Pages/Logs/Queue/components/JobTable/index.jsx
index fab85ec5d..33636b6f5 100644
--- a/client/src/Pages/Logs/Queue/components/JobTable/index.jsx
+++ b/client/src/Pages/Logs/Queue/components/JobTable/index.jsx
@@ -1,13 +1,13 @@
import Stack from "@mui/material/Stack";
-import DataTable from "../../../../../Components/Table";
+import DataTable from "@/Components/v1/Table/index.jsx";
import Typography from "@mui/material/Typography";
// Utils
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { useNavigate } from "react-router-dom";
-import { TypeToPathMap } from "../../../../../Utils/monitorUtils";
+import { TypeToPathMap } from "../../../../../Utils/monitorUtilsLegacy.js";
import { useTranslation } from "react-i18next";
-import { createHeaderFactory } from "../../../../../Components/Table/TableUtils";
+import { createHeaderFactory } from "@/Components/v1/Table/TableUtils.js";
const JobTable = ({ jobs = [] }) => {
const theme = useTheme();
diff --git a/client/src/Pages/Logs/Queue/components/Metrics/index.jsx b/client/src/Pages/Logs/Queue/components/Metrics/index.jsx
index b0112fa99..c4f4389c3 100644
--- a/client/src/Pages/Logs/Queue/components/Metrics/index.jsx
+++ b/client/src/Pages/Logs/Queue/components/Metrics/index.jsx
@@ -1,6 +1,6 @@
import Stack from "@mui/material/Stack";
-import StatBox from "../../../../../Components/StatBox";
-import StatusBoxes from "../../../../../Components/StatusBoxes";
+import StatBox from "@/Components/v1/StatBox/index.jsx";
+import StatusBoxes from "@/Components/v1/StatusBoxes/index.jsx";
import { useTranslation } from "react-i18next";
import { useTheme } from "@emotion/react";
diff --git a/client/src/Pages/Logs/Queue/components/MetricsTable/index.jsx b/client/src/Pages/Logs/Queue/components/MetricsTable/index.jsx
index 204b90035..d96f37a20 100644
--- a/client/src/Pages/Logs/Queue/components/MetricsTable/index.jsx
+++ b/client/src/Pages/Logs/Queue/components/MetricsTable/index.jsx
@@ -1,6 +1,6 @@
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
-import DataTable from "../../../../../Components/Table";
+import DataTable from "@/Components/v1/Table/index.jsx";
// Utils
import { useTranslation } from "react-i18next";
diff --git a/client/src/Pages/Logs/Queue/index.jsx b/client/src/Pages/Logs/Queue/index.jsx
index fde9073fc..b2d0dc685 100644
--- a/client/src/Pages/Logs/Queue/index.jsx
+++ b/client/src/Pages/Logs/Queue/index.jsx
@@ -1,8 +1,8 @@
// Components
import Stack from "@mui/material/Stack";
-import JobTable from "./components/JobTable";
-import Metrics from "./components/Metrics";
-import FailedJobTable from "./components/FailedJobTable";
+import JobTable from "./components/JobTable/index.jsx";
+import Metrics from "./components/Metrics/index.jsx";
+import FailedJobTable from "./components/FailedJobTable/index.jsx";
import ButtonGroup from "@mui/material/ButtonGroup";
import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";
@@ -10,7 +10,7 @@ import Divider from "@mui/material/Divider";
// Utils
import { useState } from "react";
-import { useFetchQueueData, useFlushQueue } from "../../../Hooks/logHooks";
+import { useFetchQueueData, useFlushQueue } from "../../../Hooks/logHooks.js";
import { useTranslation } from "react-i18next";
import { useTheme } from "@emotion/react";
diff --git a/client/src/Pages/Logs/index.jsx b/client/src/Pages/Logs/index.jsx
index 79c89f282..e5bdf623d 100644
--- a/client/src/Pages/Logs/index.jsx
+++ b/client/src/Pages/Logs/index.jsx
@@ -1,10 +1,10 @@
import Stack from "@mui/material/Stack";
-import Breadcrumbs from "../../Components/Breadcrumbs";
+import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
import Tabs from "@mui/material/Tabs";
import Tab from "@mui/material/Tab";
-import Queue from "./Queue";
-import LogsComponent from "./Logs";
-import Diagnostics from "./Diagnostics";
+import Queue from "./Queue/index.jsx";
+import LogsComponent from "./Logs/index.jsx";
+import Diagnostics from "./Diagnostics/index.jsx";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
diff --git a/client/src/Pages/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx b/client/src/Pages/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx
index 1627ef3d5..8d4c0ae53 100644
--- a/client/src/Pages/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx
+++ b/client/src/Pages/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx
@@ -1,4 +1,4 @@
-import Select from "../../../../../Components/Inputs/Select";
+import Select from "@/Components/v1/Inputs/Select/index.jsx";
import PropTypes from "prop-types";
const ConfigSelect = ({ configSelection, valueSelect, onChange, ...props }) => {
const getValueById = (config, id) => {
diff --git a/client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx b/client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx
index c3a4681a1..2bf7a991f 100644
--- a/client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx
+++ b/client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx
@@ -1,6 +1,6 @@
// Components
import { Stack, Typography } from "@mui/material";
-import DeleteIcon from "@mui/icons-material/Delete";
+import Icon from "@/Components/v1/Icon";
import { useTheme } from "@emotion/react";
import PropTypes from "prop-types";
@@ -14,8 +14,10 @@ const MonitorListItem = ({ monitor, onDelete }) => {
width="100%"
>
{monitor.name}
- onDelete(monitor)}
/>
@@ -24,7 +26,7 @@ const MonitorListItem = ({ monitor, onDelete }) => {
MonitorListItem.propTypes = {
monitor: PropTypes.shape({
- _id: PropTypes.string.isRequired,
+ id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
}).isRequired,
onDelete: PropTypes.func.isRequired,
@@ -33,7 +35,7 @@ MonitorListItem.propTypes = {
const MonitorList = ({ selectedMonitors, setSelectedMonitors }) => {
const onDelete = (monitorToDelete) => {
const newMonitors = selectedMonitors.filter(
- (monitor) => monitor._id !== monitorToDelete._id
+ (monitor) => monitor.id !== monitorToDelete.id
);
setSelectedMonitors(newMonitors);
};
@@ -47,7 +49,7 @@ const MonitorList = ({ selectedMonitors, setSelectedMonitors }) => {
>
{selectedMonitors?.map((monitor) => (
@@ -59,7 +61,7 @@ const MonitorList = ({ selectedMonitors, setSelectedMonitors }) => {
MonitorList.propTypes = {
selectedMonitors: PropTypes.arrayOf(
PropTypes.shape({
- _id: PropTypes.string.isRequired,
+ id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
})
).isRequired,
diff --git a/client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx b/client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx
index e792734ff..14920d81c 100644
--- a/client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx
+++ b/client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx
@@ -1,7 +1,7 @@
import PropTypes from "prop-types";
-import Search from "../../../../../Components/Inputs/Search";
+import Search from "@/Components/v1/Inputs/Search/index.jsx";
import { useState } from "react";
-import ConfigRow from "../../../../../Components/ConfigRow";
+import ConfigRow from "@/Components/v1/ConfigRow/index.jsx";
/**
* MonitorsConfig is a component that allows users to select and apply a maintenance window to a list of monitors.
diff --git a/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx b/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx
index 84333a14f..a5e853160 100644
--- a/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx
+++ b/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx
@@ -1,4 +1,4 @@
-import { networkService } from "../../../../main";
+import { networkService } from "../../../../main.jsx";
import dayjs from "dayjs";
import {
MS_PER_SECOND,
@@ -6,7 +6,7 @@ import {
MS_PER_HOUR,
MS_PER_DAY,
MS_PER_WEEK,
-} from "../../../../Utils/timeUtils";
+} from "../../../../Utils/timeUtilsLegacy.js";
const useMaintenanceActions = () => {
const MS_LOOKUP = {
@@ -22,6 +22,12 @@ const useMaintenanceActions = () => {
weekly: MS_PER_DAY * 7,
};
const handleSubmitForm = async (maintenanceWindowId, form) => {
+ const toStringId = (monitor) => {
+ if (!monitor) {
+ return "";
+ }
+ return monitor._id ?? monitor.id ?? "";
+ };
const start = dayjs(form.startDate)
.set("hour", form.startTime.hour())
.set("minute", form.startTime.minute());
@@ -33,7 +39,7 @@ const useMaintenanceActions = () => {
const repeat = REPEAT_LOOKUP[form.repeat];
const submit = {
- monitors: form.monitors.map((monitor) => monitor._id),
+ monitors: form.monitors.map((monitor) => toStringId(monitor)).filter(Boolean),
name: form.name,
start: start.toISOString(),
end: end.toISOString(),
diff --git a/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx b/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx
index 8ba9b5e80..4ab56bef9 100644
--- a/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx
+++ b/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx
@@ -1,4 +1,4 @@
-import { networkService } from "../../../../main";
+import { networkService } from "../../../../main.jsx";
import dayjs from "dayjs";
import {
MS_PER_SECOND,
@@ -6,7 +6,7 @@ import {
MS_PER_HOUR,
MS_PER_DAY,
MS_PER_WEEK,
-} from "../../../../Utils/timeUtils";
+} from "../../../../Utils/timeUtilsLegacy.js";
const useMaintenanceData = () => {
const REVERSE_REPEAT_LOOKUP = {
0: "none",
@@ -41,7 +41,7 @@ const useMaintenanceData = () => {
limit: null,
types: ["http", "ping", "pagespeed", "port"],
});
- const fetchedMonitors = response.data.data.monitors;
+ const fetchedMonitors = response.data.data;
return fetchedMonitors;
};
@@ -55,7 +55,9 @@ const useMaintenanceData = () => {
const endTime = dayjs(end);
const durationInMs = endTime.diff(startTime, "milliseconds").toString();
const { duration, durationUnit } = getDurationAndUnit(durationInMs);
- const monitor = monitorList.find((monitor) => monitor._id === monitorId);
+ const monitor = monitorList.find(
+ (monitor) => (monitor._id ?? monitor.id) === monitorId
+ );
const maintenanceWindowInformation = {
name,
repeat: REVERSE_REPEAT_LOOKUP[repeat],
diff --git a/client/src/Pages/Maintenance/CreateMaintenance/index.jsx b/client/src/Pages/Maintenance/CreateMaintenance/index.jsx
index 9dbf9d4aa..af3f34512 100644
--- a/client/src/Pages/Maintenance/CreateMaintenance/index.jsx
+++ b/client/src/Pages/Maintenance/CreateMaintenance/index.jsx
@@ -2,27 +2,27 @@ import { Box, Button, Stack, Typography } from "@mui/material";
import { useSelector } from "react-redux";
import { useTheme } from "@emotion/react";
import { useEffect, useState } from "react";
-import ConfigBox from "../../../Components/ConfigBox";
+import ConfigBox from "@/Components/v1/ConfigBox/index.jsx";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import { LocalizationProvider } from "@mui/x-date-pickers";
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
import { MobileTimePicker } from "@mui/x-date-pickers/MobileTimePicker";
-import { maintenanceWindowValidation } from "../../../Validation/validation";
-import { createToast } from "../../../Utils/toastUtils";
-import MonitorList from "./Components/MonitorList";
-import ConfigSelect from "./Components/ConfigSelect";
-import useMaintenanceData from "./hooks/useMaintenanceData";
-import useMaintenanceActions from "./hooks/useMaintenanceActions";
+import { maintenanceWindowValidation } from "@/Validation/validation.js";
+import { createToast } from "@/Utils/toastUtils.jsx";
+import MonitorList from "./Components/MonitorList/index.jsx";
+import ConfigSelect from "./Components/ConfigSelect/index.jsx";
+import useMaintenanceData from "./hooks/useMaintenanceData.jsx";
+import useMaintenanceActions from "./hooks/useMaintenanceActions.jsx";
-import TextInput from "../../../Components/Inputs/TextInput";
-import Breadcrumbs from "../../../Components/Breadcrumbs";
-import CalendarIcon from "../../../assets/icons/calendar.svg?react";
+import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
+import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
+import Icon from "@/Components/v1/Icon";
import "./index.css";
-import Search from "../../../Components/Inputs/Search";
-import { logger } from "../../../Utils/Logger";
+import Search from "@/Components/v1/Inputs/Search/index.jsx";
+import { logger } from "@/Utils/Logger.js";
import { useNavigate, useParams } from "react-router-dom";
-import { buildErrors, hasValidationErrors } from "../../../Validation/error";
+import { buildErrors, hasValidationErrors } from "@/Validation/error.js";
import { useTranslation } from "react-i18next";
import dayjs from "dayjs";
@@ -108,6 +108,7 @@ const CreateMaintenance = () => {
const handleSubmit = async () => {
if (hasValidationErrors(form, maintenanceWindowValidation, setErrors)) return;
+
const request = handleSubmitForm(maintenanceWindowId, form);
try {
setIsLoading(true);
@@ -216,10 +217,24 @@ const CreateMaintenance = () => {
disablePast
disableHighlightToday
value={form.startDate}
- slots={{ openPickerIcon: () => }}
+ slots={{
+ openPickerIcon: () => (
+
+ ),
+ }}
slotProps={{
switchViewButton: { sx: { display: "none" } },
nextIconButton: { sx: { ml: theme.spacing(2) } },
+ day: {
+ sx: {
+ "&.MuiPickersDay-root.Mui-disabled": {
+ color: theme.palette.primary.contrastTextTertiary,
+ },
+ },
+ },
field: {
sx: {
width: "fit-content",
@@ -253,30 +268,6 @@ const CreateMaintenance = () => {
"&:hover": { backgroundColor: "transparent" },
},
},
- // CAIO_REVIEW, entire popper
- popper: {
- sx: {
- "& .MuiPickersDay-root": {
- color: theme.palette.primary.contrastText,
- "&.Mui-selected": {
- backgroundColor: theme.palette.accent.main, // Selected day background
- color: theme.palette.primary.contrastText, // Selected day text color
- },
- "&:hover": {
- backgroundColor: theme.palette.accent.light, // Hover background
- },
- "&.Mui-disabled": {
- color: theme.palette.secondary.main, // Disabled day color
- },
- },
- "& .MuiDayCalendar-weekDayLabel": {
- color: theme.palette.primary.contrastText,
- },
- "& .MuiPickersCalendarHeader-label": {
- color: theme.palette.primary.contrastText,
- },
- },
- },
}}
sx={{}}
onChange={(newDate) => {
diff --git a/client/src/Pages/Maintenance/MaintenanceTable/ActionsMenu/index.jsx b/client/src/Pages/Maintenance/MaintenanceTable/ActionsMenu/index.jsx
index aa1c48433..709bca212 100644
--- a/client/src/Pages/Maintenance/MaintenanceTable/ActionsMenu/index.jsx
+++ b/client/src/Pages/Maintenance/MaintenanceTable/ActionsMenu/index.jsx
@@ -3,14 +3,14 @@ import { useTheme } from "@emotion/react";
import { useNavigate } from "react-router-dom";
import { useSelector } from "react-redux";
import { IconButton, Menu, MenuItem } from "@mui/material";
-import { logger } from "../../../../Utils/Logger";
-import Settings from "../../../../assets/icons/settings-bold.svg?react";
+import { logger } from "@/Utils/Logger.js";
+import Icon from "@/Components/v1/Icon";
import PropTypes from "prop-types";
-import { networkService } from "../../../../main";
-import { createToast } from "../../../../Utils/toastUtils";
+import { networkService } from "../../../../main.jsx";
+import { createToast } from "@/Utils/toastUtils.jsx";
import { useTranslation } from "react-i18next";
-import Dialog from "../../../../Components/Dialog";
+import Dialog from "@/Components/v1/Dialog/index.jsx";
const ActionsMenu = ({ /* isAdmin, */ maintenanceWindow, updateCallback }) => {
maintenanceWindow;
@@ -27,7 +27,7 @@ const ActionsMenu = ({ /* isAdmin, */ maintenanceWindow, updateCallback }) => {
try {
setIsLoading(true);
await networkService.deleteMaintenanceWindow({
- maintenanceWindowId: maintenanceWindow._id,
+ maintenanceWindowId: maintenanceWindow.id,
});
updateCallback();
createToast({ body: "Maintenance window deleted successfully." });
@@ -47,7 +47,7 @@ const ActionsMenu = ({ /* isAdmin, */ maintenanceWindow, updateCallback }) => {
active: !maintenanceWindow.active,
};
await networkService.editMaintenanceWindow({
- maintenanceWindowId: maintenanceWindow._id,
+ maintenanceWindowId: maintenanceWindow.id,
maintenanceWindow: data,
});
updateCallback();
@@ -60,7 +60,7 @@ const ActionsMenu = ({ /* isAdmin, */ maintenanceWindow, updateCallback }) => {
};
const handleEdit = () => {
- navigate(`/maintenance/create/${maintenanceWindow._id}`);
+ navigate(`/maintenance/create/${maintenanceWindow.id}`);
};
const openMenu = (event) => {
@@ -98,7 +98,10 @@ const ActionsMenu = ({ /* isAdmin, */ maintenanceWindow, updateCallback }) => {
},
}}
>
-
+
{sort.order === "asc" ? (
-
+
) : (
-
+
)}
@@ -89,9 +94,15 @@ const MaintenanceTable = ({
}}
>
{sort.order === "asc" ? (
-
+
) : (
-
+
)}
@@ -192,7 +203,7 @@ const MaintenanceTable = ({
},
},
onRowClick: (row) => {
- navigate(`/maintenance/create/${row._id}`);
+ navigate(`/maintenance/create/${row.id}`);
},
}}
headers={headers}
diff --git a/client/src/Pages/Maintenance/index.jsx b/client/src/Pages/Maintenance/index.jsx
index ee142f524..b49cd9f6e 100644
--- a/client/src/Pages/Maintenance/index.jsx
+++ b/client/src/Pages/Maintenance/index.jsx
@@ -2,13 +2,13 @@ import { Stack, Button } from "@mui/material";
import { useTheme } from "@emotion/react";
import { useState, useEffect } from "react";
import "./index.css";
-import MaintenanceTable from "./MaintenanceTable";
+import MaintenanceTable from "./MaintenanceTable/index.jsx";
import { useSelector } from "react-redux";
-import { networkService } from "../../main";
-import Breadcrumbs from "../../Components/Breadcrumbs";
+import { networkService } from "../../main.jsx";
+import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
-import PageStateWrapper from "../../Components/PageStateWrapper";
+import PageStateWrapper from "@/Components/v1/PageStateWrapper/index.jsx";
const Maintenance = () => {
const theme = useTheme();
diff --git a/client/src/Pages/NotFound/index.jsx b/client/src/Pages/NotFound/index.jsx
index fdae92cb5..27e3173b0 100644
--- a/client/src/Pages/NotFound/index.jsx
+++ b/client/src/Pages/NotFound/index.jsx
@@ -1,5 +1,5 @@
import PropTypes from "prop-types";
-import NotFoundSvg from "../../../src/assets/Images/sushi_404.svg";
+import NotFoundSvg from "@/assets/Images/sushi_404.svg";
import { Button, Stack, Typography } from "@mui/material";
import { useNavigate } from "react-router-dom";
import { useTheme } from "@emotion/react";
diff --git a/client/src/Pages/Notifications/components/ActionMenu.jsx b/client/src/Pages/Notifications/components/ActionMenu.jsx
index d57ad931f..a3ccc4bbb 100644
--- a/client/src/Pages/Notifications/components/ActionMenu.jsx
+++ b/client/src/Pages/Notifications/components/ActionMenu.jsx
@@ -1,7 +1,7 @@
// Components
import Menu from "@mui/material/Menu";
import IconButton from "@mui/material/IconButton";
-import SettingsOutlinedIcon from "@mui/icons-material/SettingsOutlined";
+import Icon from "@/Components/v1/Icon";
import MenuItem from "@mui/material/MenuItem";
// Utils
@@ -32,13 +32,13 @@ const ActionMenu = ({ notification, onDelete }) => {
const handleRemove = (e) => {
e.stopPropagation();
- onDelete(notification._id);
+ onDelete(notification.id);
handleClose();
};
const handleConfigure = (e) => {
e.stopPropagation();
- navigate(`/notifications/${notification._id}`);
+ navigate(`/notifications/${notification.id}`);
handleClose();
};
@@ -49,7 +49,10 @@ const ActionMenu = ({ notification, onDelete }) => {
onClick={handleClick}
onMouseDown={(e) => e.stopPropagation()}
>
-
+
{
const [notification, setNotification] = useState({
notificationName: "",
address: "",
- type: NOTIFICATION_TYPES[0]._id,
+ type: NOTIFICATION_TYPES[0].id,
});
const [errors, setErrors] = useState({});
const { t } = useTranslation();
@@ -66,7 +66,7 @@ const CreateNotifications = () => {
const [isOpen, setIsOpen] = useState(false);
const getNotificationTypeValue = (typeId) => {
- return NOTIFICATION_TYPES.find((type) => type._id === typeId)?.value || "email";
+ return NOTIFICATION_TYPES.find((type) => type.id === typeId)?.value || "email";
};
const extractError = (error, field) =>
@@ -223,18 +223,54 @@ const CreateNotifications = () => {
{t(DESCRIPTION_MAP[type])}
-
+ {type === "matrix" ? (
+ <>
+
+
+
+ >
+ ) : (
+
+ )}
-
-
+ {" "}
{
@@ -100,7 +100,7 @@ const Notifications = () => {
{t("notifications.createTitle")}
navigate(`/notifications/${row._id}`),
+ onRowClick: (row) => navigate(`/notifications/${row.id}`),
rowSX: {
cursor: "pointer",
"&:hover td": {
diff --git a/client/src/Pages/Notifications/utils.js b/client/src/Pages/Notifications/utils.js
index 0813f6868..9ad422101 100644
--- a/client/src/Pages/Notifications/utils.js
+++ b/client/src/Pages/Notifications/utils.js
@@ -1,9 +1,10 @@
export const NOTIFICATION_TYPES = [
- { _id: 1, name: "E-mail", value: "email" },
- { _id: 2, name: "Slack", value: "slack" },
- { _id: 3, name: "PagerDuty", value: "pager_duty" },
- { _id: 4, name: "Webhook", value: "webhook" },
- { _id: 5, name: "Discord", value: "discord" },
+ { id: 1, name: "E-mail", value: "email" },
+ { id: 2, name: "Slack", value: "slack" },
+ { id: 3, name: "PagerDuty", value: "pager_duty" },
+ { id: 4, name: "Webhook", value: "webhook" },
+ { id: 5, name: "Discord", value: "discord" },
+ { id: 6, name: "Matrix", value: "matrix" },
];
export const TITLE_MAP = {
@@ -12,6 +13,7 @@ export const TITLE_MAP = {
pager_duty: "createNotifications.pagerdutySettings.title",
webhook: "createNotifications.webhookSettings.title",
discord: "createNotifications.discordSettings.title",
+ matrix: "createNotifications.matrixSettings.title",
};
export const DESCRIPTION_MAP = {
@@ -20,6 +22,7 @@ export const DESCRIPTION_MAP = {
pager_duty: "createNotifications.pagerdutySettings.description",
webhook: "createNotifications.webhookSettings.description",
discord: "createNotifications.discordSettings.description",
+ matrix: "createNotifications.matrixSettings.description",
};
export const LABEL_MAP = {
@@ -28,6 +31,7 @@ export const LABEL_MAP = {
pager_duty: "createNotifications.pagerdutySettings.integrationKeyLabel",
webhook: "createNotifications.webhookSettings.webhookLabel",
discord: "createNotifications.discordSettings.webhookLabel",
+ matrix: "createNotifications.matrixSettings.homeserverLabel",
};
export const PLACEHOLDER_MAP = {
@@ -36,4 +40,5 @@ export const PLACEHOLDER_MAP = {
pager_duty: "createNotifications.pagerdutySettings.integrationKeyPlaceholder",
webhook: "createNotifications.webhookSettings.webhookPlaceholder",
discord: "createNotifications.discordSettings.webhookPlaceholder",
+ matrix: "createNotifications.matrixSettings.homeserverPlaceholder",
};
diff --git a/client/src/Pages/PageSpeed/Create/index.jsx b/client/src/Pages/PageSpeed/Create/index.jsx
index 46bc7f977..6a786eb00 100644
--- a/client/src/Pages/PageSpeed/Create/index.jsx
+++ b/client/src/Pages/PageSpeed/Create/index.jsx
@@ -1,37 +1,36 @@
// Components
import { Box, Stack, Tooltip, Typography, Button, ButtonGroup } from "@mui/material";
-import ConfigBox from "../../../Components/ConfigBox";
-import Select from "../../../Components/Inputs/Select";
-import TextInput from "../../../Components/Inputs/TextInput";
-import PauseCircleOutlineIcon from "@mui/icons-material/PauseCircleOutline";
-import Breadcrumbs from "../../../Components/Breadcrumbs";
-import PulseDot from "../../../Components/Animated/PulseDot";
-import PlayCircleOutlineRoundedIcon from "@mui/icons-material/PlayCircleOutlineRounded";
-import SkeletonLayout from "./skeleton";
-import NotificationsConfig from "../../../Components/NotificationConfig";
-import Dialog from "../../../Components/Dialog";
-import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments";
-import Radio from "../../../Components/Inputs/Radio";
+import ConfigBox from "@/Components/v1/ConfigBox/index.jsx";
+import Select from "@/Components/v1/Inputs/Select/index.jsx";
+import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
+import Icon from "@/Components/v1/Icon";
+import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
+import PulseDot from "@/Components/v1/Animated/PulseDot.jsx";
+import SkeletonLayout from "./skeleton.jsx";
+import NotificationsConfig from "@/Components/v1/NotificationConfig/index.jsx";
+import Dialog from "@/Components/v1/Dialog/index.jsx";
+import { HttpAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx";
+import Radio from "@/Components/v1/Inputs/Radio/index.jsx";
// Utils
import { useState, useEffect } from "react";
import { useSelector } from "react-redux";
-import { monitorValidation } from "../../../Validation/validation";
-import { parseDomainName } from "../../../Utils/monitorUtils";
+import { monitorValidation } from "../../../Validation/validation.js";
+import { parseDomainName } from "../../../Utils/monitorUtilsLegacy.js";
import { useTranslation } from "react-i18next";
-import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications";
+import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications.js";
import { useTheme } from "@emotion/react";
-import { createToast } from "../../../Utils/toastUtils";
+import { createToast } from "../../../Utils/toastUtils.jsx";
import { useParams } from "react-router";
-import { useMonitorUtils } from "../../../Hooks/useMonitorUtils";
+import { useMonitorUtils } from "../../../Hooks/useMonitorUtils.js";
import {
useCreateMonitor,
useFetchMonitorById,
useDeleteMonitor,
useUpdateMonitor,
usePauseMonitor,
-} from "../../../Hooks/monitorHooks";
+} from "../../../Hooks/monitorHooks.js";
const PageSpeedSetup = () => {
const { monitorId } = useParams();
@@ -178,7 +177,7 @@ const PageSpeedSetup = () => {
const handleRemove = async (event) => {
event.preventDefault();
- await deleteMonitor({ monitor, redirect: "/pagespeed" });
+ await deleteMonitor({ monitor: { id: monitorId }, redirect: "/pagespeed" });
};
const isBusy = isLoading || isCreating || isDeleting || isUpdating || isPausing;
@@ -313,12 +312,18 @@ const PageSpeedSetup = () => {
>
{monitor?.isActive ? (
<>
-
+
{t("pause")}
>
) : (
<>
-
+
{t("resume")}
>
)}
diff --git a/client/src/Pages/PageSpeed/Details/Components/Charts/AreaChart.jsx b/client/src/Pages/PageSpeed/Details/Components/Charts/AreaChart.jsx
index 40fec0ab6..d8c118237 100644
--- a/client/src/Pages/PageSpeed/Details/Components/Charts/AreaChart.jsx
+++ b/client/src/Pages/PageSpeed/Details/Components/Charts/AreaChart.jsx
@@ -11,7 +11,7 @@ import {
import { useTheme } from "@emotion/react";
import { useMemo, useState } from "react";
import { Box, Stack, Typography } from "@mui/material";
-import { formatDateWithTz } from "../../../../../Utils/timeUtils";
+import { formatDateWithTz } from "../../../../../Utils/timeUtilsLegacy.js";
import { useSelector } from "react-redux";
const config = {
diff --git a/client/src/Pages/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx b/client/src/Pages/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx
index 72811fa2a..525e4d0d2 100644
--- a/client/src/Pages/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx
+++ b/client/src/Pages/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx
@@ -1,7 +1,7 @@
import { Box, Typography, Divider } from "@mui/material";
-import Checkbox from "../../../../../Components/Inputs/Checkbox";
-import MetricsIcon from "../../../../../assets/icons/ruler-icon.svg?react";
-import LegendBox from "../../../../../Components/Charts/LegendBox";
+import Checkbox from "@/Components/v1/Inputs/Checkbox/index.jsx";
+import Icon from "@/Components/v1/Icon";
+import LegendBox from "@/Components/v1/Charts/LegendBox/index.jsx";
import { useTranslation } from "react-i18next";
import { useTheme } from "@emotion/react";
@@ -10,7 +10,12 @@ const AreaChartLegend = ({ metrics, handleMetrics }) => {
const { t } = useTranslation();
return (
}
+ icon={
+
+ }
header="Metrics"
>
diff --git a/client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx b/client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx
index 18ca0733c..5cfdad608 100644
--- a/client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx
+++ b/client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx
@@ -1,7 +1,7 @@
import { Stack, Box, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
-import LegendBox from "../../../../../Components/Charts/LegendBox";
-import SpeedometerIcon from "../../../../../assets/icons/speedometer-icon.svg?react";
+import LegendBox from "@/Components/v1/Charts/LegendBox/index.jsx";
+import Icon from "@/Components/v1/Icon";
import PropTypes from "prop-types";
const PieChartLegend = ({ audits }) => {
@@ -9,7 +9,12 @@ const PieChartLegend = ({ audits }) => {
return (
}
+ icon={
+
+ }
header="Performance metrics"
sx={{ flex: 1 }}
>
diff --git a/client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx b/client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx
index 582648e23..9020ddd6d 100644
--- a/client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx
+++ b/client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx
@@ -1,8 +1,8 @@
-import ChartBox from "../../../../../Components/Charts/ChartBox";
-import AreaChart from "../Charts/AreaChart";
-import AreaChartLegend from "../Charts/AreaChartLegend";
-import SkeletonLayout from "./skeleton";
-import ScoreIcon from "../../../../../assets/icons/monitor-graph-line.svg?react";
+import ChartBox from "@/Components/v1/Charts/ChartBox/index.jsx";
+import AreaChart from "../Charts/AreaChart.jsx";
+import AreaChartLegend from "../Charts/AreaChartLegend.jsx";
+import SkeletonLayout from "./skeleton.jsx";
+import Icon from "@/Components/v1/Icon";
import { Stack } from "@mui/material";
import PropTypes from "prop-types";
@@ -24,7 +24,12 @@ const PageSpeedAreaChart = ({ shouldRender, monitor, metrics, handleMetrics }) =
>
}
+ icon={
+
+ }
header="Score history"
height="100%"
borderRadiusRight={16}
diff --git a/client/src/Pages/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx b/client/src/Pages/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx
index cb79198e9..6ff26c739 100644
--- a/client/src/Pages/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx
+++ b/client/src/Pages/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx
@@ -1,12 +1,24 @@
-import StatusBoxes from "../../../../../Components/StatusBoxes";
-import StatBox from "../../../../../Components/StatBox";
+import StatusBoxes from "@/Components/v1/StatusBoxes/index.jsx";
+import StatBox from "@/Components/v1/StatBox/index.jsx";
import { Typography } from "@mui/material";
-import { getHumanReadableDuration } from "../../../../../Utils/timeUtils";
+import { getHumanReadableDuration } from "../../../../../Utils/timeUtilsLegacy.js";
import { useTranslation } from "react-i18next";
-const PageSpeedStatusBoxes = ({ shouldRender, monitor }) => {
- const uptimeDuration = getHumanReadableDuration(monitor?.uptimeDuration);
- const time = getHumanReadableDuration(monitor?.lastChecked);
+const PageSpeedStatusBoxes = ({ shouldRender, monitorStats }) => {
+ const lastChecked = monitorStats?.lastCheckTimestamp
+ ? Date.now() - monitorStats.lastCheckTimestamp
+ : 0;
+
+ // Determine time since last failure
+ const timeOfLastFailure = monitorStats?.timeOfLastFailure;
+ const timeSinceLastFailure =
+ timeOfLastFailure > 0
+ ? Date.now() - timeOfLastFailure
+ : Date.now() - new Date(monitorStats?.createdAt);
+
+ const streakTime = getHumanReadableDuration(timeSinceLastFailure);
+
+ const time = getHumanReadableDuration(lastChecked);
const { t } = useTranslation();
@@ -16,7 +28,7 @@ const PageSpeedStatusBoxes = ({ shouldRender, monitor }) => {
heading="checks since"
subHeading={
<>
- {uptimeDuration}
+ {streakTime}
{t("ago")}
>
}
diff --git a/client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx b/client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx
index 2f809ebbb..a2e1b5f96 100644
--- a/client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx
+++ b/client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx
@@ -1,10 +1,10 @@
-import ChartBox from "../../../../../Components/Charts/ChartBox";
-import PerformanceIcon from "../../../../../assets/icons/performance-report.svg?react";
-import PieChart from "../Charts/PieChart";
+import ChartBox from "@/Components/v1/Charts/ChartBox/index.jsx";
+import Icon from "@/Components/v1/Icon";
+import PieChart from "../Charts/PieChart.jsx";
import { Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
-import PieChartLegend from "../Charts/PieChartLegend";
-import SkeletonLayout from "./skeleton";
+import PieChartLegend from "../Charts/PieChartLegend.jsx";
+import SkeletonLayout from "./skeleton.jsx";
import { useTranslation } from "react-i18next";
const PerformanceReport = ({ shouldRender, audits }) => {
@@ -17,7 +17,12 @@ const PerformanceReport = ({ shouldRender, audits }) => {
return (
}
+ icon={
+
+ }
header="Performance report"
Legend={ }
borderRadiusRight={16}
diff --git a/client/src/Pages/PageSpeed/Details/index.tsx b/client/src/Pages/PageSpeed/Details/index.tsx
new file mode 100644
index 000000000..2028f47c0
--- /dev/null
+++ b/client/src/Pages/PageSpeed/Details/index.tsx
@@ -0,0 +1,64 @@
+import { BasePage } from "@/Components/v2/design-elements";
+import type { PageSpeedDetailsResponse } from "@/Types/Monitor";
+import { HeaderMonitorControls } from "@/Components/v2/common";
+import Stack from "@mui/material/Stack";
+import {
+ HistogramPageSpeedDetails,
+ PiePageSpeed,
+ PiePageSpeedLegend,
+ MonitorStatBoxes,
+} from "@/Components/v2/monitors";
+
+import { useIsAdmin } from "@/Hooks/useIsAdmin";
+import { useGet } from "@/Hooks/UseApi";
+import { useParams } from "react-router-dom";
+import { useTheme } from "@mui/material";
+
+const PageSpeedDetails = () => {
+ const { monitorId } = useParams();
+ const isAdmin = useIsAdmin();
+ const theme = useTheme();
+ const {
+ data: monitorData,
+ isLoading,
+ error,
+ refetch,
+ } = useGet(
+ monitorId ? `/monitors/pagespeed/details/${monitorId}?dateRange=day` : null,
+ {},
+ { keepPreviousData: true, refreshInterval: 30000 }
+ );
+
+ const monitor = monitorData?.monitor;
+ const monitorStats = monitorData?.monitorStats || null;
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
+export default PageSpeedDetails;
diff --git a/client/src/Pages/PageSpeed/Details/index.jsx b/client/src/Pages/PageSpeed/Details/old.jsx
similarity index 75%
rename from client/src/Pages/PageSpeed/Details/index.jsx
rename to client/src/Pages/PageSpeed/Details/old.jsx
index cfc799b36..3190787b3 100644
--- a/client/src/Pages/PageSpeed/Details/index.jsx
+++ b/client/src/Pages/PageSpeed/Details/old.jsx
@@ -1,24 +1,23 @@
// Components
import { Stack, Typography } from "@mui/material";
-import Breadcrumbs from "../../../Components/Breadcrumbs";
-import MonitorTimeFrameHeader from "../../../Components/MonitorTimeFrameHeader";
-import PageSpeedStatusBoxes from "./Components/PageSpeedStatusBoxes";
-import MonitorDetailsControlHeader from "../../../Components/MonitorDetailsControlHeader";
-import PageSpeedAreaChart from "./Components/PageSpeedAreaChart";
-import PerformanceReport from "./Components/PerformanceReport";
-import GenericFallback from "../../../Components/GenericFallback";
+import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
+import MonitorTimeFrameHeader from "@/Components/v1/MonitorTimeFrameHeader/index.jsx";
+import PageSpeedStatusBoxes from "./Components/PageSpeedStatusBoxes/index.jsx";
+import MonitorDetailsControlHeader from "@/Components/v1/MonitorDetailsControlHeader/index.jsx";
+import PageSpeedAreaChart from "./Components/PageSpeedAreaChart/index.jsx";
+import PerformanceReport from "./Components/PerformanceReport/index.jsx";
+import GenericFallback from "@/Components/v1/GenericFallback/index.jsx";
// Utils
import { useTheme } from "@emotion/react";
-import { useIsAdmin } from "../../../Hooks/useIsAdmin";
+import { useIsAdmin } from "@/Hooks/useIsAdmin.js";
import { useParams } from "react-router-dom";
-import { useFetchStatsByMonitorId } from "../../../Hooks/monitorHooks";
+import { useFetchPageSpeedMonitorById } from "../../../Hooks/monitorHooks.js";
import { useState } from "react";
import { useTranslation } from "react-i18next";
// Constants
const BREADCRUMBS = [
{ name: "pagespeed", path: "/pagespeed" },
{ name: "details", path: `` },
- // { name: "details", path: `/pagespeed/${monitorId}` }, // Not needed?
];
const PageSpeedDetails = () => {
@@ -36,13 +35,9 @@ const PageSpeedDetails = () => {
});
const [trigger, setTrigger] = useState(false);
// Network
- const [monitor, audits, isLoading, networkError] = useFetchStatsByMonitorId({
+ const [monitor, monitorStats, isLoading, networkError] = useFetchPageSpeedMonitorById({
monitorId,
- sortOrder: "desc",
- limit: 50,
dateRange: "day",
- numToDisplay: null,
- normalize: null,
updateTrigger: trigger,
});
@@ -100,7 +95,7 @@ const PageSpeedDetails = () => {
/>
{
/>
);
diff --git a/client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx b/client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx
index 94d6b137b..4a85863c4 100644
--- a/client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx
+++ b/client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx
@@ -1,17 +1,16 @@
import PropTypes from "prop-types";
-// import PageSpeedIcon from "../../../../assets/icons/page-speed.svg?react";
-import PageSpeedIcon from "../../../../../assets/icons/page-speed.svg?react";
-import { StatusLabel } from "../../../../../Components/Label";
+import Icon from "@/Components/v1/Icon";
+import { StatusLabel } from "@/Components/v1/Label/index.jsx";
import { Box, Grid, Stack, Typography } from "@mui/material";
import { useNavigate } from "react-router-dom";
import { useTheme } from "@emotion/react";
import { Area, AreaChart, CartesianGrid, ResponsiveContainer, Tooltip } from "recharts";
import { useSelector } from "react-redux";
-import { formatDateWithTz, formatDurationSplit } from "../../../../../Utils/timeUtils";
-import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils";
+import { formatDateWithTz, formatDurationSplit } from "@/Utils/timeUtilsLegacy.js";
+import { useMonitorUtils } from "@/Hooks/useMonitorUtils.js";
import { useState } from "react";
import { useTranslation } from "react-i18next";
-import IconBox from "../../../../../Components/IconBox";
+import IconBox from "@/Components/v1/IconBox/index.jsx";
/**
* CustomToolTip displays a tooltip with formatted date and score information.
* @param {Object} props
@@ -221,7 +220,7 @@ const Card = ({ monitor }) => {
navigate(`/pagespeed/${monitor._id}`)}
+ onClick={() => navigate(`/pagespeed/${monitor.id}`)}
border={1}
borderColor={theme.palette.primary.lowContrast}
borderRadius={theme.shape.borderRadius}
@@ -241,7 +240,10 @@ const Card = ({ monitor }) => {
}}
>
-
+
{
+ if (!shouldRender) return null;
-const MonitorGrid = ({ shouldRender, monitors }) => {
return (
{
{monitors?.map((monitor) => (
))}
diff --git a/client/src/Pages/PageSpeed/Monitors/Components/PageSpeedMonitorsTable.tsx b/client/src/Pages/PageSpeed/Monitors/Components/PageSpeedMonitorsTable.tsx
new file mode 100644
index 000000000..f38e1c706
--- /dev/null
+++ b/client/src/Pages/PageSpeed/Monitors/Components/PageSpeedMonitorsTable.tsx
@@ -0,0 +1,254 @@
+import Stack from "@mui/material/Stack";
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import { Table, Pagination, StatusLabel } from "@/Components/v2/design-elements";
+import { HistogramPageSpeed } from "@/Components/v2/monitors";
+import { ArrowDown, ArrowUp } from "lucide-react";
+import type { Header } from "@/Components/v2/design-elements/Table";
+import { ActionsMenu } from "@/Components/v2/actions-menu";
+
+import { useTranslation } from "react-i18next";
+import useMediaQuery from "@mui/material/useMediaQuery";
+import { useTheme } from "@mui/material/styles";
+import { useNavigate } from "react-router-dom";
+import { usePatch } from "@/Hooks/UseApi";
+
+import type { Monitor, MonitorWithChecks } from "@/Types/Monitor";
+import type { ActionMenuItem } from "@/Components/v2/actions-menu";
+
+export const PageSpeedMonitorsTable = ({
+ monitors,
+ refetch,
+ setSelectedMonitor,
+ sortField,
+ setSortField,
+ sortOrder,
+ setSortOrder,
+ count,
+ page,
+ setPage,
+ rowsPerPage,
+ setRowsPerPage,
+}: {
+ monitors: MonitorWithChecks[];
+ refetch: Function;
+ setSelectedMonitor: Function;
+ sortField: string;
+ setSortField: (field: string) => void;
+ sortOrder: "asc" | "desc";
+ setSortOrder: (order: "asc" | "desc") => void;
+ count: number;
+ page: number;
+ setPage: (page: number) => void;
+ rowsPerPage: number;
+ setRowsPerPage: (rowsPerPage: number) => void;
+}) => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+ const isSmall = useMediaQuery(theme.breakpoints.down("md"));
+ const navigate = useNavigate();
+ const {
+ patch,
+ // loading: isPatching,
+ // error: postError,
+ } = usePatch();
+
+ const handlePageChange = (
+ _e: React.MouseEvent | null,
+ newPage: number
+ ) => {
+ setPage(newPage);
+ };
+
+ const handleRowsPerPageChange = (
+ e: React.ChangeEvent
+ ) => {
+ const value = Number(e.target.value);
+ setPage(0);
+ setRowsPerPage(value);
+ };
+
+ const handleSort = (e: any, field: string) => {
+ e.preventDefault();
+ e.stopPropagation();
+ if (sortField === field) {
+ const newOrder = sortOrder === "asc" ? "desc" : "asc";
+ setSortOrder(newOrder);
+ } else {
+ setSortField(field);
+ setSortOrder("asc");
+ }
+ refetch();
+ };
+
+ const getActions = (monitor: Monitor): ActionMenuItem[] => {
+ return [
+ {
+ id: 1,
+ label: t("pages.common.monitors.actions.openSite"),
+ action: () => {
+ window.open(monitor.url, "_blank", "noreferrer");
+ },
+ closeMenu: true,
+ },
+ {
+ id: 2,
+ label: t("pages.common.monitors.actions.details"),
+ action: () => {
+ navigate(`${monitor.id}`);
+ },
+ },
+ {
+ id: 3,
+ label: t("pages.common.monitors.actions.incidents"),
+ action: () => {
+ navigate(`/incidents?monitorId=${monitor.id}`);
+ },
+ },
+ {
+ id: 4,
+ label: t("pages.common.monitors.actions.configure"),
+ action: () => {
+ navigate(`/pagespeed/configure/${monitor.id}`);
+ },
+ },
+ // {
+ // id: 5,
+ // label: "Clone",
+ // action: () => {
+
+ // },
+ // },
+ {
+ id: 6,
+ label:
+ monitor.isActive === false
+ ? t("common.buttons.resume")
+ : t("common.buttons.pause"),
+ action: async () => {
+ await patch(`/monitors/${monitor.id}/active`);
+ refetch();
+ },
+ closeMenu: true,
+ },
+ {
+ id: 7,
+ label: (
+
+ {t("common.buttons.delete")}
+
+ ),
+ action: () => {
+ setSelectedMonitor(monitor);
+ },
+ closeMenu: true,
+ },
+ ];
+ };
+
+ const getHeaders = () => {
+ const renderSortIcon = (isActive: boolean) => (
+
+ {isActive ? (
+ sortOrder === "asc" ? (
+
+ ) : (
+
+ )
+ ) : null}
+
+ );
+
+ const headers: Header[] = [
+ {
+ id: "name",
+ content: (
+ handleSort(e, "name")}
+ sx={{ cursor: "pointer" }}
+ >
+ {t("common.table.headers.name")}
+ {renderSortIcon(sortField === "name")}
+
+ ),
+ render: (row) => {
+ return row.name;
+ },
+ },
+ {
+ id: "status",
+ content: (
+ handleSort(e, "status")}
+ sx={{ cursor: "pointer" }}
+ >
+ {t("common.table.headers.status")}
+ {renderSortIcon(sortField === "status")}
+
+ ),
+ render: (row) => {
+ return ;
+ },
+ },
+ {
+ id: "histogram",
+ content: t("pages.pageSpeed.table.headers.pageSpeedScore"),
+ render: (row) => {
+ return (
+
+
+
+ );
+ },
+ },
+
+ {
+ id: "actions",
+ content: t("common.table.headers.actions"),
+ render: (row) => {
+ return ;
+ },
+ },
+ ];
+ return headers;
+ };
+
+ let headers = getHeaders();
+
+ if (isSmall) {
+ headers = headers.filter((h) => h.id !== "histogram");
+ }
+ return (
+
+ {
+ navigate(`/pagespeed/${row.id}`);
+ }}
+ />
+
+
+ );
+};
diff --git a/client/src/Pages/PageSpeed/Monitors/index.tsx b/client/src/Pages/PageSpeed/Monitors/index.tsx
new file mode 100644
index 000000000..887ec02c0
--- /dev/null
+++ b/client/src/Pages/PageSpeed/Monitors/index.tsx
@@ -0,0 +1,117 @@
+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";
+
+const PageSpeedMonitorsPage = () => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+ const isAdmin = useIsAdmin();
+ const { deleteFn, loading: isDeleting } = useDelete();
+
+ const monitorsUrl = "/monitors/team/with-checks?type=pagespeed&limit=10";
+ const {
+ data: monitorsData,
+ isLoading: monitorsIsLoading,
+ error: monitorsError,
+ refetch,
+ } = useGet(monitorsUrl, {}, { refreshInterval: 30000 });
+
+ const settingsUrl = "/settings";
+ const {
+ data: settingsData,
+ isLoading: settingsIsLoading,
+ error: settingsError,
+ } = useGet(settingsUrl);
+
+ const [selectedMonitor, setSelectedMonitor] = useState(null);
+ const isDialogOpen = Boolean(selectedMonitor);
+ const [sortField, setSortField] = useState("name");
+ const [sortOrder, setSortOrder] = useState<"asc" | "desc">("asc");
+ const [page, setPage] = useState(0);
+ const [rowsPerPage, setRowsPerPage] = useState(10);
+
+ const monitors = monitorsData?.monitors;
+ const monitorsCount = monitorsData?.count;
+ const summary = monitorsData?.summary;
+
+ const isLoading = monitorsIsLoading || settingsIsLoading;
+
+ const showApiKeyWarning = isAdmin && settingsData && !settingsData.pagespeedKeySet;
+
+ const handleConfirm = async () => {
+ if (!selectedMonitor) return;
+ await deleteFn(`/monitors/${selectedMonitor.id}`);
+ setSelectedMonitor(null);
+ refetch();
+ };
+
+ const handleCancel = () => {
+ setSelectedMonitor(null);
+ };
+
+ return (
+ : undefined}
+ >
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default PageSpeedMonitorsPage;
diff --git a/client/src/Pages/PageSpeed/Monitors/index.jsx b/client/src/Pages/PageSpeed/Monitors/old.jsx
similarity index 52%
rename from client/src/Pages/PageSpeed/Monitors/index.jsx
rename to client/src/Pages/PageSpeed/Monitors/old.jsx
index b5b13ba6e..f1bfcd0dd 100644
--- a/client/src/Pages/PageSpeed/Monitors/index.jsx
+++ b/client/src/Pages/PageSpeed/Monitors/old.jsx
@@ -1,18 +1,18 @@
// Components
import { useState } from "react";
-import Breadcrumbs from "../../../Components/Breadcrumbs";
+import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
import { Stack } from "@mui/material";
-import CreateMonitorHeader from "../../../Components/MonitorCreateHeader";
-import MonitorCountHeader from "../../../Components/MonitorCountHeader";
-import MonitorGrid from "./Components/MonitorGrid";
-import PageStateWrapper from "../../../Components/PageStateWrapper";
-import FallbackPageSpeedWarning from "../../../Components/Fallback/FallbackPageSpeedWarning";
+import CreateMonitorHeader from "@/Components/v1/MonitorCreateHeader/index.jsx";
+import MonitorCountHeader from "@/Components/v1/MonitorCountHeader/index.jsx";
+import MonitorGrid from "./Components/MonitorGrid/index.jsx";
+import PageStateWrapper from "@/Components/v1/PageStateWrapper/index.jsx";
+import FallbackPageSpeedWarning from "@/Components/v1/Fallback/FallbackPageSpeedWarning.jsx";
// Utils
import { useTheme } from "@emotion/react";
-import { useIsAdmin } from "../../../Hooks/useIsAdmin";
-import { useFetchMonitorsByTeamId } from "../../../Hooks/monitorHooks";
-import { useFetchSettings } from "../../../Hooks/settingsHooks";
+import { useIsAdmin } from "@/Hooks/useIsAdmin.js";
+import { useFetchMonitorsWithChecks } from "@/Hooks/monitorHooks.js";
+import { useFetchSettings } from "@/Hooks/settingsHooks.js";
// Constants
const BREADCRUMBS = [{ name: `pagespeed`, path: "/pagespeed" }];
const TYPES = ["pagespeed"];
@@ -20,14 +20,21 @@ const PageSpeed = () => {
const theme = useTheme();
const isAdmin = useIsAdmin();
- const [monitors, monitorsSummary, isLoading, networkError] = useFetchMonitorsByTeamId({
- limit: 10,
+ const [
+ summary,
+ monitorsWithChecks,
+ monitorsWithChecksCount,
+ monitorsWithChecksIsLoading,
+ monitorsWithChecksNetworkError,
+ ] = useFetchMonitorsWithChecks({
types: TYPES,
+ limit: 10,
page: null,
rowsPerPage: null,
filter: null,
field: null,
order: null,
+ monitorUpdateTrigger: null,
});
const [settingsData, setSettingsData] = useState(undefined);
@@ -40,9 +47,9 @@ const PageSpeed = () => {
return (
<>
{
diff --git a/client/src/Pages/ServerUnreachable.jsx b/client/src/Pages/ServerUnreachable.jsx
index 466633144..bfbbfa90a 100644
--- a/client/src/Pages/ServerUnreachable.jsx
+++ b/client/src/Pages/ServerUnreachable.jsx
@@ -2,14 +2,14 @@ import React, { useState } from "react";
import { Box, Typography, Button, Stack } from "@mui/material";
import { useTheme } from "@emotion/react";
import { useNavigate } from "react-router-dom";
-import { networkService } from "../Utils/NetworkService";
-import Alert from "../Components/Alert";
-import { createToast } from "../Utils/toastUtils";
+import { networkService } from "@/Utils/NetworkService.js";
+import Alert from "@/Components/v1/Alert/index.jsx";
+import { createToast } from "@/Utils/toastUtils.jsx";
import { useTranslation } from "react-i18next";
-import Background from "../assets/Images/background-grid.svg?react";
-import Logo from "../assets/icons/checkmate-icon.svg?react";
-import ThemeSwitch from "../Components/ThemeSwitch";
-import LanguageSelector from "../Components/LanguageSelector";
+import Background from "@/assets/Images/background-grid.svg?react";
+import Logo from "@/assets/icons/checkmate-icon.svg?react";
+import ThemeSwitch from "@/Components/v1/ThemeSwitch/index.jsx";
+import LanguageSelector from "@/Components/LanguageSelector.jsx";
const ServerUnreachable = () => {
const theme = useTheme();
diff --git a/client/src/Pages/Settings/DummyChart.tsx b/client/src/Pages/Settings/DummyChart.tsx
new file mode 100644
index 000000000..6996af9c5
--- /dev/null
+++ b/client/src/Pages/Settings/DummyChart.tsx
@@ -0,0 +1,67 @@
+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";
+
+interface DummyChartProps {
+ chartType: string;
+}
+
+const generateDummyChecks = (): Check[] => {
+ const checks: Check[] = [];
+ 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;
+};
+
+const dummyChecks = generateDummyChecks();
+
+const DummyChart = ({ chartType }: DummyChartProps) => {
+ const theme = useTheme();
+
+ return (
+
+
+ Preview
+
+ {chartType === "histogram" ? (
+
+ ) : (
+
+ )}
+
+ );
+};
+
+export default DummyChart;
diff --git a/client/src/Pages/Settings/SettingsAbout.jsx b/client/src/Pages/Settings/SettingsAbout.jsx
index a80351393..6a8125af3 100644
--- a/client/src/Pages/Settings/SettingsAbout.jsx
+++ b/client/src/Pages/Settings/SettingsAbout.jsx
@@ -1,10 +1,10 @@
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
-import ConfigBox from "../../Components/ConfigBox";
+import ConfigBox from "@/Components/v1/ConfigBox/index.jsx";
// Utils
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
-import Link from "../../Components/Link";
+import Link from "@/Components/v1/Link/index.jsx";
const SettingsAbout = () => {
const theme = useTheme();
diff --git a/client/src/Pages/Settings/SettingsDemoMonitors.jsx b/client/src/Pages/Settings/SettingsDemoMonitors.jsx
index 13e93421e..47e699851 100644
--- a/client/src/Pages/Settings/SettingsDemoMonitors.jsx
+++ b/client/src/Pages/Settings/SettingsDemoMonitors.jsx
@@ -1,12 +1,12 @@
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
-import ConfigBox from "../../Components/ConfigBox";
+import ConfigBox from "@/Components/v1/ConfigBox/index.jsx";
// Utils
import { useTheme } from "@emotion/react";
import { PropTypes } from "prop-types";
import { useTranslation } from "react-i18next";
-import Dialog from "../../Components/Dialog";
+import Dialog from "@/Components/v1/Dialog/index.jsx";
import { useState } from "react";
const SettingsDemoMonitors = ({ isAdmin, HEADER_SX, handleChange, isLoading }) => {
diff --git a/client/src/Pages/Settings/SettingsEmail.jsx b/client/src/Pages/Settings/SettingsEmail.jsx
index 1f86b1984..6f0b02137 100644
--- a/client/src/Pages/Settings/SettingsEmail.jsx
+++ b/client/src/Pages/Settings/SettingsEmail.jsx
@@ -1,19 +1,19 @@
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
-import ConfigBox from "../../Components/ConfigBox";
-import TextInput from "../../Components/Inputs/TextInput";
+import ConfigBox from "@/Components/v1/ConfigBox/index.jsx";
+import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
import Button from "@mui/material/Button";
import Stack from "@mui/material/Stack";
import { Switch } from "@mui/material";
-import TextLink from "../../Components/TextLink";
+import TextLink from "@/Components/v1/TextLink/index.jsx";
// Utils
import { useTheme } from "@emotion/react";
import { PropTypes } from "prop-types";
import { useState } from "react";
import { useTranslation } from "react-i18next";
-import { PasswordEndAdornment } from "../../Components/Inputs/TextInput/Adornments";
-import { useSendTestEmail } from "../../Hooks/useSendTestEmail";
-import { createToast } from "../../Utils/toastUtils";
+import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx";
+import { useSendTestEmail } from "@/Hooks/useSendTestEmail.js";
+import { createToast } from "@/Utils/toastUtils.jsx";
const SettingsEmail = ({
isAdmin,
diff --git a/client/src/Pages/Settings/SettingsExport.jsx b/client/src/Pages/Settings/SettingsExport.jsx
new file mode 100644
index 000000000..fe04697f4
--- /dev/null
+++ b/client/src/Pages/Settings/SettingsExport.jsx
@@ -0,0 +1,65 @@
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import Button from "@mui/material/Button";
+import ConfigBox from "@/Components/v1/ConfigBox/index.jsx";
+// Utils
+import { useTheme } from "@emotion/react";
+import { PropTypes } from "prop-types";
+import { useTranslation } from "react-i18next";
+import Dialog from "@/Components/v1/Dialog/index.jsx";
+import { useState } from "react";
+
+const SettingsDemoMonitors = ({ isAdmin, HEADER_SX, handleChange, isLoading }) => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+ // Local state
+ const [isOpen, setIsOpen] = useState(false);
+
+ if (!isAdmin) {
+ return null;
+ }
+
+ return (
+ <>
+
+
+
+ Export monitors to JSON
+
+
+ Export your monitors data as a JSON file for backup or transfer.
+
+
+
+ {
+ const syntheticEvent = {
+ target: {
+ name: "export",
+ },
+ };
+ handleChange(syntheticEvent);
+ }}
+ sx={{ mt: theme.spacing(4) }}
+ >
+ Export Monitors to JSON
+
+
+
+ >
+ );
+};
+
+SettingsDemoMonitors.propTypes = {
+ isAdmin: PropTypes.bool,
+ handleChange: PropTypes.func,
+ HEADER_SX: PropTypes.object,
+};
+
+export default SettingsDemoMonitors;
diff --git a/client/src/Pages/Settings/SettingsGlobalThresholds.jsx b/client/src/Pages/Settings/SettingsGlobalThresholds.jsx
index acc101ab4..ea58478e2 100644
--- a/client/src/Pages/Settings/SettingsGlobalThresholds.jsx
+++ b/client/src/Pages/Settings/SettingsGlobalThresholds.jsx
@@ -1,8 +1,8 @@
import Box from "@mui/material/Box";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
-import ConfigBox from "../../Components/ConfigBox";
-import TextInput from "../../Components/Inputs/TextInput";
+import ConfigBox from "@/Components/v1/ConfigBox/index.jsx";
+import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
import { useTheme } from "@emotion/react";
import { PropTypes } from "prop-types";
diff --git a/client/src/Pages/Settings/SettingsPagespeed.jsx b/client/src/Pages/Settings/SettingsPagespeed.jsx
index 157c75dac..b0d5d6441 100644
--- a/client/src/Pages/Settings/SettingsPagespeed.jsx
+++ b/client/src/Pages/Settings/SettingsPagespeed.jsx
@@ -2,9 +2,9 @@ import Box from "@mui/material/Box";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
-import ConfigBox from "../../Components/ConfigBox";
-import TextInput from "../../Components/Inputs/TextInput";
-import { PasswordEndAdornment } from "../../Components/Inputs/TextInput/Adornments";
+import ConfigBox from "@/Components/v1/ConfigBox/index.jsx";
+import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
+import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx";
// Utils
import { useTheme } from "@emotion/react";
import { PropTypes } from "prop-types";
diff --git a/client/src/Pages/Settings/SettingsStats.jsx b/client/src/Pages/Settings/SettingsStats.jsx
index 740ac3a37..690b306be 100644
--- a/client/src/Pages/Settings/SettingsStats.jsx
+++ b/client/src/Pages/Settings/SettingsStats.jsx
@@ -2,9 +2,9 @@ import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import Stack from "@mui/material/Stack";
-import ConfigBox from "../../Components/ConfigBox";
-import TextInput from "../../Components/Inputs/TextInput";
-import Dialog from "../../Components/Dialog";
+import ConfigBox from "@/Components/v1/ConfigBox/index.jsx";
+import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
+import Dialog from "@/Components/v1/Dialog/index.jsx";
// Utils
import { useTheme } from "@emotion/react";
@@ -36,16 +36,6 @@ const SettingsStats = ({ isAdmin, HEADING_SX, handleChange, settingsData, errors
-
{t("settingsPage.statsSettings.clearAllStatsDescription")}
diff --git a/client/src/Pages/Settings/SettingsTimeZone.jsx b/client/src/Pages/Settings/SettingsTimeZone.jsx
index ee5cf48de..8ed7e5b3b 100644
--- a/client/src/Pages/Settings/SettingsTimeZone.jsx
+++ b/client/src/Pages/Settings/SettingsTimeZone.jsx
@@ -1,9 +1,9 @@
import Box from "@mui/material/Box";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
-import ConfigBox from "../../Components/ConfigBox";
-import Search from "../../Components/Inputs/Search";
-import timezones from "../../Utils/timezones.json";
+import ConfigBox from "@/Components/v1/ConfigBox/index.jsx";
+import Search from "@/Components/v1/Inputs/Search/index.jsx";
+import timezones from "@/Utils/timezones.json";
// Utils
import { useTheme } from "@emotion/react";
diff --git a/client/src/Pages/Settings/SettingsUI.jsx b/client/src/Pages/Settings/SettingsUI.jsx
index aab5f863d..ef9f44eaa 100644
--- a/client/src/Pages/Settings/SettingsUI.jsx
+++ b/client/src/Pages/Settings/SettingsUI.jsx
@@ -1,18 +1,22 @@
import Box from "@mui/material/Box";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
-import ConfigBox from "../../Components/ConfigBox";
-import Select from "../../Components/Inputs/Select";
+import ConfigBox from "@/Components/v1/ConfigBox/index.jsx";
+import Select from "@/Components/v1/Inputs/Select/index.jsx";
+import DummyChart from "./DummyChart";
+import { ThemeProvider } from "@mui/material/styles";
+import { lightTheme, darkTheme } from "@/Utils/Theme/v2Theme";
// Utils
import { useTheme } from "@emotion/react";
import { PropTypes } from "prop-types";
import { useTranslation } from "react-i18next";
-const SettingsUI = ({ HEADING_SX, handleChange, mode, language }) => {
+const SettingsUI = ({ HEADING_SX, handleChange, mode, language, chartType }) => {
const { t, i18n } = useTranslation();
const theme = useTheme();
const languages = Object.keys(i18n.options.resources || {});
+ const v2Theme = mode === "dark" ? darkTheme : lightTheme;
return (
@@ -44,6 +48,19 @@ const SettingsUI = ({ HEADING_SX, handleChange, mode, language }) => {
onChange={handleChange}
items={languages.map((lang) => ({ _id: lang, name: lang.toUpperCase() }))}
>
+
+
+
+
);
@@ -54,6 +71,7 @@ SettingsUI.propTypes = {
handleChange: PropTypes.func,
mode: PropTypes.string,
language: PropTypes.string,
+ chartType: PropTypes.string,
};
export default SettingsUI;
diff --git a/client/src/Pages/Settings/SettingsURL.jsx b/client/src/Pages/Settings/SettingsURL.jsx
index 24c49cad1..c52dcf317 100644
--- a/client/src/Pages/Settings/SettingsURL.jsx
+++ b/client/src/Pages/Settings/SettingsURL.jsx
@@ -1,8 +1,8 @@
import Box from "@mui/material/Box";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
-import ConfigBox from "../../Components/ConfigBox";
-import Select from "../../Components/Inputs/Select";
+import ConfigBox from "@/Components/v1/ConfigBox/index.jsx";
+import Select from "@/Components/v1/Inputs/Select/index.jsx";
// Utils
import { useTheme } from "@emotion/react";
diff --git a/client/src/Pages/Settings/index.jsx b/client/src/Pages/Settings/index.jsx
index 92f58b4c0..bd3513a2c 100644
--- a/client/src/Pages/Settings/index.jsx
+++ b/client/src/Pages/Settings/index.jsx
@@ -1,37 +1,51 @@
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
-import Breadcrumbs from "../../Components/Breadcrumbs";
-import SettingsTimeZone from "./SettingsTimeZone";
-import SettingsUI from "./SettingsUI";
-import SettingsURL from "./SettingsURL";
-import SettingsPagespeed from "./SettingsPagespeed";
-import SettingsDemoMonitors from "./SettingsDemoMonitors";
-import SettingsAbout from "./SettingsAbout";
-import SettingsEmail from "./SettingsEmail";
-import SettingsGlobalThresholds from "./SettingsGlobalThresholds";
+import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
+import SettingsTimeZone from "./SettingsTimeZone.jsx";
+import SettingsUI from "./SettingsUI.jsx";
+import SettingsURL from "./SettingsURL.jsx";
+import SettingsPagespeed from "./SettingsPagespeed.jsx";
+import SettingsDemoMonitors from "./SettingsDemoMonitors.jsx";
+import SettingsAbout from "./SettingsAbout.jsx";
+import SettingsEmail from "./SettingsEmail.jsx";
+import SettingsGlobalThresholds from "./SettingsGlobalThresholds.jsx";
+import SettingsExport from "./SettingsExport.jsx";
import Button from "@mui/material/Button";
// Utils
-import { settingsValidation } from "../../Validation/validation";
+import { settingsValidation } from "@/Validation/validation.js";
import { useState } from "react";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
import { useSelector, useDispatch } from "react-redux";
-import { setTimezone, setMode, setLanguage, setShowURL } from "../../Features/UI/uiSlice";
-import SettingsStats from "./SettingsStats";
+import {
+ setTimezone,
+ setMode,
+ setLanguage,
+ setShowURL,
+ setChartType,
+} from "@/Features/UI/uiSlice.js";
+import SettingsStats from "./SettingsStats.jsx";
-import { useFetchSettings, useSaveSettings } from "../../Hooks/settingsHooks";
-import { useIsAdmin } from "../../Hooks/useIsAdmin";
+import { useFetchSettings, useSaveSettings } from "@/Hooks/settingsHooks.js";
+import { useIsAdmin } from "@/Hooks/useIsAdmin.js";
import {
useAddDemoMonitors,
useDeleteAllMonitors,
useDeleteMonitorStats,
-} from "../../Hooks/monitorHooks";
+ useFetchJson,
+} from "@/Hooks/monitorHooks.js";
// Constants
const BREADCRUMBS = [{ name: `Settings`, path: "/settings" }];
const Settings = () => {
// Redux state
- const { mode, language, timezone, showURL } = useSelector((state) => state.ui);
+ const {
+ mode,
+ language = "en",
+ timezone,
+ showURL,
+ chartType = "histogram",
+ } = useSelector((state) => state.ui);
// Local state
const [settingsData, setSettingsData] = useState({});
@@ -61,6 +75,7 @@ const Settings = () => {
});
const [deleteAllMonitors, isDeletingMonitors] = useDeleteAllMonitors();
const [deleteMonitorStats, isDeletingMonitorStats] = useDeleteMonitorStats();
+ const [fetchJson, isFetchingJson] = useFetchJson();
// Setup
const isAdmin = useIsAdmin();
@@ -72,11 +87,6 @@ const Settings = () => {
const handleChange = async (e) => {
const { name, value, checked } = e.target;
- // Special case for showURL until handled properly in the backend
- if (name === "showURL") {
- dispatch(setShowURL(value));
- return;
- }
let newValue;
if (
name === "systemEmailIgnoreTLS" ||
@@ -87,6 +97,12 @@ const Settings = () => {
) {
newValue = checked;
}
+
+ // Ensure showURL is a proper boolean
+ if (name === "showURL") {
+ newValue = value === true || value === "true";
+ }
+
// Build next state early
const newSettingsData = {
...settingsData,
@@ -108,6 +124,11 @@ const Settings = () => {
return;
}
+ if (name === "chartType") {
+ dispatch(setChartType(value));
+ return;
+ }
+
if (name === "deleteStats") {
await deleteMonitorStats();
return;
@@ -123,38 +144,72 @@ const Settings = () => {
return;
}
- // Validate
- const { error } = settingsValidation.validate(newSettingsData.settings, {
- abortEarly: false,
- });
- if (!error || error.details.length === 0) {
- setErrors({});
- } else {
- const newErrors = {};
- error.details.forEach((err) => {
- newErrors[err.path[0]] = err.message;
+ if (name === "export") {
+ const json = await fetchJson();
+ if (!json || json.length === 0) {
+ return;
+ }
+
+ const blob = new Blob([JSON.stringify(json, null, 2)], {
+ type: "application/json",
});
- setErrors(newErrors);
+ const url = URL.createObjectURL(blob);
+
+ const link = document.createElement("a");
+ link.href = url;
+ link.download = "monitors.json";
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ URL.revokeObjectURL(url);
+ return;
}
setSettingsData(newSettingsData);
+
+ // Update Redux immediately for UI feedback
+ if (name === "showURL") {
+ dispatch(setShowURL(newValue));
+ }
};
const handleSave = () => {
- const { error } = settingsValidation.validate(settingsData.settings, {
+ // Validate]
+
+ const toSubmit = {
+ checkTTL: settingsData.settings.checkTTL,
+ pagespeedApiKey: settingsData.settings.pagespeedApiKey,
+ language: settingsData.settings.language,
+ timezone: settingsData.settings.timezone,
+ systemEmailHost: settingsData.settings.systemEmailHost,
+ systemEmailPort: settingsData.settings.systemEmailPort,
+ systemEmailSecure: settingsData.settings.systemEmailSecure,
+ systemEmailPool: settingsData.settings.systemEmailPool,
+ systemEmailAddress: settingsData.settings.systemEmailAddress,
+ systemEmailPassword: settingsData.settings.systemEmailPassword,
+ systemEmailUser: settingsData.settings.systemEmailUser,
+ systemEmailConnectionHost: settingsData.settings.systemEmailConnectionHost,
+ systemEmailTLSServername: settingsData.settings.systemEmailTLSServername,
+ systemEmailIgnoreTLS: settingsData.settings.systemEmailIgnoreTLS,
+ systemEmailRequireTLS: settingsData.settings.systemEmailRequireTLS,
+ systemEmailRejectUnauthorized: settingsData.settings.systemEmailRejectUnauthorized,
+ showURL: settingsData.settings.showURL,
+ globalThresholds: settingsData.settings.globalThresholds,
+ };
+
+ const { error } = settingsValidation.validate(toSubmit, {
abortEarly: false,
});
if (!error || error.details.length === 0) {
setErrors({});
+ saveSettings(toSubmit);
} else {
const newErrors = {};
error.details.forEach((err) => {
newErrors[err.path[0]] = err.message;
});
-
setErrors(newErrors);
}
- saveSettings(settingsData?.settings);
};
return (
@@ -171,6 +226,7 @@ const Settings = () => {
handleChange={handleChange}
mode={mode}
language={language}
+ chartType={chartType}
/>
{
setEmailPasswordHasBeenReset={setEmailPasswordHasBeenReset}
/>
+
-
+
{monitor.name}
- {
onDelete(monitor);
}}
@@ -43,7 +47,7 @@ const MonitorListItem = ({
const MonitorList = ({ selectedMonitors, setSelectedMonitors }) => {
const onDelete = (monitorToDelete) => {
const newMonitors = selectedMonitors.filter(
- (monitor) => monitor._id !== monitorToDelete._id
+ (monitor) => monitor.id !== monitorToDelete.id
);
setSelectedMonitors(newMonitors);
};
@@ -80,8 +84,8 @@ const MonitorList = ({ selectedMonitors, setSelectedMonitors }) => {
>
{selectedMonitors?.map((monitor, index) => (
{(provided, snapshot) => (
diff --git a/client/src/Pages/StatusPage/Create/Components/Progress/index.jsx b/client/src/Pages/StatusPage/Create/Components/Progress/index.jsx
index 14c4ce0f5..594fd9709 100644
--- a/client/src/Pages/StatusPage/Create/Components/Progress/index.jsx
+++ b/client/src/Pages/StatusPage/Create/Components/Progress/index.jsx
@@ -1,14 +1,19 @@
import { Button, Box } from "@mui/material";
-import ProgressUpload from "../../../../../Components/ProgressBars";
-import ImageIcon from "@mui/icons-material/Image";
+import ProgressUpload from "@/Components/v1/ProgressBars/index.jsx";
+import Icon from "@/Components/v1/Icon";
import { useTranslation } from "react-i18next";
-import { formatBytes } from "../../../../../Utils/fileUtils";
+import { formatBytes } from "../../../../../Utils/fileUtils.js";
const Progress = ({ isLoading, progressValue, logo, logoType, removeLogo, errors }) => {
const { t } = useTranslation();
if (isLoading) {
return (
}
+ icon={
+
+ }
label={logo?.name}
size={formatBytes(logo?.size)}
progress={progressValue}
diff --git a/client/src/Pages/StatusPage/Create/Components/Tabs/ConfigStack.jsx b/client/src/Pages/StatusPage/Create/Components/Tabs/ConfigStack.jsx
index e313f0e85..f31c456ef 100644
--- a/client/src/Pages/StatusPage/Create/Components/Tabs/ConfigStack.jsx
+++ b/client/src/Pages/StatusPage/Create/Components/Tabs/ConfigStack.jsx
@@ -1,5 +1,5 @@
import { Stack, Typography } from "@mui/material";
-import ConfigBox from "../../../../../Components/ConfigBox";
+import ConfigBox from "@/Components/v1/ConfigBox/index.jsx";
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
diff --git a/client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx b/client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx
index 327a2fb0a..e84de16dc 100644
--- a/client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx
+++ b/client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx
@@ -1,14 +1,14 @@
// Components
import { Stack, Typography } from "@mui/material";
import { TabPanel } from "@mui/lab";
-import MonitorList from "../MonitorList";
-import Search from "../../../../../Components/Inputs/Search";
-import Checkbox from "../../../../../Components/Inputs/Checkbox";
+import MonitorList from "../MonitorList/index.jsx";
+import Search from "@/Components/v1/Inputs/Search/index.jsx";
+import Checkbox from "@/Components/v1/Inputs/Checkbox/index.jsx";
// Utils
import { useState } from "react";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
-import ConfigStack from "./ConfigStack";
+import ConfigStack from "./ConfigStack.jsx";
const Content = ({
tabValue,
form,
@@ -24,7 +24,7 @@ const Content = ({
// Handlers
const handleMonitorsChange = (selectedMonitors) => {
handleFormChange({
- target: { name: "monitors", value: selectedMonitors.map((monitor) => monitor._id) },
+ target: { name: "monitors", value: selectedMonitors.map((monitor) => monitor.id) },
});
setSelectedMonitors(selectedMonitors);
};
diff --git a/client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx b/client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx
index dd4345158..d4c583d3a 100644
--- a/client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx
+++ b/client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx
@@ -1,13 +1,13 @@
// Components
import { Stack, Typography } from "@mui/material";
import { TabPanel } from "@mui/lab";
-import ConfigBox from "../../../../../Components/ConfigBox";
-import Checkbox from "../../../../../Components/Inputs/Checkbox";
-import TextInput from "../../../../../Components/Inputs/TextInput";
-import Search from "../../../../../Components/Inputs/Search";
-import ImageUpload from "../../../../../Components/Inputs/ImageUpload";
-import ColorPicker from "../../../../../Components/Inputs/ColorPicker";
-import Progress from "../Progress";
+import ConfigBox from "@/Components/v1/ConfigBox/index.jsx";
+import Checkbox from "@/Components/v1/Inputs/Checkbox/index.jsx";
+import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
+import Search from "@/Components/v1/Inputs/Search/index.jsx";
+import ImageUpload from "@/Components/v1/Inputs/ImageUpload/index.jsx";
+import ColorPicker from "@/Components/v1/Inputs/ColorPicker/index.jsx";
+import Progress from "../Progress/index.jsx";
// Utils
import { useTheme } from "@emotion/react";
diff --git a/client/src/Pages/StatusPage/Create/Components/Tabs/index.jsx b/client/src/Pages/StatusPage/Create/Components/Tabs/index.jsx
index 60d86e1c9..e8669c474 100644
--- a/client/src/Pages/StatusPage/Create/Components/Tabs/index.jsx
+++ b/client/src/Pages/StatusPage/Create/Components/Tabs/index.jsx
@@ -1,12 +1,12 @@
// Components
import { TabContext } from "@mui/lab";
import { Tab } from "@mui/material";
-import Settings from "./Settings";
-import Content from "./Content";
+import Settings from "./Settings.jsx";
+import Content from "./Content.jsx";
// Utils
import PropTypes from "prop-types";
-import CustomTabList from "../../../../../Components/Tab";
+import CustomTabList from "@/Components/v1/Tab/index.jsx";
const Tabs = ({
isCreate,
diff --git a/client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx b/client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx
index a2ac5f55d..256fa4be9 100644
--- a/client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx
+++ b/client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx
@@ -1,14 +1,15 @@
import { useState } from "react";
-import { networkService } from "../../../../main";
-import { createToast } from "../../../../Utils/toastUtils";
+import { networkService } from "../../../../main.jsx";
+import { createToast } from "../../../../Utils/toastUtils.jsx";
-const useCreateStatusPage = (isCreate, url) => {
+const useCreateStatusPage = (isCreate) => {
const [isLoading, setIsLoading] = useState(false);
const [networkError, setNetworkError] = useState(false);
- const createStatusPage = async ({ form }) => {
+ const createStatusPage = async ({ form, id }) => {
+ console.log(id);
setIsLoading(true);
try {
- await networkService.createStatusPage({ form, isCreate, url });
+ await networkService.createStatusPage({ form, isCreate, id });
return true;
} catch (error) {
setNetworkError(true);
diff --git a/client/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx b/client/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx
index f380bed9a..03ebb07bf 100644
--- a/client/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx
+++ b/client/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx
@@ -1,7 +1,7 @@
import { useEffect, useState } from "react";
-import { networkService } from "../../../../main";
+import { networkService } from "../../../../main.jsx";
import { useSelector } from "react-redux";
-import { createToast } from "../../../../Utils/toastUtils";
+import { createToast } from "../../../../Utils/toastUtils.jsx";
const useMonitorsFetch = () => {
const { user } = useSelector((state) => state.auth);
@@ -14,9 +14,9 @@ const useMonitorsFetch = () => {
try {
const response = await networkService.getMonitorsByTeamId({
limit: null, // donot return any checks for the monitors
- types: ["http", "ping", "port"], // status page is available for uptime, ping, and port monitors
+ types: ["http", "ping", "port", "game"], // include game servers in status page monitor selection
});
- setMonitors(response.data.data.monitors);
+ setMonitors(response.data.data);
} catch (error) {
setNetworkError(true);
createToast({ body: error.message });
diff --git a/client/src/Pages/StatusPage/Create/index.jsx b/client/src/Pages/StatusPage/Create/index.jsx
index d4cbc0fd5..4eb171232 100644
--- a/client/src/Pages/StatusPage/Create/index.jsx
+++ b/client/src/Pages/StatusPage/Create/index.jsx
@@ -1,23 +1,23 @@
// Components
import { Stack, Button, Typography } from "@mui/material";
-import Tabs from "./Components/Tabs";
-import GenericFallback from "../../../Components/GenericFallback";
-import SkeletonLayout from "./Components/Skeleton";
-import Dialog from "../../../Components/Dialog";
-import Breadcrumbs from "../../../Components/Breadcrumbs";
+import Tabs from "./Components/Tabs/index.jsx";
+import GenericFallback from "@/Components/v1/GenericFallback/index.jsx";
+import SkeletonLayout from "./Components/Skeleton/index.jsx";
+import Dialog from "@/Components/v1/Dialog/index.jsx";
+import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
//Utils
import { useTheme } from "@emotion/react";
import { useState, useEffect, useRef, useCallback } from "react";
-import { statusPageValidation } from "../../../Validation/validation";
-import { buildErrors } from "../../../Validation/error";
-import { useMonitorsFetch } from "./Hooks/useMonitorsFetch";
-import { useCreateStatusPage } from "./Hooks/useCreateStatusPage";
-import { createToast } from "../../../Utils/toastUtils";
+import { statusPageValidation } from "../../../Validation/validation.js";
+import { buildErrors } from "../../../Validation/error.js";
+import { useMonitorsFetch } from "./Hooks/useMonitorsFetch.jsx";
+import { useCreateStatusPage } from "./Hooks/useCreateStatusPage.jsx";
+import { createToast } from "../../../Utils/toastUtils.jsx";
import { useNavigate } from "react-router-dom";
-import { useStatusPageFetch } from "../Status/Hooks/useStatusPageFetch";
+import { useStatusPageFetch } from "../Status/Hooks/useStatusPageFetch.jsx";
import { useParams } from "react-router-dom";
import { useTranslation } from "react-i18next";
-import { useStatusPageDelete } from "../Status/Hooks/useStatusPageDelete";
+import { useStatusPageDelete } from "../Status/Hooks/useStatusPageDelete.jsx";
//Constants
const ERROR_TAB_MAPPING = [
["companyName", "url", "timezone", "color", "isPublished", "logo"],
@@ -54,15 +54,14 @@ const CreateStatusPage = () => {
//Utils
const theme = useTheme();
const [monitors, isLoading, networkError] = useMonitorsFetch();
- const [createStatusPage] = useCreateStatusPage(isCreate);
const navigate = useNavigate();
const { t } = useTranslation();
+ const [createStatusPage] = useCreateStatusPage(isCreate);
const [statusPage, statusPageMonitors, statusPageIsLoading, , fetchStatusPage] =
useStatusPageFetch(isCreate, url);
const [deleteStatusPage, isDeleting] = useStatusPageDelete(fetchStatusPage, url);
- console.log(JSON.stringify(form, null, 2));
// Handlers
const handleFormChange = (e) => {
let { type, name, value, checked } = e.target;
@@ -144,12 +143,13 @@ const CreateStatusPage = () => {
...form,
logo: { type: form.logo?.type ?? null, size: form.logo?.size ?? null },
};
+
const { error } = statusPageValidation.validate(toSubmit, {
abortEarly: false,
});
if (typeof error === "undefined") {
- const success = await createStatusPage({ form });
+ const success = await createStatusPage({ form, id: statusPage?.id });
if (success) {
createToast({
body: isCreate ? t("statusPage.createSuccess") : t("statusPage.updateSuccess"),
@@ -205,7 +205,7 @@ const CreateStatusPage = () => {
companyName: statusPage?.companyName,
isPublished: statusPage?.isPublished,
timezone: statusPage?.timezone,
- monitors: statusPageMonitors.map((monitor) => monitor._id),
+ monitors: statusPageMonitors.map((monitor) => monitor.id),
color: statusPage?.color,
logo: newLogo,
showCharts: statusPage?.showCharts ?? true,
diff --git a/client/src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx b/client/src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx
index beac3b0d2..fe1c4c852 100644
--- a/client/src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx
+++ b/client/src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx
@@ -1,9 +1,8 @@
// Components
import { Box, Stack, Typography, Button } from "@mui/material";
-import Image from "../../../../../Components/Image";
-import SettingsIcon from "../../../../../assets/icons/settings-bold.svg?react";
-import ThemeSwitch from "../../../../../Components/ThemeSwitch";
-import ArrowOutwardIcon from "@mui/icons-material/ArrowOutward";
+import Image from "@/Components/v1/Image/index.jsx";
+import Icon from "@/Components/v1/Icon";
+import ThemeSwitch from "@/Components/v1/ThemeSwitch/index.jsx";
//Utils
import { useTheme } from "@mui/material/styles";
import { useNavigate } from "react-router-dom";
@@ -46,7 +45,12 @@ const Controls = ({ url, type }) => {
},
}}
>
- {t("configure")}
+ {" "}
+ {t("configure")}
@@ -111,7 +115,10 @@ const ControlsHeader = ({ statusPage, isPublic, url, type = "uptime" }) => {
}}
>
{t("publicLink")}
-
+
)}
diff --git a/client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx b/client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx
index 545974c57..33f771233 100644
--- a/client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx
+++ b/client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx
@@ -1,12 +1,12 @@
// Components
import { Stack, Box } from "@mui/material";
-import Host from "../../../../../Components/Host";
-import StatusPageBarChart from "../../../../../Components/Charts/StatusPageBarChart";
-import { StatusLabel } from "../../../../../Components/Label";
+import Host from "@/Components/v1/Host/index.jsx";
+import StatusPageBarChart from "@/Components/v1/Charts/StatusPageBarChart/index.jsx";
+import { StatusLabel } from "@/Components/v1/Label/index.jsx";
//Utils
import { useTheme } from "@mui/material/styles";
-import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils";
+import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils.js";
import PropTypes from "prop-types";
import { useSelector } from "react-redux";
@@ -28,35 +28,31 @@ const MonitorsList = ({
const status = determineState(monitor);
return (
{statusPage.showCharts !== false && (
)}
-
-
-
);
diff --git a/client/src/Pages/StatusPage/Status/Components/StatusBar/index.jsx b/client/src/Pages/StatusPage/Status/Components/StatusBar/index.jsx
index 1898ff3d7..7b9460068 100644
--- a/client/src/Pages/StatusPage/Status/Components/StatusBar/index.jsx
+++ b/client/src/Pages/StatusPage/Status/Components/StatusBar/index.jsx
@@ -1,7 +1,6 @@
// Components
import { Stack, Typography } from "@mui/material";
-import CheckCircleIcon from "@mui/icons-material/CheckCircle";
-import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline";
+import Icon from "@/Components/v1/Icon";
// Utils
import { useTheme } from "@mui/material/styles";
@@ -10,8 +9,10 @@ import PropTypes from "prop-types";
const getMonitorStatus = (monitors, theme) => {
const monitorsStatus = {
icon: (
-
),
};
@@ -19,8 +20,10 @@ const getMonitorStatus = (monitors, theme) => {
monitorsStatus.msg = "All systems operational";
monitorsStatus.color = theme.palette.success.lowContrast;
monitorsStatus.icon = (
-
);
}
diff --git a/client/src/Pages/StatusPage/Status/Hooks/useStatusPageDelete.jsx b/client/src/Pages/StatusPage/Status/Hooks/useStatusPageDelete.jsx
index 2c39c70b3..73b9a8a5f 100644
--- a/client/src/Pages/StatusPage/Status/Hooks/useStatusPageDelete.jsx
+++ b/client/src/Pages/StatusPage/Status/Hooks/useStatusPageDelete.jsx
@@ -1,6 +1,6 @@
import { useState } from "react";
-import { networkService } from "../../../../Utils/NetworkService";
-import { createToast } from "../../../../Utils/toastUtils";
+import { networkService } from "../../../../Utils/NetworkService.js";
+import { createToast } from "../../../../Utils/toastUtils.jsx";
import { useTranslation } from "react-i18next";
/**
diff --git a/client/src/Pages/StatusPage/Status/Hooks/useStatusPageFetch.jsx b/client/src/Pages/StatusPage/Status/Hooks/useStatusPageFetch.jsx
index ae9c7b579..7db6b6c5e 100644
--- a/client/src/Pages/StatusPage/Status/Hooks/useStatusPageFetch.jsx
+++ b/client/src/Pages/StatusPage/Status/Hooks/useStatusPageFetch.jsx
@@ -1,9 +1,9 @@
import { useEffect, useState, useCallback } from "react";
-import { networkService } from "../../../../main";
+import { networkService } from "../../../../main.jsx";
import { useSelector } from "react-redux";
-import { createToast } from "../../../../Utils/toastUtils";
+import { createToast } from "../../../../Utils/toastUtils.jsx";
import { useTheme } from "@emotion/react";
-import { useMonitorUtils } from "../../../../Hooks/useMonitorUtils";
+import { useMonitorUtils } from "../../../../Hooks/useMonitorUtils.js";
const useStatusPageFetch = (isCreate = false, url) => {
const [isLoading, setIsLoading] = useState(true);
diff --git a/client/src/Pages/StatusPage/Status/index.jsx b/client/src/Pages/StatusPage/Status/index.jsx
index a77f2a380..adcd9c2c7 100644
--- a/client/src/Pages/StatusPage/Status/index.jsx
+++ b/client/src/Pages/StatusPage/Status/index.jsx
@@ -1,18 +1,18 @@
// Components
import { Typography, Stack } from "@mui/material";
-import GenericFallback from "../../../Components/GenericFallback";
-import AdminLink from "./Components/AdminLink";
-import ControlsHeader from "./Components/ControlsHeader";
-import SkeletonLayout from "./Components/Skeleton";
-import StatusBar from "./Components/StatusBar";
-import MonitorsList from "./Components/MonitorsList";
-import Breadcrumbs from "../../../Components/Breadcrumbs/index.jsx";
-import TextLink from "../../../Components/TextLink";
+import GenericFallback from "@/Components/v1/GenericFallback/index.jsx";
+import AdminLink from "./Components/AdminLink/index.jsx";
+import ControlsHeader from "./Components/ControlsHeader/index.jsx";
+import SkeletonLayout from "./Components/Skeleton/index.jsx";
+import StatusBar from "./Components/StatusBar/index.jsx";
+import MonitorsList from "./Components/MonitorsList/index.jsx";
+import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
+import TextLink from "@/Components/v1/TextLink/index.jsx";
// Utils
-import { useStatusPageFetch } from "./Hooks/useStatusPageFetch";
+import { useStatusPageFetch } from "./Hooks/useStatusPageFetch.jsx";
import { useTheme } from "@emotion/react";
-import { useIsAdmin } from "../../../Hooks/useIsAdmin";
+import { useIsAdmin } from "@/Hooks/useIsAdmin.js";
import { useLocation } from "react-router-dom";
import { useParams } from "react-router-dom";
import { useTranslation } from "react-i18next";
diff --git a/client/src/Pages/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx b/client/src/Pages/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx
index 04d7a6cfb..9882239bd 100644
--- a/client/src/Pages/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx
+++ b/client/src/Pages/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx
@@ -1,8 +1,8 @@
-import DataTable from "../../../../../Components/Table";
+import DataTable from "@/Components/v1/Table/index.jsx";
import { useTheme } from "@emotion/react";
import { useNavigate } from "react-router-dom";
-import { StatusLabel } from "../../../../../Components/Label";
-import ArrowOutwardIcon from "@mui/icons-material/ArrowOutward";
+import { StatusLabel } from "@/Components/v1/Label/index.jsx";
+import Icon from "@/Components/v1/Icon";
import { Stack, Typography } from "@mui/material";
import { useTranslation } from "react-i18next";
const StatusPagesTable = ({ data }) => {
@@ -48,7 +48,12 @@ const StatusPagesTable = ({ data }) => {
}}
>
{content}
- {row.isPublished && }
+ {row.isPublished && (
+
+ )}
);
},
diff --git a/client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx b/client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx
index b236368c8..9336e5d31 100644
--- a/client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx
+++ b/client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx
@@ -1,7 +1,7 @@
import { useState, useEffect } from "react";
-import { networkService } from "../../../../main";
+import { networkService } from "../../../../main.jsx";
import { useSelector } from "react-redux";
-import { createToast } from "../../../../Utils/toastUtils";
+import { createToast } from "../../../../Utils/toastUtils.jsx";
const useStatusPagesFetch = () => {
const { user } = useSelector((state) => state.auth);
@@ -14,7 +14,7 @@ const useStatusPagesFetch = () => {
const fetchStatusPages = async () => {
try {
const res = await networkService.getStatusPagesByTeamId();
- setStatusPages(res?.data?.data);
+ setStatusPages(res?.data?.data || []);
} catch (error) {
setNetworkError(true);
createToast({
diff --git a/client/src/Pages/StatusPage/StatusPages/index.jsx b/client/src/Pages/StatusPage/StatusPages/index.jsx
index fa26c2b81..7137d01ad 100644
--- a/client/src/Pages/StatusPage/StatusPages/index.jsx
+++ b/client/src/Pages/StatusPage/StatusPages/index.jsx
@@ -1,13 +1,13 @@
// Components
import { Stack } from "@mui/material";
-import Breadcrumbs from "../../../Components/Breadcrumbs";
-import MonitorCreateHeader from "../../../Components/MonitorCreateHeader";
-import StatusPagesTable from "./Components/StatusPagesTable";
-import PageStateWrapper from "../../../Components/PageStateWrapper";
+import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
+import MonitorCreateHeader from "@/Components/v1/MonitorCreateHeader/index.jsx";
+import StatusPagesTable from "./Components/StatusPagesTable/index.jsx";
+import PageStateWrapper from "@/Components/v1/PageStateWrapper/index.jsx";
// Utils
import { useTheme } from "@emotion/react";
-import { useStatusPagesFetch } from "./Hooks/useStatusPagesFetch";
-import { useIsAdmin } from "../../../Hooks/useIsAdmin";
+import { useStatusPagesFetch } from "./Hooks/useStatusPagesFetch.jsx";
+import { useIsAdmin } from "@/Hooks/useIsAdmin.js";
const BREADCRUMBS = [{ name: `Status Pages`, path: "" }];
const StatusPages = () => {
diff --git a/client/src/Pages/Uptime/BulkImport/index.jsx b/client/src/Pages/Uptime/BulkImport/index.jsx
index 9490b4eab..6f2f7bcb5 100644
--- a/client/src/Pages/Uptime/BulkImport/index.jsx
+++ b/client/src/Pages/Uptime/BulkImport/index.jsx
@@ -5,14 +5,14 @@ import { useState } from "react";
import { Box, Stack, Typography, Button, Link } from "@mui/material";
//Components
-import { createToast } from "../../../Utils/toastUtils";
-import Breadcrumbs from "../../../Components/Breadcrumbs";
-import ConfigBox from "../../../Components/ConfigBox";
-import UploadFile from "./Upload";
+import { createToast } from "../../../Utils/toastUtils.jsx";
+import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
+import ConfigBox from "@/Components/v1/ConfigBox/index.jsx";
+import UploadFile from "./Upload.jsx";
import { useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import { Trans, useTranslation } from "react-i18next";
-import { useCreateBulkMonitors } from "../../../Hooks/monitorHooks";
+import { useCreateBulkMonitors } from "../../../Hooks/monitorHooks.js";
const BulkImport = () => {
const theme = useTheme();
diff --git a/client/src/Pages/Uptime/Create/index.jsx b/client/src/Pages/Uptime/Create/index.jsx
index 3f905fa11..01fc9eb7a 100644
--- a/client/src/Pages/Uptime/Create/index.jsx
+++ b/client/src/Pages/Uptime/Create/index.jsx
@@ -9,31 +9,28 @@ import {
Tooltip,
Typography,
} from "@mui/material";
-import Breadcrumbs from "../../../Components/Breadcrumbs";
-import TextInput from "../../../Components/Inputs/TextInput";
-import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments";
-import Radio from "../../../Components/Inputs/Radio";
-import Select from "../../../Components/Inputs/Select";
-import ConfigBox from "../../../Components/ConfigBox";
-import NotificationsConfig from "../../../Components/NotificationConfig";
-import Checkbox from "../../../Components/Inputs/Checkbox";
-import Dialog from "../../../Components/Dialog";
-import PulseDot from "../../../Components/Animated/PulseDot";
-import SkeletonLayout from "./skeleton";
+import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
+import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
+import { HttpAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx";
+import Radio from "@/Components/v1/Inputs/Radio/index.jsx";
+import Select from "@/Components/v1/Inputs/Select/index.jsx";
+import ConfigBox from "@/Components/v1/ConfigBox/index.jsx";
+import NotificationsConfig from "@/Components/v1/NotificationConfig/index.jsx";
+import Checkbox from "@/Components/v1/Inputs/Checkbox/index.jsx";
+import Dialog from "@/Components/v1/Dialog/index.jsx";
+import PulseDot from "@/Components/v1/Animated/PulseDot.jsx";
+import SkeletonLayout from "./skeleton.jsx";
// Utils
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
-import { monitorValidation } from "../../../Validation/validation";
-import { createToast } from "../../../Utils/toastUtils";
-import {
- PauseOutlined as PauseOutlinedIcon,
- PlayArrowOutlined as PlayArrowOutlinedIcon,
-} from "@mui/icons-material";
-import { useMonitorUtils } from "../../../Hooks/useMonitorUtils";
-import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications";
+import { monitorValidation } from "../../../Validation/validation.js";
+import { createToast } from "../../../Utils/toastUtils.jsx";
+import Icon from "@/Components/v1/Icon";
+import { useMonitorUtils } from "../../../Hooks/useMonitorUtils.js";
+import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications.js";
import { useParams } from "react-router-dom";
import {
useCreateMonitor,
@@ -42,7 +39,7 @@ import {
usePauseMonitor,
useFetchMonitorById,
useFetchMonitorGames,
-} from "../../../Hooks/monitorHooks";
+} from "../../../Hooks/monitorHooks.js";
/**
* Create page renders monitor creation or configuration views.
@@ -83,13 +80,13 @@ const UptimeCreate = ({ isClone = false }) => {
const [isFetchingMonitor] = useFetchMonitorById({
monitorId,
setMonitor,
- updateTrigger: true,
+ updateTrigger,
});
// Fetch games
const [isFetchingGames] = useFetchMonitorGames({
setGames,
- triggerUpdate: true,
+ updateTrigger,
});
// Combine the loading states
@@ -107,11 +104,16 @@ const UptimeCreate = ({ isClone = false }) => {
// Constants
const MS_PER_MINUTE = 60000;
const FREQUENCIES = [
+ { _id: 0.25, name: t("time.fifteenSeconds") },
+ { _id: 0.5, name: t("time.thirtySeconds") },
{ _id: 1, name: t("time.oneMinute") },
{ _id: 2, name: t("time.twoMinutes") },
{ _id: 3, name: t("time.threeMinutes") },
{ _id: 4, name: t("time.fourMinutes") },
{ _id: 5, name: t("time.fiveMinutes") },
+ { _id: 10, name: t("time.tenMinutes") },
+ { _id: 15, name: t("time.fifteenMinutes") },
+ { _id: 30, name: t("time.thirtyMinutes") },
];
const GAMELIST = Object.entries(games).map(([key, value]) => ({
@@ -194,7 +196,7 @@ const UptimeCreate = ({ isClone = false }) => {
};
} else {
form = {
- _id: monitor._id,
+ id: monitor.id,
url: monitor.url,
name: monitor.name || monitor.url.substring(0, 50),
statusWindowSize: monitor.statusWindowSize,
@@ -281,13 +283,10 @@ const UptimeCreate = ({ isClone = false }) => {
}));
};
- const handlePause = async () => {
- await pauseMonitor({ monitorId, triggerUpdate });
- };
-
const handleRemove = async (event) => {
event.preventDefault();
- await deleteMonitor({ monitor, redirect: "/uptime" });
+ const TEMP_MONITOR = { id: monitor.id };
+ await deleteMonitor({ monitor: TEMP_MONITOR, redirect: "/uptime" });
};
const isBusy = isLoading || isCreating || isDeleting || isUpdating || isPausing;
@@ -423,9 +422,24 @@ const UptimeCreate = ({ isClone = false }) => {
color="secondary"
loading={isBusy}
startIcon={
- monitor?.isActive ? :
+ monitor?.isActive ? (
+
+ ) : (
+
+ )
}
- onClick={handlePause}
+ onClick={() => {
+ pauseMonitor({
+ monitorId: monitor?.id,
+ triggerUpdate,
+ });
+ }}
>
{monitor?.isActive ? t("pause") : t("resume")}
diff --git a/client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx b/client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx
deleted file mode 100644
index b60a4dd04..000000000
--- a/client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx
+++ /dev/null
@@ -1,147 +0,0 @@
-// Components
-import { Stack, Typography, Box } from "@mui/material";
-import ChartBox from "../../../../../Components/Charts/ChartBox";
-import UptimeIcon from "../../../../../assets/icons/uptime-icon.svg?react";
-import IncidentsIcon from "../../../../../assets/icons/incidents.svg?react";
-import AverageResponseIcon from "../../../../../assets/icons/average-response-icon.svg?react";
-import UpBarChart from "../Charts/UpBarChart";
-import DownBarChart from "../Charts/DownBarChart";
-import ResponseGaugeChart from "../Charts/ResponseGaugeChart";
-import SkeletonLayout from "./skeleton";
-// Utils
-import { formatDateWithTz } from "../../../../../Utils/timeUtils";
-import PropTypes from "prop-types";
-import { useTheme } from "@emotion/react";
-import { useState } from "react";
-
-const ChartBoxes = ({
- isLoading = false,
- monitorData,
- dateRange,
- uiTimezone,
- dateFormat,
-}) => {
- // Local state
- const [hoveredUptimeData, setHoveredUptimeData] = useState(null);
- const [hoveredIncidentsData, setHoveredIncidentsData] = useState(null);
- const theme = useTheme();
- if (isLoading) {
- return ;
- }
-
- const noIncidentsMessage = "Great. No Incidents, yet!";
-
- return (
-
- }
- header="Uptime"
- isEmpty={monitorData?.groupedUpChecks?.length === 0}
- >
-
-
- Total Checks
-
- {hoveredUptimeData !== null
- ? hoveredUptimeData.totalChecks
- : (monitorData?.groupedUpChecks?.reduce((count, checkGroup) => {
- return count + checkGroup.totalChecks;
- }, 0) ?? 0)}
-
- {hoveredUptimeData !== null && hoveredUptimeData.time !== null && (
-
- {formatDateWithTz(hoveredUptimeData._id, dateFormat, uiTimezone)}
-
- )}
-
-
-
- {hoveredUptimeData !== null ? "Avg Response Time" : "Uptime Percentage"}
-
-
- {hoveredUptimeData !== null
- ? Math.floor(hoveredUptimeData?.originalAvgResponseTime ?? 0)
- : Math.floor((monitorData?.groupedUptimePercentage ?? 0) * 100)}
-
- {hoveredUptimeData !== null ? " ms" : " %"}
-
-
-
-
-
-
- }
- header="Incidents"
- noDataMessage={noIncidentsMessage}
- isEmpty={monitorData?.groupedDownChecks?.length === 0}
- >
-
-
-
- {hoveredIncidentsData !== null
- ? hoveredIncidentsData.totalChecks
- : (monitorData?.groupedDownChecks?.reduce((count, checkGroup) => {
- return count + checkGroup.totalChecks;
- }, 0) ?? 0)}
-
- {hoveredIncidentsData !== null && hoveredIncidentsData.time !== null && (
-
- {formatDateWithTz(hoveredIncidentsData._id, dateFormat, uiTimezone)}
-
- )}
-
-
-
-
- }
- header="Average Response Time"
- >
-
-
-
- );
-};
-
-export default ChartBoxes;
-
-ChartBoxes.propTypes = {
- isLoading: PropTypes.bool,
- monitorData: PropTypes.object,
- dateRange: PropTypes.string.isRequired,
- uiTimezone: PropTypes.string.isRequired,
- dateFormat: PropTypes.string.isRequired,
- hoveredUptimeData: PropTypes.object,
- setHoveredUptimeData: PropTypes.func,
- hoveredIncidentsData: PropTypes.object,
- setHoveredIncidentsData: PropTypes.func,
-};
diff --git a/client/src/Pages/Uptime/Details/Components/ChartBoxes/skeleton.jsx b/client/src/Pages/Uptime/Details/Components/ChartBoxes/skeleton.jsx
deleted file mode 100644
index 750b3b20a..000000000
--- a/client/src/Pages/Uptime/Details/Components/ChartBoxes/skeleton.jsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { Skeleton, Stack } from "@mui/material";
-import { useTheme } from "@emotion/react";
-
-const SkeletonLayout = () => {
- const theme = useTheme();
- return (
-
-
-
-
-
- );
-};
-
-export default SkeletonLayout;
diff --git a/client/src/Pages/Uptime/Details/Components/Charts/CustomLabels.jsx b/client/src/Pages/Uptime/Details/Components/Charts/CustomLabels.jsx
deleted file mode 100644
index 8df33953f..000000000
--- a/client/src/Pages/Uptime/Details/Components/Charts/CustomLabels.jsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import PropTypes from "prop-types";
-import { useSelector } from "react-redux";
-import { formatDateWithTz } from "../../../../../Utils/timeUtils";
-
-const CustomLabels = ({ x, width, height, firstDataPoint, lastDataPoint, type }) => {
- const uiTimezone = useSelector((state) => state.ui.timezone);
- const dateFormat = type === "day" ? "MMM D, h:mm A" : "MMM D";
-
- return (
- <>
-
- {formatDateWithTz(firstDataPoint._id, dateFormat, uiTimezone)}
-
-
- {formatDateWithTz(lastDataPoint._id, dateFormat, uiTimezone)}
-
- >
- );
-};
-
-CustomLabels.propTypes = {
- x: PropTypes.number.isRequired,
- width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
- height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
- firstDataPoint: PropTypes.object,
- lastDataPoint: PropTypes.object,
- type: PropTypes.string.isRequired,
-};
-
-export default CustomLabels;
diff --git a/client/src/Pages/Uptime/Details/Components/Charts/DownBarChart.jsx b/client/src/Pages/Uptime/Details/Components/Charts/DownBarChart.jsx
deleted file mode 100644
index 662abc831..000000000
--- a/client/src/Pages/Uptime/Details/Components/Charts/DownBarChart.jsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import { memo, useState } from "react";
-import { useTheme } from "@mui/material";
-import { ResponsiveContainer, BarChart, XAxis, Bar, Cell } from "recharts";
-import PropTypes from "prop-types";
-import CustomLabels from "./CustomLabels";
-
-const DownBarChart = memo(({ groupedDownChecks = [], type, onBarHover }) => {
- const theme = useTheme();
-
- const [chartHovered, setChartHovered] = useState(false);
- const [hoveredBarIndex, setHoveredBarIndex] = useState(null);
-
- return (
-
- {
- setChartHovered(true);
- onBarHover({ time: null, totalChecks: 0 });
- }}
- onMouseLeave={() => {
- setChartHovered(false);
- setHoveredBarIndex(null);
- onBarHover(null);
- }}
- >
-
- }
- />
-
- {groupedDownChecks?.map((entry, index) => {
- return (
- | {
- setHoveredBarIndex(index);
- onBarHover(entry);
- }}
- onMouseLeave={() => {
- setHoveredBarIndex(null);
- onBarHover({ time: null, totalChecks: 0 });
- }}
- />
- );
- })}
- |
-
-
- );
-});
-
-DownBarChart.displayName = "DownBarChart";
-DownBarChart.propTypes = {
- monitor: PropTypes.shape({
- groupedDownChecks: PropTypes.arrayOf(PropTypes.object),
- }),
- type: PropTypes.string,
- onBarHover: PropTypes.func,
-};
-export default DownBarChart;
diff --git a/client/src/Pages/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx b/client/src/Pages/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx
deleted file mode 100644
index 084a711c3..000000000
--- a/client/src/Pages/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx
+++ /dev/null
@@ -1,118 +0,0 @@
-import PropTypes from "prop-types";
-import { useTheme } from "@mui/material";
-import { ResponsiveContainer, RadialBarChart, RadialBar, Cell } from "recharts";
-import { useTranslation } from "react-i18next";
-
-const ResponseGaugeChart = ({ avgResponseTime }) => {
- const theme = useTheme();
- const { t } = useTranslation();
- let max = 1000; // max ms
-
- const data = [
- { response: max, fill: "transparent", background: false },
- { response: avgResponseTime, background: true },
- ];
- let responseTime = Math.floor(avgResponseTime);
- let responseProps =
- responseTime <= 200
- ? {
- category: "Excellent",
- main: theme.palette.success.main,
- bg: theme.palette.success.lowContrast,
- }
- : responseTime <= 500
- ? {
- category: "Fair",
- main: theme.palette.success.main,
- bg: theme.palette.success.lowContrast,
- }
- : responseTime <= 600
- ? {
- category: "Acceptable",
- main: theme.palette.warning.main,
- bg: theme.palette.warning.lowContrast,
- }
- : {
- category: "Poor",
- main: theme.palette.error.main,
- bg: theme.palette.error.lowContrast,
- };
-
- return (
-
-
-
- {t("low")}
-
-
- {t("high")}
-
-
- {responseProps.category}
-
-
- {responseTime} ms
-
-
- |
- |
-
-
-
- );
-};
-
-ResponseGaugeChart.propTypes = {
- avgResponseTime: PropTypes.number.isRequired,
-};
-
-export default ResponseGaugeChart;
diff --git a/client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx b/client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx
deleted file mode 100644
index e2d8dea80..000000000
--- a/client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import ChartBox from "../../../../../Components/Charts/ChartBox";
-import MonitorDetailsAreaChart from "../../../../../Components/Charts/MonitorDetailsAreaChart";
-import ResponseTimeIcon from "../../../../../assets/icons/response-time-icon.svg?react";
-import SkeletonLayout from "./ResponseTimeChartSkeleton";
-import PropTypes from "prop-types";
-
-const ResponseTImeChart = ({ isLoading = false, groupedChecks = [], dateRange }) => {
- if (isLoading) {
- return ;
- }
-
- return (
- }
- header="Response Times"
- >
-
-
- );
-};
-
-ResponseTImeChart.propTypes = {
- isLoading: PropTypes.bool,
- groupedChecks: PropTypes.array,
- dateRange: PropTypes.string,
-};
-
-export default ResponseTImeChart;
diff --git a/client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx b/client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx
deleted file mode 100644
index a70757e36..000000000
--- a/client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import { Skeleton } from "@mui/material";
-const ResponseTimeChartSkeleton = () => {
- return (
-
- );
-};
-
-export default ResponseTimeChartSkeleton;
diff --git a/client/src/Pages/Uptime/Details/Components/Charts/UpBarChart.jsx b/client/src/Pages/Uptime/Details/Components/Charts/UpBarChart.jsx
deleted file mode 100644
index 317147b74..000000000
--- a/client/src/Pages/Uptime/Details/Components/Charts/UpBarChart.jsx
+++ /dev/null
@@ -1,107 +0,0 @@
-import { memo, useState } from "react";
-import { useTheme } from "@mui/material";
-import { ResponsiveContainer, BarChart, XAxis, Bar, Cell } from "recharts";
-import PropTypes from "prop-types";
-import CustomLabels from "./CustomLabels";
-
-const getThemeColor = (responseTime) => {
- if (responseTime < 200) {
- return "success";
- } else if (responseTime < 300) {
- return "warning";
- } else {
- return "error";
- }
-};
-
-const UpBarChart = memo(({ groupedUpChecks = [], type, onBarHover }) => {
- const theme = useTheme();
- const [chartHovered, setChartHovered] = useState(false);
- const [hoveredBarIndex, setHoveredBarIndex] = useState(null);
-
- return (
-
- {
- setChartHovered(true);
- onBarHover({ time: null, totalChecks: 0, originalAvgResponseTime: 0 });
- }}
- onMouseLeave={() => {
- setChartHovered(false);
- setHoveredBarIndex(null);
- onBarHover(null);
- }}
- >
-
- }
- />
-
- {groupedUpChecks?.map((entry, index) => {
- const themeColor = getThemeColor(entry.originalAvgResponseTime);
- return (
- | {
- setHoveredBarIndex(index);
- onBarHover(entry);
- }}
- onMouseLeave={() => {
- setHoveredBarIndex(null);
- onBarHover({
- time: null,
- totalChecks: 0,
- groupUptimePercentage: 0,
- });
- }}
- />
- );
- })}
- |
-
-
- );
-});
-
-// Add display name for the component
-UpBarChart.displayName = "UpBarChart";
-
-// Validate props using PropTypes
-UpBarChart.propTypes = {
- monitor: PropTypes.shape({
- groupedUpChecks: PropTypes.array,
- }),
- type: PropTypes.string,
- onBarHover: PropTypes.func,
-};
-export default UpBarChart;
diff --git a/client/src/Pages/Uptime/Details/Components/ChecksTable.tsx b/client/src/Pages/Uptime/Details/Components/ChecksTable.tsx
new file mode 100644
index 000000000..2cfdc1f15
--- /dev/null
+++ b/client/src/Pages/Uptime/Details/Components/ChecksTable.tsx
@@ -0,0 +1,100 @@
+import { Table, Pagination, StatusLabel } from "@/Components/v2/design-elements";
+import Box from "@mui/material/Box";
+import type { Header } from "@/Components/v2/design-elements/";
+import type { Check } from "@/Types/Check";
+
+import { useNavigate } from "react-router";
+import { useTranslation } from "react-i18next";
+import { formatDateWithTz } from "@/Utils/TimeUtils";
+import type { RootState } from "@/Types/state";
+import { useSelector } from "react-redux";
+
+const getHeaders = (t: Function, uiTimezone: string) => {
+ const headers: Header[] = [
+ {
+ id: "status",
+ content: t("common.table.headers.status"),
+ render: (row) => {
+ return ;
+ },
+ },
+ {
+ id: "date",
+ content: t("pages.checks.table.headers.dateTime"),
+ render: (row) => {
+ return formatDateWithTz(row.createdAt, "ddd, MMMM D, YYYY, HH:mm A", uiTimezone);
+ },
+ },
+ {
+ id: "message",
+ content: t("common.table.headers.message"),
+ render: (row) => {
+ return row.message || "N/A";
+ },
+ },
+ {
+ id: "statusCode",
+ content: t("pages.checks.table.headers.statusCode"),
+ render: (row) => {
+ return row.statusCode || "N/A";
+ },
+ },
+ ];
+ return headers;
+};
+
+export const ChecksTable = ({
+ checks,
+ count,
+ page,
+ setPage,
+ rowsPerPage,
+ setRowsPerPage,
+}: {
+ checks: Check[];
+ count: number;
+ page: number;
+ setPage: (page: number) => void;
+ rowsPerPage: number;
+ setRowsPerPage: (rowsPerPage: number) => void;
+}) => {
+ const navigate = useNavigate();
+ const { t } = useTranslation();
+ const uiTimezone = useSelector((state: RootState) => state.ui.timezone);
+ const headers = getHeaders(t, uiTimezone);
+
+ const handlePageChange = (
+ _e: React.MouseEvent | null,
+ newPage: number
+ ) => {
+ setPage(newPage);
+ };
+
+ const handleRowsPerPageChange = (
+ e: React.ChangeEvent
+ ) => {
+ const value = Number(e.target.value);
+ setPage(0);
+ setRowsPerPage(value);
+ };
+
+ return (
+
+ {
+ navigate(`/checks/${row.id}`);
+ }}
+ />
+
+
+ );
+};
diff --git a/client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx b/client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx
deleted file mode 100644
index 4d482aa07..000000000
--- a/client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx
+++ /dev/null
@@ -1,91 +0,0 @@
-import ChartBox from "../../../../../Components/Charts/ChartBox";
-import PropTypes from "prop-types";
-import HistoryIcon from "../../../../../assets/icons/history-icon.svg?react";
-import Table from "../../../../../Components/Table";
-import TablePagination from "../../../../../Components/Table/TablePagination";
-import { StatusLabel } from "../../../../../Components/Label";
-import { useTranslation } from "react-i18next";
-import { formatDateWithTz } from "../../../../../Utils/timeUtils";
-import SkeletonLayout from "./skeleton";
-const ResponseTable = ({
- isLoading = false,
- checks = [],
- checksCount,
- uiTimezone,
- page,
- setPage,
- rowsPerPage,
- setRowsPerPage,
-}) => {
- const { t } = useTranslation();
- if (isLoading) {
- return ;
- }
-
- const headers = [
- {
- id: "status",
- content: t("status"),
- render: (row) => {
- const status = row.status === true ? "up" : "down";
-
- return (
-
- );
- },
- },
- {
- id: "date",
- content: t("date&Time"),
- render: (row) =>
- formatDateWithTz(row.createdAt, "ddd, MMMM D, YYYY, HH:mm A", uiTimezone),
- },
- {
- id: "statusCode",
- content: t("statusCode"),
- render: (row) => (row.statusCode ? row.statusCode : "N/A"),
- },
- {
- id: "message",
- content: t("message"),
- render: (row) => row.message,
- },
- ];
-
- return (
- }
- header="Response Times"
- height="100%"
- >
-
-
-
- );
-};
-
-ResponseTable.propTypes = {
- isLoading: PropTypes.bool,
- checks: PropTypes.array,
- checksCount: PropTypes.number,
- uiTimezone: PropTypes.string.isRequired,
- page: PropTypes.number.isRequired,
- setPage: PropTypes.func.isRequired,
- rowsPerPage: PropTypes.number.isRequired,
- setRowsPerPage: PropTypes.func.isRequired,
-};
-
-export default ResponseTable;
diff --git a/client/src/Pages/Uptime/Details/Components/ResponseTable/skeleton.jsx b/client/src/Pages/Uptime/Details/Components/ResponseTable/skeleton.jsx
deleted file mode 100644
index 2665ffaf1..000000000
--- a/client/src/Pages/Uptime/Details/Components/ResponseTable/skeleton.jsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import { Skeleton } from "@mui/material";
-
-const SkeletonLayout = () => {
- return (
-
- );
-};
-
-export default SkeletonLayout;
diff --git a/client/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx b/client/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx
deleted file mode 100644
index abeb251fe..000000000
--- a/client/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx
+++ /dev/null
@@ -1,81 +0,0 @@
-import StatusBoxes from "../../../../../Components/StatusBoxes";
-import StatBox from "../../../../../Components/StatBox";
-
-import PropTypes from "prop-types";
-import { getHumanReadableDuration } from "../../../../../Utils/timeUtils";
-import { useTheme } from "@mui/material/styles";
-import { Typography } from "@mui/material";
-import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils";
-
-const UptimeStatusBoxes = ({
- isLoading = false,
- monitor,
- monitorStats,
- certificateExpiry,
-}) => {
- const theme = useTheme();
- const { determineState } = useMonitorUtils();
-
- // Determine time since last failure
- const timeOfLastFailure = monitorStats?.timeOfLastFailure;
- const timeSinceLastFailure = timeOfLastFailure > 0 ? Date.now() - timeOfLastFailure : 0;
-
- // Determine time since last check
- const timeOfLastCheck = monitorStats?.lastCheckTimestamp;
- const timeSinceLastCheck = Date.now() - timeOfLastCheck;
-
- const streakTime = getHumanReadableDuration(timeSinceLastFailure);
-
- const lastCheckTime = getHumanReadableDuration(timeSinceLastCheck);
- return (
-
-
-
- {lastCheckTime}
- {"ago"}
- >
- }
- />
-
- {monitorStats?.lastResponseTime}
- {"ms"}
- >
- }
- />
- {monitor?.type === "http" && (
-
- {certificateExpiry}
-
- }
- />
- )}
-
- );
-};
-
-UptimeStatusBoxes.propTypes = {
- isLoading: PropTypes.bool,
- monitor: PropTypes.object,
- monitorStats: PropTypes.object,
- certificateExpiry: PropTypes.string,
-};
-
-export default UptimeStatusBoxes;
diff --git a/client/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx b/client/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx
index ad0db6f70..8912438d8 100644
--- a/client/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx
+++ b/client/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx
@@ -1,7 +1,7 @@
-import { logger } from "../../../../Utils/Logger";
+import { logger } from "../../../../Utils/Logger.js";
import { useEffect, useState } from "react";
-import { networkService } from "../../../../main";
-import { formatDateWithTz } from "../../../../Utils/timeUtils";
+import { networkService } from "../../../../main.jsx";
+import { formatDateWithTz } from "../../../../Utils/timeUtilsLegacy.js";
const useCertificateFetch = ({
monitor,
diff --git a/client/src/Pages/Uptime/Details/index.tsx b/client/src/Pages/Uptime/Details/index.tsx
new file mode 100644
index 000000000..1a6177ddc
--- /dev/null
+++ b/client/src/Pages/Uptime/Details/index.tsx
@@ -0,0 +1,166 @@
+import { BasePage } from "@/Components/v2/design-elements";
+import { HeaderMonitorControls, HeaderTimeRange } from "@/Components/v2/common";
+import Stack from "@mui/material/Stack";
+import {
+ HistogramStatus,
+ RadialAvgResponse,
+ HistogramDetails,
+} from "@/Components/v2/monitors";
+import { TrendingUp, AlertTriangle } from "lucide-react";
+import { ChecksTable } from "@/Pages/Uptime/Details/Components/ChecksTable";
+
+import { useTheme } from "@mui/material/styles";
+import { useIsAdmin } from "@/Hooks/useIsAdmin";
+import { useState, useMemo } from "react";
+import { useParams } from "react-router-dom";
+import { useSelector } from "react-redux";
+import { useGet } from "@/Hooks/UseApi";
+import type { MonitorDetailsResponse } from "@/Types/Monitor";
+import type { ChecksResponse } from "@/Types/Check";
+import type { RootState } from "@/Types/state";
+import { MonitorStatBoxes } from "@/Components/v2/monitors";
+import { formatDateWithTz } from "@/Utils/timeUtilsLegacy";
+import { t } from "i18next";
+
+const certificateDateFormat = "MMM D, YYYY h A";
+
+interface CertificateResponse {
+ certificateDate: string;
+}
+
+const UptimeDetailsPage = () => {
+ const theme = useTheme();
+ const isAdmin = useIsAdmin();
+ const { monitorId } = useParams<{ monitorId: string }>();
+ const uiTimezone = useSelector((state: RootState) => state.ui.timezone);
+
+ const [page, setPage] = useState(0);
+ const [rowsPerPage, setRowsPerPage] = useState(5);
+ const [dateRange, setDateRange] = useState("recent");
+
+ const monitorDetailsUrl = useMemo(() => {
+ if (!monitorId) {
+ return null;
+ }
+ const params = new URLSearchParams();
+ params.append("dateRange", dateRange);
+ params.append("normalize", "true");
+ return `/monitors/uptime/details/${monitorId}?${params.toString()}`;
+ }, [monitorId, dateRange]);
+
+ const {
+ data: monitorDetailsData,
+ isLoading: monitorIsLoading,
+ refetch: refetchMonitor,
+ } = useGet(
+ monitorDetailsUrl,
+ {},
+ { refreshInterval: 10000, keepPreviousData: true }
+ );
+
+ const monitorData = monitorDetailsData?.monitorData;
+ const monitor = monitorData?.monitor;
+ const monitorStats = monitorDetailsData?.monitorStats ?? null;
+
+ // Certificate fetch - only for HTTP monitors
+ const certificateUrl = useMemo(() => {
+ if (!monitorId || monitor?.type !== "http") {
+ return null;
+ }
+ return `/monitors/certificate/${monitorId}`;
+ }, [monitorId, monitor?.type]);
+
+ const { data: certificateData } = useGet(certificateUrl);
+
+ const certificateExpiry = useMemo(() => {
+ if (!certificateData?.certificateDate) {
+ return undefined;
+ }
+ return (
+ formatDateWithTz(
+ certificateData.certificateDate,
+ certificateDateFormat,
+ uiTimezone
+ ) ?? "N/A"
+ );
+ }, [certificateData, uiTimezone]);
+
+ const checksUrl = useMemo(() => {
+ if (!monitorId || !monitor?.type) {
+ return null;
+ }
+ const params = new URLSearchParams();
+ params.append("type", monitor.type);
+ params.append("sortOrder", "desc");
+ params.append("dateRange", dateRange);
+ params.append("page", String(page));
+ params.append("rowsPerPage", String(rowsPerPage));
+ return `/checks/${monitorId}?${params.toString()}`;
+ }, [monitorId, monitor?.type, dateRange, page, rowsPerPage]);
+
+ const { data: checksData, isLoading: checksIsLoading } = useGet(
+ checksUrl,
+ {},
+ { keepPreviousData: true }
+ );
+
+ const checks = checksData?.checks ?? [];
+ const checksCount = checksData?.checksCount ?? 0;
+
+ return (
+
+
+
+
+
+ }
+ checks={monitorData?.groupedUpChecks ?? []}
+ range={dateRange}
+ />
+ }
+ checks={monitorData?.groupedDownChecks ?? []}
+ range={dateRange}
+ />
+
+
+
+
+
+ );
+};
+
+export default UptimeDetailsPage;
diff --git a/client/src/Pages/Uptime/Details/index.jsx b/client/src/Pages/Uptime/Details/old.jsx
similarity index 87%
rename from client/src/Pages/Uptime/Details/index.jsx
rename to client/src/Pages/Uptime/Details/old.jsx
index c72a5756f..144ceb4dc 100644
--- a/client/src/Pages/Uptime/Details/index.jsx
+++ b/client/src/Pages/Uptime/Details/old.jsx
@@ -1,12 +1,12 @@
// Components
-import Breadcrumbs from "../../../Components/Breadcrumbs";
-import MonitorDetailsControlHeader from "../../../Components/MonitorDetailsControlHeader";
-import MonitorTimeFrameHeader from "../../../Components/MonitorTimeFrameHeader";
-import ChartBoxes from "./Components/ChartBoxes";
-import ResponseTimeChart from "./Components/Charts/ResponseTimeChart";
-import ResponseTable from "./Components/ResponseTable";
-import UptimeStatusBoxes from "./Components/UptimeStatusBoxes";
-import GenericFallback from "../../../Components/GenericFallback";
+import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
+import MonitorDetailsControlHeader from "@/Components/v1/MonitorDetailsControlHeader/index.jsx";
+import MonitorTimeFrameHeader from "@/Components/v1/MonitorTimeFrameHeader/index.jsx";
+import ChartBoxes from "./Components/ChartBoxes/index.jsx";
+import ResponseTimeChart from "./Components/Charts/ResponseTimeChart.jsx";
+import ResponseTable from "./Components/ResponseTable/index.jsx";
+import UptimeStatusBoxes from "./Components/UptimeStatusBoxes/index.jsx";
+import GenericFallback from "@/Components/v1/GenericFallback/index.jsx";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
@@ -15,10 +15,10 @@ import { useState } from "react";
import { useParams } from "react-router-dom";
import { useSelector } from "react-redux";
import { useTheme } from "@emotion/react";
-import { useIsAdmin } from "../../../Hooks/useIsAdmin";
-import { useFetchUptimeMonitorById } from "../../../Hooks/monitorHooks";
-import useCertificateFetch from "./Hooks/useCertificateFetch";
-import { useFetchChecksByMonitor } from "../../../Hooks/checkHooks";
+import { useIsAdmin } from "@/Hooks/useIsAdmin.js";
+import { useFetchUptimeMonitorById } from "../../../Hooks/monitorHooks.js";
+import useCertificateFetch from "./Hooks/useCertificateFetch.jsx";
+import { useFetchChecksByMonitor } from "../../../Hooks/checkHooks.js";
import { useTranslation } from "react-i18next";
// Constants
diff --git a/client/src/Pages/Uptime/Monitors/Components/Filter/index.jsx b/client/src/Pages/Uptime/Monitors/Components/Filter/index.jsx
index 0efb06b6c..62ba0be13 100644
--- a/client/src/Pages/Uptime/Monitors/Components/Filter/index.jsx
+++ b/client/src/Pages/Uptime/Monitors/Components/Filter/index.jsx
@@ -1,9 +1,9 @@
import { useTheme } from "@emotion/react";
import PropTypes from "prop-types";
-import FilterHeader from "../../../../../Components/FilterHeader";
+import FilterHeader from "@/Components/v1/FilterHeader/index.jsx";
import { useMemo } from "react";
import { Box, Button } from "@mui/material";
-import ClearIcon from "@mui/icons-material/Clear";
+import Icon from "@/Components/v1/Icon";
import { useTranslation } from "react-i18next";
/**
@@ -131,7 +131,12 @@ const Filter = ({
color={theme.palette.primary.contrastText}
onClick={handleReset}
variant="contained"
- endIcon={ }
+ endIcon={
+
+ }
sx={{
visibility: isFilterActive ? "visible" : "hidden",
}}
diff --git a/client/src/Pages/Uptime/Monitors/Components/SearchComponent/index.jsx b/client/src/Pages/Uptime/Monitors/Components/SearchComponent/index.jsx
index 0c6eec854..64c546a8a 100644
--- a/client/src/Pages/Uptime/Monitors/Components/SearchComponent/index.jsx
+++ b/client/src/Pages/Uptime/Monitors/Components/SearchComponent/index.jsx
@@ -1,7 +1,7 @@
import { useState } from "react";
-import Search from "../../../../../Components/Inputs/Search";
+import Search from "@/Components/v1/Inputs/Search/index.jsx";
import { Box } from "@mui/material";
-import useDebounce from "../../Hooks/useDebounce";
+import useDebounce from "../../Hooks/useDebounce.jsx";
import { useEffect, useRef } from "react";
import PropTypes from "prop-types";
diff --git a/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/index.jsx b/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/index.jsx
index 9cd29c4ca..5cba9e3b1 100644
--- a/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/index.jsx
+++ b/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/index.jsx
@@ -1,9 +1,9 @@
import PropTypes from "prop-types";
import { Stack } from "@mui/material";
-import StatusBox from "./statusBox";
+import StatusBox from "./statusBox.jsx";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
-import SkeletonLayout from "./skeleton";
+import SkeletonLayout from "./skeleton.jsx";
const StatusBoxes = ({ shouldRender, monitorsSummary }) => {
const theme = useTheme();
diff --git a/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx b/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx
index 78d555409..136c3575d 100644
--- a/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx
+++ b/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx
@@ -1,9 +1,8 @@
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { Box, Stack, Typography } from "@mui/material";
-import Arrow from "../../../../../assets/icons/top-right-arrow.svg?react";
-import Background from "../../../../../assets/Images/background-grid.svg?react";
-import ClockSnooze from "../../../../../assets/icons/clock-snooze.svg?react";
+import Icon from "@/Components/v1/Icon";
+import Background from "@/assets/Images/background-grid.svg?react";
const StatusBox = ({ title, value, status }) => {
const theme = useTheme();
@@ -20,21 +19,30 @@ const StatusBox = ({ title, value, status }) => {
color = theme.palette.success.lowContrast;
icon = (
-
+
);
} else if (status === "down") {
color = theme.palette.error.lowContrast;
icon = (
-
+
);
} else if (status === "paused") {
color = theme.palette.warning.lowContrast;
icon = (
-
+
);
}
diff --git a/client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx b/client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx
index 63a40f85a..8c3239dd3 100644
--- a/client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx
+++ b/client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx
@@ -1,19 +1,18 @@
// Components
import { Box, Stack } from "@mui/material";
-import DataTable from "../../../../../Components/Table";
-import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded";
-import ArrowUpwardRoundedIcon from "@mui/icons-material/ArrowUpwardRounded";
-import Host from "../../../../../Components/Host";
-import { StatusLabel } from "../../../../../Components/Label";
-import BarChart from "../../../../../Components/Charts/BarChart";
-import ActionsMenu from "../../../../../Components/ActionsMenu";
+import DataTable from "@/Components/v1/Table/index.jsx";
+import Icon from "@/Components/v1/Icon";
+import Host from "@/Components/v1/Host/index.jsx";
+import { StatusLabel } from "@/Components/v1/Label/index.jsx";
+import BarChart from "@/Components/v1/Charts/BarChart/index.jsx";
+import ActionsMenu from "@/Components/v1/ActionsMenu/index.jsx";
-import LoadingSpinner from "../LoadingSpinner";
-import TableSkeleton from "../../../../../Components/Table/skeleton";
+import LoadingSpinner from "../LoadingSpinner/index.jsx";
+import TableSkeleton from "@/Components/v1/Table/skeleton.jsx";
// Utils
import { useTheme } from "@emotion/react";
-import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils";
+import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils.js";
import { useNavigate } from "react-router-dom";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
@@ -24,13 +23,13 @@ import { useTranslation } from "react-i18next";
* @param {boolean} props.isAdmin - Whether the current user has admin privileges
* @param {boolean} props.isLoading - Loading state of the table
* @param {Array<{
- * _id: string,
+ * id: string,
* url: string,
* title: string,
* percentage: number,
* percentageColor: string,
* monitor: {
- * _id: string,
+ * id: string,
* type: string,
* checks: Array
* }
@@ -92,16 +91,22 @@ const UptimeDataTable = ({
}}
>
{sort?.order === "asc" ? (
-
+
) : (
-
+
)}
),
render: (row) => (
{sort?.order === "asc" ? (
-
+
) : (
-
+
)}
@@ -200,7 +211,7 @@ const UptimeDataTable = ({
},
},
onRowClick: (row) => {
- navigate(`/uptime/${row._id}`);
+ navigate(`/uptime/${row.id}`);
},
emptyView: "No monitors found",
}}
diff --git a/client/src/Pages/Uptime/Monitors/Components/UptimeMonitorsTable.tsx b/client/src/Pages/Uptime/Monitors/Components/UptimeMonitorsTable.tsx
new file mode 100644
index 000000000..c7f5c4c7f
--- /dev/null
+++ b/client/src/Pages/Uptime/Monitors/Components/UptimeMonitorsTable.tsx
@@ -0,0 +1,259 @@
+import Stack from "@mui/material/Stack";
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import {
+ Table,
+ type Header,
+ Pagination,
+ StatusLabel,
+} from "@/Components/v2/design-elements";
+import { HeatmapResponseTime, HistogramResponseTime } from "@/Components/v2/common";
+import { ActionsMenu } from "@/Components/v2/actions-menu";
+import { ArrowDown, ArrowUp } from "lucide-react";
+
+import { useTranslation } from "react-i18next";
+import { useTheme } from "@mui/material/styles";
+import { useNavigate } from "react-router-dom";
+import { usePost } from "@/Hooks/UseApi";
+import { useSelector } from "react-redux";
+
+import type { Monitor } from "@/Types/Monitor";
+import type { ActionMenuItem } from "@/Components/v2/actions-menu";
+import type { RootState } from "@/Types/state";
+
+export const MonitorTable = ({
+ monitors,
+ refetch,
+ setSelectedMonitor,
+ sortField,
+ setSortField,
+ sortOrder,
+ setSortOrder,
+ count,
+ page,
+ setPage,
+ rowsPerPage,
+ setRowsPerPage,
+}: {
+ monitors: Monitor[];
+ refetch: Function;
+ setSelectedMonitor: (monitor: Monitor | null) => void;
+ sortField: string;
+ setSortField: (field: string) => void;
+ sortOrder: "asc" | "desc";
+ setSortOrder: (order: "asc" | "desc") => void;
+ count: number;
+ page: number;
+ setPage: (page: number) => void;
+ rowsPerPage: number;
+ setRowsPerPage: (rowsPerPage: number) => void;
+}) => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+ const navigate = useNavigate();
+ const chartType = useSelector((state: RootState) => state.ui?.chartType ?? "histogram");
+ const {
+ post,
+ // loading: isPosting,
+ // error: postError,
+ } = usePost();
+
+ const handleSort = (e: any, field: string) => {
+ e.preventDefault();
+ e.stopPropagation();
+ if (sortField === field) {
+ const newOrder = sortOrder === "asc" ? "desc" : "asc";
+ setSortOrder(newOrder);
+ } else {
+ setSortField(field);
+ setSortOrder("asc");
+ }
+ refetch();
+ };
+
+ const getActions = (monitor: Monitor): ActionMenuItem[] => {
+ return [
+ {
+ id: 1,
+ label: t("pages.common.monitors.actions.openSite"),
+ action: () => {
+ window.open(monitor.url, "_blank", "noreferrer");
+ },
+ closeMenu: true,
+ },
+ {
+ id: 2,
+ label: t("pages.common.monitors.actions.details"),
+ action: () => {
+ navigate(`${monitor.id}`);
+ },
+ },
+ {
+ id: 3,
+ label: t("pages.common.monitors.actions.incidents"),
+ action: () => {
+ navigate(`/incidents?monitorId=${monitor.id}`);
+ },
+ },
+ {
+ id: 4,
+ label: t("pages.common.monitors.actions.configure"),
+ action: () => {
+ navigate(`/uptime/configure/${monitor.id}`);
+ },
+ },
+ // {
+ // id: 5,
+ // label: "Clone",
+ // action: () => {
+
+ // },
+ // },
+ {
+ id: 6,
+ label:
+ monitor.isActive === false
+ ? t("pages.common.monitors.actions.resume")
+ : t("pages.common.monitors.actions.pause"),
+ action: async () => {
+ await post(`/monitors/pause/${monitor.id}`, {});
+ refetch();
+ },
+ closeMenu: true,
+ },
+ {
+ id: 7,
+ label: (
+
+ {t("pages.common.monitors.actions.delete")}
+
+ ),
+ action: () => setSelectedMonitor(monitor),
+ closeMenu: true,
+ },
+ ];
+ };
+
+ const getHeaders = (chartType: string) => {
+ const renderSortIcon = (isActive: boolean) => (
+
+ {isActive ? (
+ sortOrder === "asc" ? (
+
+ ) : (
+
+ )
+ ) : null}
+
+ );
+ const headers: Header[] = [
+ {
+ id: "name",
+ content: (
+ handleSort(e, "name")}
+ sx={{ cursor: "pointer" }}
+ >
+ {t("common.table.headers.name")}
+ {renderSortIcon(sortField === "name")}
+
+ ),
+
+ render: (row) => {
+ return row?.name;
+ },
+ },
+ {
+ id: "status",
+ content: (
+ handleSort(e, "status")}
+ sx={{ cursor: "pointer" }}
+ >
+ {t("common.table.headers.status")}
+ {renderSortIcon(sortField === "status")}
+
+ ),
+ render: (row) => {
+ return (
+
+ );
+ },
+ },
+ {
+ id: "histogram",
+ content: t("pages.uptime.table.headers.responseTime"),
+ render: (row) => {
+ if (chartType === "histogram") {
+ return ;
+ } else {
+ return ;
+ }
+ },
+ },
+ {
+ id: "type",
+ content: (
+ handleSort(e, "type")}
+ sx={{ cursor: "pointer" }}
+ >
+ {t("common.table.headers.type")}
+ {renderSortIcon(sortField === "type")}
+
+ ),
+ render: (row) => {
+ return row.type;
+ },
+ },
+ {
+ id: "actions",
+ content: t("common.table.headers.actions"),
+ render: (row) => {
+ return ;
+ },
+ },
+ ];
+ return headers;
+ };
+
+ const headers = getHeaders(chartType);
+
+ return (
+
+ {
+ navigate(`/uptime/${row.id}`);
+ }}
+ />
+ setPage(newPage)}
+ onRowsPerPageChange={(e) => setRowsPerPage(Number(e.target.value))}
+ />
+
+ );
+};
diff --git a/client/src/Pages/Uptime/Monitors/index.tsx b/client/src/Pages/Uptime/Monitors/index.tsx
new file mode 100644
index 000000000..5c3828d07
--- /dev/null
+++ b/client/src/Pages/Uptime/Monitors/index.tsx
@@ -0,0 +1,216 @@
+import { ControlsFilter } from "@/Components/v2/monitors";
+import {
+ MonitorBasePageWithStates,
+ UpStatusBox,
+ DownStatusBox,
+ PausedStatusBox,
+} 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";
+import { HeaderCreate } from "@/Components/v2/common";
+
+import { useTranslation } from "react-i18next";
+import useMediaQuery from "@mui/material/useMediaQuery";
+import { useGet, useDelete } from "@/Hooks/UseApi";
+import type { Monitor, MonitorType, MonitorsWithChecksResponse } from "@/Types/Monitor";
+import { useState, useMemo, useCallback } from "react";
+import { useSelector, useDispatch } from "react-redux";
+import { setRowsPerPage } from "@/Features/UI/uiSlice.js";
+import { useIsAdmin } from "@/Hooks/useIsAdmin";
+import type { RootState } from "@/Types/state";
+import { useTheme } from "@mui/material";
+
+const UptimeMonitorsPage = () => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+ const isSmall = useMediaQuery(theme.breakpoints.down("md"));
+ const dispatch = useDispatch();
+ const isAdmin = useIsAdmin();
+ // Redux state
+ const rowsPerPage = useSelector(
+ (state: RootState) => state.ui?.monitors?.rowsPerPage ?? 10
+ );
+
+ // Local state
+ const [selectedTypes, setSelectedTypes] = useState([]);
+ const [selectedStatus, setSelectedStatus] = useState("");
+ const [selectedState, setSelectedState] = useState("");
+ const [page, setPage] = useState(0);
+ const [search, setSearch] = useState("");
+ const [sortField, setSortField] = useState("");
+ const [sortOrder, setSortOrder] = useState<"asc" | "desc">("asc");
+ const [selectedMonitor, setSelectedMonitor] = useState(null);
+ const isDialogOpen = Boolean(selectedMonitor);
+
+ // Convert filter selections to API filter values
+ // Status: "up" -> true, "down" -> false
+ // State: "active" -> true, "paused" -> false
+ const toFilterStatus = useMemo(() => {
+ if (selectedStatus === "up") return "true";
+ if (selectedStatus === "down") return "false";
+ return undefined;
+ }, [selectedStatus]);
+
+ const toFilterActive = useMemo(() => {
+ if (selectedState === "active") return "true";
+ if (selectedState === "paused") return "false";
+ return undefined;
+ }, [selectedState]);
+
+ // Determine field and filter for the API request
+ // Priority: status > isActive > search > sort
+ const filterLookup = new Map([
+ [toFilterStatus, "status"],
+ [toFilterActive, "isActive"],
+ ]);
+ const activeFilter = [...filterLookup].find(([key]) => key !== undefined);
+ const field = activeFilter?.[1] || (search ? "name" : sortField || undefined);
+ const filter = activeFilter?.[0] || search;
+
+ // Default to all types when none selected
+ const effectiveTypes =
+ selectedTypes.length > 0 ? selectedTypes : ["http", "ping", "docker", "port"];
+
+ // Build URL for monitors with checks
+ const monitorsWithChecksUrl = useMemo(() => {
+ const params = new URLSearchParams();
+ effectiveTypes.forEach((type) => params.append("type", type));
+ params.append("limit", "25");
+ if (page !== undefined) params.append("page", String(page));
+ if (rowsPerPage) params.append("rowsPerPage", String(rowsPerPage));
+ if (filter) params.append("filter", filter);
+ if (field) params.append("field", field);
+ if (sortOrder) params.append("order", sortOrder);
+ return `/monitors/team/with-checks?${params.toString()}`;
+ }, [effectiveTypes, page, rowsPerPage, filter, field, sortOrder]);
+
+ // Data fetching
+ const {
+ data: monitors,
+ isLoading: monitorsLoading,
+ error,
+ refetch: refetchMonitorsAndSummary,
+ } = useGet(
+ "/monitors/team?type=http&type=ping&type=port&type=docker",
+ {},
+ { keepPreviousData: true }
+ );
+
+ const {
+ data: monitorsWithChecksData,
+ isLoading: monitorsWithChecksLoading,
+ error: monitorsWithChecksError,
+ refetch,
+ } = useGet(
+ monitorsWithChecksUrl,
+ {},
+ { refreshInterval: 5000, keepPreviousData: true }
+ );
+
+ const { monitors: monitorsWithChecks, summary, count } = monitorsWithChecksData ?? {};
+
+ // Delete hook
+ const { deleteFn, loading: isDeleting } = useDelete();
+
+ // Handlers
+ const handleClearFilters = useCallback(() => {
+ setSelectedTypes([]);
+ setSelectedStatus("");
+ setSelectedState("");
+ setSearch("");
+ }, []);
+
+ const handleConfirm = async () => {
+ if (!selectedMonitor) return;
+ await deleteFn(`/monitors/${selectedMonitor.id}`);
+ setSelectedMonitor(null);
+ refetch();
+ refetchMonitorsAndSummary();
+ };
+
+ const handleCancel = () => {
+ setSelectedMonitor(null);
+ };
+
+ const isLoading = monitorsLoading || monitorsWithChecksLoading;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ {
+ setSearch(event.target.value);
+ }}
+ />
+
+ {
+ dispatch(
+ setRowsPerPage({
+ value: rowsPerPage,
+ table: "monitors",
+ })
+ );
+ setPage(0);
+ }}
+ />
+
+
+ );
+};
+
+export default UptimeMonitorsPage;
diff --git a/client/src/Pages/Uptime/Monitors/index.jsx b/client/src/Pages/Uptime/Monitors/old.jsx
similarity index 79%
rename from client/src/Pages/Uptime/Monitors/index.jsx
rename to client/src/Pages/Uptime/Monitors/old.jsx
index 7f28c1cd7..67019362f 100644
--- a/client/src/Pages/Uptime/Monitors/index.jsx
+++ b/client/src/Pages/Uptime/Monitors/old.jsx
@@ -4,32 +4,32 @@
// 2a.List of monitors must have the total number of monitors that match.
// Components
-import Breadcrumbs from "../../../Components/Breadcrumbs";
-import Greeting from "../../../Utils/greeting";
-import StatusBoxes from "./Components/StatusBoxes";
-import UptimeDataTable from "./Components/UptimeDataTable";
-import Pagination from "../../../Components/Table/TablePagination";
-import CreateMonitorHeader from "../../../Components/MonitorCreateHeader";
-import SearchComponent from "./Components/SearchComponent";
-import Filter from "./Components/Filter";
-import PageStateWrapper from "../../../Components/PageStateWrapper";
+import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
+import Greeting from "../../../Utils/greeting.jsx";
+import StatusBoxes from "./Components/StatusBoxes/index.jsx";
+import UptimeDataTable from "./Components/UptimeDataTable/index.jsx";
+import Pagination from "@/Components/v1/Table/TablePagination/index.jsx";
+import CreateMonitorHeader from "@/Components/v1/MonitorCreateHeader/index.jsx";
+import SearchComponent from "./Components/SearchComponent/index.jsx";
+import Filter from "./Components/Filter/index.jsx";
+import PageStateWrapper from "@/Components/v1/PageStateWrapper/index.jsx";
-import MonitorCountHeader from "../../../Components/MonitorCountHeader";
+import MonitorCountHeader from "@/Components/v1/MonitorCountHeader/index.jsx";
// MUI Components
import { Stack, Box, Button } from "@mui/material";
// Utils
import { useState, useCallback, useEffect } from "react";
-import { useIsAdmin } from "../../../Hooks/useIsAdmin";
+import { useIsAdmin } from "@/Hooks/useIsAdmin.js";
import { useTheme } from "@emotion/react";
import { useNavigate } from "react-router-dom";
import { useSelector, useDispatch } from "react-redux";
-import { setRowsPerPage } from "../../../Features/UI/uiSlice";
+import { setRowsPerPage } from "../../../Features/UI/uiSlice.js";
import PropTypes from "prop-types";
import {
- useFetchMonitorsWithSummary,
useFetchMonitorsWithChecks,
-} from "../../../Hooks/monitorHooks";
+ useFetchMonitorsByTeamId,
+} from "@/Hooks/monitorHooks.js";
import { useTranslation } from "react-i18next";
const TYPES = ["http", "ping", "docker", "port", "game"];
@@ -103,12 +103,6 @@ const UptimeMonitors = () => {
setMonitorUpdateTrigger((prev) => !prev);
}, []);
- const [monitors, monitorsSummary, monitorsWithSummaryIsLoading, networkError] =
- useFetchMonitorsWithSummary({
- types: TYPES,
- monitorUpdateTrigger,
- });
-
const handleReset = () => {
setSelectedState(undefined);
setSelectedTypes(undefined);
@@ -123,16 +117,17 @@ const UptimeMonitors = () => {
]);
const activeFilter = [...filterLookup].find(([key]) => key !== undefined);
- const field = activeFilter?.[1] || sort?.field;
+ const field = activeFilter?.[1] || (search ? "name" : sort?.field);
const filter = activeFilter?.[0] || search;
const effectiveTypes = selectedTypes?.length ? selectedTypes : TYPES;
const [
+ summary,
monitorsWithChecks,
monitorsWithChecksCount,
monitorsWithChecksIsLoading,
- monitorsWithChecksNetworkError,
+ networkError,
] = useFetchMonitorsWithChecks({
types: effectiveTypes,
limit: 25,
@@ -144,13 +139,17 @@ const UptimeMonitors = () => {
monitorUpdateTrigger,
});
+ const [monitors, listIsLoading, listNetworkError] = useFetchMonitorsByTeamId({
+ type: ["http", "ping", "docker", "port", "game"],
+ });
+
useEffect(() => {
if (isSearching) {
setPage(undefined);
}
}, [isSearching]);
- const isLoading = monitorsWithSummaryIsLoading || monitorsWithChecksIsLoading;
+ const isLoading = monitorsWithChecksIsLoading;
return (
<>
@@ -174,14 +173,14 @@ const UptimeMonitors = () => {
/>
{
+ const mode = useSelector((state) => state.ui.mode);
const AdminCheckedRegister = withAdminCheck(AuthRegister);
+ const v2theme = mode === "light" ? lightTheme : darkTheme;
return (
{
/>
}
+ element={
+ <>
+
+
+
+ >
+ }
/>
{
/>
}
+ element={
+ <>
+
+
+
+ >
+ }
/>
{
}
+ element={
+ <>
+
+
+
+ >
+ }
/>
{
/>
}
+ element={
+ <>
+
+
+
+ >
+ }
/>
{
path="infrastructure/:monitorId"
element={ }
/>
+ }
+ />
}
diff --git a/client/src/Types/Check.ts b/client/src/Types/Check.ts
new file mode 100644
index 000000000..0aa953dbb
--- /dev/null
+++ b/client/src/Types/Check.ts
@@ -0,0 +1,146 @@
+export interface CheckMetadata {
+ monitorId: string;
+ teamId: string;
+ type: string;
+}
+
+export interface CheckCpuInfo {
+ physical_core?: number;
+ logical_core?: number;
+ frequency?: number;
+ temperature?: number[];
+ free_percent?: number;
+ usage_percent?: number;
+}
+
+export interface CheckMemoryInfo {
+ total_bytes?: number;
+ available_bytes?: number;
+ used_bytes?: number;
+ usage_percent?: number;
+}
+
+export interface CheckHostInfo {
+ os?: string;
+ platform?: string;
+ kernel_version?: string;
+}
+
+export interface CheckCaptureInfo {
+ version?: string;
+ mode?: string;
+}
+
+export interface CheckDiskInfo {
+ device?: string;
+ mountpoint?: string;
+ read_speed_bytes?: number;
+ write_speed_bytes?: number;
+ total_bytes?: number;
+ free_bytes?: number;
+ usage_percent?: number;
+}
+
+export interface CheckErrorInfo {
+ metric: string[];
+ err: string;
+}
+
+export interface CheckNetworkInterfaceInfo {
+ name: string;
+ bytes_sent: number;
+ bytes_recv: number;
+ packets_sent: number;
+ packets_recv: number;
+ err_in: number;
+ err_out: number;
+ drop_in: number;
+ drop_out: number;
+ fifo_in: number;
+ fifo_out: number;
+}
+
+export interface CheckAudits {
+ cls?: ILighthouseAudit;
+ si?: ILighthouseAudit;
+ fcp?: ILighthouseAudit;
+ lcp?: ILighthouseAudit;
+ tbt?: ILighthouseAudit;
+}
+
+export interface ILighthouseAudit {
+ id?: string;
+ title?: string;
+ score?: number | null;
+ displayValue?: string;
+ numericValue?: number;
+ numericUnit?: string;
+}
+
+export interface CheckTimings {
+ start?: number;
+ socket?: number;
+ lookup?: number;
+ connect?: number;
+ secureConnect?: number;
+ response?: number;
+ end?: number;
+ abort?: number;
+ error?: number;
+ phases?: {
+ wait?: number;
+ dns?: number;
+ tcp?: number;
+ tls?: number;
+ request?: number;
+ firstByte?: number;
+ download?: number;
+ total?: number;
+ };
+}
+
+export interface Check {
+ id: string;
+ metadata: CheckMetadata;
+ status: boolean;
+ originalResponseTime?: number;
+ responseTime: number;
+ timings?: CheckTimings;
+ statusCode: number;
+ message: string;
+ ack: boolean;
+ ackAt?: string | null;
+ expiry: string;
+ cpu?: CheckCpuInfo;
+ memory?: CheckMemoryInfo;
+ disk?: CheckDiskInfo[];
+ host?: CheckHostInfo;
+ errors?: CheckErrorInfo[];
+ capture?: CheckCaptureInfo;
+ net?: CheckNetworkInterfaceInfo[];
+ accessibility?: number;
+ bestPractices?: number;
+ seo?: number;
+ performance?: number;
+ audits?: CheckAudits;
+ createdAt: string;
+ updatedAt: string;
+}
+
+export interface GroupedCheck {
+ bucketDate: string;
+ avgResponseTime: number;
+ totalChecks: number;
+}
+
+export interface LatestCheck {
+ status: boolean;
+ responseTime: number;
+ checkedAt: string;
+ id: string;
+}
+
+export interface ChecksResponse {
+ checks: Check[];
+ checksCount: number;
+}
diff --git a/client/src/Types/Monitor.ts b/client/src/Types/Monitor.ts
new file mode 100644
index 000000000..6baca2845
--- /dev/null
+++ b/client/src/Types/Monitor.ts
@@ -0,0 +1,83 @@
+import type { Check, GroupedCheck } from "@/Types/Check";
+export type MonitorStatus = boolean | undefined;
+
+export const MonitorTypes = [
+ "http",
+ "ping",
+ "pagespeed",
+ "hardware",
+ "docker",
+ "port",
+ "game",
+ "unknown",
+] 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 MonitorWithChecks extends Monitor {
+ checks: Check[];
+ uptimePercentage?: number;
+}
+
+export interface MonitorsSummary {
+ totalMonitors: number;
+ upMonitors: number;
+ downMonitors: number;
+ pausedMonitors: number;
+}
+
+export interface MonitorsWithChecksResponse {
+ count: number;
+ monitors: MonitorWithChecks[];
+ summary: MonitorsSummary;
+}
+
+export interface MonitorStats {
+ id: string;
+ monitorId: string;
+ avgResponseTime: number;
+ totalChecks: number;
+ totalUpChecks: number;
+ totalDownChecks: number;
+ uptimePercentage: number;
+ lastCheckTimestamp: number;
+ lastResponseTime: number;
+ timeOfLastFailure?: number;
+ createdAt: string;
+ updatedAt: string;
+}
+
+export interface MonitorData {
+ monitor: Monitor;
+ groupedChecks: GroupedCheck[];
+ groupedUpChecks: GroupedCheck[];
+ groupedDownChecks: GroupedCheck[];
+ groupedAvgResponseTime: number;
+ groupedUptimePercentage: number;
+}
+
+export interface MonitorDetailsResponse {
+ monitorData: MonitorData;
+ monitorStats: MonitorStats | null;
+}
+
+export interface PageSpeedDetailsResponse {
+ monitor: MonitorWithChecks;
+ monitorStats: MonitorStats | null;
+}
diff --git a/client/src/Types/Settings.ts b/client/src/Types/Settings.ts
new file mode 100644
index 000000000..28f7bf5ed
--- /dev/null
+++ b/client/src/Types/Settings.ts
@@ -0,0 +1,34 @@
+export interface SettingsThresholds {
+ cpu?: number;
+ memory?: number;
+ disk?: number;
+ temperature?: number;
+}
+
+export interface Settings {
+ id: string;
+ checkTTL: number;
+ language: string;
+ systemEmailHost?: string;
+ systemEmailPort?: number;
+ systemEmailAddress?: string;
+ systemEmailUser?: string;
+ systemEmailConnectionHost?: string;
+ systemEmailTLSServername?: string;
+ systemEmailSecure: boolean;
+ systemEmailPool: boolean;
+ systemEmailIgnoreTLS: boolean;
+ systemEmailRequireTLS: boolean;
+ systemEmailRejectUnauthorized: boolean;
+ showURL: boolean;
+ singleton: boolean;
+ globalThresholds?: SettingsThresholds;
+ createdAt: string;
+ updatedAt: string;
+}
+
+export interface AppSettingsResponse {
+ pagespeedKeySet: boolean;
+ emailPasswordSet: boolean;
+ settings: Settings;
+}
diff --git a/client/src/Types/env.d.ts b/client/src/Types/env.d.ts
new file mode 100644
index 000000000..74a2a29e7
--- /dev/null
+++ b/client/src/Types/env.d.ts
@@ -0,0 +1,17 @@
+///
+
+interface ImportMetaEnv {
+ readonly VITE_APP_API_V2_BASE_URL?: string;
+}
+
+interface ImportMeta {
+ readonly env: ImportMetaEnv;
+}
+
+declare module "*.svg?react" {
+ import * as React from "react";
+ const ReactComponent: React.FunctionComponent<
+ React.SVGProps & { title?: string }
+ >;
+ export default ReactComponent;
+}
diff --git a/client/src/Types/mui.d.ts b/client/src/Types/mui.d.ts
new file mode 100644
index 000000000..3878f9391
--- /dev/null
+++ b/client/src/Types/mui.d.ts
@@ -0,0 +1,16 @@
+import "@mui/material/Button";
+
+declare module "@mui/material/styles" {
+ interface Palette {}
+ interface PaletteOptions {}
+}
+
+declare module "@mui/material/Button" {
+ interface ButtonPropsColorOverrides {
+ neutral: true;
+ }
+}
+
+declare module "@mui/material/CircularProgress" {
+ interface CircularProgressPropsColorOverrides {}
+}
diff --git a/client/src/Types/state.ts b/client/src/Types/state.ts
new file mode 100644
index 000000000..137d67b66
--- /dev/null
+++ b/client/src/Types/state.ts
@@ -0,0 +1,41 @@
+export interface AuthState {
+ isLoading: boolean;
+ authToken: string;
+ user: string;
+ success: boolean | null;
+ msg: string | null;
+}
+
+export interface UIState {
+ monitors: {
+ rowsPerPage: number;
+ };
+ team: {
+ rowsPerPage: number;
+ };
+ maintenance: {
+ rowsPerPage: number;
+ };
+ infrastructure: {
+ rowsPerPage: number;
+ };
+ sidebar: {
+ collapsed: boolean;
+ };
+ mode: "light" | "dark";
+ showURL: boolean;
+ greeting: {
+ index: number;
+ lastUpdate: string | null;
+ };
+ timezone: string;
+ distributedUptimeEnabled: boolean;
+ language: string;
+ starPromptOpen: boolean;
+ chartType: string;
+}
+
+export interface RootState {
+ auth: AuthState;
+ ui: UIState;
+}
diff --git a/client/src/Utils/ApiClient.ts b/client/src/Utils/ApiClient.ts
new file mode 100644
index 000000000..e90bc9f01
--- /dev/null
+++ b/client/src/Utils/ApiClient.ts
@@ -0,0 +1,75 @@
+import axios from "axios";
+import type { AxiosError } from "axios";
+import type { AxiosRequestConfig, AxiosResponse } from "axios";
+
+const BASE_URL = import.meta.env.VITE_APP_API_BASE_URL;
+
+const api = axios.create({
+ baseURL: BASE_URL,
+});
+
+type StoreType = {
+ getState: () => { auth?: { authToken?: string } };
+};
+
+let storeInstance: StoreType | null = null;
+let interceptorsInitialized = false;
+
+export const initApiClient = (store: StoreType): void => {
+ storeInstance = store;
+
+ if (interceptorsInitialized) {
+ return;
+ }
+ interceptorsInitialized = true;
+
+ api.interceptors.request.use(
+ (config) => {
+ const authToken = storeInstance?.getState()?.auth?.authToken ?? "";
+
+ config.headers.set("Authorization", `Bearer ${authToken}`);
+ if (!config.headers.has("Accept-Language")) {
+ config.headers.set("Accept-Language", "en");
+ }
+
+ return config;
+ },
+ (error) => {
+ return Promise.reject(error);
+ }
+ );
+
+ const onSuccess = (response: AxiosResponse) => response;
+ const onError = (error: AxiosError) => {
+ if (error.response?.status === 401) {
+ window.location.href = "/login";
+ }
+ return Promise.reject(error);
+ };
+
+ api.interceptors.response.use(onSuccess, onError);
+};
+
+export const get = (
+ url: string,
+ config: AxiosRequestConfig = {}
+): Promise> => api.get(url, config);
+
+export const post = (
+ url: string,
+ data: unknown,
+ config: AxiosRequestConfig = {}
+): Promise> => api.post(url, data, config);
+
+export const patch = (
+ url: string,
+ data: unknown,
+ config: AxiosRequestConfig = {}
+): Promise> => api.patch(url, data, config);
+
+export const deleteOp = (
+ url: string,
+ config: AxiosRequestConfig = {}
+): Promise> => api.delete(url, config);
+
+export default api;
diff --git a/client/src/Utils/DataUtils.ts b/client/src/Utils/DataUtils.ts
new file mode 100644
index 000000000..c0bbb2d2a
--- /dev/null
+++ b/client/src/Utils/DataUtils.ts
@@ -0,0 +1,129 @@
+const MIN_OUT = 10;
+const MAX_OUT = 100;
+
+export const normalizeResponseTimes = (
+ checks: T[],
+ key: K
+): (T & { normalResponseTime: number })[] => {
+ if (!Array.isArray(checks) || checks.length === 0)
+ return checks as (T & {
+ normalResponseTime: number;
+ })[];
+
+ if (checks.length === 1) {
+ return [
+ {
+ ...checks[0],
+ normalResponseTime: 50,
+ },
+ ];
+ }
+
+ const getVal = (check: T): number => {
+ const v = check[key] as unknown;
+ return typeof v === "number" && Number.isFinite(v) ? v : 0;
+ };
+
+ const { min, max } = checks.reduce(
+ (acc, check) => {
+ const v = getVal(check);
+ if (v > acc.max) acc.max = v;
+ if (v < acc.min) acc.min = v;
+ return acc;
+ },
+ { max: -Infinity, min: Infinity }
+ );
+
+ const range = max - min || 1;
+
+ return checks.map((check) => ({
+ ...check,
+ normalResponseTime: MIN_OUT + ((getVal(check) - min) * (MAX_OUT - MIN_OUT)) / range,
+ }));
+};
+
+// Interpolate color between three theme colors over 0-100 range.
+// 0-50 => start (success.light), 50-75 => mid (warning.light), 75-100 => end (error.light)
+export const getResponseColor = (
+ ms: number,
+ colors: {
+ start: string | undefined;
+ mid: string | undefined;
+ end: string | undefined;
+ }
+): string => {
+ // New ranges with open high end:
+ // 0–300ms: interpolate start (good) -> mid (warning)
+ // 300–600ms: interpolate mid (warning) -> end (error)
+ // >600ms: solid end (error)
+ const safe = { ...colors };
+ if (!safe.start) safe.start = "#4caf50"; // fallback green
+ if (!safe.mid) safe.mid = "#ff9800"; // fallback orange
+ if (!safe.end) safe.end = "#f44336"; // fallback red
+
+ const toHex = (c: number) => c.toString(16).padStart(2, "0");
+ const clamp = (n: number) => Math.min(255, Math.max(0, Math.round(n)));
+ const rgbToHex = (r: number, g: number, b: number) =>
+ `#${toHex(clamp(r))}${toHex(clamp(g))}${toHex(clamp(b))}`;
+
+ const normalizeToHex = (value: string) => {
+ const v = value.trim();
+ if (v.startsWith("#")) {
+ const h = v.slice(1);
+ const full =
+ h.length === 3
+ ? h
+ .split("")
+ .map((c) => c + c)
+ .join("")
+ : h.substring(0, 6);
+ return `#${full.toLowerCase()}`;
+ }
+ const m = v
+ .replace(/\s+/g, "")
+ .match(/^rgba?\((\d{1,3}),(\d{1,3}),(\d{1,3})(?:,(0|0?\.\d+|1))?\)$/i);
+ if (m) {
+ const r = parseInt(m[1], 10);
+ const g = parseInt(m[2], 10);
+ const b = parseInt(m[3], 10);
+ return rgbToHex(r, g, b);
+ }
+ // Fallback neutral
+ return "#7f7f7f";
+ };
+
+ const parseHex = (hex: string) => {
+ const h = hex.replace("#", "");
+ const full =
+ h.length === 3
+ ? h
+ .split("")
+ .map((c) => c + c)
+ .join("")
+ : h;
+ const n = parseInt(full, 16);
+ return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: n & 255 };
+ };
+ const mix = (a: number, b: number, t: number) => Math.round(a + (b - a) * t);
+
+ const v = Math.max(0, ms);
+ if (v <= 300) {
+ const t = v / 300; // 0..1 from start->mid
+ const s = parseHex(normalizeToHex(safe.start));
+ const m = parseHex(normalizeToHex(safe.mid));
+ const r = mix(s.r, m.r, t);
+ const g = mix(s.g, m.g, t);
+ const b = mix(s.b, m.b, t);
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
+ }
+ if (v <= 600) {
+ const t = (v - 300) / 300; // 0..1 from mid->end
+ const m = parseHex(normalizeToHex(safe.mid));
+ const e = parseHex(normalizeToHex(safe.end));
+ const r = mix(m.r, e.r, t);
+ const g = mix(m.g, e.g, t);
+ const b = mix(m.b, e.b, t);
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
+ }
+ return normalizeToHex(safe.end);
+};
diff --git a/client/src/Utils/Logger.js b/client/src/Utils/Logger.js
index f132ad408..849e10854 100644
--- a/client/src/Utils/Logger.js
+++ b/client/src/Utils/Logger.js
@@ -1,14 +1,25 @@
-import store from "../store";
const LOG_LEVEL = import.meta.env.VITE_APP_LOG_LEVEL || "debug";
const NO_OP = () => {};
class Logger {
constructor() {
let logLevel = LOG_LEVEL;
- this.unsubscribe = store.subscribe(() => {
- logLevel = "debug";
- this.updateLogLevel(logLevel);
- });
+ this.updateLogLevel(logLevel);
+ // Defer store subscription to avoid circular dependency during HMR
+ setTimeout(() => {
+ try {
+ import("../store").then(({ default: store }) => {
+ if (store) {
+ this.unsubscribe = store.subscribe(() => {
+ logLevel = "debug";
+ this.updateLogLevel(logLevel);
+ });
+ }
+ });
+ } catch (e) {
+ // Store not ready yet, ignore
+ }
+ }, 0);
}
updateLogLevel(logLevel) {
diff --git a/client/src/Utils/MonitorUtils.ts b/client/src/Utils/MonitorUtils.ts
new file mode 100644
index 000000000..ed4e2894e
--- /dev/null
+++ b/client/src/Utils/MonitorUtils.ts
@@ -0,0 +1,120 @@
+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 getMonitorPath = (type: MonitorType): string => {
+ const pathMap: Record = {
+ http: "uptime",
+ port: "uptime",
+ ping: "uptime",
+ hardware: "hardware",
+ pagespeed: "pagespeed",
+ docker: "docker",
+ game: "game-servers",
+ unknown: "uptime",
+ };
+ return pathMap[type];
+};
+
+export const getStatusPalette = (status: MonitorStatus): PaletteKey => {
+ if (status === true) {
+ return "success";
+ }
+ if (status === false) {
+ return "error";
+ }
+ return "warning";
+};
+
+export const getValuePalette = (value: ValueType): PaletteKey => {
+ const paletteMap: Record = {
+ positive: "success",
+ negative: "error",
+ neutral: "warning",
+ };
+ return paletteMap[value];
+};
+
+export const getStatusColor = (status: MonitorStatus, theme: any): string => {
+ if (status === true) {
+ return theme.palette.success.light;
+ }
+
+ if (status === false) {
+ return theme.palette.error.light;
+ }
+
+ return theme.palette.warning.light;
+};
+
+export const getResponseTimeColor = (responseTime: number): PaletteKey => {
+ if (responseTime < 200) {
+ return "success";
+ } else if (responseTime < 300) {
+ return "warning";
+ } else {
+ return "error";
+ }
+};
+
+export const getInfraGaugeColor = (val: number, theme: any) => {
+ if (val < 50) {
+ return theme.palette.success.main;
+ } else if (val < 80) {
+ return theme.palette.warning.light;
+ } else {
+ return theme.palette.error.light;
+ }
+};
+
+export const getPageSpeedPalette = (score: number): PaletteKey => {
+ if (score >= 90) return "success";
+ else if (score >= 50) return "warning";
+ else return "error";
+};
+
+export const formatUrl = (url: string, maxLength: number = 55) => {
+ if (!url) return "";
+
+ const strippedUrl = url.replace(/^https?:\/\//, "");
+ return strippedUrl.length > maxLength
+ ? `${strippedUrl.slice(0, maxLength)}…`
+ : strippedUrl;
+};
+
+export interface IStatusPageHeaderConfig {
+ paletteKey: PaletteKey;
+ message: string;
+}
+export const getStatusPageHeaderConfig = (
+ monitors: Monitor[],
+ t: any
+): IStatusPageHeaderConfig => {
+ if (!monitors || monitors.length === 0) {
+ 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);
+
+ if (allUp)
+ return {
+ paletteKey: "success",
+ message: t("statusPage.details.statusHeader.allUp"),
+ };
+ if (allDown)
+ return {
+ paletteKey: "error",
+ message: t("statusPage.details.statusHeader.allDown"),
+ };
+ if (anyDown)
+ return {
+ paletteKey: "warning",
+ message: t("statusPage.details.statusHeader.anyDown"),
+ };
+ return {
+ paletteKey: "warning",
+ message: t("statusPage.details.statusHeader.anyDown"),
+ };
+};
diff --git a/client/src/Utils/NetworkService.js b/client/src/Utils/NetworkService.js
index dce0faaf5..4a096badd 100644
--- a/client/src/Utils/NetworkService.js
+++ b/client/src/Utils/NetworkService.js
@@ -144,20 +144,16 @@ class NetworkService {
*/
async getMonitorsByTeamId(config) {
- const { limit, types, page, rowsPerPage, filter, field, order } = config;
+ const { types, filter } = config ?? {};
const params = new URLSearchParams();
- if (limit) params.append("limit", limit);
if (types) {
types.forEach((type) => {
params.append("type", type);
});
}
- if (page) params.append("page", page);
- if (rowsPerPage) params.append("rowsPerPage", rowsPerPage);
- if (filter) params.append("filter", filter);
- if (field) params.append("field", field);
- if (order) params.append("order", order);
+ if (filter !== undefined && filter !== null && filter !== "")
+ params.append("filter", filter);
return this.axiosInstance.get(`/monitors/team?${params.toString()}`, {
headers: {
@@ -202,6 +198,14 @@ class NetworkService {
`/monitors/hardware/details/${config.monitorId}?${params.toString()}`
);
}
+ async getPageSpeedDetailsByMonitorId(config) {
+ const params = new URLSearchParams();
+ if (config.dateRange) params.append("dateRange", config.dateRange);
+
+ return this.axiosInstance.get(
+ `/monitors/pagespeed/details/${config.monitorId}?${params.toString()}`
+ );
+ }
async getUptimeDetailsById(config) {
const params = new URLSearchParams();
if (config.dateRange) params.append("dateRange", config.dateRange);
@@ -343,17 +347,8 @@ class NetworkService {
return this.axiosInstance.get(`/monitors/certificate/${config.monitorId}`);
}
- /**
- * ************************************
- * Registers a new user
- * ************************************
- *
- * @async
- * @param {Object} form - The form data for the new user to be registered.
- * @returns {Promise} The response from the axios POST request.
- */
- async registerUser(form) {
- return this.axiosInstance.post(`/auth/register`, form);
+ async registerUser(user, token) {
+ return this.axiosInstance.post(`/auth/register`, { user, token });
}
/**
@@ -914,7 +909,7 @@ class NetworkService {
}
async createStatusPage(config) {
- const { form, isCreate } = config;
+ const { form, isCreate, id } = config;
const fd = new FormData();
fd.append("type", form.type);
@@ -960,7 +955,7 @@ class NetworkService {
return this.axiosInstance.post(`/status-page`, fd, {});
}
- return this.axiosInstance.put(`/status-page`, fd, {});
+ return this.axiosInstance.put(`/status-page/${id}`, fd, {});
}
async deleteStatusPage(config) {
@@ -1136,6 +1131,50 @@ class NetworkService {
},
});
}
+
+ async changePasswordByAdmin(config) {
+ const { userId, passwordForm } = config;
+ return this.axiosInstance.put(`auth/users/${userId}/password`, passwordForm, {
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+ }
+
+ async fetchJson() {
+ return this.axiosInstance.get("/monitors/export/json");
+ }
+
+ getIncidentsByTeam = async (config) => {
+ const params = new URLSearchParams();
+ if (config.monitorId) params.append("monitorId", config.monitorId);
+ if (config.status !== undefined) params.append("status", config.status);
+ if (config.resolutionType) params.append("resolutionType", config.resolutionType);
+ if (config.sortOrder) params.append("sortOrder", config.sortOrder);
+ if (config.dateRange) params.append("dateRange", config.dateRange);
+ if (config.page) params.append("page", config.page);
+ if (config.rowsPerPage) params.append("rowsPerPage", config.rowsPerPage);
+ return this.axiosInstance.get(`/incidents/team?${params.toString()}`);
+ };
+
+ getIncidentById = async (incidentId) => {
+ return this.axiosInstance.get(`/incidents/${incidentId}`);
+ };
+
+ resolveIncidentManually = async (incidentId, options = {}) => {
+ const body = {};
+ if (options.comment) body.comment = options.comment;
+ return this.axiosInstance.put(`/incidents/${incidentId}/resolve`, body);
+ };
+
+ getIncidentSummary = async (config = {}) => {
+ const params = new URLSearchParams();
+ if (config.limit) params.append("limit", config.limit);
+ const queryString = params.toString();
+ return this.axiosInstance.get(
+ `/incidents/team/summary${queryString ? `?${queryString}` : ""}`
+ );
+ };
}
export default NetworkService;
diff --git a/client/src/Utils/Theme/constants.js b/client/src/Utils/Theme/constants.js
index 9df6665d0..2664c6126 100644
--- a/client/src/Utils/Theme/constants.js
+++ b/client/src/Utils/Theme/constants.js
@@ -1,4 +1,4 @@
-import { lighten, darken } from "@mui/material/styles"; // CAIO_REVIEW
+import { lighten } from "@mui/material/styles"; // CAIO_REVIEW
const typographyBase = 13;
@@ -100,7 +100,6 @@ const newColors = {
blueGray900: "#515151",
blueBlueWave: "#1570EF",
lightBlueWave: "#CDE2FF",
- /* I changed green 100 and green 700. Need to change red and warning as well, and refactor the object following the structure */
green100: "#67cd78",
green200: "#4B9B77",
green400: "#079455",
@@ -134,12 +133,6 @@ const newSemanticColors = {
light: lighten(newColors.blueBlueWave, 0.2), //CAIO_REVIEW
dark: lighten(newColors.blueBlueWave, 0.2), //CAIO_REVIEW
},
- darker: {
- // CAIO_REVIEW
- light: darken(newColors.blueBlueWave, 0.2), // CAIO_REVIEW
- dark: darken(newColors.blueBlueWave, 0.2), // CAIO_REVIEW
- },
-
contrastText: {
light: newColors.offWhite,
dark: newColors.offWhite,
@@ -166,14 +159,6 @@ const newSemanticColors = {
light: newColors.blueGray500,
dark: newColors.gray500,
},
- contrastBorder: {
- light: newColors.gray500,
- dark: newColors.blueGray600,
- },
- contrastBorderDisabled: {
- light: newColors.gray100,
- dark: newColors.blueGray800,
- },
lowContrast: {
light: newColors.gray250,
dark: newColors.blueGray600,
@@ -208,18 +193,6 @@ const newSemanticColors = {
light: newColors.blueGray800,
dark: newColors.gray100,
},
- background: {
- light: newColors.gray50,
- dark: newColors.offBlack,
- },
- border: {
- light: newColors.gray500,
- dark: newColors.blueGray600,
- },
- cardShadow: {
- light: "0 0 0 1px rgba(0, 0, 0, 0.04), 0 12px 24px rgba(0, 0, 0, 0.08)",
- dark: "0 2px 10px rgba(0, 0, 0, 0.2), 0 0 0 1px rgba(255, 255, 255, 0.04)",
- },
},
success: {
main: {
@@ -235,20 +208,6 @@ const newSemanticColors = {
dark: newColors.green200,
},
},
- successSecondary: {
- main: {
- light: undefined,
- dark: "#073C2D",
- },
- contrastText: {
- light: undefined,
- dark: "#34D399",
- },
- lowContrast: {
- light: undefined,
- dark: "#094434",
- },
- },
warning: {
main: {
light: newColors.orange700,
@@ -317,30 +276,6 @@ const newSemanticColors = {
},
},
- map: {
- main: {
- light: newColors.offWhite,
- dark: undefined,
- },
- lowContrast: {
- light: newColors.gray200,
- dark: undefined,
- },
- highContrast: {
- light: newColors.gray500,
- dark: undefined,
- },
- },
- chatbot: {
- background: {
- light: newColors.gray200,
- dark: "#112B2B",
- },
- textAccent: {
- light: "#C084FC",
- dark: "#C084FC",
- },
- },
alert: {
main: {
light: newColors.gray200,
diff --git a/client/src/Utils/Theme/globalTheme.js b/client/src/Utils/Theme/globalTheme.js
index bda415c90..37143d730 100644
--- a/client/src/Utils/Theme/globalTheme.js
+++ b/client/src/Utils/Theme/globalTheme.js
@@ -58,13 +58,13 @@ const baseTheme = (palette) => ({
variants: [
{
props: (props) => props.variant === "contained" && props.color === "accent",
+ backgroundColor: theme.palette.accent.main,
style: {
- backgroundColor: theme.palette.accent.main,
color: theme.palette.primary.contrastTextSecondaryDarkBg,
letterSpacing: "0.5px",
textShadow: "0 0 1px rgba(0, 0, 0, 0.15)",
"&:hover": {
- backgroundColor: darken(theme.palette.accent.darker, 0.05),
+ backgroundColor: darken(theme.palette.accent.main, 0.25),
boxShadow: `0 2px 6px rgba(0, 0, 0, 0.1)`,
transition: "all 0.2s ease-in-out",
},
@@ -74,7 +74,7 @@ const baseTheme = (palette) => ({
props: (props) => props.color === "accent",
style: {
"&:hover": {
- backgroundColor: theme.palette.accent.darker,
+ backgroundColor: darken(theme.palette.accent.main, 0.2),
},
},
},
@@ -687,6 +687,58 @@ const baseTheme = (palette) => ({
}),
},
},
+ // DatePicker calendar popup styling
+ MuiPickersPopper: {
+ styleOverrides: {
+ paper: ({ theme }) => ({
+ backgroundColor: theme.palette.primary.main,
+ }),
+ },
+ },
+ MuiDateCalendar: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ backgroundColor: theme.palette.primary.main,
+ }),
+ },
+ },
+ MuiPickersCalendarHeader: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ "& .MuiPickersCalendarHeader-label": {
+ color: theme.palette.primary.contrastText,
+ },
+ }),
+ },
+ },
+ MuiDayCalendar: {
+ styleOverrides: {
+ weekDayLabel: ({ theme }) => ({
+ color: theme.palette.primary.contrastTextTertiary,
+ }),
+ },
+ },
+ MuiPickersDay: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ color: theme.palette.primary.contrastText,
+ "&:hover": {
+ backgroundColor: theme.palette.tertiary.main,
+ },
+ "&.Mui-selected": {
+ backgroundColor: theme.palette.accent.main,
+ color: theme.palette.accent.contrastText,
+ "&:hover": {
+ backgroundColor: theme.palette.accent.main,
+ },
+ },
+ "&.Mui-disabled": {
+ color: theme.palette.primary.contrastTextTertiary,
+ opacity: 0.5,
+ },
+ }),
+ },
+ },
// code ends here.
// For labels of input fields
diff --git a/client/src/Utils/Theme/v2Palette.ts b/client/src/Utils/Theme/v2Palette.ts
new file mode 100644
index 000000000..d314c8e27
--- /dev/null
+++ b/client/src/Utils/Theme/v2Palette.ts
@@ -0,0 +1,34 @@
+const typographyBase = 13;
+
+export const typographyLevels = {
+ base: typographyBase,
+ xs: `${(typographyBase - 4) / 16}rem`,
+ s: `${(typographyBase - 2) / 16}rem`,
+ m: `${typographyBase / 16}rem`,
+ l: `${(typographyBase + 2) / 16}rem`,
+ xl: `${(typographyBase + 10) / 16}rem`,
+};
+
+export const colors = {
+ gray200: "#EFEFEF",
+ gray700: "#313131",
+ blueBlueWave: "#1570EF",
+};
+
+export const lightPalette = {
+ primary: {
+ main: colors.blueBlueWave,
+ },
+ secondary: {
+ main: colors.gray200,
+ },
+};
+
+export const darkPalette = {
+ primary: {
+ main: colors.blueBlueWave,
+ },
+ secondary: {
+ main: colors.gray700,
+ },
+};
diff --git a/client/src/Utils/Theme/v2Theme.ts b/client/src/Utils/Theme/v2Theme.ts
new file mode 100644
index 000000000..57ca76669
--- /dev/null
+++ b/client/src/Utils/Theme/v2Theme.ts
@@ -0,0 +1,148 @@
+import { createTheme } from "@mui/material";
+import { lightPalette, darkPalette, typographyLevels } from "@/Utils/Theme/v2Palette";
+
+import type { Theme } from "@mui/material/styles";
+
+export type PaletteKey = {
+ [K in keyof Theme["palette"]]: Theme["palette"][K] extends { main: any } ? K : never;
+}[keyof Theme["palette"]];
+
+const fontFamilyPrimary = "system-ui, sans-serif";
+const shadow =
+ "0px 4px 24px -4px rgba(16, 24, 40, 0.08), 0px 3px 3px -3px rgba(16, 24, 40, 0.03)";
+
+export const theme = (mode: string, palette: any) =>
+ createTheme({
+ breakpoints: {
+ values: {
+ xs: 300,
+ sm: 600,
+ md: 900,
+ lg: 1200,
+ xl: 1536,
+ },
+ },
+ spacing: 2,
+ palette: {
+ mode: mode,
+ ...palette,
+ },
+ typography: {
+ fontFamily: fontFamilyPrimary,
+ fontSize: typographyLevels.base,
+ h1: {
+ fontSize: typographyLevels.xl,
+ fontWeight: 500,
+ },
+ h2: {
+ fontSize: typographyLevels.l,
+ fontWeight: 400,
+ },
+ body1: {
+ fontSize: typographyLevels.m,
+ fontWeight: 400,
+ },
+ body2: {
+ fontSize: typographyLevels.s,
+ fontWeight: 400,
+ },
+ },
+
+ components: {
+ MuiButtonBase: {
+ defaultProps: {
+ disableRipple: true,
+ disableTouchRipple: true,
+ },
+ },
+ MuiButton: {
+ defaultProps: {
+ disableRipple: true,
+ disableTouchRipple: true,
+ disableFocusRipple: true,
+ },
+ },
+ MuiIconButton: {
+ defaultProps: {
+ disableRipple: true,
+ disableTouchRipple: true,
+ disableFocusRipple: true,
+ },
+ },
+ MuiListItemButton: {
+ defaultProps: {
+ disableRipple: true,
+ disableTouchRipple: true,
+ },
+ },
+ MuiTab: {
+ defaultProps: {
+ disableRipple: true,
+ disableTouchRipple: true,
+ disableFocusRipple: true,
+ },
+ },
+ MuiToggleButton: {
+ defaultProps: {
+ disableRipple: true,
+ disableTouchRipple: true,
+ disableFocusRipple: true,
+ },
+ },
+
+ MuiFormLabel: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ fontSize: typographyLevels.base,
+ "&.Mui-focused": {
+ color: theme.palette.secondary.contrastText,
+ },
+ }),
+ },
+ },
+ MuiInputLabel: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ top: `-${theme.spacing(4)}`,
+ "&.MuiInputLabel-shrink": {
+ top: 0,
+ },
+ }),
+ },
+ },
+
+ MuiOutlinedInput: {
+ styleOverrides: {
+ root: {
+ "&.Mui-focused .MuiOutlinedInput-notchedOutline": {
+ borderColor: palette.primary.main,
+ },
+ },
+ },
+ },
+
+ MuiPaper: {
+ styleOverrides: {
+ root: ({ theme }) => {
+ return {
+ marginTop: 4,
+ padding: 0,
+ border: 1,
+ borderStyle: "solid",
+ borderColor: theme.palette.divider,
+ borderRadius: 4,
+ boxShadow: shadow,
+ backgroundColor: theme.palette.background.paper,
+ backgroundImage: "none",
+ };
+ },
+ },
+ },
+ },
+ shape: {
+ borderRadius: 2,
+ },
+ });
+
+export const lightTheme = createTheme(theme("light", lightPalette));
+export const darkTheme = createTheme(theme("dark", darkPalette));
diff --git a/client/src/Utils/TimeUtils.ts b/client/src/Utils/TimeUtils.ts
new file mode 100644
index 000000000..a19a0bf8b
--- /dev/null
+++ b/client/src/Utils/TimeUtils.ts
@@ -0,0 +1,52 @@
+import dayjs from "dayjs";
+import duration from "dayjs/plugin/duration";
+import utc from "dayjs/plugin/utc";
+import timezone from "dayjs/plugin/timezone";
+import customParseFormat from "dayjs/plugin/customParseFormat";
+
+dayjs.extend(utc);
+dayjs.extend(timezone);
+dayjs.extend(customParseFormat);
+dayjs.extend(duration);
+
+export const MS_PER_SECOND = 1000;
+export const MS_PER_MINUTE = 60 * MS_PER_SECOND;
+export const MS_PER_HOUR = 60 * MS_PER_MINUTE;
+export const MS_PER_DAY = 24 * MS_PER_HOUR;
+export const MS_PER_WEEK = MS_PER_DAY * 7;
+
+export const formatDateWithTz = (timestamp: string, format: string, timezone: string) => {
+ if (!timestamp) {
+ return "Unknown time";
+ }
+ const formattedDate = dayjs(timestamp).tz(timezone).format(format);
+ return formattedDate;
+};
+
+export const tickDateFormatLookup = (range: string) => {
+ const tickFormatLookup: Record = {
+ recent: "h:mm A",
+ day: "h:mm A",
+ week: "MM/D, h:mm A",
+ month: "ddd. M/D",
+ };
+ const format = tickFormatLookup[range];
+ if (format === undefined) {
+ return "";
+ }
+ return format;
+};
+
+export const tooltipDateFormatLookup = (range: string) => {
+ const dateFormatLookup: Record = {
+ recent: "ddd. MMMM D, YYYY, hh:mm A",
+ day: "ddd. MMMM D, YYYY, hh:mm A",
+ week: "ddd. MMMM D, YYYY, hh:mm A",
+ month: "ddd. MMMM D, YYYY",
+ };
+ const format = dateFormatLookup[range];
+ if (format === undefined) {
+ return "";
+ }
+ return format;
+};
diff --git a/client/src/Utils/greeting.jsx b/client/src/Utils/greeting.jsx
index d2b7fa033..c4afbf88c 100644
--- a/client/src/Utils/greeting.jsx
+++ b/client/src/Utils/greeting.jsx
@@ -136,8 +136,8 @@ const Greeting = ({ type = "" }) => {
const dispatch = useDispatch();
const { t } = useTranslation();
const { firstName } = useSelector((state) => state.auth.user);
- const index = useSelector((state) => state.ui.greeting.index);
- const lastUpdate = useSelector((state) => state.ui.greeting.lastUpdate);
+ const index = useSelector((state) => state.ui.greeting?.index ?? 0);
+ const lastUpdate = useSelector((state) => state.ui.greeting?.lastUpdate ?? null);
const now = new Date();
const hour = now.getHours();
diff --git a/client/src/Utils/monitorUtils.js b/client/src/Utils/monitorUtilsLegacy.js
similarity index 100%
rename from client/src/Utils/monitorUtils.js
rename to client/src/Utils/monitorUtilsLegacy.js
diff --git a/client/src/Utils/timeUtils.js b/client/src/Utils/timeUtilsLegacy.js
similarity index 87%
rename from client/src/Utils/timeUtils.js
rename to client/src/Utils/timeUtilsLegacy.js
index f6e2dcec0..365572f6b 100644
--- a/client/src/Utils/timeUtils.js
+++ b/client/src/Utils/timeUtilsLegacy.js
@@ -135,3 +135,33 @@ export const formatDateWithTz = (timestamp, format, timezone) => {
const formattedDate = dayjs(timestamp).tz(timezone).format(format);
return formattedDate;
};
+
+export const tickDateFormatLookup = (range) => {
+ switch (range) {
+ case "recent":
+ return "h:mm A";
+ case "day":
+ return "h A";
+ case "week":
+ return "MMM D";
+ case "month":
+ return "MMM D";
+ default:
+ return "MMM D, h A";
+ }
+};
+
+export const tooltipDateFormatLookup = (range) => {
+ switch (range) {
+ case "recent":
+ return "MMM D, h:mm A";
+ case "day":
+ return "MMM D, h:mm A";
+ case "week":
+ return "ddd, MMM D";
+ case "month":
+ return "ddd, MMM D";
+ default:
+ return "MMM D, h:mm A";
+ }
+};
diff --git a/client/src/Utils/toastUtils.jsx b/client/src/Utils/toastUtils.jsx
index 21cf13e2b..41b2d5482 100644
--- a/client/src/Utils/toastUtils.jsx
+++ b/client/src/Utils/toastUtils.jsx
@@ -1,6 +1,6 @@
import PropTypes from "prop-types";
import { toast, Slide } from "react-toastify";
-import Toast from "../Components/Toast";
+import Toast from "../Components/v1/Toast";
/**
* @param {object} props
* @param {'info' | 'error' | 'warning'} - The variant of the alert (e.g., "info", "error").
diff --git a/client/src/Validation/validation.js b/client/src/Validation/validation.js
index 67c267ffd..27d585074 100644
--- a/client/src/Validation/validation.js
+++ b/client/src/Validation/validation.js
@@ -112,7 +112,7 @@ const loginCredentials = joi.object({
});
const monitorValidation = joi.object({
- _id: joi.string(),
+ id: joi.string(),
userId: joi.string(),
teamId: joi.string(),
statusWindowSize: joi.number().min(1).max(20).default(5).messages({
@@ -328,6 +328,7 @@ const settingsValidation = joi.object({
systemEmailIgnoreTLS: joi.boolean(),
systemEmailRequireTLS: joi.boolean(),
systemEmailRejectUnauthorized: joi.boolean(),
+ showURL: joi.boolean().optional(),
globalThresholds: joi
.object({
cpu: joi.number().min(1).max(100).allow("").optional(),
@@ -448,6 +449,7 @@ const infrastructureMonitorValidation = joi.object({
"number.max": "Status window threshold cannot exceed 100%.",
}),
notifications: joi.array().items(joi.string()),
+ selectedDisks: joi.array().items(joi.string()).optional(),
});
const notificationValidation = joi.object({
@@ -458,7 +460,7 @@ const notificationValidation = joi.object({
type: joi
.string()
- .valid("email", "webhook", "slack", "discord", "pager_duty")
+ .valid("email", "webhook", "slack", "discord", "pager_duty", "matrix")
.required()
.messages({
"string.empty": "Notification type is required",
@@ -495,8 +497,40 @@ const notificationValidation = joi.object({
"string.uri": "Please enter a valid Webhook URL",
}),
},
+ {
+ is: "matrix",
+ then: joi.string().allow("").optional(),
+ },
],
}),
+
+ homeserverUrl: joi.when("type", {
+ is: "matrix",
+ then: joi.string().uri().required().messages({
+ "string.empty": "Homeserver URL cannot be empty",
+ "any.required": "Homeserver URL is required",
+ "string.uri": "Please enter a valid Homeserver URL",
+ }),
+ otherwise: joi.string().allow("").optional(),
+ }),
+
+ roomId: joi.when("type", {
+ is: "matrix",
+ then: joi.string().required().messages({
+ "string.empty": "Room ID cannot be empty",
+ "any.required": "Room ID is required",
+ }),
+ otherwise: joi.string().allow("").optional(),
+ }),
+
+ accessToken: joi.when("type", {
+ is: "matrix",
+ then: joi.string().required().messages({
+ "string.empty": "Access Token cannot be empty",
+ "any.required": "Access Token is required",
+ }),
+ otherwise: joi.string().allow("").optional(),
+ }),
});
const editUserValidation = joi.object({
diff --git a/client/src/assets/icons/alert-icon.svg b/client/src/assets/icons/alert-icon.svg
deleted file mode 100644
index 1dc7fc2b7..000000000
--- a/client/src/assets/icons/alert-icon.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/average-response-icon.svg b/client/src/assets/icons/average-response-icon.svg
deleted file mode 100644
index 572e2002f..000000000
--- a/client/src/assets/icons/average-response-icon.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/bwu-icon.svg b/client/src/assets/icons/bwu-icon.svg
deleted file mode 100644
index 80ab50ad9..000000000
--- a/client/src/assets/icons/bwu-icon.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/client/src/assets/icons/calendar-check.svg b/client/src/assets/icons/calendar-check.svg
deleted file mode 100644
index 07d96dad9..000000000
--- a/client/src/assets/icons/calendar-check.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/calendar.svg b/client/src/assets/icons/calendar.svg
deleted file mode 100644
index be023a4de..000000000
--- a/client/src/assets/icons/calendar.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/certificate.svg b/client/src/assets/icons/certificate.svg
deleted file mode 100644
index 2bc6c7e74..000000000
--- a/client/src/assets/icons/certificate.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/client/src/assets/icons/changeLog.svg b/client/src/assets/icons/changeLog.svg
deleted file mode 100644
index d12f6c10e..000000000
--- a/client/src/assets/icons/changeLog.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/check-icon.svg b/client/src/assets/icons/check-icon.svg
deleted file mode 100644
index 9a2cfac5a..000000000
--- a/client/src/assets/icons/check-icon.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/check-outlined.svg b/client/src/assets/icons/check-outlined.svg
deleted file mode 100644
index 1e779d547..000000000
--- a/client/src/assets/icons/check-outlined.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/check.svg b/client/src/assets/icons/check.svg
deleted file mode 100644
index 357378f40..000000000
--- a/client/src/assets/icons/check.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/client/src/assets/icons/checkbox-filled.svg b/client/src/assets/icons/checkbox-filled.svg
deleted file mode 100644
index 8a5be81f0..000000000
--- a/client/src/assets/icons/checkbox-filled.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/client/src/assets/icons/checkbox-green.svg b/client/src/assets/icons/checkbox-green.svg
deleted file mode 100644
index dd5676298..000000000
--- a/client/src/assets/icons/checkbox-green.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/client/src/assets/icons/checkbox-outline.svg b/client/src/assets/icons/checkbox-outline.svg
deleted file mode 100644
index bf5a7218c..000000000
--- a/client/src/assets/icons/checkbox-outline.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/checkbox-red.svg b/client/src/assets/icons/checkbox-red.svg
deleted file mode 100644
index 3c43a2042..000000000
--- a/client/src/assets/icons/checkbox-red.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/client/src/assets/icons/clock-snooze.svg b/client/src/assets/icons/clock-snooze.svg
deleted file mode 100644
index 708e7ba91..000000000
--- a/client/src/assets/icons/clock-snooze.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/close-icon.svg b/client/src/assets/icons/close-icon.svg
deleted file mode 100644
index e1b6b690a..000000000
--- a/client/src/assets/icons/close-icon.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/cpu-chip.svg b/client/src/assets/icons/cpu-chip.svg
deleted file mode 100644
index 5645840b1..000000000
--- a/client/src/assets/icons/cpu-chip.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/dashboard.svg b/client/src/assets/icons/dashboard.svg
deleted file mode 100644
index 8958e90ed..000000000
--- a/client/src/assets/icons/dashboard.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/discussions.svg b/client/src/assets/icons/discussions.svg
deleted file mode 100644
index 293cd51f2..000000000
--- a/client/src/assets/icons/discussions.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
diff --git a/client/src/assets/icons/distributed-uptime.svg b/client/src/assets/icons/distributed-uptime.svg
deleted file mode 100644
index e612081e4..000000000
--- a/client/src/assets/icons/distributed-uptime.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/docs.svg b/client/src/assets/icons/docs.svg
deleted file mode 100644
index d36458de2..000000000
--- a/client/src/assets/icons/docs.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/dots-vertical.svg b/client/src/assets/icons/dots-vertical.svg
deleted file mode 100644
index e720066c4..000000000
--- a/client/src/assets/icons/dots-vertical.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/client/src/assets/icons/down-arrow.svg b/client/src/assets/icons/down-arrow.svg
deleted file mode 100644
index 59b4b6bc5..000000000
--- a/client/src/assets/icons/down-arrow.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/edit.svg b/client/src/assets/icons/edit.svg
deleted file mode 100644
index be2c78408..000000000
--- a/client/src/assets/icons/edit.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/email.svg b/client/src/assets/icons/email.svg
deleted file mode 100644
index 01f55e961..000000000
--- a/client/src/assets/icons/email.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/folder.svg b/client/src/assets/icons/folder.svg
deleted file mode 100644
index 15941e5b5..000000000
--- a/client/src/assets/icons/folder.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/client/src/assets/icons/groups.svg b/client/src/assets/icons/groups.svg
deleted file mode 100644
index fa2cae03c..000000000
--- a/client/src/assets/icons/groups.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/client/src/assets/icons/history-icon.svg b/client/src/assets/icons/history-icon.svg
deleted file mode 100644
index 7343455ec..000000000
--- a/client/src/assets/icons/history-icon.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/incidents.svg b/client/src/assets/icons/incidents.svg
deleted file mode 100644
index ffe04d09f..000000000
--- a/client/src/assets/icons/incidents.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/integrations.svg b/client/src/assets/icons/integrations.svg
deleted file mode 100644
index 50e817f00..000000000
--- a/client/src/assets/icons/integrations.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/interval-check.svg b/client/src/assets/icons/interval-check.svg
deleted file mode 100644
index 923475c8b..000000000
--- a/client/src/assets/icons/interval-check.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/jupiter_ag_logo.svg b/client/src/assets/icons/jupiter_ag_logo.svg
deleted file mode 100644
index b344961c8..000000000
--- a/client/src/assets/icons/jupiter_ag_logo.svg
+++ /dev/null
@@ -1,52 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/client/src/assets/icons/jupiter_logo_banner_dark.svg b/client/src/assets/icons/jupiter_logo_banner_dark.svg
deleted file mode 100644
index 3d3e23b14..000000000
--- a/client/src/assets/icons/jupiter_logo_banner_dark.svg
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/client/src/assets/icons/jupiter_logo_banner_light.svg b/client/src/assets/icons/jupiter_logo_banner_light.svg
deleted file mode 100644
index 4744e0bcb..000000000
--- a/client/src/assets/icons/jupiter_logo_banner_light.svg
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/client/src/assets/icons/key.svg b/client/src/assets/icons/key.svg
deleted file mode 100644
index 5db8d5f16..000000000
--- a/client/src/assets/icons/key.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/left-arrow-double.svg b/client/src/assets/icons/left-arrow-double.svg
deleted file mode 100644
index 4cb2a5186..000000000
--- a/client/src/assets/icons/left-arrow-double.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/client/src/assets/icons/left-arrow-long.svg b/client/src/assets/icons/left-arrow-long.svg
deleted file mode 100644
index 56a774730..000000000
--- a/client/src/assets/icons/left-arrow-long.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/client/src/assets/icons/left-arrow.svg b/client/src/assets/icons/left-arrow.svg
deleted file mode 100644
index 649d1681c..000000000
--- a/client/src/assets/icons/left-arrow.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/client/src/assets/icons/lock.svg b/client/src/assets/icons/lock.svg
deleted file mode 100644
index a87535d54..000000000
--- a/client/src/assets/icons/lock.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/logout.svg b/client/src/assets/icons/logout.svg
deleted file mode 100644
index 3f3b69c49..000000000
--- a/client/src/assets/icons/logout.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/logs.svg b/client/src/assets/icons/logs.svg
deleted file mode 100644
index 556c3ba33..000000000
--- a/client/src/assets/icons/logs.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/client/src/assets/icons/mail.svg b/client/src/assets/icons/mail.svg
deleted file mode 100644
index 75c22b111..000000000
--- a/client/src/assets/icons/mail.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/client/src/assets/icons/maintenance.svg b/client/src/assets/icons/maintenance.svg
deleted file mode 100644
index 9486f6086..000000000
--- a/client/src/assets/icons/maintenance.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/monitor-graph-line.svg b/client/src/assets/icons/monitor-graph-line.svg
deleted file mode 100644
index 86049eb9e..000000000
--- a/client/src/assets/icons/monitor-graph-line.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/monitors.svg b/client/src/assets/icons/monitors.svg
deleted file mode 100644
index f40f36433..000000000
--- a/client/src/assets/icons/monitors.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/notifications.svg b/client/src/assets/icons/notifications.svg
deleted file mode 100644
index c09ac5746..000000000
--- a/client/src/assets/icons/notifications.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/client/src/assets/icons/open-in-new-page.svg b/client/src/assets/icons/open-in-new-page.svg
deleted file mode 100644
index 70599f390..000000000
--- a/client/src/assets/icons/open-in-new-page.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/page-speed.svg b/client/src/assets/icons/page-speed.svg
deleted file mode 100644
index 30a1c8a53..000000000
--- a/client/src/assets/icons/page-speed.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/pause-icon.svg b/client/src/assets/icons/pause-icon.svg
deleted file mode 100644
index c73abfc1d..000000000
--- a/client/src/assets/icons/pause-icon.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/performance-report.svg b/client/src/assets/icons/performance-report.svg
deleted file mode 100644
index 8b7073ac3..000000000
--- a/client/src/assets/icons/performance-report.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/radio-checked.svg b/client/src/assets/icons/radio-checked.svg
deleted file mode 100644
index 1c584b79a..000000000
--- a/client/src/assets/icons/radio-checked.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/client/src/assets/icons/response-time-icon.svg b/client/src/assets/icons/response-time-icon.svg
deleted file mode 100644
index db5b2af38..000000000
--- a/client/src/assets/icons/response-time-icon.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/resume-icon.svg b/client/src/assets/icons/resume-icon.svg
deleted file mode 100644
index 1da787081..000000000
--- a/client/src/assets/icons/resume-icon.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/client/src/assets/icons/right-arrow-double.svg b/client/src/assets/icons/right-arrow-double.svg
deleted file mode 100644
index 6ad4882ad..000000000
--- a/client/src/assets/icons/right-arrow-double.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/client/src/assets/icons/right-arrow.svg b/client/src/assets/icons/right-arrow.svg
deleted file mode 100644
index 77d022798..000000000
--- a/client/src/assets/icons/right-arrow.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/client/src/assets/icons/ruler-icon.svg b/client/src/assets/icons/ruler-icon.svg
deleted file mode 100644
index 7266d2876..000000000
--- a/client/src/assets/icons/ruler-icon.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/search.svg b/client/src/assets/icons/search.svg
deleted file mode 100644
index a1bc74224..000000000
--- a/client/src/assets/icons/search.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/selector-vertical.svg b/client/src/assets/icons/selector-vertical.svg
deleted file mode 100644
index 29d9a4998..000000000
--- a/client/src/assets/icons/selector-vertical.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/settings-bold.svg b/client/src/assets/icons/settings-bold.svg
deleted file mode 100644
index d8057286c..000000000
--- a/client/src/assets/icons/settings-bold.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/client/src/assets/icons/settings.svg b/client/src/assets/icons/settings.svg
deleted file mode 100644
index eb86f1d19..000000000
--- a/client/src/assets/icons/settings.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/client/src/assets/icons/solana_logo.svg b/client/src/assets/icons/solana_logo.svg
deleted file mode 100644
index bd349af26..000000000
--- a/client/src/assets/icons/solana_logo.svg
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/client/src/assets/icons/solana_logo_banner.svg b/client/src/assets/icons/solana_logo_banner.svg
deleted file mode 100644
index 08ec87cb1..000000000
--- a/client/src/assets/icons/solana_logo_banner.svg
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/client/src/assets/icons/spedometer-icon.svg b/client/src/assets/icons/spedometer-icon.svg
deleted file mode 100644
index 19b13f44e..000000000
--- a/client/src/assets/icons/spedometer-icon.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/speedometer-icon.svg b/client/src/assets/icons/speedometer-icon.svg
deleted file mode 100644
index 19b13f44e..000000000
--- a/client/src/assets/icons/speedometer-icon.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/status-pages.svg b/client/src/assets/icons/status-pages.svg
deleted file mode 100644
index 97cea88fb..000000000
--- a/client/src/assets/icons/status-pages.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/support.svg b/client/src/assets/icons/support.svg
deleted file mode 100644
index 7de16379a..000000000
--- a/client/src/assets/icons/support.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/top-right-arrow.svg b/client/src/assets/icons/top-right-arrow.svg
deleted file mode 100644
index d569cdd7e..000000000
--- a/client/src/assets/icons/top-right-arrow.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/trash-bin.svg b/client/src/assets/icons/trash-bin.svg
deleted file mode 100644
index 83bca0c6f..000000000
--- a/client/src/assets/icons/trash-bin.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/up-arrow.svg b/client/src/assets/icons/up-arrow.svg
deleted file mode 100644
index 0f8f3bc34..000000000
--- a/client/src/assets/icons/up-arrow.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/uptime-icon.svg b/client/src/assets/icons/uptime-icon.svg
deleted file mode 100644
index 55706ae71..000000000
--- a/client/src/assets/icons/uptime-icon.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/user-edit.svg b/client/src/assets/icons/user-edit.svg
deleted file mode 100644
index cbd1a252f..000000000
--- a/client/src/assets/icons/user-edit.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/user-two.svg b/client/src/assets/icons/user-two.svg
deleted file mode 100644
index 031b22787..000000000
--- a/client/src/assets/icons/user-two.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/user.svg b/client/src/assets/icons/user.svg
deleted file mode 100644
index 231c89d40..000000000
--- a/client/src/assets/icons/user.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/warning-icon.svg b/client/src/assets/icons/warning-icon.svg
deleted file mode 100644
index 468611758..000000000
--- a/client/src/assets/icons/warning-icon.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/index.css b/client/src/index.css
index a5d305d46..48737d4bb 100644
--- a/client/src/index.css
+++ b/client/src/index.css
@@ -4,6 +4,11 @@
box-sizing: border-box;
}
+html,
+body {
+ overscroll-behavior: none;
+}
+
html {
scroll-behavior: smooth;
}
@@ -15,15 +20,11 @@ html {
-moz-osx-font-smoothing: grayscale;
/* Generalized Stylings */
- --env-var-radius-1: 4px;
--env-var-radius-2: 8px;
- --env-var-width-1: 100vw;
--env-var-width-2: 360px;
- --env-var-width-3: 250px;
--env-var-width-4: 100px;
- --env-var-height-1: 100vh;
--env-var-height-2: 34px;
--env-var-nav-bar-height: 70px;
@@ -35,9 +36,6 @@ html {
--env-var-spacing-1-plus: 16px;
--env-var-spacing-1-minus: 10px;
--env-var-spacing-2: 24px;
- --env-var-spacing-3: 32px;
- --env-var-spacing-4: 40px;
- --env-var-spacing-5: 65px;
--env-var-font-size-small: 11px;
--env-var-font-size-small-plus: 12px;
@@ -46,23 +44,14 @@ html {
--env-var-font-size-large: 16px;
--env-var-font-size-large-plus: 22px;
--env-var-font-size-xlarge: 30px;
-
- --env-var-img-width-1: 20px;
- --env-var-img-width-2: 16px;
- --env-var-img-width-3: 12px;
-
- --env-var-default-1: 24px;
- --env-var-default-2: 40px;
-
- --env-var-shadow-1:
- 0px 4px 24px -4px rgba(16, 24, 40, 0.08), 0px 3px 3px -3px rgba(16, 24, 40, 0.03);
}
.MuiInputBase-root.Mui-disabled input {
cursor: not-allowed;
}
-.Toastify__toast-container {
+/* .Toastify__toast-container {
+ min-width: 300px;
width: auto;
}
.Toastify__toast-body .alert {
@@ -80,7 +69,7 @@ html {
}
.Toastify [class*="Toastify__toast-theme"] {
background-color: transparent;
-}
+} */
.MuiTouchRipple-root {
display: none;
diff --git a/client/src/locales/ar.json b/client/src/locales/ar.json
index d8019d5de..6cec253d8 100644
--- a/client/src/locales/ar.json
+++ b/client/src/locales/ar.json
@@ -421,7 +421,13 @@
}
}
},
- "role": ""
+ "role": "",
+ "changeTeamPassword": {
+ "changePasswordMenu": "",
+ "title": "",
+ "description": "",
+ "success": ""
+ }
},
"monitorState": {
"paused": "",
@@ -483,7 +489,8 @@
"ping": "",
"docker": "",
"port": "",
- "game": ""
+ "game": "",
+ "https": ""
},
"common": {
"appName": "",
@@ -1071,7 +1078,11 @@
"incidentConfigDescription": "",
"incidentConfigStatusWindowLabel": "",
"incidentConfigStatusWindowThresholdLabel": "",
- "incidentConfigTitle": ""
+ "incidentConfigTitle": "",
+ "incidentConfigDescriptionV2": "",
+ "incidentConfigStatusCheckNumber": "",
+ "intervalTitle": "",
+ "intervalDescription": ""
},
"dataRate": "",
"dataReceived": "",
diff --git a/client/src/locales/cs.json b/client/src/locales/cs.json
index 71c7e24b9..837b49803 100644
--- a/client/src/locales/cs.json
+++ b/client/src/locales/cs.json
@@ -421,7 +421,13 @@
}
}
},
- "role": ""
+ "role": "",
+ "changeTeamPassword": {
+ "changePasswordMenu": "",
+ "title": "",
+ "description": "",
+ "success": ""
+ }
},
"monitorState": {
"paused": "",
@@ -483,7 +489,8 @@
"ping": "",
"docker": "",
"port": "",
- "game": ""
+ "game": "",
+ "https": ""
},
"common": {
"appName": "Checkmate",
@@ -1071,7 +1078,11 @@
"incidentConfigDescription": "",
"incidentConfigStatusWindowLabel": "",
"incidentConfigStatusWindowThresholdLabel": "",
- "incidentConfigTitle": ""
+ "incidentConfigTitle": "",
+ "incidentConfigDescriptionV2": "",
+ "incidentConfigStatusCheckNumber": "",
+ "intervalTitle": "",
+ "intervalDescription": ""
},
"dataRate": "",
"dataReceived": "",
diff --git a/client/src/locales/de.json b/client/src/locales/de.json
index ef8df5df4..fd3918378 100644
--- a/client/src/locales/de.json
+++ b/client/src/locales/de.json
@@ -382,50 +382,56 @@
"created": "Angelegt"
},
"addTeamMember": {
- "addMemberMenu": "",
- "title": "",
- "description": "",
- "addButton": ""
+ "addMemberMenu": "Teammitglied hinzufügen",
+ "title": "Neues Teammitglied registrieren",
+ "description": "Einen neuen Benutzer anlegen und Zugangsdaten mit ihm teilen. Diese Methode gibt dem Mitglied direkt Zugriff auf alle Überwachungen.",
+ "addButton": "Mitglied hinzufügen"
},
- "register": "",
+ "register": "Teammitglied registrieren",
"registerToast": {
- "success": "",
- "dbUserExists": "",
- "unknownError": ""
+ "success": "Benutzer erstellt, Zugangsdaten auf sichere Weise weitergeben.",
+ "dbUserExists": "Der Benutzer existiert bereits.",
+ "unknownError": "Unbekannter Fehler aufgetreten."
},
"registerTeamMember": {
- "title": "",
+ "title": "Teammitglied registrieren",
"auth": {
"common": {
"inputs": {
"firstName": {
"errors": {
- "empty": "",
- "pattern": ""
+ "empty": "Bitte einen Namen eingeben",
+ "pattern": "Der Name darf nur Buchstaben, Leerzeichen, Apostrophe oder Bindestriche enthalten"
}
},
"lastName": {
"errors": {
- "empty": "",
- "pattern": ""
+ "empty": "Bitte einen Vornamen eingeben",
+ "pattern": "Der Vorname darf nur Buchstaben, Leerzeichen, Apostrophe oder Bindestriche enthalten"
}
},
"email": {
"errors": {
- "empty": "",
- "invalid": ""
+ "empty": "Zum Fortsetzen bitte eine E-Mail-Adresse eingeben",
+ "invalid": "Bitte die Gültigkeit der E-Mail-Adresse erneut prüfen"
}
},
"role": {
"errors": {
- "empty": ""
+ "empty": "Eine Rolle wird benötigt"
}
}
}
}
}
},
- "role": ""
+ "role": "Rolle",
+ "changeTeamPassword": {
+ "changePasswordMenu": "Passwort zurücksetzen",
+ "title": "Passwort des Teammitgliedes zurücksetzen",
+ "description": "Neues Passwort für dieses Teammitglied erstellen. Es muss auf einem Sicheren weg zur Verfügung gestellt werden.",
+ "success": "Passwort erfolgreich zurückgesetzt. Bitte sicherstellen, dass die Zugangsdaten auf sichere Weise weitergegeben werden."
+ }
},
"monitorState": {
"paused": "Pausiert",
@@ -487,7 +493,8 @@
"ping": "Geben Sie die IP-Adresse oder den Hostnamen ein, der angepingt werden soll (z. B. 192.168.1.100 oder example.com), und fügen Sie einen eindeutigen Anzeigenamen hinzu, der auf dem Dashboard angezeigt wird.",
"docker": "Geben Sie die Docker-ID Ihres Containers ein. Docker-IDs müssen die vollständige 64-Zeichen-ID sein. Sie können docker inspect ausführen, um die vollständige Container-ID abzurufen.",
"port": "Geben Sie die URL oder IP des Servers, die Portnummer und einen eindeutigen Anzeigenamen ein, der auf dem Dashboard angezeigt wird.",
- "game": ""
+ "game": "IP Adresse oder Hostnamen und Port für Ping eingeben (z.B. 192.168.1.100 oder beispiel.de) und Spieletyp auswählen.",
+ "https": "Zu überwachende Adresse oder IP (z.B. https://beispiel.de/ oder 192.168.1.100) eingeben und einen klaren Anzeigenamen, der im Dashboard erscheint."
},
"common": {
"appName": "Checkmate",
@@ -607,7 +614,7 @@
"forgotPassword": "Passwort vergessen?",
"register": "Noch keinen Account?",
"forgotPasswordLink": "Passwort zurücksetzen",
- "registerLink": "Hier resistrieren"
+ "registerLink": "Hier registrieren"
},
"toasts": {
"success": "Willkommen zurück. Erfolgreich angemeldet.",
@@ -618,7 +625,7 @@
"incorrect": "Das Passwort stimmt nicht mit dem gespeicherten überein"
}
},
- "welcome": ""
+ "welcome": "Willkommen zurück bei Checkmate!"
},
"registration": {
"heading": {
@@ -645,7 +652,7 @@
"toasts": {
"success": "Willkommen! Benutzer erfolgreich angelegt"
},
- "welcome": ""
+ "welcome": "Willkommen bei Checkmate!"
},
"forgotPassword": {
"heading": "Passwort vergessen?",
@@ -746,8 +753,8 @@
"monitorStatus": {
"checkingEvery": "Überprüfung alle {{interval}}",
"withCaptureAgent": "mit Capture-Agent {{version}}",
- "up": "hoch",
- "down": "runter",
+ "up": "Online",
+ "down": "Offline",
"paused": "pausiert"
},
"advancedMatching": "Erweiterter Abgleich",
@@ -792,7 +799,7 @@
"lastRunHeader": "Letzter Lauf um",
"lastFinishedAtHeader": "Zuletzt abgeschlossen um",
"lastRunTookHeader": "Letzter Lauf dauerte",
- "intervalHeader": ""
+ "intervalHeader": "Invervall"
},
"metricsTable": {
"title": "Warteschlangenmetriken",
@@ -900,8 +907,8 @@
"title": "Überwachen Sie IP/URL auf der Statusseite"
},
"globalThresholds": {
- "title": "",
- "description": ""
+ "title": "Globale Schwellenwerte",
+ "description": "Globale CPU-, Speicher-, Festplatten- und Temperaturschwellenwerte festlegen. Wenn ein Wert angegeben ist, wird er automatisch für die Überwachung genutzt."
}
},
"statusPageCreate": {
@@ -975,11 +982,11 @@
"oneHour": "1 Stunde",
"oneDay": "1 Tag",
"oneWeek": "1 Woche",
- "fourMinutes": "",
- "oneMinute": "",
- "twoMinutes": "",
- "fifteenSeconds": "",
- "thirtySeconds": ""
+ "fourMinutes": "4 Minuten",
+ "oneMinute": "Eine Minute",
+ "twoMinutes": "Zwei Minuten",
+ "fifteenSeconds": "15 Sekunden",
+ "thirtySeconds": "30 Sekunden"
},
"general": {
"noOptionsFound": "Keine {{unit}} gefunden"
@@ -1050,73 +1057,84 @@
"incidentsPageActionResolveMonitor": "Monitor-Vorfälle beheben",
"incidentsPageActionResolveAll": "Alle Vorfälle lösen",
"matchMethodOptions": {
- "equal": "",
- "equalPlaceholder": "",
- "include": "",
- "includePlaceholder": "",
- "regex": "",
- "regexPlaceholder": "",
- "text": ""
+ "equal": "Gleich",
+ "equalPlaceholder": "Erfolg",
+ "include": "Enthält",
+ "includePlaceholder": "OK",
+ "regex": "Regex",
+ "regexPlaceholder": "^(success|ok)$",
+ "text": "Übereinstummungsmethode"
},
"monitorType": {
"docker": {
- "label": "",
- "namePlaceholder": "",
- "placeholder": ""
+ "label": "Container Name/ID",
+ "namePlaceholder": "Mein Container",
+ "placeholder": "Meine-App"
},
"http": {
- "label": "",
- "namePlaceholder": "",
- "placeholder": ""
+ "label": "Zu überwachende URL",
+ "namePlaceholder": "Google",
+ "placeholder": "google.de"
},
"ping": {
- "label": "",
- "namePlaceholder": "",
- "placeholder": ""
+ "label": "Zu überwachende IP-Adresse",
+ "namePlaceholder": "Google",
+ "placeholder": "1.1.1.1"
},
"port": {
- "label": "",
- "namePlaceholder": "",
- "placeholder": ""
+ "label": "Zu überwachende URL",
+ "namePlaceholder": "localhost:5173",
+ "placeholder": "localhost"
},
"game": {
- "label": "",
- "namePlaceholder": "",
- "placeholder": ""
+ "label": "Zu überwachende URL",
+ "namePlaceholder": "localhost:5173",
+ "placeholder": "localhost"
}
},
"uptimeAdvancedMatching": {
- "jsonPath": ""
+ "jsonPath": "JSON Pfad"
},
- "bytesPerSecond": "",
- "bytesReceived": "",
- "bytesSent": "",
- "chooseGame": "",
+ "bytesPerSecond": "Bytes pro Sekunde",
+ "bytesReceived": "Empfangene Bytes",
+ "bytesSent": "Gesendete Bytes",
+ "chooseGame": "Spiel auswählen",
"createMonitorPage": {
- "incidentConfigDescription": "",
- "incidentConfigStatusWindowLabel": "",
- "incidentConfigStatusWindowThresholdLabel": "",
- "incidentConfigTitle": ""
+ "incidentConfigDescription": "Mithilfe eines gleitenden Fensters wird ermittelt, wann ein Monitor ausfällt. Der Status eines Monitors ändert sich nur, wenn der Prozentsatz der Prüfungen im gleitenden Fenster den angegebenen Wert erreicht.",
+ "incidentConfigStatusWindowLabel": "Wie viele Prüfungen sollen im gleitenden Fenster ausgeführt werden?",
+ "incidentConfigStatusWindowThresholdLabel": "Wie viel Prozent der Prüfungen im gleitenden Fenster sollen fehlschlagen/erfolgreich sein, bevor sich der Status ändert?",
+ "incidentConfigTitle": "Vorfälle",
+ "incidentConfigDescriptionV2": "Anzahl der aufeinanderfolgenden Prüfungen, die zum Ändern des Monitorstatus erforderlich sind. Maximal 25",
+ "incidentConfigStatusCheckNumber": "Anzahl der Prüfungen bevor sich er Status ändert",
+ "intervalTitle": "Prüfintervall",
+ "intervalDescription": "Wie oft soll die Überwachung geprüft werden."
},
- "dataRate": "",
- "dataReceived": "",
- "dataSent": "",
- "details": "",
- "drops": "",
- "errors": "",
- "errorsIn": "",
- "errorsOut": "",
- "gameServerMonitoring": "",
- "gameServerMonitoringDescription": "",
- "network": "",
- "networkDrops": "",
- "networkErrors": "",
- "networkInterface": "",
- "noNetworkStatsAvailable": "",
- "packetsPerSecond": "",
- "packetsReceived": "",
- "packetsReceivedRate": "",
- "packetsSent": "",
- "rate": "",
- "selectInterface": ""
+ "dataRate": "Datenrate",
+ "dataReceived": "Empfangene Daten",
+ "dataSent": "Gesendete Daten",
+ "details": "Details",
+ "drops": "verloren",
+ "errors": "Fehler",
+ "errorsIn": "Fehler eingehend",
+ "errorsOut": "Fehler ausgehend",
+ "gameServerMonitoring": "Spiele Server Überwachung",
+ "gameServerMonitoringDescription": "Prüfen ob der Spiele-Server läuft oder nicht",
+ "network": "Netzwerk",
+ "networkDrops": "Netzwerkabbrüche",
+ "networkErrors": "Netzwerkfehler",
+ "networkInterface": "Netzwerkschnittstelle",
+ "noNetworkStatsAvailable": "Keine Netzwerkstatistiken verfügbar.",
+ "packetsPerSecond": "Pakete pro Sekunde",
+ "packetsReceived": "Empfangene Pakete",
+ "packetsReceivedRate": "Rate empfangener Pakete",
+ "packetsSent": "Gesendete Pakete",
+ "rate": "Rate",
+ "selectInterface": "Schnittstelle auswählen",
+ "v1": {
+ "infrastructure": {
+ "disk_selection_title": "Festplattenauswahl",
+ "disk_selection_info": "Momentan wurde keine Festplatte erkannt.",
+ "disk_selection_description": "Wählen Sie die spezifischen Festplatten aus, die Sie überwachen möchten."
+ }
+ }
}
diff --git a/client/src/locales/en.json b/client/src/locales/en.json
index d89ebc44e..7386ced7d 100644
--- a/client/src/locales/en.json
+++ b/client/src/locales/en.json
@@ -1,8 +1,61 @@
{
+ "common": {
+ "appName": "Checkmate",
+ "monitoringAgentName": "Capture",
+ "labels": {
+ "responseTime": "Response time"
+ },
+ "buttons": {
+ "toggleTheme": "Toggles light & dark",
+ "testNotifications": "Test notifications",
+ "create": "Create",
+ "incidents": "Incidents",
+ "pause": "Pause",
+ "resume": "Resume",
+ "delete": "Delete",
+ "cancel": "Cancel",
+ "confirm": "Confirm"
+ },
+ "alerts": {
+ "pageSpeedApiKey": {
+ "content": "Warning: You haven't added a Google PageSpeed API key yet. Visit Settings to add one. Without it, the PageSpeed monitor won't function."
+ }
+ },
+ "dialogs": {
+ "delete": {
+ "description": "This action cannot be undone.",
+ "title": "Are you sure you want to delete this?"
+ }
+ },
+ "toasts": {
+ "networkError": "Network error",
+ "checkConnection": "Please check your connection",
+ "unknownError": "Unknown error"
+ },
+ "table": {
+ "headers": {
+ "actions": "Actions",
+ "active": "Active",
+ "message": "Message",
+ "monitor": "Monitor",
+ "name": "Name",
+ "status": "Status",
+ "type": "Type"
+ },
+ "empty": "Nothing here"
+ },
+ "charts": {
+ "labels": {
+ "uptime": "Uptime",
+ "downtime": "Downtime",
+ "averageResponseTime": "Average response time",
+ "low": "Low",
+ "high": "High"
+ }
+ }
+ },
"submit": "Submit",
"title": "Title",
- "distributedStatusHeaderText": "Real-time, real-device coverage",
- "distributedStatusSubHeaderText": "Powered by millions devices worldwide, view a system performance by global region, country or city",
"settingsDisabled": "Disabled",
"settingsSuccessSaved": "Settings saved successfully",
"settingsFailedToSave": "Failed to save settings",
@@ -20,6 +73,13 @@
"delete": "Delete",
"configure": "Configure",
"responseTime": "Response time",
+ "responseTimeBreakdown": "Response time breakdown",
+ "dnsLookup": "DNS lookup",
+ "tcpConnection": "TCP connection",
+ "tlsHandshake": "TLS handshake",
+ "waitTime": "Wait time",
+ "timeToFirstByte": "Time to first byte",
+ "download": "Download",
"ms": "ms",
"bar": "Bar",
"area": "Area",
@@ -31,18 +91,11 @@
"webhookSendSuccess": "Webhook notification sent successfully",
"webhookSendError": "Error sending webhook notification to {platform}",
"webhookUnsupportedPlatform": "Unsupported platform: {platform}",
- "distributedRightCategoryTitle": "Monitor",
- "distributedStatusServerMonitors": "Server Monitors",
- "distributedStatusServerMonitorsDescription": "Monitor status of related servers",
"distributedUptimeCreateSelectURL": "Here you can select the URL of the host, together with the type of monitor.",
"distributedUptimeCreateChecks": "Checks to perform",
"distributedUptimeCreateChecksDescription": "You can always add or remove checks after adding your site.",
- "distributedUptimeCreateIncidentNotification": "Incident notifications",
- "distributedUptimeCreateIncidentDescription": "When there is an incident, notify users.",
"distributedUptimeCreateAdvancedSettings": "Advanced settings",
"distributedUptimeDetailsNoMonitorHistory": "There is no check history for this monitor yet.",
- "distributedUptimeDetailsStatusHeaderUptime": "Uptime:",
- "distributedUptimeDetailsStatusHeaderLastUpdate": "Last updated",
"notifications": {
"enableNotifications": "Enable {{platform}} notifications",
"testNotification": "Test notification",
@@ -115,37 +168,87 @@
"failed": "Failed to send test notification"
}
},
- "testLocale": "testLocale",
"add": "Add",
"monitors": "monitors",
- "distributedUptimeStatusCreateStatusPage": "status page",
- "distributedUptimeStatusCreateStatusPageAccess": "Access",
- "distributedUptimeStatusCreateStatusPageReady": "If your status page is ready, you can mark it as published.",
- "distributedUptimeStatusBasicInfoHeader": "Basic Information",
- "distributedUptimeStatusBasicInfoDescription": "Define company name and the subdomain that your status page points to.",
- "distributedUptimeStatusLogoHeader": "Logo",
- "distributedUptimeStatusLogoDescription": "Upload a logo for your status page",
- "distributedUptimeStatusLogoUploadButton": "Upload logo",
- "distributedUptimeStatusStandardMonitorsHeader": "Standard Monitors",
- "distributedUptimeStatusStandardMonitorsDescription": "Attach standard monitors to your status page.",
- "distributedUptimeStatusCreateYour": "Create your",
- "distributedUptimeStatusEditYour": "Edit your",
- "distributedUptimeStatusPublishedLabel": "Published and visible to the public",
- "distributedUptimeStatusCompanyNameLabel": "Company name",
- "distributedUptimeStatusPageAddressLabel": "Your status page address",
- "distributedUptimeStatus30Days": "30 days",
- "distributedUptimeStatus60Days": "60 days",
- "distributedUptimeStatus90Days": "90 days",
- "distributedUptimeStatusPageNotSetUp": "A status page is not set up.",
- "distributedUptimeStatusContactAdmin": "Please contact your administrator",
- "distributedUptimeStatusPageNotPublic": "This status page is not public.",
- "distributedUptimeStatusPageDeleteDialog": "Do you want to delete this status page?",
- "distributedUptimeStatusPageDeleteConfirm": "Yes, delete status page",
- "distributedUptimeStatusPageDeleteDescription": "Once deleted, your status page cannot be retrieved.",
- "distributedUptimeStatusDevices": "Devices",
- "distributedUptimeStatusUpt": "UPT",
- "distributedUptimeStatusUptBurned": "UPT Burned",
- "distributedUptimeStatusUptLogo": "Upt Logo",
+ "pages": {
+ "common": {
+ "monitors": {
+ "status": {
+ "up": "up",
+ "down": "down",
+ "paused": "paused",
+ "initializing": "initializing"
+ },
+ "actions": {
+ "openSite": "Open site",
+ "details": "Details",
+ "incidents": "Incidents",
+ "configure": "Configure",
+ "pause": "Pause",
+ "resume": "Resume",
+ "delete": "Delete"
+ },
+ "statBoxes": {
+ "activeFor": "Active for",
+ "lastCheck": "Last check",
+ "lastResponseTime": "Last response time",
+ "certificateExpiry": "Certificate expiry"
+ }
+ }
+ },
+ "uptime": {
+ "table": {
+ "headers": {
+ "responseTime": "Response time"
+ }
+ },
+ "fallback": {
+ "title": "An uptime monitor is used to:",
+ "actionButton": "Create a monitor!",
+ "checks": [
+ "Check if websites or servers are online & responsive",
+ "Alert teams about downtime or performance issues",
+ "Monitor HTTP endpoints, pings, containers & ports",
+ "Track historical uptime and reliability trends"
+ ]
+ },
+ "filters": {
+ "search": {
+ "placeholder": "Search monitors..."
+ }
+ }
+ },
+ "checks": {
+ "table": {
+ "headers": {
+ "dateTime": "Date & time",
+ "statusCode": "Status code"
+ }
+ }
+ },
+ "pageSpeed": {
+ "table": {
+ "headers": {
+ "pageSpeedScore": "PageSpeed score"
+ }
+ },
+ "charts": {
+ "common": {
+ "cls": "Cumulative Layout Shift (CLS)",
+ "fcp": "First Contentful Paint (FCP)",
+ "lcp": "Largest Contentful Paint (LCP)",
+ "si": "Speed Index (SI)",
+ "tbt": "Total Blocking Time (TBT)"
+ },
+ "pie": { "title": "Performance report" },
+ "legend": {
+ "title": "PageSpeed report",
+
+ "weight": "Weight"
+ }
+ }
+ }
+ },
"incidentsTableNoIncidents": "No incidents recorded",
"incidentsTablePaginationLabel": "incidents",
"incidentsTableMonitorName": "Monitor Name",
@@ -185,6 +288,8 @@
"total": "Total",
"cores": "Cores",
"frequency": "Frequency",
+ "device": "Device",
+ "mountedOn": "Mounted on",
"status": "Status",
"cpuPhysical": "CPU (Physical)",
"cpuLogical": "CPU (Logical)",
@@ -239,6 +344,7 @@
"addMonitors": "Add monitors",
"window": "window",
"cancel": "Cancel",
+ "close": "Close",
"message": "Message",
"low": "low",
"high": "high",
@@ -425,7 +531,13 @@
}
}
},
- "role": "Role"
+ "role": "Role",
+ "changeTeamPassword": {
+ "changePasswordMenu": "Reset Password",
+ "title": "Reset team member password",
+ "description": "Create a new password for this team member. You will need to share the password with them securely.",
+ "success": "Password successfully reset. Make sure to provide the credentials to the member in a secure way."
+ }
},
"monitorState": {
"paused": "Paused",
@@ -450,7 +562,8 @@
"team": "Team",
"logOut": "Log out",
"notifications": "Notifications",
- "logs": "Logs"
+ "logs": "Logs",
+ "checks": "Checks"
},
"settingsEmailUser": "Email user - Username for authentication, overrides email address if specified",
"state": "State",
@@ -459,6 +572,7 @@
"commonSaving": "Saving...",
"navControls": "Controls",
"incidentsPageTitle": "Incidents",
+ "checksPageTitle": "Checks",
"passwordPanel": {
"passwordChangedSuccess": "Your password was changed successfully.",
"passwordInputIncorrect": "Your password input was incorrect.",
@@ -467,7 +581,7 @@
"newPassword": "New password",
"enterNewPassword": "Enter your new password",
"confirmNewPassword": "Confirm new password",
- "passwordRequirements": "New password must contain at least 8 characters and must have at least one uppercase letter, one lowercase letter, one number and one special character.",
+ "passwordRequirements": "New password must contain at least 8 characters and must have at least one uppercase letter, one lowercase letter, one number and one special character (!?@#$%^&*()-_=+[]{}|;:'\",./\\~`).",
"saving": "Saving..."
},
"emailSent": "Email sent successfully",
@@ -487,19 +601,8 @@
"ping": "Enter the IP address or hostname to ping (e.g., 192.168.1.100 or example.com) and add a clear display name that appears on the dashboard.",
"docker": "Enter the Docker container name or ID. You can use either the container name (e.g., my-app) or the container ID (full 64-char ID or short ID).",
"port": "Enter the URL or IP of the server, the port number and a clear display name that appears on the dashboard.",
- "game": "Enter the IP address or hostname and the port number to ping (e.g., 192.168.1.100 or example.com) and choose game type."
- },
- "common": {
- "appName": "Checkmate",
- "monitoringAgentName": "Capture",
- "buttons": {
- "toggleTheme": "Toggles light & dark"
- },
- "toasts": {
- "networkError": "Network error",
- "checkConnection": "Please check your connection",
- "unknownError": "Unknown error"
- }
+ "game": "Enter the IP address or hostname and the port number to ping (e.g., 192.168.1.100 or example.com) and choose game type.",
+ "https": "Enter the URL or IP to monitor (e.g., https://example.com/ or 192.168.1.100) and add a clear display name that appears on the dashboard."
},
"auth": {
"common": {
@@ -525,7 +628,7 @@
},
"special": {
"beginning": "Must contain at least",
- "highlighted": "one special character"
+ "highlighted": "one special character (!?@#$%^&*()-_=+[]{}|;:'\",./\\~`)"
},
"number": {
"beginning": "Must contain at least",
@@ -550,7 +653,7 @@
"uppercase": "Password must contain at least 1 uppercase letter",
"lowercase": "Password must contain at least 1 lowercase letter",
"number": "Password must contain at least 1 number",
- "special": "Password must contain at least 1 special character",
+ "special": "Password must contain at least 1 special character (!?@#$%^&*()-_=+[]{}|;:'\",./\\~`)",
"incorrect": "The password you provided does not match our records"
}
},
@@ -774,7 +877,16 @@
"error": "Error",
"debug": "Debug"
}
- }
+ },
+ "table": {
+ "timestamp": "Timestamp",
+ "level": "Level",
+ "service": "Service",
+ "method": "Method",
+ "message": "Message",
+ "logs": "logs"
+ },
+ "noLogs": "No logs found"
},
"queuePage": {
"title": "Queue",
@@ -890,6 +1002,9 @@
"description": "Switch between light and dark mode, or change user interface language.",
"labelLanguage": "Language",
"labelTheme": "Theme mode",
+ "labelChartType": "Chart type",
+ "chartTypeHistogram": "Histogram",
+ "chartTypeHeatmap": "Heatmap",
"title": "Appearance"
},
"urlSettings": {
@@ -968,6 +1083,10 @@
},
"pageSpeedLighthouseAPI": "Use the Lighthouse PageSpeed API to monitor your website",
"time": {
+ "fifteenSeconds": "15 seconds",
+ "thirtySeconds": "30 seconds",
+ "fifteenMinutes": "15 minutes",
+ "thirtyMinutes": "30 minutes",
"threeMinutes": "3 minutes",
"fiveMinutes": "5 minutes",
"tenMinutes": "10 minutes",
@@ -977,9 +1096,7 @@
"oneWeek": "1 week",
"fourMinutes": "4 minutes",
"oneMinute": "1 minute",
- "twoMinutes": "2 minutes",
- "fifteenSeconds": "15 seconds",
- "thirtySeconds": "30 seconds"
+ "twoMinutes": "2 minutes"
},
"general": {
"noOptionsFound": "No {{unit}} found"
@@ -1048,7 +1165,9 @@
}
},
"incidentsPageActionResolveMonitor": "Resolve monitor incidents",
+ "checksPageActionResolveMonitor": "Resolve monitor checks",
"incidentsPageActionResolveAll": "Resolve all incidents",
+ "checksPageActionResolveAllMonitor": "Resolve all checks",
"matchMethodOptions": {
"equal": "Equal",
"equalPlaceholder": "success",
@@ -1096,7 +1215,11 @@
"incidentConfigDescription": "A sliding window is used to determine when a monitor goes down. The status of a monitor will only change when the percentage of checks in the sliding window meet the specified value.",
"incidentConfigStatusWindowLabel": "How many checks should be in the sliding window?",
"incidentConfigStatusWindowThresholdLabel": "What percentage of checks in the sliding window fail/succeed before monitor status changes?",
- "incidentConfigTitle": "Incidents"
+ "incidentConfigTitle": "Incidents",
+ "incidentConfigDescriptionV2": "Number of consecutive checks needed to change monitor status. Maximum 25",
+ "incidentConfigStatusCheckNumber": "How many checks before status change?",
+ "intervalTitle": "Check interval",
+ "intervalDescription": "How often the monitor should be checked."
},
"dataRate": "Data Rate",
"dataReceived": "Data Received",
@@ -1118,5 +1241,88 @@
"packetsReceivedRate": "Packets Received Rate",
"packetsSent": "Packets Sent",
"rate": "Rate",
- "selectInterface": "Select Interface"
+ "selectInterface": "Select Interface",
+ "v1": {
+ "infrastructure": {
+ "disk_selection_title": "Disk Selection",
+ "disk_selection_info": "No disk detected for the moment.",
+ "disk_selection_description": "Select the specific disks you want to monitor."
+ }
+ },
+ "incidentsPage": {
+ "title": "Incidents",
+ "description": "Manage incidents for your monitors. You can resolve incidents here.",
+ "resolved": "Resolved",
+ "active": "Active",
+ "startTime": "Start Time",
+ "endTime": "End Time",
+ "resolutionType": "Resolution Type",
+ "unresolved": "Unresolved",
+ "all": "All",
+ "filterByMonitor": "Filter by monitor",
+ "filterByStatus": "Filter by status",
+ "filterByType": "Filter by type",
+ "filterByDate": "Filter by date",
+ "filterByPriority": "Filter by priority",
+ "incidentsActivePanelError": "Error loading active incidents",
+ "allSystemsAreOperational": "All Systems Are Operational",
+ "incidentsActivePanelTitle": "Total Active Incidents",
+ "incidentsLatestPanelError": "Error loading latest incidents",
+ "incidentsLatestPanelTitle": "Latest Incidents",
+ "incidentsLatestPanelEmpty": "No recent incidents",
+ "incidentItemMonitor": "Monitor",
+ "incidentItemStatus": "Status",
+ "unknownMonitor": "Unknown Monitor",
+ "incidentsStatisticsPanelError": "Error loading statistics",
+ "incidentsStatisticsPanelTitle": "General Statistics",
+ "totalIncidents": "Total Incidents",
+ "mostAffectedMonitor": "Most Affected Monitor",
+ "avgResolutionTime": "Avg. Resolution Time",
+ "resolutions": "Resolutions",
+ "automatic": "Automatic",
+ "manual": "Manual",
+ "incidentResolvedSuccessfully": "Incident resolved successfully",
+ "incidentResolvedFailed": "Error resolving incident",
+ "errorFetchingIncidents": "Error fetching incidents",
+ "errorFetchingIncident": "Error fetching incident",
+ "noIncidentIdProvided": "No incident ID provided",
+ "errorFetchingIncidentSummary": "Error fetching incident summary",
+ "incidentsOptionsHeaderFilterManual": "Manual",
+ "incidentsOptionsHeaderFilterAutomatic": "Automatic",
+ "incidentsOptionsHeaderFilterAll": "All",
+ "incidentsOptionsHeaderFilterActive": "Active",
+ "incidentsOptionsHeaderFilterResolved": "Resolved",
+ "incidentsTableResolved": "Closed",
+ "incidentsTableActionResolveManually": "Resolve Manually",
+ "incidentsTableActionDetails": "Details",
+ "incidentsTableActionGoToMonitor": "Go to monitor",
+ "resolveIncidentDialogTitle": "Resolve Incident",
+ "resolveIncidentDialogDescription": "Are you sure you want to resolve this incident manually?",
+ "resolveIncidentDialogCommentLabel": "Comment (optional)",
+ "resolveIncidentDialogCommentPlaceholder": "Add a comment about the resolution...",
+ "resolveIncidentDialogConfirm": "Resolve",
+ "hours": "hours",
+ "loading": "Loading incident details...",
+ "incidentNotFound": "Incident not found",
+ "incidentDetails": "INCIDENT DETAILS",
+ "activeIncident": "ACTIVE INCIDENT",
+ "resolvedIncident": "RESOLVED INCIDENT",
+ "overview": "OVERVIEW",
+ "URL": "URL",
+ "incidentAnalysis": "INCIDENT ANALYSIS",
+ "timeline": "Timeline",
+ "startedAt": "Started at",
+ "endedAt": "Ended at",
+ "downtime": "Downtime",
+ "errorDetails": "Error details",
+ "resolutionDetails": "Resolution details",
+ "statusCode": "Status code",
+ "resolutionMethod": "Resolution method",
+ "resolvedBy": "Resolved by",
+ "comment": "Comment",
+ "noCommentProvided": "No comment provided",
+ "loadingIncidentDetails": "Loading incident details...",
+ "none": "None",
+ "message": "Message"
+ }
}
diff --git a/client/src/locales/es.json b/client/src/locales/es.json
index 440d40027..ed6a98c5d 100644
--- a/client/src/locales/es.json
+++ b/client/src/locales/es.json
@@ -129,32 +129,32 @@
"distributedUptimeStatusPublishedLabel": "",
"distributedUptimeStatusCompanyNameLabel": "",
"distributedUptimeStatusPageAddressLabel": "",
- "distributedUptimeStatus30Days": "",
- "distributedUptimeStatus60Days": "",
- "distributedUptimeStatus90Days": "",
+ "distributedUptimeStatus30Days": "30 dias",
+ "distributedUptimeStatus60Days": "60 días",
+ "distributedUptimeStatus90Days": "90 dias",
"distributedUptimeStatusPageNotSetUp": "",
"distributedUptimeStatusContactAdmin": "",
"distributedUptimeStatusPageNotPublic": "",
"distributedUptimeStatusPageDeleteDialog": "",
"distributedUptimeStatusPageDeleteConfirm": "",
"distributedUptimeStatusPageDeleteDescription": "",
- "distributedUptimeStatusDevices": "",
+ "distributedUptimeStatusDevices": "Dispositivos",
"distributedUptimeStatusUpt": "",
"distributedUptimeStatusUptBurned": "",
"distributedUptimeStatusUptLogo": "",
"incidentsTableNoIncidents": "",
- "incidentsTablePaginationLabel": "",
+ "incidentsTablePaginationLabel": "Incidentes",
"incidentsTableMonitorName": "",
- "incidentsTableStatus": "",
+ "incidentsTableStatus": "Estado",
"incidentsTableDateTime": "",
"incidentsTableStatusCode": "",
- "incidentsTableMessage": "",
+ "incidentsTableMessage": "Mensaje",
"incidentsOptionsHeader": "",
"incidentsOptionsHeaderFilterBy": "",
- "incidentsOptionsHeaderFilterAll": "",
+ "incidentsOptionsHeaderFilterAll": "Todo",
"incidentsOptionsHeaderFilterDown": "",
"incidentsOptionsHeaderFilterCannotResolve": "",
- "incidentsOptionsHeaderShow": "",
+ "incidentsOptionsHeaderShow": "Mostrar:",
"incidentsOptionsHeaderLastHour": "",
"incidentsOptionsHeaderLastDay": "",
"incidentsOptionsHeaderLastWeek": "",
@@ -165,22 +165,22 @@
"infrastructureCustomizeAlerts": "",
"infrastructureAlertNotificationDescription": "",
"infrastructureCreateMonitor": "",
- "infrastructureProtocol": "",
+ "infrastructureProtocol": "Protocolo",
"infrastructureServerUrlLabel": "",
"infrastructureDisplayNameLabel": "",
"infrastructureAuthorizationSecretLabel": "",
- "gb": "",
- "mb": "",
- "mem": "",
+ "gb": "GB",
+ "mb": "MB",
+ "mem": "Mem",
"memoryUsage": "",
- "cpu": "",
+ "cpu": "CPU",
"cpuUsage": "",
"cpuTemperature": "",
"diskUsage": "",
"used": "",
- "total": "",
- "cores": "",
- "frequency": "",
+ "total": "Total",
+ "cores": "Nucleos",
+ "frequency": "Frecuencia",
"status": "",
"cpuPhysical": "",
"cpuLogical": "",
@@ -189,25 +189,25 @@
"memory": "",
"disk": "",
"uptime": "",
- "os": "",
+ "os": "SO",
"host": "",
"actions": "",
"integrations": "",
"integrationsPrism": "",
- "integrationsSlack": "",
+ "integrationsSlack": "Slack",
"integrationsSlackInfo": "",
- "integrationsDiscord": "",
+ "integrationsDiscord": "Discord",
"integrationsDiscordInfo": "",
- "integrationsZapier": "",
+ "integrationsZapier": "Zapier",
"integrationsZapierInfo": "",
- "commonSave": "",
+ "commonSave": "Guardar",
"createYour": "",
"createMonitor": "",
- "pause": "",
- "resume": "",
- "editing": "",
- "url": "",
- "access": "",
+ "pause": "Pausar",
+ "resume": "Resumir",
+ "editing": "Editando...",
+ "url": "URL",
+ "access": "Acceso",
"timezone": "",
"features": "",
"administrator": "",
@@ -231,13 +231,13 @@
"friendlyNamePlaceholder": "",
"maintenanceRepeat": "",
"maintenance": "",
- "duration": "",
+ "duration": "Duración",
"addMonitors": "",
- "window": "",
- "cancel": "",
- "message": "",
- "low": "",
- "high": "",
+ "window": "ventana",
+ "cancel": "Cancelar",
+ "message": "Mensaje",
+ "low": "bajo",
+ "high": "alto",
"statusCode": "",
"date&Time": "",
"type": "",
@@ -421,7 +421,13 @@
}
}
},
- "role": ""
+ "role": "",
+ "changeTeamPassword": {
+ "changePasswordMenu": "",
+ "title": "",
+ "description": "",
+ "success": ""
+ }
},
"monitorState": {
"paused": "",
@@ -483,7 +489,8 @@
"ping": "",
"docker": "",
"port": "",
- "game": ""
+ "game": "",
+ "https": ""
},
"common": {
"appName": "",
@@ -1071,7 +1078,11 @@
"incidentConfigDescription": "",
"incidentConfigStatusWindowLabel": "",
"incidentConfigStatusWindowThresholdLabel": "",
- "incidentConfigTitle": ""
+ "incidentConfigTitle": "",
+ "incidentConfigDescriptionV2": "",
+ "incidentConfigStatusCheckNumber": "",
+ "intervalTitle": "",
+ "intervalDescription": ""
},
"dataRate": "",
"dataReceived": "",
diff --git a/client/src/locales/fi.json b/client/src/locales/fi.json
index d98ece35b..4949817c7 100644
--- a/client/src/locales/fi.json
+++ b/client/src/locales/fi.json
@@ -421,7 +421,13 @@
}
}
},
- "role": ""
+ "role": "",
+ "changeTeamPassword": {
+ "changePasswordMenu": "",
+ "title": "",
+ "description": "",
+ "success": ""
+ }
},
"monitorState": {
"paused": "Tauota",
@@ -483,7 +489,8 @@
"ping": "",
"docker": "",
"port": "",
- "game": ""
+ "game": "",
+ "https": ""
},
"common": {
"appName": "",
@@ -1071,7 +1078,11 @@
"incidentConfigDescription": "",
"incidentConfigStatusWindowLabel": "",
"incidentConfigStatusWindowThresholdLabel": "",
- "incidentConfigTitle": ""
+ "incidentConfigTitle": "",
+ "incidentConfigDescriptionV2": "",
+ "incidentConfigStatusCheckNumber": "",
+ "intervalTitle": "",
+ "intervalDescription": ""
},
"dataRate": "",
"dataReceived": "",
diff --git a/client/src/locales/fr.json b/client/src/locales/fr.json
index bfce12843..9e76440f0 100644
--- a/client/src/locales/fr.json
+++ b/client/src/locales/fr.json
@@ -425,7 +425,13 @@
}
}
},
- "role": ""
+ "role": "",
+ "changeTeamPassword": {
+ "changePasswordMenu": "",
+ "title": "",
+ "description": "",
+ "success": ""
+ }
},
"monitorState": {
"paused": "Pause",
@@ -487,7 +493,8 @@
"ping": "Entrez l'adresse IP ou le nom d'hôte à tester (par exemple, 192.168.1.100 ou exemple.fr) et ajoutez un nom familier qui apparaîtra sur le tableau de bord.",
"docker": "Entrer l'ID Docker du container. Les identifiants Docker doivent être les 64 caractères de l'ID Docker. Vous pouvez utiliser la commande docker inspect pour avoir l'ID complet.",
"port": "Entrez l'URL ou l'adresse IP du serveur, le numéro de port et un nom d'affichage familier qui apparaîtra sur le tableau de bord.",
- "game": ""
+ "game": "",
+ "https": ""
},
"common": {
"appName": "Checkmate",
@@ -1096,7 +1103,11 @@
"incidentConfigDescription": "",
"incidentConfigStatusWindowLabel": "",
"incidentConfigStatusWindowThresholdLabel": "",
- "incidentConfigTitle": ""
+ "incidentConfigTitle": "",
+ "incidentConfigDescriptionV2": "",
+ "incidentConfigStatusCheckNumber": "",
+ "intervalTitle": "",
+ "intervalDescription": ""
},
"dataRate": "",
"dataReceived": "",
@@ -1118,5 +1129,12 @@
"packetsReceivedRate": "",
"packetsSent": "",
"rate": "",
- "selectInterface": ""
+ "selectInterface": "",
+ "v1": {
+ "infrastructure": {
+ "disk_selection_title": "Sélection des disques",
+ "disk_selection_info": "Aucun disque détecté pour le moment.",
+ "disk_selection_description": "Sélectionnez les disques spécifiques que vous souhaitez surveiller."
+ }
+ }
}
diff --git a/client/src/locales/ja.json b/client/src/locales/ja.json
index 6107c19d0..68d94139f 100644
--- a/client/src/locales/ja.json
+++ b/client/src/locales/ja.json
@@ -425,7 +425,13 @@
}
}
},
- "role": ""
+ "role": "",
+ "changeTeamPassword": {
+ "changePasswordMenu": "",
+ "title": "",
+ "description": "",
+ "success": ""
+ }
},
"monitorState": {
"paused": "一時停止",
@@ -487,7 +493,8 @@
"ping": "pingするIPアドレスまたはホスト名を入力(例: 192.168.1.100 または example.com)し、ダッシュボードに表示される明確な表示名を追加。",
"docker": "コンテナのDocker IDを入力。Docker IDは64文字のフルDocker IDである必要があります。docker inspect を実行してフルコンテナIDを取得できます。",
"port": "サーバーのURLまたはIP、ポート番号、ダッシュボードに表示される明確な表示名を入力。",
- "game": ""
+ "game": "",
+ "https": ""
},
"common": {
"appName": "Checkmate",
@@ -1075,7 +1082,11 @@
"incidentConfigDescription": "",
"incidentConfigStatusWindowLabel": "",
"incidentConfigStatusWindowThresholdLabel": "",
- "incidentConfigTitle": ""
+ "incidentConfigTitle": "",
+ "incidentConfigDescriptionV2": "",
+ "incidentConfigStatusCheckNumber": "",
+ "intervalTitle": "",
+ "intervalDescription": ""
},
"dataRate": "",
"dataReceived": "",
@@ -1097,5 +1108,12 @@
"packetsReceivedRate": "",
"packetsSent": "",
"rate": "",
- "selectInterface": ""
+ "selectInterface": "",
+ "v1": {
+ "infrastructure": {
+ "disk_selection_title": "ディスク選択",
+ "disk_selection_info": "現在ディスクが検出されていません。",
+ "disk_selection_description": "監視したい特定のディスクを選択してください。"
+ }
+ }
}
diff --git a/client/src/locales/pt-BR.json b/client/src/locales/pt-BR.json
index 9e1fa6bc0..193c53788 100644
--- a/client/src/locales/pt-BR.json
+++ b/client/src/locales/pt-BR.json
@@ -425,7 +425,13 @@
}
}
},
- "role": ""
+ "role": "",
+ "changeTeamPassword": {
+ "changePasswordMenu": "",
+ "title": "",
+ "description": "",
+ "success": ""
+ }
},
"monitorState": {
"paused": "Pausado",
@@ -487,7 +493,8 @@
"ping": "Insira o endereço IP ou nome de domínio para ping (ex. 192.168.1.100 ou exemplo.com.br) e adicione uma descrição que aparecerá na dashboard.",
"docker": "Insira o Docker Id do seu container. Docker Ids devem ser todos os 64 caracteres. Você pode executar docker inspect para descobrir o Id completo.",
"port": "Insira a URL ou o IP do servidor, aporta e uma descrição que aparecerá na dashboard.",
- "game": ""
+ "game": "",
+ "https": ""
},
"common": {
"appName": "Checkmate",
@@ -1096,7 +1103,11 @@
"incidentConfigDescription": "",
"incidentConfigStatusWindowLabel": "",
"incidentConfigStatusWindowThresholdLabel": "",
- "incidentConfigTitle": ""
+ "incidentConfigTitle": "",
+ "incidentConfigDescriptionV2": "",
+ "incidentConfigStatusCheckNumber": "",
+ "intervalTitle": "",
+ "intervalDescription": ""
},
"dataRate": "",
"dataReceived": "",
@@ -1118,5 +1129,12 @@
"packetsReceivedRate": "",
"packetsSent": "",
"rate": "",
- "selectInterface": ""
+ "selectInterface": "",
+ "v1": {
+ "infrastructure": {
+ "disk_selection_title": "Seleção de Discos",
+ "disk_selection_info": "Nenhum disco detectado no momento.",
+ "disk_selection_description": "Selecione os discos específicos que você deseja monitorar."
+ }
+ }
}
diff --git a/client/src/locales/ru.json b/client/src/locales/ru.json
index aa54de393..ab40ecb0d 100644
--- a/client/src/locales/ru.json
+++ b/client/src/locales/ru.json
@@ -10,7 +10,7 @@
"settingsFailedToClearStats": "Не удалось очистить статистику",
"settingsMonitorsDeleted": "Успешно удалены все мониторы",
"settingsFailedToDeleteMonitors": "Не удалось удалить все мониторы",
- "starPromptTitle": "",
+ "starPromptTitle": "Запустить Checkmate.",
"starPromptDescription": "Ознакомьтесь с последними релизами и помогите развить сообщество на GitHub",
"https": "HTTPS",
"http": "HTTP",
@@ -84,31 +84,35 @@
"unsupportedType": "Неподдерживаемый тип уведомления",
"networkError": "Произошла сетевая ошибка",
"fallback": {
- "title": "",
- "checks": [""],
- "actionButton": ""
+ "title": "Канал уведомлений используется для:",
+ "checks": [
+ "Дайте знать команде, если сервер ложится или что-то виснет",
+ "Сообщите инженерам, когда появляется какая-то проблема",
+ "Уведомляйте админов, если в системе что-то поменялось"
+ ],
+ "actionButton": "Давайте создадим ваш первый канал уведомлений!"
},
- "createButton": "",
- "createTitle": "",
+ "createButton": "Создать канал уведомлений",
+ "createTitle": "Канал уведомлений",
"create": {
- "success": "",
- "failed": ""
+ "success": "Уведомление успешно создано",
+ "failed": "Не удалось создать уведомление."
},
"fetch": {
- "success": "",
- "failed": ""
+ "success": "Уведомления успешно получены",
+ "failed": "Не удалось получить уведомления."
},
"delete": {
- "success": "",
- "failed": ""
+ "success": "Уведомление успешно удалено",
+ "failed": "Не удалось удалить уведомление."
},
"edit": {
- "success": "",
- "failed": ""
+ "success": "Уведомление успешно обновлено",
+ "failed": "Не удалось обновить уведомление."
},
"test": {
- "success": "",
- "failed": ""
+ "success": "Тестовое уведомление успешно отправлено",
+ "failed": "Не удалось отправить тестовое уведомление."
}
},
"testLocale": "testLocale",
@@ -332,8 +336,8 @@
"validationFailed": "Не удалось выполнить проверку",
"noFileSelected": "Файл не выбран",
"fallbackPage": "Импортируйте файл для массовой загрузки списка серверов",
- "invalidFileType": "",
- "uploadFailed": ""
+ "invalidFileType": "Неверный тип файла",
+ "uploadFailed": "Загрузка не удалась"
},
"DeleteAccountTitle": "Удалить аккаунт",
"DeleteAccountButton": "Удалить аккаунт",
@@ -378,50 +382,56 @@
"created": "Создано"
},
"addTeamMember": {
- "addMemberMenu": "",
- "title": "",
- "description": "",
- "addButton": ""
+ "addMemberMenu": "Добавить члена команды",
+ "title": "Зарегистрируйте нового члена команды",
+ "description": "Создайте нового пользователя и передайте ему учётные данные. Этот метод даёт участнику мгновенный доступ ко всем мониторам.",
+ "addButton": "Добавить участника"
},
- "register": "",
+ "register": "Зарегистрируйте члена команды",
"registerToast": {
- "success": "",
- "dbUserExists": "",
- "unknownError": ""
+ "success": "Созданный пользователем, безопасный обмен учетными данными с участником.",
+ "dbUserExists": "Пользователь уже существует.",
+ "unknownError": "Произошла неизвестная ошибка."
},
"registerTeamMember": {
- "title": "",
+ "title": "Зарегистрировать члена команды",
"auth": {
"common": {
"inputs": {
"firstName": {
"errors": {
- "empty": "",
- "pattern": ""
+ "empty": "Пожалуйста, введите имя",
+ "pattern": "Имя должно содержать только буквы, пробелы, апострофы или дефисы."
}
},
"lastName": {
"errors": {
- "empty": "",
- "pattern": ""
+ "empty": "Пожалуйста, введите фамилию",
+ "pattern": "Фамилия должна содержать только буквы, пробелы, апострофы или дефисы."
}
},
"email": {
"errors": {
- "empty": "",
- "invalid": ""
+ "empty": "Чтобы продолжить, введите адрес электронной почты.",
+ "invalid": "Пожалуйста, перепроверьте правильность введенного адреса электронной почты."
}
},
"role": {
"errors": {
- "empty": ""
+ "empty": "Требуется роль"
}
}
}
}
}
},
- "role": ""
+ "role": "Роль",
+ "changeTeamPassword": {
+ "changePasswordMenu": "Сбросить пароль",
+ "title": "Сбросить пароль члена команды",
+ "description": "Создайте новый пароль для этого члена команды. Вам необходимо будет сообщить ему пароль по секретному адресу.",
+ "success": "Пароль успешно сброшен. Убедитесь, что вы предоставили учётные данные участнику безопасным способом."
+ }
},
"monitorState": {
"paused": "Остановлено",
@@ -445,8 +455,8 @@
"password": "Пароль",
"team": "Команда",
"logOut": "Выйти",
- "notifications": "",
- "logs": ""
+ "notifications": "Уведомления",
+ "logs": "Логи"
},
"settingsEmailUser": "Email пользователь",
"state": "Состояние",
@@ -474,8 +484,8 @@
"settingsTestEmailUnknownError": "Неизвестная ошибка",
"statusMsg": {
"paused": "Мониторинг приостановлен",
- "up": "",
- "down": "",
+ "up": "Ваш сайт запущен.",
+ "down": "Сайт оффлайн.",
"pending": "Ожидание..."
},
"uptimeGeneralInstructions": {
@@ -483,13 +493,14 @@
"ping": "Ведите IP-адрес или имя хоста для пинга (например, 192.168.1.100 или example.com) и добавьте понятное имя, которое будет отображаться на панели инструментов.",
"docker": "Введите Docker ID вашего контейнера. Docker ID должен быть полным 64-символьным Docker ID. Вы можете запустить docker inspect , чтобы получить полный ID контейнера.",
"port": "Введите URL-адрес или IP-адрес сервера, номер порта и понятное имя, которое будет отображаться на панели инструментов.",
- "game": ""
+ "game": "Введите IP-адрес или имя хоста и номер порта для пинга (например, 192.168.1.100 или example.com) и выберите тип игры.",
+ "https": "Введите URL-адрес или IP-адрес для мониторинга (например, https://example.com/ или 192.168.1.100) и добавьте понятное отображаемое имя, которое будет отображаться на панели управления."
},
"common": {
- "appName": "",
- "monitoringAgentName": "",
+ "appName": "Checkmate",
+ "monitoringAgentName": "Захватить",
"buttons": {
- "toggleTheme": ""
+ "toggleTheme": "Переключить тему"
},
"toasts": {
"networkError": "Ошибка сети",
@@ -506,7 +517,7 @@
"inputs": {
"email": {
"label": "Электронная почта",
- "placeholder": "",
+ "placeholder": "jordan.ellis@domain.com",
"errors": {
"empty": "Для продолжения введите свой адрес электронной почты",
"invalid": "Пожалуйста, проверьте правильность введённого адреса электронной почты"
@@ -560,7 +571,7 @@
},
"firstName": {
"label": "Имя",
- "placeholder": "",
+ "placeholder": "Jordan",
"errors": {
"empty": "Пожалуйста, введите свое имя",
"length": "Имя должно быть короче 50 символов",
@@ -569,7 +580,7 @@
},
"lastName": {
"label": "Фамилия",
- "placeholder": "",
+ "placeholder": "Ellis",
"errors": {
"empty": "Пожалуйста, введите свою фамилию",
"length": "Фамилия должна быть короче 50 символов",
@@ -583,12 +594,12 @@
"fields": {
"password": {
"errors": {
- "incorrect": ""
+ "incorrect": "Введённый вами пароль неверный"
}
},
"role": {
"errors": {
- "min": ""
+ "min": "Требуется хотя бы одна роль"
}
}
}
@@ -602,8 +613,8 @@
"links": {
"forgotPassword": "Забыли пароль?",
"register": "У вас нет учетной записи?",
- "forgotPasswordLink": "",
- "registerLink": ""
+ "forgotPasswordLink": "Сбросить пароль",
+ "registerLink": "Зарегистрируйтесь здесь"
},
"toasts": {
"success": "С возвращением! Вы успешно вошли в систему.",
@@ -611,10 +622,10 @@
},
"errors": {
"password": {
- "incorrect": ""
+ "incorrect": "Предоставленный вами пароль не соответствует нашим записям."
}
},
- "welcome": ""
+ "welcome": "Добро пожаловать обратно в Checkmate!"
},
"registration": {
"heading": {
@@ -641,7 +652,7 @@
"toasts": {
"success": "Добро пожаловать! Ваша учетная запись была создана успешно."
},
- "welcome": ""
+ "welcome": "Добро пожаловать в Checkmate!"
},
"forgotPassword": {
"heading": "Забыли пароль?",
@@ -652,446 +663,478 @@
"stepFour": "Ваш пароль был успешно сброшен. Нажмите ниже, чтобы волшебным образом войти в систему."
},
"buttons": {
- "openEmail": "",
+ "openEmail": "Открыть Gmail",
"resetPassword": "Сбросить пароль"
},
"imageAlts": {
- "passwordKey": "",
- "email": "",
- "lock": "",
- "passwordConfirm": ""
+ "passwordKey": "Password key icon",
+ "email": "Email icon",
+ "lock": "Lock icon",
+ "passwordConfirm": "Password confirm icon"
},
"links": {
- "login": "",
- "resend": ""
+ "login": "Вернуться к Входу ",
+ "resend": "Не получили письмо? Нажмите, чтобы отправить повторно "
},
"toasts": {
- "sent": "",
- "emailNotFound": "",
- "redirect": "",
- "success": "",
- "error": ""
+ "sent": "Инструкция отправлена на .",
+ "emailNotFound": "Email не найден.",
+ "redirect": "Перенаправление через ...",
+ "success": "Ваш пароль успешно сброшен.",
+ "error": "Не удалось сбросить пароль. Повторите попытку позже или обратитесь в службу поддержки."
}
}
},
"errorPages": {
"serverUnreachable": {
"toasts": {
- "reconnected": "",
- "stillUnreachable": ""
+ "reconnected": "Успешное повторное подключение к серверу.",
+ "stillUnreachable": "Сервер по-прежнему недоступен. Повторите попытку позже."
},
- "alertBox": "",
- "description": "",
+ "alertBox": "Ошибка подключения к серверу",
+ "description": "Не удаётся подключиться к серверу. Проверьте подключение к Интернету или конфигурацию развёртывания, если проблема не устранена.",
"retryButton": {
- "default": "",
- "processing": ""
+ "default": "Повторите попытку подключения",
+ "processing": "Подключение..."
}
}
},
"createNotifications": {
- "title": "",
+ "title": "Создать канал уведомлений",
"nameSettings": {
- "title": "",
- "description": "",
- "nameLabel": "",
- "namePlaceholder": ""
+ "title": "Имя",
+ "description": "Описательное имя для вашей интеграции.",
+ "nameLabel": "Имя",
+ "namePlaceholder": "например, уведомления Slack"
},
"typeSettings": {
- "title": "",
- "description": "",
- "typeLabel": ""
+ "title": "Тип",
+ "description": "Выберите тип канала уведомлений, который вы хотите создать.",
+ "typeLabel": "Тип"
},
"emailSettings": {
- "title": "",
- "description": "",
- "emailLabel": "",
- "emailPlaceholder": ""
+ "title": "Электронная почта",
+ "description": "Адреса электронной почты получателей.",
+ "emailLabel": "Адрес электронной почты",
+ "emailPlaceholder": "например, john@example.com"
},
"slackSettings": {
- "title": "",
- "description": "",
- "webhookLabel": "",
- "webhookPlaceholder": ""
+ "title": "Slack",
+ "description": "Настройте свой вебхук Slack здесь",
+ "webhookLabel": "Slack вебхук URL",
+ "webhookPlaceholder": "https://hooks.slack.com/services/..."
},
"pagerdutySettings": {
- "title": "",
- "description": "",
- "integrationKeyLabel": "",
- "integrationKeyPlaceholder": ""
+ "title": "PagerDuty",
+ "description": "Настройте интеграцию PagerDuty здесь",
+ "integrationKeyLabel": "Интеграционный ключ",
+ "integrationKeyPlaceholder": "1234567890"
},
"discordSettings": {
- "title": "",
- "description": "",
- "webhookLabel": "",
- "webhookPlaceholder": ""
+ "title": "Discord",
+ "description": "Настройте вебхук Discord здесь",
+ "webhookLabel": "Discord вебхук URL",
+ "webhookPlaceholder": "https://your-server.com/webhook"
},
"webhookSettings": {
- "title": "",
- "description": "",
- "webhookLabel": "",
- "webhookPlaceholder": ""
+ "title": "Вебхук",
+ "description": "Настройте свой вебхук здесь",
+ "webhookLabel": "URL-адрес веб-перехватчика",
+ "webhookPlaceholder": "https://your-server.com/webhook"
},
- "testNotification": "",
- "dialogDeleteTitle": "",
- "dialogDeleteConfirm": ""
+ "testNotification": "Тестовое уведомление",
+ "dialogDeleteTitle": "Вы уверены, что хотите удалить это уведомление?",
+ "dialogDeleteConfirm": "Удалить"
},
"notificationConfig": {
- "title": "",
- "description": ""
+ "title": "Уведомления",
+ "description": "Выберите каналы уведомлений, которые вы хотите использовать"
},
"monitorStatus": {
- "checkingEvery": "",
- "withCaptureAgent": "",
- "up": "",
- "down": "",
- "paused": ""
+ "checkingEvery": "Проверка каждый {{interval}}",
+ "withCaptureAgent": "с агентом захвата {{version}}",
+ "up": "вверх",
+ "down": "вниз",
+ "paused": "приостановлено"
},
- "advancedMatching": "",
- "sendTestNotifications": "",
- "selectAll": "",
- "showAdminLoginLink": "",
+ "advancedMatching": "Расширенное сопоставление",
+ "sendTestNotifications": "Отправить тестовое уведомление",
+ "selectAll": "Выбрать все",
+ "showAdminLoginLink": "Показывать ссылку «Администратор? Войти здесь» на странице статуса",
"logsPage": {
- "title": "",
- "description": "",
+ "title": "Логи",
+ "description": "Системные логи - последние 1000 строк",
"tabs": {
- "queue": "",
- "logs": "",
- "diagnostics": ""
+ "queue": "Очередь заданий",
+ "logs": "Серверные логи",
+ "diagnostics": "Диагностика"
},
"toast": {
- "fetchLogsSuccess": ""
+ "fetchLogsSuccess": "Логи успешно получены"
},
"logLevelSelect": {
- "title": "",
+ "title": "Уровень логов",
"values": {
- "all": "",
- "info": "",
- "warn": "",
- "error": "",
- "debug": ""
+ "all": "Все",
+ "info": "Информация",
+ "warn": "Предупреждение",
+ "error": "Ошибка",
+ "debug": "Дебаг"
}
}
},
"queuePage": {
- "title": "",
- "refreshButton": "",
- "flushButton": "",
+ "title": "Очередь",
+ "refreshButton": "Обновить",
+ "flushButton": "Очистить очередь",
"jobTable": {
- "title": "",
- "idHeader": "",
- "urlHeader": "",
- "typeHeader": "",
- "activeHeader": "",
- "lockedAtHeader": "",
- "runCountHeader": "",
- "failCountHeader": "",
- "lastRunHeader": "",
- "lastFinishedAtHeader": "",
- "lastRunTookHeader": "",
- "intervalHeader": ""
+ "title": "Задачи в настоящее время в очереди",
+ "idHeader": "ID монитора",
+ "urlHeader": "URL",
+ "typeHeader": "Тип",
+ "activeHeader": "Активный",
+ "lockedAtHeader": "Заблокировано на",
+ "runCountHeader": "Количество запусков",
+ "failCountHeader": "Количество неудач",
+ "lastRunHeader": "Последний запуск в",
+ "lastFinishedAtHeader": "Последний раз закончил в",
+ "lastRunTookHeader": "Последний забег занял",
+ "intervalHeader": "Интервал"
},
"metricsTable": {
- "title": "",
- "metricHeader": "",
- "valueHeader": ""
+ "title": "Метрики очереди",
+ "metricHeader": "Метрическая",
+ "valueHeader": "Ценить"
},
"failedJobTable": {
- "title": "",
- "monitorIdHeader": "",
- "monitorUrlHeader": "",
- "failCountHeader": "",
- "failedAtHeader": "",
- "failReasonHeader": ""
+ "title": "Неудачные задачи",
+ "monitorIdHeader": "ID монитора",
+ "monitorUrlHeader": "URL монитора",
+ "failCountHeader": "Количество падений",
+ "failedAtHeader": "Последнее падение в",
+ "failReasonHeader": "Причина падения"
}
},
"export": {
- "title": "",
- "success": "",
- "failed": ""
+ "title": "Экспорт мониторов",
+ "success": "Мониторы успешно экспортированы!",
+ "failed": "Не удалось экспортировать мониторы"
},
"monitorActions": {
- "title": "",
- "import": "",
- "export": "",
- "deleteSuccess": "",
- "deleteFailed": "",
- "details": ""
+ "title": "Экспорт/Импорт",
+ "import": "Импортные мониторы",
+ "export": "Экспорт мониторов",
+ "deleteSuccess": "Монитор успешно удален",
+ "deleteFailed": "Не удалось удалить монитор",
+ "details": "Подробности"
},
"settingsPage": {
"aboutSettings": {
- "labelDevelopedBy": "",
- "labelVersion": "",
- "title": ""
+ "labelDevelopedBy": "Разработано Bluewave Labs",
+ "labelVersion": "Версия",
+ "title": "Об"
},
"demoMonitorsSettings": {
- "buttonAddMonitors": "",
- "description": "",
- "title": ""
+ "buttonAddMonitors": "Добавить демо мониторы",
+ "description": "Добавьте образцы мониторов для демонстрации.",
+ "title": "Демо мониторы"
},
"emailSettings": {
- "buttonSendTestEmail": "",
- "description": "",
- "descriptionTransport": "",
- "labelAddress": "",
- "labelConnectionHost": "",
- "labelHost": "",
- "labelIgnoreTLS": "",
- "labelPassword": "",
- "labelPasswordSet": "",
- "labelPool": "",
- "labelPort": "",
- "labelRejectUnauthorized": "",
- "labelRequireTLS": "",
- "labelSecure": "",
- "labelTLSServername": "",
- "labelUser": "",
- "linkTransport": "",
- "placeholderUser": "",
- "title": "",
- "toastEmailRequiredFieldsError": ""
+ "buttonSendTestEmail": "Отправить тестовое письмо",
+ "description": "Настройте параметры электронной почты в вашей системе. Она используется для отправки уведомлений и оповещений.",
+ "descriptionTransport": "Это создает SMTP-транспорт для NodeMailer.",
+ "labelAddress": "Адрес электронной почты — используется для аутентификации.",
+ "labelConnectionHost": "Хост подключения электронной почты — имя хоста для использования в приветствии HELO/EHLO.",
+ "labelHost": "Хост электронной почты — имя хоста или IP-адрес для подключения.",
+ "labelIgnoreTLS": "Отключить STARTTLS: не использовать TLS, даже если сервер его поддерживает.",
+ "labelPassword": "Пароль электронной почты - Пароль для аутентификации",
+ "labelPasswordSet": "Пароль установлен. Нажмите «Сброс», чтобы изменить его.",
+ "labelPool": "Включить пул соединений: повторно использовать существующие соединения для повышения производительности.",
+ "labelPort": "Порт электронной почты — порт для подключения",
+ "labelRejectUnauthorized": "Отклонять недействительные сертификаты: отклонять соединения с самоподписанными или ненадежными сертификатами.",
+ "labelRequireTLS": "Принудительный STARTTLS: требуется обновление TLS, выдается ошибка, если не поддерживается",
+ "labelSecure": "Использовать SSL (рекомендуется): зашифровать соединение с помощью SSL/TLS.",
+ "labelTLSServername": "Имя сервера TLS — необязательное имя хоста для проверки TLS, если хост — это IP-адрес.",
+ "labelUser": "Пользователь электронной почты — имя пользователя для аутентификации, переопределяет адрес электронной почты, если указан",
+ "linkTransport": "Технические характеристики смотрите здесь.",
+ "placeholderUser": "Оставьте пустым, если не требуется",
+ "title": "Электронная почта",
+ "toastEmailRequiredFieldsError": "Требуется адрес электронной почты, хост, порт и пароль"
},
"pageSpeedSettings": {
- "description": "",
- "labelApiKeySet": "",
- "labelApiKey": "",
- "title": ""
+ "description": "Введите ключ API Google PageSpeed, чтобы включить мониторинг Google PageSpeed. Нажмите «Сбросить», чтобы обновить ключ.",
+ "labelApiKeySet": "API-ключ установлен. Нажмите «Сброс», чтобы изменить его.",
+ "labelApiKey": "API-ключ PageSpeed",
+ "title": "API-ключ Google PageSpeed"
},
- "saveButtonLabel": "",
+ "saveButtonLabel": "Сохранить",
"statsSettings": {
- "clearAllStatsButton": "",
- "clearAllStatsDescription": "",
- "clearAllStatsDialogConfirm": "",
- "clearAllStatsDialogDescription": "",
- "clearAllStatsDialogTitle": "",
- "description": "",
- "labelTTL": "",
- "labelTTLOptional": "",
- "title": ""
+ "clearAllStatsButton": "Очистить всю статистику",
+ "clearAllStatsDescription": "Очистить всю статистику. Это необратимо.",
+ "clearAllStatsDialogConfirm": "Да, очистить всю статистику",
+ "clearAllStatsDialogDescription": "После удаления история и статистика мониторинга не могут быть восстановлены.",
+ "clearAllStatsDialogTitle": "Вы хотите очистить всю статистику?",
+ "description": "Укажите, как долго вы хотите хранить данные. Вы также можете удалить все существующие данные.",
+ "labelTTL": "Дни, за которые вы хотите посмотреть историю.",
+ "labelTTLOptional": "0 для бесконечности",
+ "title": "История монитора"
},
"systemResetSettings": {
- "buttonRemoveAllMonitors": "",
- "description": "",
- "dialogConfirm": "",
- "dialogDescription": "",
- "dialogTitle": "",
- "title": ""
+ "buttonRemoveAllMonitors": "Удалить все мониторы",
+ "description": "Удалите все мониторы из вашей системы.",
+ "dialogConfirm": "Да, удалить все мониторы",
+ "dialogDescription": "После удаления, мониторы нельзя будет воостановить.",
+ "dialogTitle": "Вы хотите удалить все мониторы?",
+ "title": "Сброс системы"
},
"timezoneSettings": {
- "description": "",
- "label": "",
- "title": ""
+ "description": "Выберите часовой пояс, используемый для отображения даты и времени в приложении.",
+ "label": "Отображение часового пояса",
+ "title": "Отображение часового пояса"
},
- "title": "",
+ "title": "Настройки",
"uiSettings": {
- "description": "",
- "labelLanguage": "",
- "labelTheme": "",
- "title": ""
+ "description": "Переключайтесь между светлым и темным режимами или меняйте язык пользовательского интерфейса.",
+ "labelLanguage": "Язык",
+ "labelTheme": "Режим темы",
+ "title": "Появление"
},
"urlSettings": {
- "description": "",
- "label": "",
- "selectDisabled": "",
- "selectEnabled": "",
- "title": ""
+ "description": "Отображение IP-адреса или URL-адреса монитора на общедоступной странице состояния. Если эта опция отключена, для защиты конфиденциальной информации будет отображаться только имя монитора.",
+ "label": "Отображать IP/URL на странице статуса",
+ "selectDisabled": "Выключено",
+ "selectEnabled": "Включено",
+ "title": "Мониторинг IP/URL на странице статуса"
},
"globalThresholds": {
- "title": "",
- "description": ""
+ "title": "Глобальные пороги",
+ "description": "Настройте глобальные пороговые значения для процессора, памяти, диска и температуры. Если значение указано, оно будет автоматически включено для мониторинга."
}
},
"statusPageCreate": {
- "buttonSave": ""
+ "buttonSave": "Сохранить"
},
- "incidentsOptionsHeaderFilterResolved": "",
- "settingsSave": "",
- "statusPageCreateAppearanceTitle": "",
- "confirmPassword": "",
+ "incidentsOptionsHeaderFilterResolved": "Решено",
+ "settingsSave": "Сохранить",
+ "statusPageCreateAppearanceTitle": "Внешний вид",
+ "confirmPassword": "Подтвердите пароль",
"monitorHooks": {
- "failureAddDemoMonitors": "",
- "successAddDemoMonitors": ""
+ "failureAddDemoMonitors": "Не удалось добавить демонстрационные мониторы.",
+ "successAddDemoMonitors": "Демонстрационные мониторы успешно добавлены"
},
- "settingsAppearance": "",
- "settingsDisplayTimezone": "",
- "settingsGeneralSettings": "",
- "incidentsOptionsHeaderTotalIncidents": "",
+ "settingsAppearance": "Внешний вид",
+ "settingsDisplayTimezone": "Отображение часового пояса",
+ "settingsGeneralSettings": "Общие настройки",
+ "incidentsOptionsHeaderTotalIncidents": "Всего инцидентов",
"statusPage": {
- "deleteSuccess": "",
- "deleteFailed": "",
- "createSuccess": "",
- "updateSuccess": "",
- "generalSettings": "",
- "contents": "",
+ "deleteSuccess": "Страница статуса успешно удалена",
+ "deleteFailed": "Не удалось удалить страницу статуса",
+ "createSuccess": "Страница статуса успешно создана",
+ "updateSuccess": "Страница статуса успешно обновлена",
+ "generalSettings": "Общие настройки",
+ "contents": "Содержание",
"fallback": {
- "checks": [""],
- "title": "",
- "actionButton": ""
+ "checks": [
+ "Мониторинг и отображение состояния ваших сервисов в режиме реального времени",
+ "Отслеживайте состояние нескольких сервисов и делитесь их статусом",
+ "Дайте пользователям информацию о сбоях и производительности"
+ ],
+ "title": "Страница статуса используется для:",
+ "actionButton": "Давайте создадим вашу первую страницу статуса!"
}
},
- "testNotificationsDisabled": "",
- "incidentsTableResolvedAt": "",
- "incidentsTableActionResolve": "",
+ "testNotificationsDisabled": "Для этого монитора не настроены уведомления. Вам нужно добавить их, нажав кнопку «Настроить».",
+ "incidentsTableResolvedAt": "Решено в",
+ "incidentsTableActionResolve": "Решено",
"checkHooks": {
- "failureResolveOne": "",
- "failureResolveAll": "",
- "failureResolveMonitor": ""
+ "failureResolveOne": "Не удалось разрешить инцидент.",
+ "failureResolveAll": "Не удалось разрешить все инциденты.",
+ "failureResolveMonitor": "Не удалось устранить инциденты с монитором."
},
- "checkFormError": "",
+ "checkFormError": "Пожалуйста, проверьте форму на наличие ошибок.",
"diagnosticsPage": {
- "diagnosticDescription": "",
- "statsDescription": "",
+ "diagnosticDescription": "Системная диагностика",
+ "statsDescription": "Системная статистика",
"gauges": {
- "heapAllocationTitle": "",
- "heapAllocationSubtitle": "",
- "heapUsageTitle": "",
- "heapUsageSubtitle": "",
- "heapUtilizationTitle": "",
- "heapUtilizationSubtitle": "",
- "instantCpuUsageTitle": "",
- "instantCpuUsageSubtitle": ""
+ "heapAllocationTitle": "Использовано кучи",
+ "heapAllocationSubtitle": "% доступной памяти",
+ "heapUsageTitle": "Выделено памяти в куче",
+ "heapUsageSubtitle": "% доступно памяти",
+ "heapUtilizationTitle": "Использовано кучи",
+ "heapUtilizationSubtitle": "% от доступного",
+ "instantCpuUsageTitle": "Загруженность CPU",
+ "instantCpuUsageSubtitle": "% загрузка CPU за 1s"
},
"stats": {
- "eventLoopDelayTitle": "",
- "uptimeTitle": "",
- "usedHeapSizeTitle": "",
- "totalHeapSizeTitle": "",
- "osMemoryLimitTitle": ""
+ "eventLoopDelayTitle": "Задержка цикла событий",
+ "uptimeTitle": "Аптайм",
+ "usedHeapSizeTitle": "Размер используемой кучи",
+ "totalHeapSizeTitle": "Общий размер кучи",
+ "osMemoryLimitTitle": "Ограничение памяти ОS"
}
},
- "pageSpeedLighthouseAPI": "",
+ "pageSpeedLighthouseAPI": "Используйте API Lighthouse PageSpeed для мониторинга вашего сайта",
"time": {
- "threeMinutes": "",
- "fiveMinutes": "",
- "tenMinutes": "",
- "twentyMinutes": "",
- "oneHour": "",
- "oneDay": "",
- "oneWeek": "",
- "fourMinutes": "",
- "oneMinute": "",
- "twoMinutes": "",
- "fifteenSeconds": "",
- "thirtySeconds": ""
+ "threeMinutes": "3 минуты",
+ "fiveMinutes": "5 минут",
+ "tenMinutes": "10 минут",
+ "twentyMinutes": "20 минут",
+ "oneHour": "1 час",
+ "oneDay": "1 день",
+ "oneWeek": "1 неделя",
+ "fourMinutes": "4 минуты",
+ "oneMinute": "1 минута",
+ "twoMinutes": "2 минуты",
+ "fifteenSeconds": "15 секунд",
+ "thirtySeconds": "30 секунд"
},
"general": {
- "noOptionsFound": ""
+ "noOptionsFound": "{{unit}} не найдено"
},
"infrastructureMonitor": {
"fallback": {
- "checks": [""],
- "title": "",
- "actionButton": ""
+ "checks": [
+ "Отслеживайте производительность серверов",
+ "Выявляйте узкие места и оптимизируйте использование",
+ "Обеспечивайте надежность с помощью мониторинга в режиме реального времени"
+ ],
+ "title": "Монитор инфраструктуры используется для:",
+ "actionButton": "Давайте создадим ваш первый монитор инфраструктуры!"
}
},
"maintenanceWindow": {
"fallback": {
- "checks": [""],
- "title": "",
- "actionButton": ""
+ "checks": [
+ "Отметьте периоды технического обслуживания.",
+ "Устраните любые недопонимания.",
+ "Прекратите отправку оповещений в периоды технического обслуживания."
+ ],
+ "title": "Период технического обслуживания используется для:",
+ "actionButton": "Давайте создадим ваше первое окно технического обслуживания!"
}
},
"pageSpeed": {
"fallback": {
- "checks": [""],
- "title": "",
- "actionButton": ""
+ "checks": [
+ "Отчет об удобстве использования страницы",
+ "Помощь в анализе скорости загрузки веб-страницы",
+ "Предложение о том, как можно улучшить страницу"
+ ],
+ "title": "Монитор PageSpeed используется для:",
+ "actionButton": "Давайте создадим ваш первый монитор PageSpeed!"
}
},
"uptimeMonitor": {
"fallback": {
- "checks": [""],
- "title": "",
- "actionButton": ""
+ "checks": [
+ "Проверьте, доступны ли веб-сайты или серверы и отвечают ли они",
+ "Оповещайте команды о сбоях или проблемах с производительностью",
+ "Мониторинг конечных точек HTTP, пингов, контейнеров и портов",
+ "Отслеживайте динамику времени безотказной работы и надежности"
+ ],
+ "title": "Монитор работоспособности используется для:",
+ "actionButton": "Давайте создадим ваш первый монитор времени безотказной работы!"
}
},
"editUserPage": {
"form": {
- "email": "",
- "firstName": "",
- "lastName": "",
- "role": "",
- "save": ""
+ "email": "Электронная почта",
+ "firstName": "Имя",
+ "lastName": "Фамилия",
+ "role": "Роли",
+ "save": "Сохранить"
},
"table": {
- "actionHeader": "",
- "roleHeader": ""
+ "actionHeader": "Действие",
+ "roleHeader": "Роль"
},
- "title": "",
+ "title": "Редактировать пользователя",
"toast": {
- "successUserUpdate": "",
- "validationErrors": ""
+ "successUserUpdate": "Пользователь успешно обновлен",
+ "validationErrors": "Ошибки проверки"
}
},
- "incidentsPageActionResolveMonitor": "",
- "incidentsPageActionResolveAll": "",
+ "incidentsPageActionResolveMonitor": "Разрешение инцидентов мониторинга",
+ "incidentsPageActionResolveAll": "Решить все инциденты",
"matchMethodOptions": {
- "equal": "",
- "equalPlaceholder": "",
- "include": "",
- "includePlaceholder": "",
- "regex": "",
- "regexPlaceholder": "",
- "text": ""
+ "equal": "Равный",
+ "equalPlaceholder": "успех",
+ "include": "Включить",
+ "includePlaceholder": "хорошо",
+ "regex": "Regex",
+ "regexPlaceholder": "^(success|ok)$",
+ "text": "Метод сопоставления"
},
"monitorType": {
"docker": {
- "label": "",
- "namePlaceholder": "",
- "placeholder": ""
+ "label": "Имя/ID контейнера",
+ "namePlaceholder": "Мой контейнер",
+ "placeholder": "my-app или abcd1234"
},
"http": {
- "label": "",
- "namePlaceholder": "",
- "placeholder": ""
+ "label": "URL для мониторинга",
+ "namePlaceholder": "Google",
+ "placeholder": "google.com"
},
"ping": {
- "label": "",
- "namePlaceholder": "",
- "placeholder": ""
+ "label": "IP-адрес для мониторинга",
+ "namePlaceholder": "Google",
+ "placeholder": "1.1.1.1"
},
"port": {
- "label": "",
- "namePlaceholder": "",
- "placeholder": ""
+ "label": "URL для мониторинга",
+ "namePlaceholder": "Локальный хост:5173",
+ "placeholder": "локальный хост"
},
"game": {
- "label": "",
- "namePlaceholder": "",
- "placeholder": ""
+ "label": "URL для мониторинга",
+ "namePlaceholder": "localhost:5173",
+ "placeholder": "localhost"
}
},
"uptimeAdvancedMatching": {
- "jsonPath": ""
+ "jsonPath": "путь к JSON"
},
- "bytesPerSecond": "",
- "bytesReceived": "",
- "bytesSent": "",
- "chooseGame": "",
+ "bytesPerSecond": "Байты в секунду",
+ "bytesReceived": "Получено байтов",
+ "bytesSent": "Отправлено байтов",
+ "chooseGame": "Выбрать игру",
"createMonitorPage": {
- "incidentConfigDescription": "",
- "incidentConfigStatusWindowLabel": "",
- "incidentConfigStatusWindowThresholdLabel": "",
- "incidentConfigTitle": ""
+ "incidentConfigDescription": "Мониторинг использует скользящее окно, чтобы понять, когда ресурс недоступен. Статус изменится, если нужный процент проверок покажет сбой.",
+ "incidentConfigStatusWindowLabel": "Сколько проверок должно быть в скользящем окне?",
+ "incidentConfigStatusWindowThresholdLabel": "Какой процент проверок в скользящем окне завершается неудачей/успешностью до изменения статуса монитора?",
+ "incidentConfigTitle": "Инциденты",
+ "incidentConfigDescriptionV2": "Количество последовательных проверок, необходимых для изменения статуса монитора. Максимум 25.",
+ "incidentConfigStatusCheckNumber": "Сколько проверок перед сменой статуса?",
+ "intervalTitle": "Интервал проверки",
+ "intervalDescription": "Как часто следует проверять монитор."
},
- "dataRate": "",
- "dataReceived": "",
- "dataSent": "",
- "details": "",
- "drops": "",
- "errors": "",
- "errorsIn": "",
- "errorsOut": "",
- "gameServerMonitoring": "",
- "gameServerMonitoringDescription": "",
- "network": "",
- "networkDrops": "",
- "networkErrors": "",
- "networkInterface": "",
- "noNetworkStatsAvailable": "",
- "packetsPerSecond": "",
- "packetsReceived": "",
- "packetsReceivedRate": "",
- "packetsSent": "",
- "rate": "",
- "selectInterface": ""
+ "dataRate": "Скорость передачи данных",
+ "dataReceived": "Данные получены",
+ "dataSent": "Данные отправлены",
+ "details": "Подробности",
+ "drops": "Падений",
+ "errors": "Ошибки",
+ "errorsIn": "Ошибки в",
+ "errorsOut": "Ошибки вне",
+ "gameServerMonitoring": "Мониторинг игрового сервера",
+ "gameServerMonitoringDescription": "Проверьте, запущен ли ваш игровой сервер.",
+ "network": "Сеть",
+ "networkDrops": "Сетевые падения",
+ "networkErrors": "Ошибки сети",
+ "networkInterface": "Сетевой интерфейс",
+ "noNetworkStatsAvailable": "Статистика сети отсутствует.",
+ "packetsPerSecond": "Пакетов в секунду",
+ "packetsReceived": "Получено пакетов",
+ "packetsReceivedRate": "Скорость получения пакетов",
+ "packetsSent": "Отправлено пакетов",
+ "rate": "коэффициент",
+ "selectInterface": "Выберите интерфейс",
+ "v1": {
+ "infrastructure": {
+ "disk_selection_title": "Выбор дисков",
+ "disk_selection_info": "На данный момент диск не обнаружен.",
+ "disk_selection_description": "Выберите конкретные диски, которые вы хотите отслеживать."
+ }
+ }
}
diff --git a/client/src/locales/th.json b/client/src/locales/th.json
index c3f571b6b..d5f9ad137 100644
--- a/client/src/locales/th.json
+++ b/client/src/locales/th.json
@@ -425,7 +425,13 @@
}
}
},
- "role": "บทบาท"
+ "role": "บทบาท",
+ "changeTeamPassword": {
+ "changePasswordMenu": "",
+ "title": "",
+ "description": "",
+ "success": ""
+ }
},
"monitorState": {
"paused": "หยุดชั่วคราว",
@@ -487,7 +493,8 @@
"ping": "กรอกที่อยู่ IP หรือชื่อโฮสต์ที่ต้องการ ping (เช่น 192.168.1.100 หรือ example.com) และตั้งชื่อที่ชัดเจนให้แสดงบนแดชบอร์ด",
"docker": "กรอกชื่อหรือ ID ของคอนเทนเนอร์ Docker คุณสามารถใช้ชื่อคอนเทนเนอร์ (เช่น my-app) หรือ ID ของคอนเทนเนอร์ (ID เต็ม 64 ตัวอักษร หรือ ID สั้น)",
"port": "กรอก URL หรือ IP ของเซิร์ฟเวอร์ หมายเลขพอร์ต และตั้งชื่อที่ชัดเจนให้แสดงบนแดชบอร์ด",
- "game": "กรอกที่อยู่ IP หรือชื่อโฮสต์และหมายเลขพอร์ตเพื่อทำการ ping (เช่น 192.168.1.100 หรือ example.com) และเลือกประเภทเกม"
+ "game": "กรอกที่อยู่ IP หรือชื่อโฮสต์และหมายเลขพอร์ตเพื่อทำการ ping (เช่น 192.168.1.100 หรือ example.com) และเลือกประเภทเกม",
+ "https": ""
},
"common": {
"appName": "Checkmate",
@@ -1096,7 +1103,11 @@
"incidentConfigDescription": "การใช้หน้าต่างเลื่อนจะช่วยกำหนดเวลาที่ตัวตรวจสอบล้มเหลว สถานะของตัวตรวจสอบจะเปลี่ยนก็ต่อเมื่อเปอร์เซ็นต์ของการตรวจสอบในหน้าต่างเลื่อนตรงตามค่าที่กำหนด",
"incidentConfigStatusWindowLabel": "ต้องมีการตรวจสอบกี่ครั้งในหน้าต่างเลื่อน?",
"incidentConfigStatusWindowThresholdLabel": "ต้องการให้กี่เปอร์เซ็นต์ของการตรวจสอบในหน้าต่างเลื่อนล้มเหลว/สำเร็จ ก่อนที่จะเปลี่ยนสถานะของตัวตรวจสอบ?",
- "incidentConfigTitle": "เหตุการณ์"
+ "incidentConfigTitle": "เหตุการณ์",
+ "incidentConfigDescriptionV2": "",
+ "incidentConfigStatusCheckNumber": "",
+ "intervalTitle": "",
+ "intervalDescription": ""
},
"dataRate": "อัตราการส่งข้อมูล",
"dataReceived": "ข้อมูลที่รับ",
@@ -1118,5 +1129,12 @@
"packetsReceivedRate": "อัตราการรับแพ็กเก็ต",
"packetsSent": "แพ็กเก็ตที่ส่ง",
"rate": "อัตรา",
- "selectInterface": "เลือกอินเทอร์เฟซ"
+ "selectInterface": "เลือกอินเทอร์เฟซ",
+ "v1": {
+ "infrastructure": {
+ "disk_selection_title": "การเลือกดิสก์",
+ "disk_selection_info": "ไม่พบดิสก์ในขณะนี้",
+ "disk_selection_description": "เลือกดิสก์ที่คุณต้องการตรวจสอบ"
+ }
+ }
}
diff --git a/client/src/locales/tr.json b/client/src/locales/tr.json
index 0ab45ebe9..59f294533 100644
--- a/client/src/locales/tr.json
+++ b/client/src/locales/tr.json
@@ -421,7 +421,13 @@
}
}
},
- "role": ""
+ "role": "",
+ "changeTeamPassword": {
+ "changePasswordMenu": "",
+ "title": "",
+ "description": "",
+ "success": ""
+ }
},
"monitorState": {
"paused": "Durduruldu",
@@ -483,7 +489,8 @@
"ping": "",
"docker": "",
"port": "",
- "game": ""
+ "game": "",
+ "https": ""
},
"common": {
"appName": "Checkmate",
@@ -1071,7 +1078,11 @@
"incidentConfigDescription": "",
"incidentConfigStatusWindowLabel": "",
"incidentConfigStatusWindowThresholdLabel": "",
- "incidentConfigTitle": ""
+ "incidentConfigTitle": "",
+ "incidentConfigDescriptionV2": "",
+ "incidentConfigStatusCheckNumber": "",
+ "intervalTitle": "",
+ "intervalDescription": ""
},
"dataRate": "",
"dataReceived": "",
diff --git a/client/src/locales/zh-CN.json b/client/src/locales/zh-CN.json
index c2d1aa6fc..67b419445 100644
--- a/client/src/locales/zh-CN.json
+++ b/client/src/locales/zh-CN.json
@@ -425,7 +425,13 @@
}
}
},
- "role": "角色"
+ "role": "角色",
+ "changeTeamPassword": {
+ "changePasswordMenu": "",
+ "title": "",
+ "description": "",
+ "success": ""
+ }
},
"monitorState": {
"paused": "暂停",
@@ -487,7 +493,8 @@
"ping": "输入要 ping 的 IP 地址或主机名(例如.. 192.168.1.100 或 example.com),并添加显示在仪表板上的清晰显示名称。",
"docker": "码输入 Docker 容器名称或 ID。您可以使用容器名称(例如.. my-app)或容器 ID(完整的 64 字符 ID 或短 ID)。",
"port": "输入服务器的 URL 或 IP、端口号和仪表板上显示的清晰显示名称。",
- "game": "输入要 ping 的 IP 地址或主机名和端口号(例如.. 192.168.1.100 或 example.com)并选择游戏类型。"
+ "game": "输入要 ping 的 IP 地址或主机名和端口号(例如.. 192.168.1.100 或 example.com)并选择游戏类型。",
+ "https": ""
},
"common": {
"appName": "Checkmate",
@@ -1088,7 +1095,11 @@
"incidentConfigDescription": "滑动窗口用于确定监视器何时发生故障。仅当滑动窗口中的检查百分比满足指定值时,监视器的状态才会改变。",
"incidentConfigStatusWindowLabel": "在滑动窗口中应包含多少次检测?",
"incidentConfigStatusWindowThresholdLabel": "在监视器状态改变之前,滑动窗口中的检查失败/成功的百分比是多少?",
- "incidentConfigTitle": "事件"
+ "incidentConfigTitle": "事件",
+ "incidentConfigDescriptionV2": "",
+ "incidentConfigStatusCheckNumber": "",
+ "intervalTitle": "",
+ "intervalDescription": ""
},
"dataRate": "数据速率",
"dataReceived": "已接收数据",
@@ -1110,5 +1121,12 @@
"packetsReceivedRate": "数据包接收率",
"packetsSent": "已发送数据包",
"rate": "速度",
- "selectInterface": "选择接口"
+ "selectInterface": "选择接口",
+ "v1": {
+ "infrastructure": {
+ "disk_selection_title": "磁盘选择",
+ "disk_selection_info": "目前没有检测到磁盘。",
+ "disk_selection_description": "选择您要监控的特定磁盘。"
+ }
+ }
}
diff --git a/client/src/locales/zh-TW.json b/client/src/locales/zh-TW.json
index e57ebd487..e5ee58e58 100644
--- a/client/src/locales/zh-TW.json
+++ b/client/src/locales/zh-TW.json
@@ -425,7 +425,13 @@
}
}
},
- "role": ""
+ "role": "",
+ "changeTeamPassword": {
+ "changePasswordMenu": "",
+ "title": "",
+ "description": "",
+ "success": ""
+ }
},
"monitorState": {
"paused": "",
@@ -487,7 +493,8 @@
"ping": "",
"docker": "",
"port": "",
- "game": ""
+ "game": "",
+ "https": ""
},
"common": {
"appName": "Checkmate",
@@ -1075,7 +1082,11 @@
"incidentConfigDescription": "",
"incidentConfigStatusWindowLabel": "",
"incidentConfigStatusWindowThresholdLabel": "",
- "incidentConfigTitle": ""
+ "incidentConfigTitle": "",
+ "incidentConfigDescriptionV2": "",
+ "incidentConfigStatusCheckNumber": "",
+ "intervalTitle": "",
+ "intervalDescription": ""
},
"dataRate": "",
"dataReceived": "",
diff --git a/client/src/main.jsx b/client/src/main.jsx
index 01ecefcc6..5cfbe6f33 100644
--- a/client/src/main.jsx
+++ b/client/src/main.jsx
@@ -8,7 +8,10 @@ import { PersistGate } from "redux-persist/integration/react";
import NetworkServiceProvider from "./Utils/NetworkServiceProvider.jsx";
import { networkService } from "./Utils/NetworkService";
export { networkService };
-import I18nLoader from "./Components/I18nLoader";
+import I18nLoader from "./Components/v1/I18nLoader";
+import { initApiClient } from "./Utils/ApiClient";
+
+initApiClient(store);
ReactDOM.createRoot(document.getElementById("root")).render(
diff --git a/client/tsconfig.app.json b/client/tsconfig.app.json
new file mode 100644
index 000000000..f44839e9f
--- /dev/null
+++ b/client/tsconfig.app.json
@@ -0,0 +1,32 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+ "target": "ES2022",
+ "useDefineForClassFields": true,
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true,
+ "paths": {
+ "@/*": ["./src/*"]
+ },
+ "allowJs": true,
+ "checkJs": false
+ },
+ "include": ["src"]
+}
diff --git a/client/tsconfig.json b/client/tsconfig.json
new file mode 100644
index 000000000..3b77ae840
--- /dev/null
+++ b/client/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "files": [],
+ "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }],
+ "compilerOptions": {
+ "types": ["node"],
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "skipLibCheck": true,
+ "paths": {
+ "@/*": ["./src/*"]
+ },
+ "allowJs": true,
+ "checkJs": false
+ }
+}
diff --git a/client/tsconfig.node.json b/client/tsconfig.node.json
new file mode 100644
index 000000000..bbd18d2b2
--- /dev/null
+++ b/client/tsconfig.node.json
@@ -0,0 +1,27 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "target": "ES2023",
+ "lib": ["ES2023"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true,
+ "allowJs": true,
+ "checkJs": false
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/client/tsconfig.tsbuildinfo b/client/tsconfig.tsbuildinfo
new file mode 100644
index 000000000..685a9569a
--- /dev/null
+++ b/client/tsconfig.tsbuildinfo
@@ -0,0 +1 @@
+{"root":["./vite.config.ts","./dist/assets/index-BtHE-Ph5.js","./src/App.jsx","./src/main.jsx","./src/store.js","./src/Components/LanguageSelector.jsx","./src/Components/ActionsMenu/index.jsx","./src/Components/Alert/index.jsx","./src/Components/Animated/PulseDot.jsx","./src/Components/ArrowLeft/index.jsx","./src/Components/ArrowRight/index.jsx","./src/Components/Avatar/index.jsx","./src/Components/Breadcrumbs/index.jsx","./src/Components/Buttons/RoundGradientButton.jsx","./src/Components/Charts/AreaChart/index.jsx","./src/Components/Charts/BarChart/index.jsx","./src/Components/Charts/ChartBox/EmptyView.jsx","./src/Components/Charts/ChartBox/index.jsx","./src/Components/Charts/CustomGauge/index.jsx","./src/Components/Charts/DePINStatusPageBarChart/index.jsx","./src/Components/Charts/LegendBox/index.jsx","./src/Components/Charts/MonitorDetailsAreaChart/index.jsx","./src/Components/Charts/StatusPageBarChart/index.jsx","./src/Components/Charts/Utils/chartUtilFunctions.js","./src/Components/Charts/Utils/chartUtils.jsx","./src/Components/Charts/Utils/gradientUtils.jsx","./src/Components/Check/Check.jsx","./src/Components/CircularCount/index.jsx","./src/Components/Common/AppBar.jsx","./src/Components/Common/Footer.jsx","./src/Components/ConfigBox/index.jsx","./src/Components/ConfigRow/index.jsx","./src/Components/Dialog/genericDialog.jsx","./src/Components/Dialog/index.jsx","./src/Components/Dot/index.jsx","./src/Components/Fallback/FallBackActionButtons.jsx","./src/Components/Fallback/FallbackBackground.jsx","./src/Components/Fallback/FallbackCheckList.jsx","./src/Components/Fallback/FallbackContainer.jsx","./src/Components/Fallback/FallbackPageSpeedWarning.jsx","./src/Components/Fallback/FallbackTitle.jsx","./src/Components/Fallback/index.jsx","./src/Components/FilterHeader/index.jsx","./src/Components/GenericFallback/NetworkError.jsx","./src/Components/GenericFallback/index.jsx","./src/Components/HOC/withAdminCheck.jsx","./src/Components/Heading/index.jsx","./src/Components/Host/index.jsx","./src/Components/HttpStatusLabel/index.jsx","./src/Components/I18nLoader/index.jsx","./src/Components/IconBox/index.jsx","./src/Components/Image/index.jsx","./src/Components/InfoBox/index.jsx","./src/Components/Inputs/Checkbox/index.jsx","./src/Components/Inputs/ColorPicker/index.jsx","./src/Components/Inputs/FieldWrapper/index.jsx","./src/Components/Inputs/ImageUpload/index.jsx","./src/Components/Inputs/Radio/index.jsx","./src/Components/Inputs/Search/index.jsx","./src/Components/Inputs/Select/index.jsx","./src/Components/Inputs/TextInput/index.jsx","./src/Components/Inputs/TextInput/indexV2.tsx","./src/Components/Inputs/TextInput/Adornments/index.jsx","./src/Components/Label/index.jsx","./src/Components/Layouts/AppLayout/index.jsx","./src/Components/Layouts/HomeLayout/index.jsx","./src/Components/Link/index.jsx","./src/Components/MonitorActions/index.jsx","./src/Components/MonitorCountHeader/index.jsx","./src/Components/MonitorCountHeader/skeleton.jsx","./src/Components/MonitorCreateHeader/index.jsx","./src/Components/MonitorCreateHeader/skeleton.jsx","./src/Components/MonitorDetailsControlHeader/index.jsx","./src/Components/MonitorDetailsControlHeader/skeleton.jsx","./src/Components/MonitorDetailsControlHeader/status.jsx","./src/Components/MonitorTimeFrameHeader/index.jsx","./src/Components/MonitorTimeFrameHeader/skeleton.jsx","./src/Components/NotificationConfig/index.jsx","./src/Components/NotificationIntegrationModal/Components/NotificationIntegrationModal.jsx","./src/Components/NotificationIntegrationModal/Components/TabComponent.jsx","./src/Components/NotificationIntegrationModal/Components/TabPanel.jsx","./src/Components/NotificationIntegrationModal/Hooks/useNotification.js","./src/Components/PageStateWrapper/index.jsx","./src/Components/ProgressBars/index.jsx","./src/Components/ProgressStepper/index.jsx","./src/Components/ProtectedRoute/index.jsx","./src/Components/RoleProtectedRoute/index.jsx","./src/Components/ShareComponent/index.jsx","./src/Components/Sidebar/index.jsx","./src/Components/Sidebar/components/authFooter.jsx","./src/Components/Sidebar/components/collapseButton.jsx","./src/Components/Sidebar/components/logo.jsx","./src/Components/Sidebar/components/navItem.jsx","./src/Components/Skeletons/FullPage/index.jsx","./src/Components/StandardContainer/index.jsx","./src/Components/StarPrompt/index.jsx","./src/Components/StatBox/index.jsx","./src/Components/StatusBoxes/index.jsx","./src/Components/StatusBoxes/skeleton.jsx","./src/Components/Subheader/index.jsx","./src/Components/Tab/index.jsx","./src/Components/Table/TableUtils.js","./src/Components/Table/index.jsx","./src/Components/Table/skeleton.jsx","./src/Components/Table/TablePagination/index.jsx","./src/Components/Table/TablePagination/Actions/index.jsx","./src/Components/TextLink/index.jsx","./src/Components/ThemeSwitch/SunAndMoonIcon.jsx","./src/Components/ThemeSwitch/index.jsx","./src/Components/Toast/body.jsx","./src/Components/Toast/index.jsx","./src/Components/WalletProvider/index.jsx","./src/Features/Auth/authSlice.js","./src/Features/UI/uiSlice.js","./src/Hooks/v1/checkHooks.js","./src/Hooks/v1/inviteHooks.js","./src/Hooks/v1/logHooks.js","./src/Hooks/v1/monitorHooks.js","./src/Hooks/v1/settingsHooks.js","./src/Hooks/v1/useIsAdmin.js","./src/Hooks/v1/useMonitorUtils.js","./src/Hooks/v1/useNotifications.js","./src/Hooks/v1/useSendTestEmail.js","./src/Hooks/v1/userHooks.js","./src/Hooks/v2/UseApi.tsx","./src/Pages/v1/ServerUnreachable.jsx","./src/Pages/v1/About/index.jsx","./src/Pages/v1/Account/index.jsx","./src/Pages/v1/Account/EditUser/index.jsx","./src/Pages/v1/Account/EditUser/hooks/editUser.js","./src/Pages/v1/Account/components/PasswordPanel.jsx","./src/Pages/v1/Account/components/ProfilePanel.jsx","./src/Pages/v1/Account/components/TeamPanel.jsx","./src/Pages/v1/Account/components/AddMemberMenu/index.jsx","./src/Pages/v1/Account/components/AddTeamMember/index.jsx","./src/Pages/v1/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx","./src/Pages/v1/Account/components/RoleTable/index.jsx","./src/Pages/v1/Auth/CheckEmail.jsx","./src/Pages/v1/Auth/ForgotPassword.jsx","./src/Pages/v1/Auth/NewPasswordConfirmed.jsx","./src/Pages/v1/Auth/SetNewPassword.jsx","./src/Pages/v1/Auth/Login/index.jsx","./src/Pages/v1/Auth/Login/hooks/useLoadingSubmit.jsx","./src/Pages/v1/Auth/Login/hooks/useLoginForm.jsx","./src/Pages/v1/Auth/Login/hooks/useLoginSubmit.jsx","./src/Pages/v1/Auth/Login/hooks/useValidateLoginForm.jsx","./src/Pages/v1/Auth/Register/index.jsx","./src/Pages/v1/Auth/components/AuthHeader.jsx","./src/Pages/v1/Auth/components/AuthPageWrapper.jsx","./src/Pages/v1/Auth/components/PasswordTooltip.jsx","./src/Pages/v1/Auth/hooks/usePasswordFeedback.jsx","./src/Pages/v1/Auth/hooks/useValidatePassword.jsx","./src/Pages/v1/Incidents/index.jsx","./src/Pages/v1/Incidents/Components/IncidentTable/index.jsx","./src/Pages/v1/Incidents/Components/OptionsHeader/index.jsx","./src/Pages/v1/Incidents/Components/OptionsHeader/skeleton.jsx","./src/Pages/v1/Incidents/Components/StatusBoxes/StatusBox.jsx","./src/Pages/v1/Incidents/Components/StatusBoxes/index.jsx","./src/Pages/v1/Incidents/Components/StatusBoxes/skeleton.jsx","./src/Pages/v1/Infrastructure/Create/index.jsx","./src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx","./src/Pages/v1/Infrastructure/Create/Components/MonitorActionButtons.jsx","./src/Pages/v1/Infrastructure/Create/Components/MonitorStatusHeader.jsx","./src/Pages/v1/Infrastructure/Create/Components/CustomThreshold/index.jsx","./src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx","./src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx","./src/Pages/v1/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx","./src/Pages/v1/Infrastructure/Details/index.jsx","./src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx","./src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/index.jsx","./src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/skeleton.jsx","./src/Pages/v1/Infrastructure/Details/Components/BaseContainer/index.jsx","./src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx","./src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/index.jsx","./src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx","./src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx","./src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx","./src/Pages/v1/Infrastructure/Details/Components/NetworkStats/index.jsx","./src/Pages/v1/Infrastructure/Details/Components/NetworkStats/skeleton.jsx","./src/Pages/v1/Infrastructure/Details/Components/StatusBoxes/index.jsx","./src/Pages/v1/Infrastructure/Details/Hooks/useHardwareUtils.jsx","./src/Pages/v1/Infrastructure/Monitors/index.jsx","./src/Pages/v1/Infrastructure/Monitors/Components/Filters/index.jsx","./src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx","./src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx","./src/Pages/v1/Integrations/index.jsx","./src/Pages/v1/Logs/index.jsx","./src/Pages/v1/Logs/Diagnostics/index.jsx","./src/Pages/v1/Logs/Diagnostics/components/gauges/index.jsx","./src/Pages/v1/Logs/Diagnostics/components/stats/index.jsx","./src/Pages/v1/Logs/Diagnostics/utils/utils.js","./src/Pages/v1/Logs/Logs/index.jsx","./src/Pages/v1/Logs/Queue/index.jsx","./src/Pages/v1/Logs/Queue/components/FailedJobTable/index.jsx","./src/Pages/v1/Logs/Queue/components/JobTable/index.jsx","./src/Pages/v1/Logs/Queue/components/Metrics/index.jsx","./src/Pages/v1/Logs/Queue/components/MetricsTable/index.jsx","./src/Pages/v1/Maintenance/index.jsx","./src/Pages/v1/Maintenance/CreateMaintenance/index.jsx","./src/Pages/v1/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx","./src/Pages/v1/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx","./src/Pages/v1/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx","./src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx","./src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx","./src/Pages/v1/Maintenance/MaintenanceTable/index.jsx","./src/Pages/v1/Maintenance/MaintenanceTable/ActionsMenu/index.jsx","./src/Pages/v1/NotFound/index.jsx","./src/Pages/v1/Notifications/index.jsx","./src/Pages/v1/Notifications/utils.js","./src/Pages/v1/Notifications/components/ActionMenu.jsx","./src/Pages/v1/Notifications/create/index.jsx","./src/Pages/v1/PageSpeed/Create/index.jsx","./src/Pages/v1/PageSpeed/Create/skeleton.jsx","./src/Pages/v1/PageSpeed/Details/index.jsx","./src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChart.jsx","./src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx","./src/Pages/v1/PageSpeed/Details/Components/Charts/PieChart.jsx","./src/Pages/v1/PageSpeed/Details/Components/Charts/PieChartLegend.jsx","./src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx","./src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx","./src/Pages/v1/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx","./src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/index.jsx","./src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx","./src/Pages/v1/PageSpeed/Monitors/index.jsx","./src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx","./src/Pages/v1/PageSpeed/Monitors/Components/MonitorGrid/index.jsx","./src/Pages/v1/Settings/SettingsAbout.jsx","./src/Pages/v1/Settings/SettingsDemoMonitors.jsx","./src/Pages/v1/Settings/SettingsEmail.jsx","./src/Pages/v1/Settings/SettingsGlobalThresholds.jsx","./src/Pages/v1/Settings/SettingsPagespeed.jsx","./src/Pages/v1/Settings/SettingsStats.jsx","./src/Pages/v1/Settings/SettingsTimeZone.jsx","./src/Pages/v1/Settings/SettingsUI.jsx","./src/Pages/v1/Settings/SettingsURL.jsx","./src/Pages/v1/Settings/index.jsx","./src/Pages/v1/StatusPage/Create/index.jsx","./src/Pages/v1/StatusPage/Create/Components/MonitorList/index.jsx","./src/Pages/v1/StatusPage/Create/Components/Progress/index.jsx","./src/Pages/v1/StatusPage/Create/Components/Skeleton/index.jsx","./src/Pages/v1/StatusPage/Create/Components/Tabs/ConfigStack.jsx","./src/Pages/v1/StatusPage/Create/Components/Tabs/Content.jsx","./src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx","./src/Pages/v1/StatusPage/Create/Components/Tabs/index.jsx","./src/Pages/v1/StatusPage/Create/Hooks/useCreateStatusPage.jsx","./src/Pages/v1/StatusPage/Create/Hooks/useMonitorsFetch.jsx","./src/Pages/v1/StatusPage/Status/index.jsx","./src/Pages/v1/StatusPage/Status/Components/AdminLink/index.jsx","./src/Pages/v1/StatusPage/Status/Components/ControlsHeader/index.jsx","./src/Pages/v1/StatusPage/Status/Components/MonitorsList/index.jsx","./src/Pages/v1/StatusPage/Status/Components/Skeleton/index.jsx","./src/Pages/v1/StatusPage/Status/Components/StatusBar/index.jsx","./src/Pages/v1/StatusPage/Status/Hooks/useStatusPageDelete.jsx","./src/Pages/v1/StatusPage/Status/Hooks/useStatusPageFetch.jsx","./src/Pages/v1/StatusPage/StatusPages/index.jsx","./src/Pages/v1/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx","./src/Pages/v1/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx","./src/Pages/v1/Uptime/BulkImport/Upload.jsx","./src/Pages/v1/Uptime/BulkImport/index.jsx","./src/Pages/v1/Uptime/Create/index.jsx","./src/Pages/v1/Uptime/Create/skeleton.jsx","./src/Pages/v1/Uptime/Details/index.jsx","./src/Pages/v1/Uptime/Details/Components/ChartBoxes/index.jsx","./src/Pages/v1/Uptime/Details/Components/ChartBoxes/skeleton.jsx","./src/Pages/v1/Uptime/Details/Components/Charts/CustomLabels.jsx","./src/Pages/v1/Uptime/Details/Components/Charts/DownBarChart.jsx","./src/Pages/v1/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx","./src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChart.jsx","./src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx","./src/Pages/v1/Uptime/Details/Components/Charts/UpBarChart.jsx","./src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx","./src/Pages/v1/Uptime/Details/Components/ResponseTable/skeleton.jsx","./src/Pages/v1/Uptime/Details/Components/UptimeStatusBoxes/index.jsx","./src/Pages/v1/Uptime/Details/Hooks/useCertificateFetch.jsx","./src/Pages/v1/Uptime/Monitors/index.jsx","./src/Pages/v1/Uptime/Monitors/Components/Filter/index.jsx","./src/Pages/v1/Uptime/Monitors/Components/LoadingSpinner/index.jsx","./src/Pages/v1/Uptime/Monitors/Components/SearchComponent/index.jsx","./src/Pages/v1/Uptime/Monitors/Components/Skeleton/index.jsx","./src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/index.jsx","./src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/skeleton.jsx","./src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx","./src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx","./src/Pages/v1/Uptime/Monitors/Hooks/useDebounce.jsx","./src/Pages/v2/Auth/Login.tsx","./src/Routes/index.jsx","./src/Utils/ApiClient.ts","./src/Utils/Logger.js","./src/Utils/NetworkService.js","./src/Utils/NetworkServiceProvider.jsx","./src/Utils/debounce.jsx","./src/Utils/fileUtils.js","./src/Utils/greeting.jsx","./src/Utils/i18n.js","./src/Utils/monitorUtils.js","./src/Utils/roleUtils.js","./src/Utils/stringUtils.js","./src/Utils/timeUtils.js","./src/Utils/toastUtils.jsx","./src/Utils/utils.js","./src/Utils/Theme/constants.js","./src/Utils/Theme/darkTheme.js","./src/Utils/Theme/extractColorObject.js","./src/Utils/Theme/globalTheme.js","./src/Utils/Theme/lightTheme.js","./src/Utils/Theme/v2/palette.ts","./src/Utils/Theme/v2/theme.ts","./src/Validation/error.js","./src/Validation/validation.js","./src/types/env.d.ts","./src/types/mui.d.ts"],"errors":true,"version":"5.9.2"}
\ No newline at end of file
diff --git a/client/vite.config.js b/client/vite.config.js
deleted file mode 100644
index 07b5de748..000000000
--- a/client/vite.config.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import { defineConfig, loadEnv } from "vite";
-import react from "@vitejs/plugin-react";
-import svgr from "vite-plugin-svgr";
-import { execSync } from "child_process";
-
-export default defineConfig(({ mode }) => {
- const env = loadEnv(mode, process.cwd(), "");
- let version = "3.2.0";
-
- return {
- base: "/",
- plugins: [svgr(), react()],
- optimizeDeps: {
- include: ["@mui/material/Tooltip", "@emotion/styled"],
- },
- define: {
- __APP_VERSION__: JSON.stringify(version),
- },
- };
-});
diff --git a/client/vite.config.ts b/client/vite.config.ts
new file mode 100644
index 000000000..3d8d66335
--- /dev/null
+++ b/client/vite.config.ts
@@ -0,0 +1,30 @@
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+import svgr from "vite-plugin-svgr";
+import path from "path";
+import { fileURLToPath } from "url";
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+export default defineConfig(({}) => {
+ let version = "3.2.1";
+
+ return {
+ base: "/",
+ plugins: [svgr(), react()],
+ server: {
+ host: true,
+ },
+ resolve: {
+ alias: {
+ "@": path.resolve(__dirname, "src"),
+ },
+ },
+ optimizeDeps: {
+ include: ["@mui/material/Tooltip", "@emotion/styled"],
+ },
+ define: {
+ __APP_VERSION__: JSON.stringify(version),
+ },
+ };
+});
diff --git a/docker/dev/build_images.sh b/docker/dev/build_images.sh
index e94be9fa5..eb43e31d8 100755
--- a/docker/dev/build_images.sh
+++ b/docker/dev/build_images.sh
@@ -5,11 +5,10 @@ cd "$(dirname "$0")"
cd ../..
# Define service names and their corresponding Dockerfiles in parallel arrays
-services=("uptime_client" "uptime_mongo" "uptime_redis" "uptime_server")
+services=("uptime_client" "uptime_mongo" "uptime_server")
dockerfiles=(
"./docker/dev/client.Dockerfile"
"./docker/dev/mongoDB.Dockerfile"
- "./docker/dev/redis.Dockerfile"
"./docker/dev/server.Dockerfile"
)
diff --git a/docker/dev/server.Dockerfile b/docker/dev/server.Dockerfile
index d24bff63f..2a6ebcb7c 100755
--- a/docker/dev/server.Dockerfile
+++ b/docker/dev/server.Dockerfile
@@ -13,6 +13,8 @@ RUN npm install
COPY ./server/ ./
+RUN npm run build
+
EXPOSE 52345
-CMD ["node", "src/index.js"]
\ No newline at end of file
+CMD ["node", "dist/index.js"]
\ No newline at end of file
diff --git a/docker/dist-arm/docker-compose.yaml b/docker/dist-arm/docker-compose.yaml
index 0a5825b3e..9a87aa59b 100644
--- a/docker/dist-arm/docker-compose.yaml
+++ b/docker/dist-arm/docker-compose.yaml
@@ -15,7 +15,7 @@ services:
depends_on:
- mongodb
mongodb:
- image: mongo:4.4.18
+ image: mongo:4.4.30
restart: always
command: ["mongod", "--quiet", "--bind_ip_all"]
ports:
diff --git a/docker/dist-arm/server.Dockerfile b/docker/dist-arm/server.Dockerfile
index e47db9a74..91ccd427e 100644
--- a/docker/dist-arm/server.Dockerfile
+++ b/docker/dist-arm/server.Dockerfile
@@ -35,10 +35,12 @@ RUN npm install
COPY server ./
-COPY --from=frontend-build /app/client/dist ./public
-
RUN chmod +x ./scripts/inject-vars.sh
+RUN npm run build
+
+COPY --from=frontend-build /app/client/dist ./public
+
EXPOSE 52345
-CMD ["sh", "-c", "./scripts/inject-vars.sh && node ./src/index.js"]
+CMD ["sh", "-c", "./scripts/inject-vars.sh && node ./dist/index.js"]
diff --git a/docker/dist-mono/docker-compose-local.yaml b/docker/dist-mono/docker-compose-local.yaml
new file mode 100755
index 000000000..a5616695b
--- /dev/null
+++ b/docker/dist-mono/docker-compose-local.yaml
@@ -0,0 +1,27 @@
+services:
+ server:
+ image: mono_server
+ restart: always
+ ports:
+ - "52345:52345"
+ environment:
+ - UPTIME_APP_API_BASE_URL=http://localhost:52345/api/v1
+ - UPTIME_APP_CLIENT_HOST=http://localhost
+ - DB_CONNECTION_STRING=mongodb://mongodb:27017/uptime_db
+ - CLIENT_HOST=http://localhost
+ - JWT_SECRET=my_secret
+ depends_on:
+ - mongodb
+ mongodb:
+ image: mono_mongo
+ restart: always
+ command: ["mongod", "--quiet", "--bind_ip_all"]
+ volumes:
+ - ./mongo/data:/data/db
+ healthcheck:
+ test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')", "--quiet"]
+ interval: 5s
+ timeout: 30s
+ start_period: 0s
+ start_interval: 1s
+ retries: 30
diff --git a/docker/dist-mono/server.Dockerfile b/docker/dist-mono/server.Dockerfile
index 405f9e6ac..84b7127ed 100644
--- a/docker/dist-mono/server.Dockerfile
+++ b/docker/dist-mono/server.Dockerfile
@@ -19,12 +19,15 @@ WORKDIR /app/server
COPY server ./
-COPY --from=frontend-build /app/client/dist ./public
RUN npm install
+RUN npm run build
+
+COPY --from=frontend-build /app/client/dist ./public
+
RUN chmod +x ./scripts/inject-vars.sh
EXPOSE 52345
-CMD ./scripts/inject-vars.sh && node ./src/index.js
+CMD ./scripts/inject-vars.sh && node ./dist/index.js
diff --git a/docker/dist/server.Dockerfile b/docker/dist/server.Dockerfile
index d24bff63f..2a6ebcb7c 100755
--- a/docker/dist/server.Dockerfile
+++ b/docker/dist/server.Dockerfile
@@ -13,6 +13,8 @@ RUN npm install
COPY ./server/ ./
+RUN npm run build
+
EXPOSE 52345
-CMD ["node", "src/index.js"]
\ No newline at end of file
+CMD ["node", "dist/index.js"]
\ No newline at end of file
diff --git a/docker/prod/server.Dockerfile b/docker/prod/server.Dockerfile
index b78de2e33..9d9800e7b 100755
--- a/docker/prod/server.Dockerfile
+++ b/docker/prod/server.Dockerfile
@@ -15,6 +15,8 @@ RUN npm install
COPY ./server ./
+RUN npm run build
+
EXPOSE 52345
CMD ["node", "src/index.js"]
\ No newline at end of file
diff --git a/docker/staging/server.Dockerfile b/docker/staging/server.Dockerfile
index b78de2e33..7341b1e9b 100755
--- a/docker/staging/server.Dockerfile
+++ b/docker/staging/server.Dockerfile
@@ -15,6 +15,8 @@ RUN npm install
COPY ./server ./
+RUN npm run build
+
EXPOSE 52345
-CMD ["node", "src/index.js"]
\ No newline at end of file
+CMD ["node", "dist/index.js"]
\ No newline at end of file
diff --git a/docs/infrastructure-disk-selection.md b/docs/infrastructure-disk-selection.md
new file mode 100644
index 000000000..500e3be1e
--- /dev/null
+++ b/docs/infrastructure-disk-selection.md
@@ -0,0 +1,74 @@
+# Infrastructure Monitoring - Disk Selection
+
+This guide explains how to use the selective disk monitoring feature in Checkmate's infrastructure monitoring.
+
+## Overview
+
+By default, Checkmate monitors all detected disks on your server. The disk selection feature allows you to choose specific disks/mountpoints to monitor, giving you more control over what gets tracked and displayed.
+
+## Prerequisites
+
+- Checkmate server running with [Capture agent](https://github.com/bluewave-labs/capture) installed on target server
+- An existing infrastructure monitor already created and configured
+- At least one disk detected by the Capture agent
+
+## Using Disk Selection
+
+### Accessing the Feature
+
+1. Navigate to your Infrastructure monitors page
+2. Click on an existing infrastructure monitor to view details
+3. Click the "Configure" or "Edit" button
+4. Scroll down to the **Disk Selection** section
+
+> **Note:** The disk selection feature is only available when editing existing monitors that have already detected disks.
+
+### Selecting Disks to Monitor
+
+1. **View Available Disks**: The system automatically detects all disks/mountpoints from your server
+2. **Select Disks**: Check the boxes next to the disks you want to monitor
+3. **Apply Changes**: Click "Save" to update your monitor configuration
+
+### Understanding Disk Identifiers
+
+Disks are identified by their mountpoint or device name:
+- **Mountpoint**: `/` (root), `/home`, `/var`, etc.
+- **Device**: `/dev/sda1`, `/dev/nvme0n1p1`, etc.
+
+### Behavior
+
+- **All disks selected**: Shows all detected disks in gauges and charts
+- **Specific disks selected**: Only shows selected disks in the monitoring interface
+- **No disks detected**: Displays "No disk detected for the moment" message
+
+## What Gets Filtered
+
+When you select specific disks, the following elements are filtered:
+
+- **Disk Usage Gauges**: Only selected disks appear in the dashboard
+- **Disk Usage Charts**: Time-series charts show only selected disks
+- **Monitor Table**: Summary view reflects only selected disk usage
+
+## Example Use Cases
+
+- **Server with multiple drives**: Monitor only critical system disks, ignore temporary storage
+- **Database servers**: Focus monitoring on database partition while ignoring logs partition
+- **Web servers**: Monitor web content disk separately from system disk
+- **Development environments**: Track only project-specific mountpoints
+
+## Troubleshooting
+
+### No Disks Appearing
+- Verify the Capture agent is running on the target server
+- Check that the agent has proper permissions to read disk information
+- Ensure the monitor has been running long enough to collect at least one check
+
+### Missing Disk Information
+- Some disks may not be accessible to the Capture agent due to permissions
+- Virtual or network-mounted disks may not appear in the list
+- Check the Capture agent logs for any disk detection errors
+
+## Related Documentation
+
+- [Infrastructure Monitoring Setup](https://docs.checkmate.so/checkmate-2.1)
+- [Capture Agent Installation](https://github.com/bluewave-labs/capture)
\ No newline at end of file
diff --git a/server/.gitignore b/server/.gitignore
index 4bc621f83..9ae4e58ed 100755
--- a/server/.gitignore
+++ b/server/.gitignore
@@ -8,4 +8,5 @@ coverage
.clinic
node_modules
.vscode/*
-public
\ No newline at end of file
+public
+dist
\ No newline at end of file
diff --git a/server/jest.config.ts b/server/jest.config.ts
new file mode 100644
index 000000000..fd0c87ad7
--- /dev/null
+++ b/server/jest.config.ts
@@ -0,0 +1,25 @@
+import type { Config } from "jest";
+
+const config: Config = {
+ rootDir: ".",
+ testEnvironment: "node",
+ extensionsToTreatAsEsm: [".ts"],
+ transform: {
+ "^.+\\.(t|j)sx?$": ["ts-jest", { useESM: true, tsconfig: "./tsconfig.jest.json" }],
+ },
+ moduleNameMapper: {
+ "^@/validation/(.*)\\.js$": "/src/validation/$1.js",
+ "^@/utils/(AppError)\\.js$": "/src/utils/$1.ts",
+ "^@/utils/(.*)\\.js$": "/src/utils/$1.js",
+ "^@/(.*)\\.ts$": "/src/$1.ts",
+ "^@/(.*)\\.js$": "/src/$1.ts",
+ "^@/(.*)$": "/src/$1",
+ },
+ testMatch: ["/test/**/*.test.ts"],
+ setupFilesAfterEnv: [],
+ collectCoverageFrom: ["src/**/*.ts"],
+ coveragePathIgnorePatterns: ["/node_modules/", "/test/"],
+ clearMocks: true,
+};
+
+export default config;
diff --git a/server/nodemon.json b/server/nodemon.json
index 88d6ac686..3faf346d3 100755
--- a/server/nodemon.json
+++ b/server/nodemon.json
@@ -1,5 +1,5 @@
{
- "ignore": ["src/locales/*", "*.log", "node_modules/*"],
- "watch": ["src/**/*.js", "*.json"],
- "ext": "js,json"
+ "ignore": ["node_modules/*"],
+ "watch": ["src/**/*.ts", "src/**/*.js", "*.json"],
+ "ext": "ts,js,json"
}
diff --git a/server/openapi.json b/server/openapi.json
index 6cc2e7de0..d56c619bf 100644
--- a/server/openapi.json
+++ b/server/openapi.json
@@ -21,13 +21,9 @@
"description": "Local Development Server",
"variables": {
"PORT": {
- "description": "API Port",
- "enum": ["52345"],
"default": "52345"
},
"API_PATH": {
- "description": "API Base Path",
- "enum": ["api/v1"],
"default": "api/v1"
}
}
@@ -37,8 +33,6 @@
"description": "Checkmate Demo Server",
"variables": {
"API_PATH": {
- "description": "API Base Path",
- "enum": ["api/v1"],
"default": "api/v1"
}
}
@@ -88,6 +82,10 @@
{
"name": "diagnostic",
"description": "System health and performance diagnostics"
+ },
+ {
+ "name": "incidents",
+ "description": "Incident management and resolution"
}
],
"paths": {
@@ -164,51 +162,6 @@
}
}
},
- "/auth/refresh": {
- "post": {
- "tags": ["auth"],
- "summary": "Refresh access token",
- "description": "Generate new access token using refresh token",
- "parameters": [
- {
- "name": "x-refresh-token",
- "in": "header",
- "description": "Refresh token",
- "required": true,
- "schema": {
- "type": "string"
- }
- },
- {
- "name": "authorization",
- "in": "header",
- "description": "Bearer token (old access token)",
- "required": true,
- "schema": {
- "type": "string"
- }
- }
- ],
- "responses": {
- "200": {
- "description": "Token refreshed successfully",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/AuthResponse"
- }
- }
- }
- },
- "401": {
- "$ref": "#/components/responses/Unauthorized"
- },
- "500": {
- "$ref": "#/components/responses/InternalServerError"
- }
- }
- }
- },
"/auth/user/{userId}": {
"put": {
"tags": ["auth"],
@@ -731,28 +684,54 @@
]
}
},
- "/monitors/team/summary": {
+ "/monitors/team/groups": {
"get": {
"tags": ["monitors"],
- "summary": "Get monitors summary",
- "description": "Get team monitors with summary statistics",
- "parameters": [
- {
- "name": "type",
- "in": "query",
- "description": "Filter by monitor type",
- "schema": {
- "type": "array",
- "items": {
- "type": "string",
- "enum": ["http", "ping", "pagespeed", "hardware", "docker", "port"]
- }
- }
- }
- ],
+ "summary": "Get monitor groups by team",
+ "description": "Get all monitor groups for the authenticated user's team",
"responses": {
"200": {
- "description": "Monitor summary retrieved successfully"
+ "description": "Monitor groups retrieved successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "success": {
+ "type": "boolean",
+ "example": true
+ },
+ "msg": {
+ "type": "string",
+ "example": "Monitor groups retrieved successfully"
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "_id": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "monitors": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
},
"500": {
"$ref": "#/components/responses/InternalServerError"
@@ -992,24 +971,78 @@
]
}
},
- "/monitors/stats/{monitorId}": {
+ "/monitors/pagespeed/details/{monitorId}": {
"get": {
"tags": ["monitors"],
- "summary": "Get monitor statistics",
- "description": "Get comprehensive statistics for a monitor",
+ "summary": "Get PageSpeed monitor details",
+ "description": "Get detailed PageSpeed monitoring data including performance metrics and Core Web Vitals",
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
+ "description": "The PageSpeed monitor ID",
"schema": {
"type": "string"
}
+ },
+ {
+ "name": "sortOrder",
+ "in": "query",
+ "description": "Sort order for results",
+ "schema": {
+ "type": "string",
+ "enum": ["asc", "desc"],
+ "default": "desc"
+ }
+ },
+ {
+ "name": "limit",
+ "in": "query",
+ "description": "Number of results to return",
+ "schema": {
+ "type": "integer",
+ "default": 50
+ }
}
],
"responses": {
"200": {
- "description": "Monitor statistics retrieved successfully"
+ "description": "PageSpeed details retrieved successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "success": {
+ "type": "boolean",
+ "example": true
+ },
+ "msg": {
+ "type": "string",
+ "example": "PageSpeed details retrieved successfully"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "monitor": {
+ "$ref": "#/components/schemas/Monitor"
+ },
+ "checks": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Check"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
},
"404": {
"$ref": "#/components/responses/NotFound"
@@ -1078,22 +1111,44 @@
]
}
},
- "/monitors/export": {
+ "/monitors/export/json": {
"get": {
"tags": ["monitors"],
- "summary": "Export monitors to CSV",
- "description": "Export all monitors to CSV format",
+ "summary": "Export monitors to JSON",
+ "description": "Export all monitors to JSON format (admin/superadmin only)",
"responses": {
"200": {
- "description": "CSV file generated successfully",
+ "description": "JSON file generated successfully",
"content": {
- "text/csv": {
+ "application/json": {
"schema": {
- "type": "string"
+ "type": "object",
+ "properties": {
+ "success": {
+ "type": "boolean",
+ "example": true
+ },
+ "msg": {
+ "type": "string",
+ "example": "Monitors exported successfully"
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Monitor"
+ }
+ }
+ }
}
}
}
},
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
+ },
+ "403": {
+ "$ref": "#/components/responses/Forbidden"
+ },
"500": {
"$ref": "#/components/responses/InternalServerError"
}
@@ -1262,6 +1317,39 @@
}
},
"/notifications": {
+ "post": {
+ "tags": ["notifications"],
+ "summary": "Create notification",
+ "description": "Create a new notification integration",
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateNotificationRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Notification created successfully"
+ },
+ "422": {
+ "$ref": "#/components/responses/ValidationError"
+ },
+ "500": {
+ "$ref": "#/components/responses/InternalServerError"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ]
+ }
+ },
+ "/notifications/team": {
"get": {
"tags": ["notifications"],
"summary": "Get team notifications",
@@ -1301,37 +1389,6 @@
"bearerAuth": []
}
]
- },
- "post": {
- "tags": ["notifications"],
- "summary": "Create notification",
- "description": "Create a new notification integration",
- "requestBody": {
- "required": true,
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/CreateNotificationRequest"
- }
- }
- }
- },
- "responses": {
- "201": {
- "description": "Notification created successfully"
- },
- "422": {
- "$ref": "#/components/responses/ValidationError"
- },
- "500": {
- "$ref": "#/components/responses/InternalServerError"
- }
- },
- "security": [
- {
- "bearerAuth": []
- }
- ]
}
},
"/notifications/{id}": {
@@ -1722,6 +1779,64 @@
]
}
},
+ "/auth/users/{userId}/password": {
+ "put": {
+ "tags": ["auth"],
+ "summary": "Update user password by ID",
+ "description": "Update a specific user's password by ID (superadmin only)",
+ "parameters": [
+ {
+ "name": "userId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": ["password"],
+ "properties": {
+ "password": {
+ "type": "string",
+ "format": "password",
+ "minLength": 8,
+ "description": "New password for the user"
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "$ref": "#/components/responses/Success"
+ },
+ "404": {
+ "$ref": "#/components/responses/NotFound"
+ },
+ "403": {
+ "$ref": "#/components/responses/Forbidden"
+ },
+ "422": {
+ "$ref": "#/components/responses/ValidationError"
+ },
+ "500": {
+ "$ref": "#/components/responses/InternalServerError"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ]
+ }
+ },
"/invite": {
"post": {
"tags": ["invite"],
@@ -2030,6 +2145,21 @@
}
}
],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "ack": {
+ "type": "boolean",
+ "description": "Acknowledgement status"
+ }
+ }
+ }
+ }
+ }
+ },
"responses": {
"200": {
"$ref": "#/components/responses/Success"
@@ -2048,6 +2178,164 @@
]
}
},
+ "/checks/{action}": {
+ "put": {
+ "tags": ["checks"],
+ "summary": "Acknowledge all checks for all monitors",
+ "description": "Acknowledge all checks across all monitors for the team",
+ "parameters": [
+ {
+ "name": "action",
+ "in": "path",
+ "required": true,
+ "description": "Action to perform (e.g., 'ack')",
+ "schema": {
+ "type": "string",
+ "enum": ["ack"]
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": ["ack"],
+ "properties": {
+ "ack": {
+ "type": "boolean",
+ "description": "Acknowledgement status to set"
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Checks acknowledged successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "success": {
+ "type": "boolean",
+ "example": true
+ },
+ "msg": {
+ "type": "string",
+ "example": "All checks acknowledged successfully"
+ },
+ "data": {
+ "type": "object",
+ "description": "Updated checks information"
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
+ },
+ "500": {
+ "$ref": "#/components/responses/InternalServerError"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ]
+ }
+ },
+ "/checks/{action}/{monitorId}": {
+ "put": {
+ "tags": ["checks"],
+ "summary": "Acknowledge all checks for a specific monitor",
+ "description": "Acknowledge all checks for a specific monitor",
+ "parameters": [
+ {
+ "name": "action",
+ "in": "path",
+ "required": true,
+ "description": "Action to perform (e.g., 'ack')",
+ "schema": {
+ "type": "string",
+ "enum": ["ack"]
+ }
+ },
+ {
+ "name": "monitorId",
+ "in": "path",
+ "required": true,
+ "description": "Monitor ID to acknowledge checks for",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": ["ack"],
+ "properties": {
+ "ack": {
+ "type": "boolean",
+ "description": "Acknowledgement status to set"
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Checks acknowledged successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "success": {
+ "type": "boolean",
+ "example": true
+ },
+ "msg": {
+ "type": "string",
+ "example": "All checks acknowledged successfully"
+ },
+ "data": {
+ "type": "object",
+ "description": "Updated checks information"
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
+ },
+ "404": {
+ "$ref": "#/components/responses/NotFound"
+ },
+ "500": {
+ "$ref": "#/components/responses/InternalServerError"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ]
+ }
+ },
"/maintenance-window": {
"post": {
"tags": ["maintenance-window"],
@@ -2291,6 +2579,142 @@
]
}
},
+ "/queue/jobs": {
+ "get": {
+ "tags": ["queue"],
+ "summary": "Get queue jobs",
+ "description": "Retrieve all jobs in the queue (admin/superadmin only)",
+ "responses": {
+ "200": {
+ "description": "Queue jobs retrieved successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "success": {
+ "type": "boolean",
+ "example": true
+ },
+ "msg": {
+ "type": "string",
+ "example": "Queue jobs fetched successfully"
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/QueueJob"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
+ },
+ "403": {
+ "$ref": "#/components/responses/Forbidden"
+ },
+ "500": {
+ "$ref": "#/components/responses/InternalServerError"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ]
+ },
+ "post": {
+ "tags": ["queue"],
+ "summary": "Add job to queue",
+ "description": "Add a new job to the queue (admin/superadmin only)",
+ "responses": {
+ "200": {
+ "description": "Job added successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "success": {
+ "type": "boolean",
+ "example": true
+ },
+ "msg": {
+ "type": "string",
+ "example": "Job added to queue successfully"
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
+ },
+ "403": {
+ "$ref": "#/components/responses/Forbidden"
+ },
+ "500": {
+ "$ref": "#/components/responses/InternalServerError"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ]
+ }
+ },
+ "/queue/metrics": {
+ "get": {
+ "tags": ["queue"],
+ "summary": "Get queue metrics",
+ "description": "Retrieve queue metrics (admin/superadmin only)",
+ "responses": {
+ "200": {
+ "description": "Queue metrics retrieved successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "success": {
+ "type": "boolean",
+ "example": true
+ },
+ "msg": {
+ "type": "string",
+ "example": "Queue metrics fetched successfully"
+ },
+ "data": {
+ "$ref": "#/components/schemas/QueueMetrics"
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
+ },
+ "403": {
+ "$ref": "#/components/responses/Forbidden"
+ },
+ "500": {
+ "$ref": "#/components/responses/InternalServerError"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ]
+ }
+ },
"/queue/health": {
"get": {
"tags": ["queue"],
@@ -2618,6 +3042,286 @@
}
]
}
+ },
+ "/incidents/team": {
+ "get": {
+ "tags": ["incidents"],
+ "summary": "Get incidents by team",
+ "description": "Retrieve all incidents for the authenticated user's team with optional filtering",
+ "parameters": [
+ {
+ "name": "status",
+ "in": "query",
+ "description": "Filter by incident status (true = ongoing, false = resolved)",
+ "schema": {
+ "type": "boolean"
+ }
+ },
+ {
+ "name": "monitorId",
+ "in": "query",
+ "description": "Filter by monitor ID",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number for pagination",
+ "schema": {
+ "type": "integer",
+ "default": 1
+ }
+ },
+ {
+ "name": "limit",
+ "in": "query",
+ "description": "Number of items per page",
+ "schema": {
+ "type": "integer",
+ "default": 10
+ }
+ },
+ {
+ "name": "sortOrder",
+ "in": "query",
+ "description": "Sort order for results",
+ "schema": {
+ "type": "string",
+ "enum": ["asc", "desc"],
+ "default": "desc"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Incidents retrieved successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "success": {
+ "type": "boolean",
+ "example": true
+ },
+ "msg": {
+ "type": "string",
+ "example": "Incidents retrieved successfully"
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Incident"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
+ },
+ "500": {
+ "$ref": "#/components/responses/InternalServerError"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ]
+ }
+ },
+ "/incidents/team/summary": {
+ "get": {
+ "tags": ["incidents"],
+ "summary": "Get incident summary",
+ "description": "Retrieve a summary of incidents for the authenticated user's team",
+ "parameters": [
+ {
+ "name": "dateRange",
+ "in": "query",
+ "description": "Date range for summary",
+ "schema": {
+ "type": "string",
+ "enum": ["day", "week", "month"],
+ "default": "week"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Incident summary retrieved successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "success": {
+ "type": "boolean",
+ "example": true
+ },
+ "msg": {
+ "type": "string",
+ "example": "Incident summary retrieved successfully"
+ },
+ "data": {
+ "$ref": "#/components/schemas/IncidentSummary"
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
+ },
+ "500": {
+ "$ref": "#/components/responses/InternalServerError"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ]
+ }
+ },
+ "/incidents/{incidentId}": {
+ "get": {
+ "tags": ["incidents"],
+ "summary": "Get incident by ID",
+ "description": "Retrieve a specific incident by its ID",
+ "parameters": [
+ {
+ "name": "incidentId",
+ "in": "path",
+ "required": true,
+ "description": "The incident ID",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Incident retrieved successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "success": {
+ "type": "boolean",
+ "example": true
+ },
+ "msg": {
+ "type": "string",
+ "example": "Incident retrieved successfully"
+ },
+ "data": {
+ "$ref": "#/components/schemas/Incident"
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
+ },
+ "404": {
+ "$ref": "#/components/responses/NotFound"
+ },
+ "500": {
+ "$ref": "#/components/responses/InternalServerError"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ]
+ }
+ },
+ "/incidents/{incidentId}/resolve": {
+ "put": {
+ "tags": ["incidents"],
+ "summary": "Resolve incident manually",
+ "description": "Manually resolve an ongoing incident. Requires admin or superadmin role.",
+ "parameters": [
+ {
+ "name": "incidentId",
+ "in": "path",
+ "required": true,
+ "description": "The incident ID to resolve",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "comment": {
+ "type": "string",
+ "description": "Optional comment about the resolution",
+ "example": "Issue resolved by restarting the service"
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Incident resolved successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "success": {
+ "type": "boolean",
+ "example": true
+ },
+ "msg": {
+ "type": "string",
+ "example": "Incident resolved successfully"
+ },
+ "data": {
+ "$ref": "#/components/schemas/Incident"
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
+ },
+ "403": {
+ "$ref": "#/components/responses/Forbidden"
+ },
+ "404": {
+ "$ref": "#/components/responses/NotFound"
+ },
+ "500": {
+ "$ref": "#/components/responses/InternalServerError"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ]
+ }
}
},
"components": {
@@ -2680,6 +3384,16 @@
}
}
},
+ "BadRequest": {
+ "description": "Invalid request parameters",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ },
"InternalServerError": {
"description": "Internal server error",
"content": {
@@ -3375,6 +4089,557 @@
}
}
}
+ },
+ "Incident": {
+ "type": "object",
+ "properties": {
+ "_id": {
+ "type": "string",
+ "description": "Unique incident identifier"
+ },
+ "monitorId": {
+ "type": "string",
+ "description": "ID of the associated monitor"
+ },
+ "teamId": {
+ "type": "string",
+ "description": "ID of the team"
+ },
+ "startTime": {
+ "type": "string",
+ "format": "date-time",
+ "description": "When the incident started"
+ },
+ "endTime": {
+ "type": ["string", "null"],
+ "format": "date-time",
+ "description": "When the incident was resolved (null if ongoing)"
+ },
+ "status": {
+ "type": "boolean",
+ "description": "Incident status (true = ongoing, false = resolved)"
+ },
+ "message": {
+ "type": ["string", "null"],
+ "description": "Error message or description"
+ },
+ "statusCode": {
+ "type": ["integer", "null"],
+ "description": "HTTP status code if applicable"
+ },
+ "resolutionType": {
+ "type": ["string", "null"],
+ "enum": ["automatic", "manual", null],
+ "description": "How the incident was resolved"
+ },
+ "resolvedBy": {
+ "type": ["string", "null"],
+ "description": "User ID who resolved the incident (for manual resolution)"
+ },
+ "comment": {
+ "type": ["string", "null"],
+ "description": "Resolution comment"
+ },
+ "checks": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Array of check IDs associated with this incident"
+ },
+ "createdAt": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "updatedAt": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ },
+ "IncidentSummary": {
+ "type": "object",
+ "properties": {
+ "totalIncidents": {
+ "type": "integer",
+ "description": "Total number of incidents"
+ },
+ "activeIncidents": {
+ "type": "integer",
+ "description": "Number of ongoing incidents"
+ },
+ "resolvedIncidents": {
+ "type": "integer",
+ "description": "Number of resolved incidents"
+ },
+ "averageResolutionTime": {
+ "type": "number",
+ "description": "Average time to resolve incidents in milliseconds"
+ },
+ "incidentsByMonitor": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "monitorId": {
+ "type": "string"
+ },
+ "monitorName": {
+ "type": "string"
+ },
+ "count": {
+ "type": "integer"
+ }
+ }
+ }
+ }
+ }
+ },
+ "QueueJob": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "description": "Job ID"
+ },
+ "name": {
+ "type": "string",
+ "description": "Job name"
+ },
+ "data": {
+ "type": "object",
+ "description": "Job payload data"
+ },
+ "status": {
+ "type": "string",
+ "enum": ["waiting", "active", "completed", "failed", "delayed"],
+ "description": "Current job status"
+ },
+ "progress": {
+ "type": "number",
+ "description": "Job progress percentage"
+ },
+ "attemptsMade": {
+ "type": "integer",
+ "description": "Number of attempts made"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "When the job was created"
+ },
+ "processedOn": {
+ "type": ["string", "null"],
+ "format": "date-time",
+ "description": "When the job started processing"
+ },
+ "finishedOn": {
+ "type": ["string", "null"],
+ "format": "date-time",
+ "description": "When the job finished"
+ }
+ }
+ },
+ "QueueMetrics": {
+ "type": "object",
+ "properties": {
+ "waiting": {
+ "type": "integer",
+ "description": "Number of jobs waiting"
+ },
+ "active": {
+ "type": "integer",
+ "description": "Number of jobs currently processing"
+ },
+ "completed": {
+ "type": "integer",
+ "description": "Number of completed jobs"
+ },
+ "failed": {
+ "type": "integer",
+ "description": "Number of failed jobs"
+ },
+ "delayed": {
+ "type": "integer",
+ "description": "Number of delayed jobs"
+ }
+ }
+ },
+ "Check": {
+ "type": "object",
+ "description": "A monitoring check result",
+ "properties": {
+ "_id": {
+ "type": "string",
+ "description": "Check ID"
+ },
+ "metadata": {
+ "type": "object",
+ "properties": {
+ "monitorId": {
+ "type": "string",
+ "description": "Associated monitor ID"
+ },
+ "teamId": {
+ "type": "string",
+ "description": "Associated team ID"
+ },
+ "type": {
+ "type": "string",
+ "enum": ["http", "ping", "pagespeed", "hardware", "docker", "port"],
+ "description": "Monitor type"
+ }
+ },
+ "required": ["monitorId", "teamId", "type"]
+ },
+ "status": {
+ "type": "boolean",
+ "description": "Check status (true = up, false = down)"
+ },
+ "responseTime": {
+ "type": "number",
+ "description": "Response time in milliseconds"
+ },
+ "statusCode": {
+ "type": "integer",
+ "description": "HTTP status code (for HTTP monitors)"
+ },
+ "message": {
+ "type": "string",
+ "description": "Status message or error description"
+ },
+ "timings": {
+ "$ref": "#/components/schemas/CheckTimings"
+ },
+ "cpu": {
+ "$ref": "#/components/schemas/CheckCpuInfo"
+ },
+ "memory": {
+ "$ref": "#/components/schemas/CheckMemoryInfo"
+ },
+ "disk": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/CheckDiskInfo"
+ }
+ },
+ "host": {
+ "$ref": "#/components/schemas/CheckHostInfo"
+ },
+ "errors": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/CheckErrorInfo"
+ }
+ },
+ "capture": {
+ "$ref": "#/components/schemas/CheckCaptureInfo"
+ },
+ "net": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/CheckNetworkInfo"
+ }
+ },
+ "accessibility": {
+ "type": "number",
+ "description": "PageSpeed accessibility score (0-100)"
+ },
+ "bestPractices": {
+ "type": "number",
+ "description": "PageSpeed best practices score (0-100)"
+ },
+ "seo": {
+ "type": "number",
+ "description": "PageSpeed SEO score (0-100)"
+ },
+ "performance": {
+ "type": "number",
+ "description": "PageSpeed performance score (0-100)"
+ },
+ "audits": {
+ "$ref": "#/components/schemas/CheckAudits"
+ },
+ "ack": {
+ "type": "boolean",
+ "description": "Whether the check has been acknowledged"
+ },
+ "ackAt": {
+ "type": ["string", "null"],
+ "format": "date-time",
+ "description": "When the check was acknowledged"
+ },
+ "expiry": {
+ "type": "string",
+ "format": "date-time",
+ "description": "When the check expires"
+ },
+ "createdAt": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "updatedAt": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ },
+ "CheckTimings": {
+ "type": "object",
+ "description": "Detailed timing information for HTTP checks",
+ "properties": {
+ "start": {
+ "type": "number"
+ },
+ "socket": {
+ "type": "number"
+ },
+ "lookup": {
+ "type": "number"
+ },
+ "connect": {
+ "type": "number"
+ },
+ "secureConnect": {
+ "type": "number"
+ },
+ "upload": {
+ "type": "number"
+ },
+ "response": {
+ "type": "number"
+ },
+ "end": {
+ "type": "number"
+ },
+ "phases": {
+ "type": "object",
+ "properties": {
+ "wait": {
+ "type": "number"
+ },
+ "dns": {
+ "type": "number"
+ },
+ "tcp": {
+ "type": "number"
+ },
+ "tls": {
+ "type": "number"
+ },
+ "request": {
+ "type": "number"
+ },
+ "firstByte": {
+ "type": "number"
+ },
+ "download": {
+ "type": "number"
+ },
+ "total": {
+ "type": "number"
+ }
+ }
+ }
+ }
+ },
+ "CheckCpuInfo": {
+ "type": "object",
+ "description": "CPU information for infrastructure checks",
+ "properties": {
+ "physical_core": {
+ "type": "integer"
+ },
+ "logical_core": {
+ "type": "integer"
+ },
+ "frequency": {
+ "type": "number"
+ },
+ "temperature": {
+ "type": "array",
+ "items": {
+ "type": "number"
+ }
+ },
+ "free_percent": {
+ "type": "number"
+ },
+ "usage_percent": {
+ "type": "number"
+ }
+ }
+ },
+ "CheckMemoryInfo": {
+ "type": "object",
+ "description": "Memory information for infrastructure checks",
+ "properties": {
+ "total_bytes": {
+ "type": "number"
+ },
+ "available_bytes": {
+ "type": "number"
+ },
+ "used_bytes": {
+ "type": "number"
+ },
+ "usage_percent": {
+ "type": "number"
+ }
+ }
+ },
+ "CheckDiskInfo": {
+ "type": "object",
+ "description": "Disk information for infrastructure checks",
+ "properties": {
+ "device": {
+ "type": "string"
+ },
+ "mountpoint": {
+ "type": "string"
+ },
+ "read_speed_bytes": {
+ "type": "number"
+ },
+ "write_speed_bytes": {
+ "type": "number"
+ },
+ "total_bytes": {
+ "type": "number"
+ },
+ "free_bytes": {
+ "type": "number"
+ },
+ "usage_percent": {
+ "type": "number"
+ }
+ }
+ },
+ "CheckHostInfo": {
+ "type": "object",
+ "description": "Host information for infrastructure checks",
+ "properties": {
+ "os": {
+ "type": "string"
+ },
+ "platform": {
+ "type": "string"
+ },
+ "kernel_version": {
+ "type": "string"
+ }
+ }
+ },
+ "CheckErrorInfo": {
+ "type": "object",
+ "description": "Error information from checks",
+ "properties": {
+ "metric": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "err": {
+ "type": "string"
+ }
+ }
+ },
+ "CheckCaptureInfo": {
+ "type": "object",
+ "description": "Capture agent information",
+ "properties": {
+ "version": {
+ "type": "string"
+ },
+ "mode": {
+ "type": "string"
+ }
+ }
+ },
+ "CheckNetworkInfo": {
+ "type": "object",
+ "description": "Network interface information",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "bytes_sent": {
+ "type": "number"
+ },
+ "bytes_recv": {
+ "type": "number"
+ },
+ "packets_sent": {
+ "type": "number"
+ },
+ "packets_recv": {
+ "type": "number"
+ },
+ "err_in": {
+ "type": "number"
+ },
+ "err_out": {
+ "type": "number"
+ },
+ "drop_in": {
+ "type": "number"
+ },
+ "drop_out": {
+ "type": "number"
+ },
+ "fifo_in": {
+ "type": "number"
+ },
+ "fifo_out": {
+ "type": "number"
+ }
+ }
+ },
+ "CheckAudits": {
+ "type": "object",
+ "description": "PageSpeed audit results",
+ "properties": {
+ "cls": {
+ "$ref": "#/components/schemas/LighthouseAudit"
+ },
+ "si": {
+ "$ref": "#/components/schemas/LighthouseAudit"
+ },
+ "fcp": {
+ "$ref": "#/components/schemas/LighthouseAudit"
+ },
+ "lcp": {
+ "$ref": "#/components/schemas/LighthouseAudit"
+ },
+ "tbt": {
+ "$ref": "#/components/schemas/LighthouseAudit"
+ }
+ }
+ },
+ "LighthouseAudit": {
+ "type": "object",
+ "description": "Individual Lighthouse audit result",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "score": {
+ "type": "number"
+ },
+ "displayValue": {
+ "type": "string"
+ },
+ "numericValue": {
+ "type": "number"
+ },
+ "numericUnit": {
+ "type": "string"
+ }
+ }
}
}
}
diff --git a/server/package-lock.json b/server/package-lock.json
index 3e1130dc4..3f5b824b0 100644
--- a/server/package-lock.json
+++ b/server/package-lock.json
@@ -13,6 +13,7 @@
"axios": "^1.7.2",
"bcryptjs": "3.0.2",
"bullmq": "5.41.2",
+ "cacheable-lookup": "7.0.0",
"compression": "1.8.1",
"cookie-parser": "^1.4.7",
"cors": "^2.8.5",
@@ -35,26 +36,47 @@
"mongoose": "^8.3.3",
"multer": "^1.4.5-lts.1",
"nodemailer": "^6.9.14",
- "papaparse": "^5.5.2",
"ping": "0.4.4",
"sharp": "0.33.5",
"ssl-checker": "2.0.10",
- "super-simple-scheduler": "1.4.1",
+ "super-simple-scheduler": "1.4.5",
"swagger-ui-express": "5.0.1",
"winston": "^3.13.0"
},
"devDependencies": {
"@eslint/js": "^9.17.0",
+ "@types/compression": "1.8.1",
+ "@types/cookie-parser": "1.4.10",
+ "@types/cors": "2.8.19",
+ "@types/dockerode": "^4.0.0",
+ "@types/express": "5.0.3",
+ "@types/gamedig": "^5.0.3",
+ "@types/jest": "^30.0.0",
+ "@types/jmespath": "^0.15.2",
+ "@types/jsdom": "^27.0.0",
+ "@types/jsonwebtoken": "9.0.10",
+ "@types/mjml": "^4.7.4",
+ "@types/multer": "^2.0.0",
+ "@types/nodemailer": "7.0.1",
+ "@types/papaparse": "^5.5.2",
+ "@types/ping": "0.4.4",
+ "@types/swagger-ui-express": "4.1.8",
"c8": "10.1.3",
- "chai": "5.2.0",
"eslint": "^9.17.0",
"eslint-plugin-mocha": "^10.5.0",
"esm": "3.2.25",
"globals": "^15.14.0",
- "mocha": "11.1.0",
- "nodemon": "3.1.9",
+ "jest": "^30.2.0",
+ "nodemon": "^3.1.11",
"prettier": "^3.3.3",
- "sinon": "19.0.2"
+ "ts-jest": "^29.4.6",
+ "ts-node": "^10.9.2",
+ "tsc-alias": "1.8.16",
+ "tsx": "4.20.5",
+ "typescript": "5.9.2"
+ },
+ "engines": {
+ "node": ">=20"
}
},
"node_modules/@asamuzakjp/css-color": {
@@ -70,13 +92,764 @@
"lru-cache": "^10.4.3"
}
},
- "node_modules/@babel/code-frame": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
- "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "node_modules/@asamuzakjp/dom-selector": {
+ "version": "6.7.3",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.3.tgz",
+ "integrity": "sha512-kiGFeY+Hxf5KbPpjRLf+ffWbkos1aGo8MBfd91oxS3O57RgU3XhZrt/6UzoVF9VMpWbC3v87SRc9jxGrc9qHtQ==",
"license": "MIT",
"dependencies": {
- "@babel/helper-validator-identifier": "^7.27.1",
+ "@asamuzakjp/nwsapi": "^2.3.9",
+ "bidi-js": "^1.0.3",
+ "css-tree": "^3.1.0",
+ "is-potential-custom-element-name": "^1.0.1",
+ "lru-cache": "^11.2.2"
+ }
+ },
+ "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": {
+ "version": "11.2.2",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz",
+ "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==",
+ "license": "ISC",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/@asamuzakjp/nwsapi": {
+ "version": "2.3.9",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz",
+ "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==",
+ "license": "MIT"
+ },
+ "node_modules/@aws-crypto/sha256-browser": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz",
+ "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-crypto/sha256-js": "^5.2.0",
+ "@aws-crypto/supports-web-crypto": "^5.2.0",
+ "@aws-crypto/util": "^5.2.0",
+ "@aws-sdk/types": "^3.222.0",
+ "@aws-sdk/util-locate-window": "^3.0.0",
+ "@smithy/util-utf8": "^2.0.0",
+ "tslib": "^2.6.2"
+ }
+ },
+ "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz",
+ "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz",
+ "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/is-array-buffer": "^2.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz",
+ "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/util-buffer-from": "^2.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-crypto/sha256-js": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz",
+ "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-crypto/util": "^5.2.0",
+ "@aws-sdk/types": "^3.222.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/@aws-crypto/supports-web-crypto": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz",
+ "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.6.2"
+ }
+ },
+ "node_modules/@aws-crypto/util": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz",
+ "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/types": "^3.222.0",
+ "@smithy/util-utf8": "^2.0.0",
+ "tslib": "^2.6.2"
+ }
+ },
+ "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz",
+ "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz",
+ "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/is-array-buffer": "^2.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz",
+ "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/util-buffer-from": "^2.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/client-sesv2": {
+ "version": "3.917.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sesv2/-/client-sesv2-3.917.0.tgz",
+ "integrity": "sha512-Jwz+3rO9fc2H8JyF4ainL5x06tiQ0n4ewKIpdUeGIauKdndMItYP1aeM0Y8wJQPTF1XEpWFC36cTSDgk/eypxA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-crypto/sha256-browser": "5.2.0",
+ "@aws-crypto/sha256-js": "5.2.0",
+ "@aws-sdk/core": "3.916.0",
+ "@aws-sdk/credential-provider-node": "3.917.0",
+ "@aws-sdk/middleware-host-header": "3.914.0",
+ "@aws-sdk/middleware-logger": "3.914.0",
+ "@aws-sdk/middleware-recursion-detection": "3.914.0",
+ "@aws-sdk/middleware-user-agent": "3.916.0",
+ "@aws-sdk/region-config-resolver": "3.914.0",
+ "@aws-sdk/signature-v4-multi-region": "3.916.0",
+ "@aws-sdk/types": "3.914.0",
+ "@aws-sdk/util-endpoints": "3.916.0",
+ "@aws-sdk/util-user-agent-browser": "3.914.0",
+ "@aws-sdk/util-user-agent-node": "3.916.0",
+ "@smithy/config-resolver": "^4.4.0",
+ "@smithy/core": "^3.17.1",
+ "@smithy/fetch-http-handler": "^5.3.4",
+ "@smithy/hash-node": "^4.2.3",
+ "@smithy/invalid-dependency": "^4.2.3",
+ "@smithy/middleware-content-length": "^4.2.3",
+ "@smithy/middleware-endpoint": "^4.3.5",
+ "@smithy/middleware-retry": "^4.4.5",
+ "@smithy/middleware-serde": "^4.2.3",
+ "@smithy/middleware-stack": "^4.2.3",
+ "@smithy/node-config-provider": "^4.3.3",
+ "@smithy/node-http-handler": "^4.4.3",
+ "@smithy/protocol-http": "^5.3.3",
+ "@smithy/smithy-client": "^4.9.1",
+ "@smithy/types": "^4.8.0",
+ "@smithy/url-parser": "^4.2.3",
+ "@smithy/util-base64": "^4.3.0",
+ "@smithy/util-body-length-browser": "^4.2.0",
+ "@smithy/util-body-length-node": "^4.2.1",
+ "@smithy/util-defaults-mode-browser": "^4.3.4",
+ "@smithy/util-defaults-mode-node": "^4.2.6",
+ "@smithy/util-endpoints": "^3.2.3",
+ "@smithy/util-middleware": "^4.2.3",
+ "@smithy/util-retry": "^4.2.3",
+ "@smithy/util-utf8": "^4.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/client-sso": {
+ "version": "3.916.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.916.0.tgz",
+ "integrity": "sha512-Eu4PtEUL1MyRvboQnoq5YKg0Z9vAni3ccebykJy615xokVZUdA3di2YxHM/hykDQX7lcUC62q9fVIvh0+UNk/w==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-crypto/sha256-browser": "5.2.0",
+ "@aws-crypto/sha256-js": "5.2.0",
+ "@aws-sdk/core": "3.916.0",
+ "@aws-sdk/middleware-host-header": "3.914.0",
+ "@aws-sdk/middleware-logger": "3.914.0",
+ "@aws-sdk/middleware-recursion-detection": "3.914.0",
+ "@aws-sdk/middleware-user-agent": "3.916.0",
+ "@aws-sdk/region-config-resolver": "3.914.0",
+ "@aws-sdk/types": "3.914.0",
+ "@aws-sdk/util-endpoints": "3.916.0",
+ "@aws-sdk/util-user-agent-browser": "3.914.0",
+ "@aws-sdk/util-user-agent-node": "3.916.0",
+ "@smithy/config-resolver": "^4.4.0",
+ "@smithy/core": "^3.17.1",
+ "@smithy/fetch-http-handler": "^5.3.4",
+ "@smithy/hash-node": "^4.2.3",
+ "@smithy/invalid-dependency": "^4.2.3",
+ "@smithy/middleware-content-length": "^4.2.3",
+ "@smithy/middleware-endpoint": "^4.3.5",
+ "@smithy/middleware-retry": "^4.4.5",
+ "@smithy/middleware-serde": "^4.2.3",
+ "@smithy/middleware-stack": "^4.2.3",
+ "@smithy/node-config-provider": "^4.3.3",
+ "@smithy/node-http-handler": "^4.4.3",
+ "@smithy/protocol-http": "^5.3.3",
+ "@smithy/smithy-client": "^4.9.1",
+ "@smithy/types": "^4.8.0",
+ "@smithy/url-parser": "^4.2.3",
+ "@smithy/util-base64": "^4.3.0",
+ "@smithy/util-body-length-browser": "^4.2.0",
+ "@smithy/util-body-length-node": "^4.2.1",
+ "@smithy/util-defaults-mode-browser": "^4.3.4",
+ "@smithy/util-defaults-mode-node": "^4.2.6",
+ "@smithy/util-endpoints": "^3.2.3",
+ "@smithy/util-middleware": "^4.2.3",
+ "@smithy/util-retry": "^4.2.3",
+ "@smithy/util-utf8": "^4.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/core": {
+ "version": "3.916.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.916.0.tgz",
+ "integrity": "sha512-1JHE5s6MD5PKGovmx/F1e01hUbds/1y3X8rD+Gvi/gWVfdg5noO7ZCerpRsWgfzgvCMZC9VicopBqNHCKLykZA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/types": "3.914.0",
+ "@aws-sdk/xml-builder": "3.914.0",
+ "@smithy/core": "^3.17.1",
+ "@smithy/node-config-provider": "^4.3.3",
+ "@smithy/property-provider": "^4.2.3",
+ "@smithy/protocol-http": "^5.3.3",
+ "@smithy/signature-v4": "^5.3.3",
+ "@smithy/smithy-client": "^4.9.1",
+ "@smithy/types": "^4.8.0",
+ "@smithy/util-base64": "^4.3.0",
+ "@smithy/util-middleware": "^4.2.3",
+ "@smithy/util-utf8": "^4.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/credential-provider-env": {
+ "version": "3.916.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.916.0.tgz",
+ "integrity": "sha512-3gDeqOXcBRXGHScc6xb7358Lyf64NRG2P08g6Bu5mv1Vbg9PKDyCAZvhKLkG7hkdfAM8Yc6UJNhbFxr1ud/tCQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/core": "3.916.0",
+ "@aws-sdk/types": "3.914.0",
+ "@smithy/property-provider": "^4.2.3",
+ "@smithy/types": "^4.8.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/credential-provider-http": {
+ "version": "3.916.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.916.0.tgz",
+ "integrity": "sha512-NmooA5Z4/kPFJdsyoJgDxuqXC1C6oPMmreJjbOPqcwo6E/h2jxaG8utlQFgXe5F9FeJsMx668dtxVxSYnAAqHQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/core": "3.916.0",
+ "@aws-sdk/types": "3.914.0",
+ "@smithy/fetch-http-handler": "^5.3.4",
+ "@smithy/node-http-handler": "^4.4.3",
+ "@smithy/property-provider": "^4.2.3",
+ "@smithy/protocol-http": "^5.3.3",
+ "@smithy/smithy-client": "^4.9.1",
+ "@smithy/types": "^4.8.0",
+ "@smithy/util-stream": "^4.5.4",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/credential-provider-ini": {
+ "version": "3.917.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.917.0.tgz",
+ "integrity": "sha512-rvQ0QamLySRq+Okc0ZqFHZ3Fbvj3tYuWNIlzyEKklNmw5X5PM1idYKlOJflY2dvUGkIqY3lUC9SC2WL+1s7KIw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/core": "3.916.0",
+ "@aws-sdk/credential-provider-env": "3.916.0",
+ "@aws-sdk/credential-provider-http": "3.916.0",
+ "@aws-sdk/credential-provider-process": "3.916.0",
+ "@aws-sdk/credential-provider-sso": "3.916.0",
+ "@aws-sdk/credential-provider-web-identity": "3.917.0",
+ "@aws-sdk/nested-clients": "3.916.0",
+ "@aws-sdk/types": "3.914.0",
+ "@smithy/credential-provider-imds": "^4.2.3",
+ "@smithy/property-provider": "^4.2.3",
+ "@smithy/shared-ini-file-loader": "^4.3.3",
+ "@smithy/types": "^4.8.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/credential-provider-node": {
+ "version": "3.917.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.917.0.tgz",
+ "integrity": "sha512-n7HUJ+TgU9wV/Z46yR1rqD9hUjfG50AKi+b5UXTlaDlVD8bckg40i77ROCllp53h32xQj/7H0yBIYyphwzLtmg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/credential-provider-env": "3.916.0",
+ "@aws-sdk/credential-provider-http": "3.916.0",
+ "@aws-sdk/credential-provider-ini": "3.917.0",
+ "@aws-sdk/credential-provider-process": "3.916.0",
+ "@aws-sdk/credential-provider-sso": "3.916.0",
+ "@aws-sdk/credential-provider-web-identity": "3.917.0",
+ "@aws-sdk/types": "3.914.0",
+ "@smithy/credential-provider-imds": "^4.2.3",
+ "@smithy/property-provider": "^4.2.3",
+ "@smithy/shared-ini-file-loader": "^4.3.3",
+ "@smithy/types": "^4.8.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/credential-provider-process": {
+ "version": "3.916.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.916.0.tgz",
+ "integrity": "sha512-SXDyDvpJ1+WbotZDLJW1lqP6gYGaXfZJrgFSXIuZjHb75fKeNRgPkQX/wZDdUvCwdrscvxmtyJorp2sVYkMcvA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/core": "3.916.0",
+ "@aws-sdk/types": "3.914.0",
+ "@smithy/property-provider": "^4.2.3",
+ "@smithy/shared-ini-file-loader": "^4.3.3",
+ "@smithy/types": "^4.8.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/credential-provider-sso": {
+ "version": "3.916.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.916.0.tgz",
+ "integrity": "sha512-gu9D+c+U/Dp1AKBcVxYHNNoZF9uD4wjAKYCjgSN37j4tDsazwMEylbbZLuRNuxfbXtizbo4/TiaxBXDbWM7AkQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/client-sso": "3.916.0",
+ "@aws-sdk/core": "3.916.0",
+ "@aws-sdk/token-providers": "3.916.0",
+ "@aws-sdk/types": "3.914.0",
+ "@smithy/property-provider": "^4.2.3",
+ "@smithy/shared-ini-file-loader": "^4.3.3",
+ "@smithy/types": "^4.8.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/credential-provider-web-identity": {
+ "version": "3.917.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.917.0.tgz",
+ "integrity": "sha512-pZncQhFbwW04pB0jcD5OFv3x2gAddDYCVxyJVixgyhSw7bKCYxqu6ramfq1NxyVpmm+qsw+ijwi/3cCmhUHF/A==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/core": "3.916.0",
+ "@aws-sdk/nested-clients": "3.916.0",
+ "@aws-sdk/types": "3.914.0",
+ "@smithy/property-provider": "^4.2.3",
+ "@smithy/shared-ini-file-loader": "^4.3.3",
+ "@smithy/types": "^4.8.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-host-header": {
+ "version": "3.914.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.914.0.tgz",
+ "integrity": "sha512-7r9ToySQ15+iIgXMF/h616PcQStByylVkCshmQqcdeynD/lCn2l667ynckxW4+ql0Q+Bo/URljuhJRxVJzydNA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/types": "3.914.0",
+ "@smithy/protocol-http": "^5.3.3",
+ "@smithy/types": "^4.8.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-logger": {
+ "version": "3.914.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.914.0.tgz",
+ "integrity": "sha512-/gaW2VENS5vKvJbcE1umV4Ag3NuiVzpsANxtrqISxT3ovyro29o1RezW/Avz/6oJqjnmgz8soe9J1t65jJdiNg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/types": "3.914.0",
+ "@smithy/types": "^4.8.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-recursion-detection": {
+ "version": "3.914.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.914.0.tgz",
+ "integrity": "sha512-yiAjQKs5S2JKYc+GrkvGMwkUvhepXDigEXpSJqUseR/IrqHhvGNuOxDxq+8LbDhM4ajEW81wkiBbU+Jl9G82yQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/types": "3.914.0",
+ "@aws/lambda-invoke-store": "^0.0.1",
+ "@smithy/protocol-http": "^5.3.3",
+ "@smithy/types": "^4.8.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-sdk-s3": {
+ "version": "3.916.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.916.0.tgz",
+ "integrity": "sha512-pjmzzjkEkpJObzmTthqJPq/P13KoNFuEi/x5PISlzJtHofCNcyXeVAQ90yvY2dQ6UXHf511Rh1/ytiKy2A8M0g==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/core": "3.916.0",
+ "@aws-sdk/types": "3.914.0",
+ "@aws-sdk/util-arn-parser": "3.893.0",
+ "@smithy/core": "^3.17.1",
+ "@smithy/node-config-provider": "^4.3.3",
+ "@smithy/protocol-http": "^5.3.3",
+ "@smithy/signature-v4": "^5.3.3",
+ "@smithy/smithy-client": "^4.9.1",
+ "@smithy/types": "^4.8.0",
+ "@smithy/util-config-provider": "^4.2.0",
+ "@smithy/util-middleware": "^4.2.3",
+ "@smithy/util-stream": "^4.5.4",
+ "@smithy/util-utf8": "^4.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-user-agent": {
+ "version": "3.916.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.916.0.tgz",
+ "integrity": "sha512-mzF5AdrpQXc2SOmAoaQeHpDFsK2GE6EGcEACeNuoESluPI2uYMpuuNMYrUufdnIAIyqgKlis0NVxiahA5jG42w==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/core": "3.916.0",
+ "@aws-sdk/types": "3.914.0",
+ "@aws-sdk/util-endpoints": "3.916.0",
+ "@smithy/core": "^3.17.1",
+ "@smithy/protocol-http": "^5.3.3",
+ "@smithy/types": "^4.8.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/nested-clients": {
+ "version": "3.916.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.916.0.tgz",
+ "integrity": "sha512-tgg8e8AnVAer0rcgeWucFJ/uNN67TbTiDHfD+zIOPKep0Z61mrHEoeT/X8WxGIOkEn4W6nMpmS4ii8P42rNtnA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-crypto/sha256-browser": "5.2.0",
+ "@aws-crypto/sha256-js": "5.2.0",
+ "@aws-sdk/core": "3.916.0",
+ "@aws-sdk/middleware-host-header": "3.914.0",
+ "@aws-sdk/middleware-logger": "3.914.0",
+ "@aws-sdk/middleware-recursion-detection": "3.914.0",
+ "@aws-sdk/middleware-user-agent": "3.916.0",
+ "@aws-sdk/region-config-resolver": "3.914.0",
+ "@aws-sdk/types": "3.914.0",
+ "@aws-sdk/util-endpoints": "3.916.0",
+ "@aws-sdk/util-user-agent-browser": "3.914.0",
+ "@aws-sdk/util-user-agent-node": "3.916.0",
+ "@smithy/config-resolver": "^4.4.0",
+ "@smithy/core": "^3.17.1",
+ "@smithy/fetch-http-handler": "^5.3.4",
+ "@smithy/hash-node": "^4.2.3",
+ "@smithy/invalid-dependency": "^4.2.3",
+ "@smithy/middleware-content-length": "^4.2.3",
+ "@smithy/middleware-endpoint": "^4.3.5",
+ "@smithy/middleware-retry": "^4.4.5",
+ "@smithy/middleware-serde": "^4.2.3",
+ "@smithy/middleware-stack": "^4.2.3",
+ "@smithy/node-config-provider": "^4.3.3",
+ "@smithy/node-http-handler": "^4.4.3",
+ "@smithy/protocol-http": "^5.3.3",
+ "@smithy/smithy-client": "^4.9.1",
+ "@smithy/types": "^4.8.0",
+ "@smithy/url-parser": "^4.2.3",
+ "@smithy/util-base64": "^4.3.0",
+ "@smithy/util-body-length-browser": "^4.2.0",
+ "@smithy/util-body-length-node": "^4.2.1",
+ "@smithy/util-defaults-mode-browser": "^4.3.4",
+ "@smithy/util-defaults-mode-node": "^4.2.6",
+ "@smithy/util-endpoints": "^3.2.3",
+ "@smithy/util-middleware": "^4.2.3",
+ "@smithy/util-retry": "^4.2.3",
+ "@smithy/util-utf8": "^4.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/region-config-resolver": {
+ "version": "3.914.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.914.0.tgz",
+ "integrity": "sha512-KlmHhRbn1qdwXUdsdrJ7S/MAkkC1jLpQ11n+XvxUUUCGAJd1gjC7AjxPZUM7ieQ2zcb8bfEzIU7al+Q3ZT0u7Q==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/types": "3.914.0",
+ "@smithy/config-resolver": "^4.4.0",
+ "@smithy/types": "^4.8.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/signature-v4-multi-region": {
+ "version": "3.916.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.916.0.tgz",
+ "integrity": "sha512-fuzUMo6xU7e0NBzBA6TQ4FUf1gqNbg4woBSvYfxRRsIfKmSMn9/elXXn4sAE5UKvlwVQmYnb6p7dpVRPyFvnQA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/middleware-sdk-s3": "3.916.0",
+ "@aws-sdk/types": "3.914.0",
+ "@smithy/protocol-http": "^5.3.3",
+ "@smithy/signature-v4": "^5.3.3",
+ "@smithy/types": "^4.8.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/token-providers": {
+ "version": "3.916.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.916.0.tgz",
+ "integrity": "sha512-13GGOEgq5etbXulFCmYqhWtpcEQ6WI6U53dvXbheW0guut8fDFJZmEv7tKMTJgiybxh7JHd0rWcL9JQND8DwoQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/core": "3.916.0",
+ "@aws-sdk/nested-clients": "3.916.0",
+ "@aws-sdk/types": "3.914.0",
+ "@smithy/property-provider": "^4.2.3",
+ "@smithy/shared-ini-file-loader": "^4.3.3",
+ "@smithy/types": "^4.8.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/types": {
+ "version": "3.914.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.914.0.tgz",
+ "integrity": "sha512-kQWPsRDmom4yvAfyG6L1lMmlwnTzm1XwMHOU+G5IFlsP4YEaMtXidDzW/wiivY0QFrhfCz/4TVmu0a2aPU57ug==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/types": "^4.8.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/util-arn-parser": {
+ "version": "3.893.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.893.0.tgz",
+ "integrity": "sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/util-endpoints": {
+ "version": "3.916.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.916.0.tgz",
+ "integrity": "sha512-bAgUQwvixdsiGNcuZSDAOWbyHlnPtg8G8TyHD6DTfTmKTHUW6tAn+af/ZYJPXEzXhhpwgJqi58vWnsiDhmr7NQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/types": "3.914.0",
+ "@smithy/types": "^4.8.0",
+ "@smithy/url-parser": "^4.2.3",
+ "@smithy/util-endpoints": "^3.2.3",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/util-locate-window": {
+ "version": "3.893.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.893.0.tgz",
+ "integrity": "sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/util-user-agent-browser": {
+ "version": "3.914.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.914.0.tgz",
+ "integrity": "sha512-rMQUrM1ECH4kmIwlGl9UB0BtbHy6ZuKdWFrIknu8yGTRI/saAucqNTh5EI1vWBxZ0ElhK5+g7zOnUuhSmVQYUA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/types": "3.914.0",
+ "@smithy/types": "^4.8.0",
+ "bowser": "^2.11.0",
+ "tslib": "^2.6.2"
+ }
+ },
+ "node_modules/@aws-sdk/util-user-agent-node": {
+ "version": "3.916.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.916.0.tgz",
+ "integrity": "sha512-CwfWV2ch6UdjuSV75ZU99N03seEUb31FIUrXBnwa6oONqj/xqXwrxtlUMLx6WH3OJEE4zI3zt5PjlTdGcVwf4g==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/middleware-user-agent": "3.916.0",
+ "@aws-sdk/types": "3.914.0",
+ "@smithy/node-config-provider": "^4.3.3",
+ "@smithy/types": "^4.8.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "aws-crt": ">=1.0.0"
+ },
+ "peerDependenciesMeta": {
+ "aws-crt": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@aws-sdk/xml-builder": {
+ "version": "3.914.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.914.0.tgz",
+ "integrity": "sha512-k75evsBD5TcIjedycYS7QXQ98AmOtbnxRJOPtCo0IwYRmy7UvqgS/gBL5SmrIqeV6FDSYRQMgdBxSMp6MLmdew==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/types": "^4.8.0",
+ "fast-xml-parser": "5.2.5",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@aws/lambda-invoke-store": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.0.1.tgz",
+ "integrity": "sha512-ORHRQ2tmvnBXc8t/X9Z8IcSbBA4xTLKuN873FopzklHMeqBst7YG0d+AX97inkvDX+NChYtSr+qGfcqGFaI8Zw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz",
+ "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.28.5",
"js-tokens": "^4.0.0",
"picocolors": "^1.1.1"
},
@@ -84,20 +857,515 @@
"node": ">=6.9.0"
}
},
- "node_modules/@babel/helper-validator-identifier": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
- "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
+ "node_modules/@babel/compat-data": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz",
+ "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
- "node_modules/@babel/runtime": {
- "version": "7.28.3",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz",
- "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==",
+ "node_modules/@babel/core": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz",
+ "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==",
+ "dev": true,
"license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/generator": "^7.28.6",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helpers": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/template": "^7.28.6",
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/core/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz",
+ "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
+ "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.28.6",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
+ "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz",
+ "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz",
+ "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.6"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-async-generators": {
+ "version": "7.8.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz",
+ "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-bigint": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz",
+ "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-class-properties": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz",
+ "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.12.13"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-class-static-block": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz",
+ "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-attributes": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz",
+ "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-meta": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz",
+ "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-json-strings": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz",
+ "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-jsx": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz",
+ "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-logical-assignment-operators": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz",
+ "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz",
+ "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-numeric-separator": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz",
+ "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-object-rest-spread": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz",
+ "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-optional-catch-binding": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz",
+ "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-optional-chaining": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz",
+ "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-private-property-in-object": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz",
+ "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-top-level-await": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz",
+ "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-typescript": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz",
+ "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
+ "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz",
+ "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/generator": "^7.28.6",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.28.6",
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.28.6",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz",
+ "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
"engines": {
"node": ">=6.9.0"
}
@@ -127,6 +1395,30 @@
"node": ">=0.1.90"
}
},
+ "node_modules/@cspotcode/source-map-support": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+ "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "0.3.9"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+ "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.0.3",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ }
+ },
"node_modules/@csstools/color-helpers": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
@@ -211,6 +1503,7 @@
}
],
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=18"
},
@@ -218,6 +1511,28 @@
"@csstools/css-tokenizer": "^3.0.4"
}
},
+ "node_modules/@csstools/css-syntax-patches-for-csstree": {
+ "version": "1.0.14",
+ "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.14.tgz",
+ "integrity": "sha512-zSlIxa20WvMojjpCSy8WrNpcZ61RqfTfX3XTaOeVlGJrt/8HF3YbzgFZa01yTbT4GWQLwfTcC3EB8i3XnB647Q==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT-0",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4"
+ }
+ },
"node_modules/@csstools/css-tokenizer": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz",
@@ -233,35 +1548,501 @@
}
],
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=18"
}
},
"node_modules/@dabh/diagnostics": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz",
- "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==",
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz",
+ "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==",
"license": "MIT",
"dependencies": {
- "colorspace": "1.1.x",
+ "@so-ric/colorspace": "^1.1.6",
"enabled": "2.0.x",
"kuler": "^2.0.0"
}
},
+ "node_modules/@emnapi/core": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz",
+ "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.1.0",
+ "tslib": "^2.4.0"
+ }
+ },
"node_modules/@emnapi/runtime": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz",
- "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==",
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.6.0.tgz",
+ "integrity": "sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA==",
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz",
+ "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz",
+ "integrity": "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.11.tgz",
+ "integrity": "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.11.tgz",
+ "integrity": "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.11.tgz",
+ "integrity": "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.11.tgz",
+ "integrity": "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.11.tgz",
+ "integrity": "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.11.tgz",
+ "integrity": "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.11.tgz",
+ "integrity": "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.11.tgz",
+ "integrity": "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.11.tgz",
+ "integrity": "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.11.tgz",
+ "integrity": "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.11.tgz",
+ "integrity": "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.11.tgz",
+ "integrity": "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.11.tgz",
+ "integrity": "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.11.tgz",
+ "integrity": "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.11.tgz",
+ "integrity": "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.11.tgz",
+ "integrity": "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.11.tgz",
+ "integrity": "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.11.tgz",
+ "integrity": "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.11.tgz",
+ "integrity": "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.11.tgz",
+ "integrity": "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.11.tgz",
+ "integrity": "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.11.tgz",
+ "integrity": "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.11.tgz",
+ "integrity": "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.11.tgz",
+ "integrity": "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.11.tgz",
+ "integrity": "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@eslint-community/eslint-utils": {
- "version": "4.8.0",
- "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.8.0.tgz",
- "integrity": "sha512-MJQFqrZgcW0UNYLGOuQpey/oTN59vyWwplvCGZztn1cKz9agZPPYpJB7h2OMmuu7VLqkvEjN8feFZJmxNF9D+Q==",
+ "version": "4.9.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
+ "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -291,9 +2072,9 @@
}
},
"node_modules/@eslint-community/regexpp": {
- "version": "4.12.1",
- "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
- "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
"dev": true,
"license": "MIT",
"engines": {
@@ -301,13 +2082,13 @@
}
},
"node_modules/@eslint/config-array": {
- "version": "0.21.0",
- "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz",
- "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==",
+ "version": "0.21.1",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
+ "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "@eslint/object-schema": "^2.1.6",
+ "@eslint/object-schema": "^2.1.7",
"debug": "^4.3.1",
"minimatch": "^3.1.2"
},
@@ -316,19 +2097,22 @@
}
},
"node_modules/@eslint/config-helpers": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz",
- "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==",
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.1.tgz",
+ "integrity": "sha512-csZAzkNhsgwb0I/UAV6/RGFTbiakPCf0ZrGmrIxQpYvGZ00PhTkSnyKNolphgIvmnJeGw6rcGVEXfTzUnFuEvw==",
"dev": true,
"license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.16.0"
+ },
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/core": {
- "version": "0.15.2",
- "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz",
- "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==",
+ "version": "0.16.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz",
+ "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -376,9 +2160,9 @@
}
},
"node_modules/@eslint/js": {
- "version": "9.34.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.34.0.tgz",
- "integrity": "sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw==",
+ "version": "9.38.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.38.0.tgz",
+ "integrity": "sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -389,9 +2173,9 @@
}
},
"node_modules/@eslint/object-schema": {
- "version": "2.1.6",
- "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz",
- "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==",
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
+ "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@@ -399,13 +2183,13 @@
}
},
"node_modules/@eslint/plugin-kit": {
- "version": "0.3.5",
- "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz",
- "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==",
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz",
+ "integrity": "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "@eslint/core": "^0.15.2",
+ "@eslint/core": "^0.16.0",
"levn": "^0.4.1"
},
"engines": {
@@ -413,18 +2197,36 @@
}
},
"node_modules/@grpc/grpc-js": {
- "version": "1.13.4",
- "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.13.4.tgz",
- "integrity": "sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg==",
+ "version": "1.14.0",
+ "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.0.tgz",
+ "integrity": "sha512-N8Jx6PaYzcTRNzirReJCtADVoq4z7+1KQ4E70jTg/koQiMoUSN1kbNjPOqpPbhMFhfU1/l7ixspPl8dNY+FoUg==",
"license": "Apache-2.0",
"dependencies": {
- "@grpc/proto-loader": "^0.7.13",
+ "@grpc/proto-loader": "^0.8.0",
"@js-sdsl/ordered-map": "^4.4.2"
},
"engines": {
"node": ">=12.10.0"
}
},
+ "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz",
+ "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "lodash.camelcase": "^4.3.0",
+ "long": "^5.0.0",
+ "protobufjs": "^7.5.3",
+ "yargs": "^17.7.2"
+ },
+ "bin": {
+ "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/@grpc/proto-loader": {
"version": "0.7.15",
"resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz",
@@ -872,9 +2674,9 @@
}
},
"node_modules/@ioredis/commands": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.3.1.tgz",
- "integrity": "sha512-bYtU8avhGIcje3IhvF9aSjsa5URMZBHnwKtOvXsT4sfYy9gppW11gLPT/9oNqlJZD47yPKveQFTAFWpHjKvUoQ==",
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.4.0.tgz",
+ "integrity": "sha512-aFT2yemJJo+TZCmieA7qnYGQooOS7QfNmYrzGtsYd3g9j5iDP8AimYYAesf79ohjbLG12XxC4nG5DyEnC88AsQ==",
"license": "MIT"
},
"node_modules/@isaacs/cliui": {
@@ -894,6 +2696,113 @@
"node": ">=12"
}
},
+ "node_modules/@istanbuljs/load-nyc-config": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
+ "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "camelcase": "^5.3.1",
+ "find-up": "^4.1.0",
+ "get-package-type": "^0.1.0",
+ "js-yaml": "^3.13.1",
+ "resolve-from": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
+ "version": "3.14.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
+ "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/@istanbuljs/schema": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
@@ -904,6 +2813,377 @@
"node": ">=8"
}
},
+ "node_modules/@jest/console": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.2.0.tgz",
+ "integrity": "sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "jest-message-util": "30.2.0",
+ "jest-util": "30.2.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/core": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.2.0.tgz",
+ "integrity": "sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/console": "30.2.0",
+ "@jest/pattern": "30.0.1",
+ "@jest/reporters": "30.2.0",
+ "@jest/test-result": "30.2.0",
+ "@jest/transform": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "ansi-escapes": "^4.3.2",
+ "chalk": "^4.1.2",
+ "ci-info": "^4.2.0",
+ "exit-x": "^0.2.2",
+ "graceful-fs": "^4.2.11",
+ "jest-changed-files": "30.2.0",
+ "jest-config": "30.2.0",
+ "jest-haste-map": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-regex-util": "30.0.1",
+ "jest-resolve": "30.2.0",
+ "jest-resolve-dependencies": "30.2.0",
+ "jest-runner": "30.2.0",
+ "jest-runtime": "30.2.0",
+ "jest-snapshot": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-validate": "30.2.0",
+ "jest-watcher": "30.2.0",
+ "micromatch": "^4.0.8",
+ "pretty-format": "30.2.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@jest/diff-sequences": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz",
+ "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/environment": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.2.0.tgz",
+ "integrity": "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/fake-timers": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "jest-mock": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/expect": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.2.0.tgz",
+ "integrity": "sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "expect": "30.2.0",
+ "jest-snapshot": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/expect-utils": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz",
+ "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/fake-timers": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.2.0.tgz",
+ "integrity": "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.2.0",
+ "@sinonjs/fake-timers": "^13.0.0",
+ "@types/node": "*",
+ "jest-message-util": "30.2.0",
+ "jest-mock": "30.2.0",
+ "jest-util": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/get-type": {
+ "version": "30.1.0",
+ "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz",
+ "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/globals": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.2.0.tgz",
+ "integrity": "sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "30.2.0",
+ "@jest/expect": "30.2.0",
+ "@jest/types": "30.2.0",
+ "jest-mock": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/pattern": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz",
+ "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "jest-regex-util": "30.0.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/reporters": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.2.0.tgz",
+ "integrity": "sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@bcoe/v8-coverage": "^0.2.3",
+ "@jest/console": "30.2.0",
+ "@jest/test-result": "30.2.0",
+ "@jest/transform": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "collect-v8-coverage": "^1.0.2",
+ "exit-x": "^0.2.2",
+ "glob": "^10.3.10",
+ "graceful-fs": "^4.2.11",
+ "istanbul-lib-coverage": "^3.0.0",
+ "istanbul-lib-instrument": "^6.0.0",
+ "istanbul-lib-report": "^3.0.0",
+ "istanbul-lib-source-maps": "^5.0.0",
+ "istanbul-reports": "^3.1.3",
+ "jest-message-util": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-worker": "30.2.0",
+ "slash": "^3.0.0",
+ "string-length": "^4.0.2",
+ "v8-to-istanbul": "^9.0.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@jest/reporters/node_modules/@bcoe/v8-coverage": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
+ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jest/schemas": {
+ "version": "30.0.5",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz",
+ "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sinclair/typebox": "^0.34.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/snapshot-utils": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz",
+ "integrity": "sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.2.0",
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "natural-compare": "^1.4.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/source-map": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz",
+ "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "callsites": "^3.1.0",
+ "graceful-fs": "^4.2.11"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/test-result": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.2.0.tgz",
+ "integrity": "sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/console": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/istanbul-lib-coverage": "^2.0.6",
+ "collect-v8-coverage": "^1.0.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/test-sequencer": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz",
+ "integrity": "sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/test-result": "30.2.0",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.2.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/transform": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.2.0.tgz",
+ "integrity": "sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.27.4",
+ "@jest/types": "30.2.0",
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "babel-plugin-istanbul": "^7.0.1",
+ "chalk": "^4.1.2",
+ "convert-source-map": "^2.0.0",
+ "fast-json-stable-stringify": "^2.1.0",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.2.0",
+ "jest-regex-util": "30.0.1",
+ "jest-util": "30.2.0",
+ "micromatch": "^4.0.8",
+ "pirates": "^4.0.7",
+ "slash": "^3.0.0",
+ "write-file-atomic": "^5.0.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/types": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz",
+ "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/pattern": "30.0.1",
+ "@jest/schemas": "30.0.5",
+ "@types/istanbul-lib-coverage": "^2.0.6",
+ "@types/istanbul-reports": "^3.0.4",
+ "@types/node": "*",
+ "@types/yargs": "^17.0.33",
+ "chalk": "^4.1.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
@@ -922,9 +3202,9 @@
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.30",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz",
- "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==",
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -943,9 +3223,9 @@
}
},
"node_modules/@mongodb-js/saslprep": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.0.tgz",
- "integrity": "sha512-zlayKCsIjYb7/IdfqxorK5+xUMyi4vOKcFy10wKJYc63NSdKI8mNME+uJqfatkPmOSMMUiojrL58IePKBm3gvQ==",
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.2.tgz",
+ "integrity": "sha512-QgA5AySqB27cGTXBFmnpifAi7HxoGUeezwo6p9dI03MuDB6Pp33zgclqVb6oVK3j6I9Vesg0+oojW2XxB59SGg==",
"license": "MIT",
"dependencies": {
"sparse-bitfield": "^3.0.3"
@@ -1029,6 +3309,57 @@
"win32"
]
},
+ "node_modules/@napi-rs/wasm-runtime": {
+ "version": "0.2.12",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
+ "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.4.3",
+ "@emnapi/runtime": "^1.4.3",
+ "@tybys/wasm-util": "^0.10.0"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -1039,6 +3370,19 @@
"node": ">=14"
}
},
+ "node_modules/@pkgr/core": {
+ "version": "0.2.9",
+ "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
+ "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/pkgr"
+ }
+ },
"node_modules/@protobufjs/aspromise": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
@@ -1151,10 +3495,17 @@
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==",
"license": "BSD-3-Clause"
},
+ "node_modules/@sinclair/typebox": {
+ "version": "0.34.47",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.47.tgz",
+ "integrity": "sha512-ZGIBQ+XDvO5JQku9wmwtabcVTHJsgSWAHYtVuM9pBNNR5E88v6Jcj/llpmsjivig5X8A8HHOb4/mbEKPS5EvAw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@sindresorhus/is": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.0.2.tgz",
- "integrity": "sha512-d9xRovfKNz1SKieM0qJdO+PQonjnnIfSNWfHYnBSJ9hkjm0ZPw6HlxscDXYstp3z+7V2GOFHc+J0CYrYTjqCJw==",
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.1.0.tgz",
+ "integrity": "sha512-7F/yz2IphV39hiS2zB4QYVkivrptHHh0K8qJJd9HhuWSdvf8AN7NpebW3CcDZDBQsUPMoDKWsY2WWgW7bqOcfA==",
"license": "MIT",
"engines": {
"node": ">=18"
@@ -1183,33 +3534,681 @@
"@sinonjs/commons": "^3.0.1"
}
},
- "node_modules/@sinonjs/samsam": {
- "version": "8.0.3",
- "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.3.tgz",
- "integrity": "sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ==",
+ "node_modules/@smithy/abort-controller": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.3.tgz",
+ "integrity": "sha512-xWL9Mf8b7tIFuAlpjKtRPnHrR8XVrwTj5NPYO/QwZPtc0SDLsPxb56V5tzi5yspSMytISHybifez+4jlrx0vkQ==",
"dev": true,
- "license": "BSD-3-Clause",
+ "license": "Apache-2.0",
"dependencies": {
- "@sinonjs/commons": "^3.0.1",
- "type-detect": "^4.1.0"
+ "@smithy/types": "^4.8.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
}
},
- "node_modules/@sinonjs/samsam/node_modules/type-detect": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz",
- "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==",
+ "node_modules/@smithy/config-resolver": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.0.tgz",
+ "integrity": "sha512-Kkmz3Mup2PGp/HNJxhCWkLNdlajJORLSjwkcfrj0E7nu6STAEdcMR1ir5P9/xOmncx8xXfru0fbUYLlZog/cFg==",
"dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/node-config-provider": "^4.3.3",
+ "@smithy/types": "^4.8.0",
+ "@smithy/util-config-provider": "^4.2.0",
+ "@smithy/util-endpoints": "^3.2.3",
+ "@smithy/util-middleware": "^4.2.3",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/core": {
+ "version": "3.17.1",
+ "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.17.1.tgz",
+ "integrity": "sha512-V4Qc2CIb5McABYfaGiIYLTmo/vwNIK7WXI5aGveBd9UcdhbOMwcvIMxIw/DJj1S9QgOMa/7FBkarMdIC0EOTEQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/middleware-serde": "^4.2.3",
+ "@smithy/protocol-http": "^5.3.3",
+ "@smithy/types": "^4.8.0",
+ "@smithy/util-base64": "^4.3.0",
+ "@smithy/util-body-length-browser": "^4.2.0",
+ "@smithy/util-middleware": "^4.2.3",
+ "@smithy/util-stream": "^4.5.4",
+ "@smithy/util-utf8": "^4.2.0",
+ "@smithy/uuid": "^1.1.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/credential-provider-imds": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.3.tgz",
+ "integrity": "sha512-hA1MQ/WAHly4SYltJKitEsIDVsNmXcQfYBRv2e+q04fnqtAX5qXaybxy/fhUeAMCnQIdAjaGDb04fMHQefWRhw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/node-config-provider": "^4.3.3",
+ "@smithy/property-provider": "^4.2.3",
+ "@smithy/types": "^4.8.0",
+ "@smithy/url-parser": "^4.2.3",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/fetch-http-handler": {
+ "version": "5.3.4",
+ "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.4.tgz",
+ "integrity": "sha512-bwigPylvivpRLCm+YK9I5wRIYjFESSVwl8JQ1vVx/XhCw0PtCi558NwTnT2DaVCl5pYlImGuQTSwMsZ+pIavRw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/protocol-http": "^5.3.3",
+ "@smithy/querystring-builder": "^4.2.3",
+ "@smithy/types": "^4.8.0",
+ "@smithy/util-base64": "^4.3.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/hash-node": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.3.tgz",
+ "integrity": "sha512-6+NOdZDbfuU6s1ISp3UOk5Rg953RJ2aBLNLLBEcamLjHAg1Po9Ha7QIB5ZWhdRUVuOUrT8BVFR+O2KIPmw027g==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/types": "^4.8.0",
+ "@smithy/util-buffer-from": "^4.2.0",
+ "@smithy/util-utf8": "^4.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/invalid-dependency": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.3.tgz",
+ "integrity": "sha512-Cc9W5DwDuebXEDMpOpl4iERo8I0KFjTnomK2RMdhhR87GwrSmUmwMxS4P5JdRf+LsjOdIqumcerwRgYMr/tZ9Q==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/types": "^4.8.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/is-array-buffer": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz",
+ "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/middleware-content-length": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.3.tgz",
+ "integrity": "sha512-/atXLsT88GwKtfp5Jr0Ks1CSa4+lB+IgRnkNrrYP0h1wL4swHNb0YONEvTceNKNdZGJsye+W2HH8W7olbcPUeA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/protocol-http": "^5.3.3",
+ "@smithy/types": "^4.8.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/middleware-endpoint": {
+ "version": "4.3.5",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.5.tgz",
+ "integrity": "sha512-SIzKVTvEudFWJbxAaq7f2GvP3jh2FHDpIFI6/VAf4FOWGFZy0vnYMPSRj8PGYI8Hjt29mvmwSRgKuO3bK4ixDw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/core": "^3.17.1",
+ "@smithy/middleware-serde": "^4.2.3",
+ "@smithy/node-config-provider": "^4.3.3",
+ "@smithy/shared-ini-file-loader": "^4.3.3",
+ "@smithy/types": "^4.8.0",
+ "@smithy/url-parser": "^4.2.3",
+ "@smithy/util-middleware": "^4.2.3",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/middleware-retry": {
+ "version": "4.4.5",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.5.tgz",
+ "integrity": "sha512-DCaXbQqcZ4tONMvvdz+zccDE21sLcbwWoNqzPLFlZaxt1lDtOE2tlVpRSwcTOJrjJSUThdgEYn7HrX5oLGlK9A==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/node-config-provider": "^4.3.3",
+ "@smithy/protocol-http": "^5.3.3",
+ "@smithy/service-error-classification": "^4.2.3",
+ "@smithy/smithy-client": "^4.9.1",
+ "@smithy/types": "^4.8.0",
+ "@smithy/util-middleware": "^4.2.3",
+ "@smithy/util-retry": "^4.2.3",
+ "@smithy/uuid": "^1.1.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/middleware-serde": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.3.tgz",
+ "integrity": "sha512-8g4NuUINpYccxiCXM5s1/V+uLtts8NcX4+sPEbvYQDZk4XoJfDpq5y2FQxfmUL89syoldpzNzA0R9nhzdtdKnQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/protocol-http": "^5.3.3",
+ "@smithy/types": "^4.8.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/middleware-stack": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.3.tgz",
+ "integrity": "sha512-iGuOJkH71faPNgOj/gWuEGS6xvQashpLwWB1HjHq1lNNiVfbiJLpZVbhddPuDbx9l4Cgl0vPLq5ltRfSaHfspA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/types": "^4.8.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/node-config-provider": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.3.tgz",
+ "integrity": "sha512-NzI1eBpBSViOav8NVy1fqOlSfkLgkUjUTlohUSgAEhHaFWA3XJiLditvavIP7OpvTjDp5u2LhtlBhkBlEisMwA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/property-provider": "^4.2.3",
+ "@smithy/shared-ini-file-loader": "^4.3.3",
+ "@smithy/types": "^4.8.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/node-http-handler": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.3.tgz",
+ "integrity": "sha512-MAwltrDB0lZB/H6/2M5PIsISSwdI5yIh6DaBB9r0Flo9nx3y0dzl/qTMJPd7tJvPdsx6Ks/cwVzheGNYzXyNbQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/abort-controller": "^4.2.3",
+ "@smithy/protocol-http": "^5.3.3",
+ "@smithy/querystring-builder": "^4.2.3",
+ "@smithy/types": "^4.8.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/property-provider": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.3.tgz",
+ "integrity": "sha512-+1EZ+Y+njiefCohjlhyOcy1UNYjT+1PwGFHCxA/gYctjg3DQWAU19WigOXAco/Ql8hZokNehpzLd0/+3uCreqQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/types": "^4.8.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/protocol-http": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.3.tgz",
+ "integrity": "sha512-Mn7f/1aN2/jecywDcRDvWWWJF4uwg/A0XjFMJtj72DsgHTByfjRltSqcT9NyE9RTdBSN6X1RSXrhn/YWQl8xlw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/types": "^4.8.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/querystring-builder": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.3.tgz",
+ "integrity": "sha512-LOVCGCmwMahYUM/P0YnU/AlDQFjcu+gWbFJooC417QRB/lDJlWSn8qmPSDp+s4YVAHOgtgbNG4sR+SxF/VOcJQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/types": "^4.8.0",
+ "@smithy/util-uri-escape": "^4.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/querystring-parser": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.3.tgz",
+ "integrity": "sha512-cYlSNHcTAX/wc1rpblli3aUlLMGgKZ/Oqn8hhjFASXMCXjIqeuQBei0cnq2JR8t4RtU9FpG6uyl6PxyArTiwKA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/types": "^4.8.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/service-error-classification": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.3.tgz",
+ "integrity": "sha512-NkxsAxFWwsPsQiwFG2MzJ/T7uIR6AQNh1SzcxSUnmmIqIQMlLRQDKhc17M7IYjiuBXhrQRjQTo3CxX+DobS93g==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/types": "^4.8.0"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/shared-ini-file-loader": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.3.3.tgz",
+ "integrity": "sha512-9f9Ixej0hFhroOK2TxZfUUDR13WVa8tQzhSzPDgXe5jGL3KmaM9s8XN7RQwqtEypI82q9KHnKS71CJ+q/1xLtQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/types": "^4.8.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/signature-v4": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.3.tgz",
+ "integrity": "sha512-CmSlUy+eEYbIEYN5N3vvQTRfqt0lJlQkaQUIf+oizu7BbDut0pozfDjBGecfcfWf7c62Yis4JIEgqQ/TCfodaA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/is-array-buffer": "^4.2.0",
+ "@smithy/protocol-http": "^5.3.3",
+ "@smithy/types": "^4.8.0",
+ "@smithy/util-hex-encoding": "^4.2.0",
+ "@smithy/util-middleware": "^4.2.3",
+ "@smithy/util-uri-escape": "^4.2.0",
+ "@smithy/util-utf8": "^4.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/smithy-client": {
+ "version": "4.9.1",
+ "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.9.1.tgz",
+ "integrity": "sha512-Ngb95ryR5A9xqvQFT5mAmYkCwbXvoLavLFwmi7zVg/IowFPCfiqRfkOKnbc/ZRL8ZKJ4f+Tp6kSu6wjDQb8L/g==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/core": "^3.17.1",
+ "@smithy/middleware-endpoint": "^4.3.5",
+ "@smithy/middleware-stack": "^4.2.3",
+ "@smithy/protocol-http": "^5.3.3",
+ "@smithy/types": "^4.8.0",
+ "@smithy/util-stream": "^4.5.4",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/types": {
+ "version": "4.8.0",
+ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.8.0.tgz",
+ "integrity": "sha512-QpELEHLO8SsQVtqP+MkEgCYTFW0pleGozfs3cZ183ZBj9z3VC1CX1/wtFMK64p+5bhtZo41SeLK1rBRtd25nHQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/url-parser": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.3.tgz",
+ "integrity": "sha512-I066AigYvY3d9VlU3zG9XzZg1yT10aNqvCaBTw9EPgu5GrsEl1aUkcMvhkIXascYH1A8W0LQo3B1Kr1cJNcQEw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/querystring-parser": "^4.2.3",
+ "@smithy/types": "^4.8.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/util-base64": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz",
+ "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/util-buffer-from": "^4.2.0",
+ "@smithy/util-utf8": "^4.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/util-body-length-browser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz",
+ "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/util-body-length-node": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz",
+ "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/util-buffer-from": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz",
+ "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/is-array-buffer": "^4.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/util-config-provider": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz",
+ "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/util-defaults-mode-browser": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.4.tgz",
+ "integrity": "sha512-qI5PJSW52rnutos8Bln8nwQZRpyoSRN6k2ajyoUHNMUzmWqHnOJCnDELJuV6m5PML0VkHI+XcXzdB+6awiqYUw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/property-provider": "^4.2.3",
+ "@smithy/smithy-client": "^4.9.1",
+ "@smithy/types": "^4.8.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/util-defaults-mode-node": {
+ "version": "4.2.6",
+ "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.6.tgz",
+ "integrity": "sha512-c6M/ceBTm31YdcFpgfgQAJaw3KbaLuRKnAz91iMWFLSrgxRpYm03c3bu5cpYojNMfkV9arCUelelKA7XQT36SQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/config-resolver": "^4.4.0",
+ "@smithy/credential-provider-imds": "^4.2.3",
+ "@smithy/node-config-provider": "^4.3.3",
+ "@smithy/property-provider": "^4.2.3",
+ "@smithy/smithy-client": "^4.9.1",
+ "@smithy/types": "^4.8.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/util-endpoints": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.3.tgz",
+ "integrity": "sha512-aCfxUOVv0CzBIkU10TubdgKSx5uRvzH064kaiPEWfNIvKOtNpu642P4FP1hgOFkjQIkDObrfIDnKMKkeyrejvQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/node-config-provider": "^4.3.3",
+ "@smithy/types": "^4.8.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/util-hex-encoding": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz",
+ "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/util-middleware": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.3.tgz",
+ "integrity": "sha512-v5ObKlSe8PWUHCqEiX2fy1gNv6goiw6E5I/PN2aXg3Fb/hse0xeaAnSpXDiWl7x6LamVKq7senB+m5LOYHUAHw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/types": "^4.8.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/util-retry": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.3.tgz",
+ "integrity": "sha512-lLPWnakjC0q9z+OtiXk+9RPQiYPNAovt2IXD3CP4LkOnd9NpUsxOjMx1SnoUVB7Orb7fZp67cQMtTBKMFDvOGg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/service-error-classification": "^4.2.3",
+ "@smithy/types": "^4.8.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/util-stream": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.4.tgz",
+ "integrity": "sha512-+qDxSkiErejw1BAIXUFBSfM5xh3arbz1MmxlbMCKanDDZtVEQ7PSKW9FQS0Vud1eI/kYn0oCTVKyNzRlq+9MUw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/fetch-http-handler": "^5.3.4",
+ "@smithy/node-http-handler": "^4.4.3",
+ "@smithy/types": "^4.8.0",
+ "@smithy/util-base64": "^4.3.0",
+ "@smithy/util-buffer-from": "^4.2.0",
+ "@smithy/util-hex-encoding": "^4.2.0",
+ "@smithy/util-utf8": "^4.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/util-uri-escape": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz",
+ "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/util-utf8": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz",
+ "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/util-buffer-from": "^4.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/uuid": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz",
+ "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@so-ric/colorspace": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz",
+ "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==",
+ "license": "MIT",
+ "dependencies": {
+ "color": "^5.0.2",
+ "text-hex": "1.0.x"
+ }
+ },
+ "node_modules/@so-ric/colorspace/node_modules/color": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/color/-/color-5.0.2.tgz",
+ "integrity": "sha512-e2hz5BzbUPcYlIRHo8ieAhYgoajrJr+hWoceg6E345TPsATMUKqDgzt8fSXZJJbxfpiPzkWyphz8yn8At7q3fA==",
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^3.0.1",
+ "color-string": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@so-ric/colorspace/node_modules/color-convert": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.2.tgz",
+ "integrity": "sha512-UNqkvCDXstVck3kdowtOTWROIJQwafjOfXSmddoDrXo4cewMKmusCeF22Q24zvjR8nwWib/3S/dfyzPItPEiJg==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.6"
+ }
+ },
+ "node_modules/@so-ric/colorspace/node_modules/color-name": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz",
+ "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==",
"license": "MIT",
"engines": {
- "node": ">=4"
+ "node": ">=12.20"
}
},
- "node_modules/@sinonjs/text-encoding": {
- "version": "0.7.3",
- "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz",
- "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==",
- "dev": true,
- "license": "(Unlicense OR Apache-2.0)"
+ "node_modules/@so-ric/colorspace/node_modules/color-string": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.2.tgz",
+ "integrity": "sha512-RxmjYxbWemV9gKu4zPgiZagUxbH3RQpEIO77XoSSX0ivgABDZ+h8Zuash/EMFLTI4N9QgFPOJ6JQpPZKFxa+dA==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
},
"node_modules/@szmarczak/http-timer": {
"version": "5.0.1",
@@ -1229,11 +4228,169 @@
"integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==",
"license": "ISC",
"optional": true,
- "peer": true,
"engines": {
"node": ">=10.13.0"
}
},
+ "node_modules/@tsconfig/node10": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz",
+ "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tsconfig/node12": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
+ "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tsconfig/node14": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
+ "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tsconfig/node16": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
+ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.10.1",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
+ "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/body-parser": {
+ "version": "1.19.6",
+ "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
+ "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/connect": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/compression": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.8.1.tgz",
+ "integrity": "sha512-kCFuWS0ebDbmxs0AXYn6e2r2nrGAb5KwQhknjSPSPgJcGd8+HVSILlUyFhGqML2gk39HcG7D1ydW9/qpYkN00Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/express": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/connect": {
+ "version": "3.4.38",
+ "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
+ "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/cookie-parser": {
+ "version": "1.4.10",
+ "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.10.tgz",
+ "integrity": "sha512-B4xqkqfZ8Wek+rCOeRxsjMS9OgvzebEzzLYw7NHYuvzb7IdxOkI0ZHGgeEBX4PUM7QGVvNSK60T3OvWj3YfBRg==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/express": "*"
+ }
+ },
+ "node_modules/@types/cors": {
+ "version": "2.8.19",
+ "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz",
+ "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/docker-modem": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/docker-modem/-/docker-modem-3.0.6.tgz",
+ "integrity": "sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@types/ssh2": "*"
+ }
+ },
+ "node_modules/@types/dockerode": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-4.0.0.tgz",
+ "integrity": "sha512-NBm/UIL1KYWivh9ihJELK4Jv0dzOQCleS0OT5JklErra8mZ/t0UQpHfTkMmPrAqtra7OyFius1EFLVs0DhiHQA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/docker-modem": "*",
+ "@types/node": "*",
+ "@types/ssh2": "*"
+ }
+ },
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -1241,12 +4398,52 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/express": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.3.tgz",
+ "integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/body-parser": "*",
+ "@types/express-serve-static-core": "^5.0.0",
+ "@types/serve-static": "*"
+ }
+ },
+ "node_modules/@types/express-serve-static-core": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz",
+ "integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@types/qs": "*",
+ "@types/range-parser": "*",
+ "@types/send": "*"
+ }
+ },
+ "node_modules/@types/gamedig": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/@types/gamedig/-/gamedig-5.0.3.tgz",
+ "integrity": "sha512-VLrOVRf72wVE6P0DhFtRVodiONwL51uojn6dNpP/Ks/Vdm3CDH6dZTAw74WmCDLuyvpytNy8auKzlTKfBlO8jg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/http-cache-semantics": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz",
"integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==",
"license": "MIT"
},
+ "node_modules/@types/http-errors": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
+ "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
@@ -1254,6 +4451,56 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/istanbul-lib-report": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz",
+ "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/istanbul-lib-coverage": "*"
+ }
+ },
+ "node_modules/@types/istanbul-reports": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz",
+ "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/istanbul-lib-report": "*"
+ }
+ },
+ "node_modules/@types/jest": {
+ "version": "30.0.0",
+ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz",
+ "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "expect": "^30.0.0",
+ "pretty-format": "^30.0.0"
+ }
+ },
+ "node_modules/@types/jmespath": {
+ "version": "0.15.2",
+ "resolved": "https://registry.npmjs.org/@types/jmespath/-/jmespath-0.15.2.tgz",
+ "integrity": "sha512-pegh49FtNsC389Flyo9y8AfkVIZn9MMPE9yJrO9svhq6Fks2MwymULWjZqySuxmctd3ZH4/n7Mr98D+1Qo5vGA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/jsdom": {
+ "version": "27.0.0",
+ "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-27.0.0.tgz",
+ "integrity": "sha512-NZyFl/PViwKzdEkQg96gtnB8wm+1ljhdDay9ahn4hgb+SfVtPCbm3TlmDUFXTA+MGN3CijicnMhG18SI5H3rFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@types/tough-cookie": "*",
+ "parse5": "^7.0.0"
+ }
+ },
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -1261,21 +4508,201 @@
"dev": true,
"license": "MIT"
},
- "node_modules/@types/node": {
- "version": "24.3.0",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz",
- "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==",
+ "node_modules/@types/jsonwebtoken": {
+ "version": "9.0.10",
+ "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz",
+ "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "undici-types": "~7.10.0"
+ "@types/ms": "*",
+ "@types/node": "*"
}
},
+ "node_modules/@types/mime": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
+ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/mjml": {
+ "version": "4.7.4",
+ "resolved": "https://registry.npmjs.org/@types/mjml/-/mjml-4.7.4.tgz",
+ "integrity": "sha512-vyi1vzWgMzFMwZY7GSZYX0GU0dmtC8vLHwpgk+NWmwbwRSrlieVyJ9sn5elodwUfklJM7yGl0zQeet1brKTWaQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/mjml-core": "*"
+ }
+ },
+ "node_modules/@types/mjml-core": {
+ "version": "4.15.2",
+ "resolved": "https://registry.npmjs.org/@types/mjml-core/-/mjml-core-4.15.2.tgz",
+ "integrity": "sha512-Q7SxFXgoX979HP57DEVsRI50TV8x1V4lfCA4Up9AvfINDM5oD/X9ARgfoyX1qS987JCnDLv85JjkqAjt3hZSiQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/ms": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
+ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/multer": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@types/multer/-/multer-2.0.0.tgz",
+ "integrity": "sha512-C3Z9v9Evij2yST3RSBktxP9STm6OdMc5uR1xF1SGr98uv8dUlAL2hqwrZ3GVB3uyMyiegnscEK6PGtYvNrjTjw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/express": "*"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "24.9.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz",
+ "integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "undici-types": "~7.16.0"
+ }
+ },
+ "node_modules/@types/nodemailer": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-7.0.1.tgz",
+ "integrity": "sha512-UfHAghPmGZVzaL8x9y+mKZMWyHC399+iq0MOmya5tIyenWX3lcdSb60vOmp0DocR6gCDTYTozv/ULQnREyyjkg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@aws-sdk/client-sesv2": "^3.839.0",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/papaparse": {
+ "version": "5.5.2",
+ "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.5.2.tgz",
+ "integrity": "sha512-gFnFp/JMzLHCwRf7tQHrNnfhN4eYBVYYI897CGX4MY1tzY9l2aLkVyx2IlKZ/SAqDbB3I1AOZW5gTMGGsqWliA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/ping": {
+ "version": "0.4.4",
+ "resolved": "https://registry.npmjs.org/@types/ping/-/ping-0.4.4.tgz",
+ "integrity": "sha512-ifvo6w2f5eJYlXm+HiVx67iJe8WZp87sfa683nlqED5Vnt9Z93onkokNoWqOG21EaE8fMxyKPobE+mkPEyxsdw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/qs": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz",
+ "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/range-parser": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
+ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/relateurl": {
"version": "0.2.33",
"resolved": "https://registry.npmjs.org/@types/relateurl/-/relateurl-0.2.33.tgz",
"integrity": "sha512-bTQCKsVbIdzLqZhLkF5fcJQreE4y1ro4DIyVrlDNSCJRRwHhB8Z+4zXXa8jN6eDvc2HbRsEYgbvrnGvi54EpSw==",
"license": "MIT"
},
+ "node_modules/@types/send": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/serve-static": {
+ "version": "1.15.10",
+ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz",
+ "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/http-errors": "*",
+ "@types/node": "*",
+ "@types/send": "<1"
+ }
+ },
+ "node_modules/@types/serve-static/node_modules/@types/send": {
+ "version": "0.17.6",
+ "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz",
+ "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/mime": "^1",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/ssh2": {
+ "version": "1.15.5",
+ "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.15.5.tgz",
+ "integrity": "sha512-N1ASjp/nXH3ovBHddRJpli4ozpk6UdDYIX4RJWFa9L1YKnzdhTlVmiGHm4DZnj/jLbqZpes4aeR30EFGQtvhQQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "^18.11.18"
+ }
+ },
+ "node_modules/@types/ssh2/node_modules/@types/node": {
+ "version": "18.19.130",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz",
+ "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~5.26.4"
+ }
+ },
+ "node_modules/@types/ssh2/node_modules/undici-types": {
+ "version": "5.26.5",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
+ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/stack-utils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
+ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/swagger-ui-express": {
+ "version": "4.1.8",
+ "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.8.tgz",
+ "integrity": "sha512-AhZV8/EIreHFmBV5wAs0gzJUNq9JbbSXgJLQubCC0jtIo6prnI9MIRRxnU4MZX9RB9yXxF1V4R7jtLl/Wcj31g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/express": "*",
+ "@types/serve-static": "*"
+ }
+ },
+ "node_modules/@types/tough-cookie": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
+ "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/triple-beam": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz",
@@ -1304,6 +4731,299 @@
"@types/webidl-conversions": "*"
}
},
+ "node_modules/@types/yargs": {
+ "version": "17.0.35",
+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz",
+ "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/yargs-parser": "*"
+ }
+ },
+ "node_modules/@types/yargs-parser": {
+ "version": "21.0.3",
+ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
+ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/@unrs/resolver-binding-android-arm-eabi": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz",
+ "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-android-arm64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz",
+ "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-darwin-arm64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz",
+ "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-darwin-x64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz",
+ "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-freebsd-x64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz",
+ "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz",
+ "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz",
+ "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz",
+ "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm64-musl": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz",
+ "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz",
+ "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz",
+ "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-riscv64-musl": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz",
+ "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-s390x-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz",
+ "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-x64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz",
+ "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-x64-musl": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz",
+ "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-wasm32-wasi": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz",
+ "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@napi-rs/wasm-runtime": "^0.2.11"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@unrs/resolver-binding-win32-arm64-msvc": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz",
+ "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-win32-ia32-msvc": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz",
+ "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-win32-x64-msvc": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz",
+ "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@@ -1332,6 +5052,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -1349,6 +5070,19 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
+ "node_modules/acorn-walk": {
+ "version": "8.3.4",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
+ "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.11.0"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/agent-base": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
@@ -1384,10 +5118,39 @@
"node": ">=6"
}
},
+ "node_modules/ansi-escapes": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
+ "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "type-fest": "^0.21.3"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ansi-escapes/node_modules/type-fest": {
+ "version": "0.21.3",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
+ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/ansi-regex": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz",
- "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==",
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
"license": "MIT",
"engines": {
"node": ">=12"
@@ -1436,6 +5199,13 @@
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
"license": "MIT"
},
+ "node_modules/arg": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -1448,6 +5218,16 @@
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"license": "MIT"
},
+ "node_modules/array-union": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/asn1": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
@@ -1457,16 +5237,6 @@
"safer-buffer": "~2.1.0"
}
},
- "node_modules/assertion-error": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
- "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- }
- },
"node_modules/async": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
@@ -1480,9 +5250,9 @@
"license": "MIT"
},
"node_modules/axios": {
- "version": "1.11.0",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz",
- "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.0.tgz",
+ "integrity": "sha512-zt40Pz4zcRXra9CVV31KeyofwiNvAbJ5B6YPz9pMJ+yOSLikvPT4Yi5LjfgjRa9CawVYBaD1JQzIVcIvBejKeA==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
@@ -1490,6 +5260,142 @@
"proxy-from-env": "^1.1.0"
}
},
+ "node_modules/babel-jest": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz",
+ "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/transform": "30.2.0",
+ "@types/babel__core": "^7.20.5",
+ "babel-plugin-istanbul": "^7.0.1",
+ "babel-preset-jest": "30.2.0",
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.11.0 || ^8.0.0-0"
+ }
+ },
+ "node_modules/babel-plugin-istanbul": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz",
+ "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "workspaces": [
+ "test/babel-8"
+ ],
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@istanbuljs/load-nyc-config": "^1.0.0",
+ "@istanbuljs/schema": "^0.1.3",
+ "istanbul-lib-instrument": "^6.0.2",
+ "test-exclude": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/babel-plugin-istanbul/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/babel-plugin-istanbul/node_modules/test-exclude": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
+ "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@istanbuljs/schema": "^0.1.2",
+ "glob": "^7.1.4",
+ "minimatch": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/babel-plugin-jest-hoist": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz",
+ "integrity": "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/babel__core": "^7.20.5"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/babel-preset-current-node-syntax": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz",
+ "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/plugin-syntax-async-generators": "^7.8.4",
+ "@babel/plugin-syntax-bigint": "^7.8.3",
+ "@babel/plugin-syntax-class-properties": "^7.12.13",
+ "@babel/plugin-syntax-class-static-block": "^7.14.5",
+ "@babel/plugin-syntax-import-attributes": "^7.24.7",
+ "@babel/plugin-syntax-import-meta": "^7.10.4",
+ "@babel/plugin-syntax-json-strings": "^7.8.3",
+ "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
+ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
+ "@babel/plugin-syntax-numeric-separator": "^7.10.4",
+ "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
+ "@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
+ "@babel/plugin-syntax-optional-chaining": "^7.8.3",
+ "@babel/plugin-syntax-private-property-in-object": "^7.14.5",
+ "@babel/plugin-syntax-top-level-await": "^7.14.5"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0 || ^8.0.0-0"
+ }
+ },
+ "node_modules/babel-preset-jest": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz",
+ "integrity": "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "babel-plugin-jest-hoist": "30.2.0",
+ "babel-preset-current-node-syntax": "^1.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.11.0 || ^8.0.0-beta.1"
+ }
+ },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -1543,6 +5449,15 @@
],
"license": "MIT"
},
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.8.20",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.20.tgz",
+ "integrity": "sha512-JMWsdF+O8Orq3EMukbUN1QfbLK9mX2CkUmQBcW2T0s8OmdAUL5LLM/6wFwSrqXzlXB13yhyK9gTKS1rIizOduQ==",
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.js"
+ }
+ },
"node_modules/bcrypt-pbkdf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
@@ -1561,6 +5476,15 @@
"bcrypt": "bin/bcrypt"
}
},
+ "node_modules/bidi-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
+ "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
+ "license": "MIT",
+ "dependencies": {
+ "require-from-string": "^2.0.2"
+ }
+ },
"node_modules/binary-extensions": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@@ -1585,23 +5509,23 @@
}
},
"node_modules/body-parser": {
- "version": "1.20.3",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
- "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
+ "version": "1.20.4",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
+ "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
"license": "MIT",
"dependencies": {
- "bytes": "3.1.2",
+ "bytes": "~3.1.2",
"content-type": "~1.0.5",
"debug": "2.6.9",
"depd": "2.0.0",
- "destroy": "1.2.0",
- "http-errors": "2.0.0",
- "iconv-lite": "0.4.24",
- "on-finished": "2.4.1",
- "qs": "6.13.0",
- "raw-body": "2.5.2",
+ "destroy": "~1.2.0",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.4.24",
+ "on-finished": "~2.4.1",
+ "qs": "~6.14.0",
+ "raw-body": "~2.5.3",
"type-is": "~1.6.18",
- "unpipe": "1.0.0"
+ "unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.8",
@@ -1617,18 +5541,54 @@
"ms": "2.0.0"
}
},
+ "node_modules/body-parser/node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/body-parser/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
+ "node_modules/body-parser/node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
"license": "ISC"
},
+ "node_modules/bowser": {
+ "version": "2.12.1",
+ "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.12.1.tgz",
+ "integrity": "sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
@@ -1652,17 +5612,10 @@
"node": ">=8"
}
},
- "node_modules/browser-stdout": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
- "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
- "dev": true,
- "license": "ISC"
- },
"node_modules/browserslist": {
- "version": "4.25.4",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.4.tgz",
- "integrity": "sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg==",
+ "version": "4.27.0",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz",
+ "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==",
"funding": [
{
"type": "opencollective",
@@ -1678,11 +5631,13 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
- "caniuse-lite": "^1.0.30001737",
- "electron-to-chromium": "^1.5.211",
- "node-releases": "^2.0.19",
- "update-browserslist-db": "^1.1.3"
+ "baseline-browser-mapping": "^2.8.19",
+ "caniuse-lite": "^1.0.30001751",
+ "electron-to-chromium": "^1.5.238",
+ "node-releases": "^2.0.26",
+ "update-browserslist-db": "^1.1.4"
},
"bin": {
"browserslist": "cli.js"
@@ -1691,6 +5646,29 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
+ "node_modules/bs-logger": {
+ "version": "0.2.6",
+ "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz",
+ "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-json-stable-stringify": "2.x"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/bser": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
+ "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "node-int64": "^0.4.0"
+ }
+ },
"node_modules/bson": {
"version": "6.10.4",
"resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz",
@@ -1880,16 +5858,13 @@
}
},
"node_modules/camelcase": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
- "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "node": ">=6"
}
},
"node_modules/caniuse-api": {
@@ -1905,9 +5880,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001739",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001739.tgz",
- "integrity": "sha512-y+j60d6ulelrNSwpPyrHdl+9mJnQzHBr08xm48Qno0nSk4h3Qojh+ziv2qE6rXf4k3tadF4o1J/1tAbVm1NtnA==",
+ "version": "1.0.30001751",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001751.tgz",
+ "integrity": "sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==",
"funding": [
{
"type": "opencollective",
@@ -1924,23 +5899,6 @@
],
"license": "CC-BY-4.0"
},
- "node_modules/chai": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz",
- "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "assertion-error": "^2.0.1",
- "check-error": "^2.1.1",
- "deep-eql": "^5.0.1",
- "loupe": "^3.1.0",
- "pathval": "^2.0.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -1958,14 +5916,14 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
- "node_modules/check-error": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",
- "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==",
+ "node_modules/char-regex": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
+ "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">= 16"
+ "node": ">=10"
}
},
"node_modules/cheerio": {
@@ -2048,6 +6006,29 @@
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
"license": "ISC"
},
+ "node_modules/ci-info": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz",
+ "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/sibiraj-s"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cjs-module-lexer": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz",
+ "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
@@ -2129,6 +6110,24 @@
"node": ">=0.10.0"
}
},
+ "node_modules/co": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+ "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">= 1.0.0",
+ "node": ">= 0.12.0"
+ }
+ },
+ "node_modules/collect-v8-coverage": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz",
+ "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
@@ -2176,41 +6175,6 @@
"integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==",
"license": "MIT"
},
- "node_modules/colorspace": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz",
- "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==",
- "license": "MIT",
- "dependencies": {
- "color": "^3.1.3",
- "text-hex": "1.0.x"
- }
- },
- "node_modules/colorspace/node_modules/color": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz",
- "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==",
- "license": "MIT",
- "dependencies": {
- "color-convert": "^1.9.3",
- "color-string": "^1.6.0"
- }
- },
- "node_modules/colorspace/node_modules/color-convert": {
- "version": "1.9.3",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
- "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
- "license": "MIT",
- "dependencies": {
- "color-name": "1.1.3"
- }
- },
- "node_modules/colorspace/node_modules/color-name": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
- "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
- "license": "MIT"
- },
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -2450,6 +6414,13 @@
"node": ">=10.0.0"
}
},
+ "node_modules/create-require": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/cron-parser": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz",
@@ -2477,9 +6448,9 @@
}
},
"node_modules/css-declaration-sorter": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz",
- "integrity": "sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==",
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.3.0.tgz",
+ "integrity": "sha512-LQF6N/3vkAMYF4xoHLJfG718HRJh34Z8BnNhd6bosOMIVjMlhuZK5++oZa3uYAgrI5+7x2o27gUqTR2U/KjUOQ==",
"license": "ISC",
"engines": {
"node": "^14 || ^16 || >=18"
@@ -2505,14 +6476,12 @@
}
},
"node_modules/css-tree": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
- "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz",
+ "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==",
"license": "MIT",
- "optional": true,
- "peer": true,
"dependencies": {
- "mdn-data": "2.0.30",
+ "mdn-data": "2.12.2",
"source-map-js": "^1.0.1"
},
"engines": {
@@ -2548,6 +6517,7 @@
"resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.1.1.tgz",
"integrity": "sha512-fm4D8ti0dQmFPeF8DXSAA//btEmqCOgAc/9Oa3C1LW94h5usNrJEfrON7b4FkPZgnDEn6OUs5NdxiJZmAtGOpQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"cssnano-preset-default": "^7.0.9",
"lilconfig": "^3.1.3"
@@ -2728,19 +6698,6 @@
}
}
},
- "node_modules/decamelize": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz",
- "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/decimal.js": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
@@ -2774,14 +6731,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/deep-eql": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
- "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
+ "node_modules/dedent": {
+ "version": "1.7.1",
+ "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz",
+ "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==",
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=6"
+ "peerDependencies": {
+ "babel-plugin-macros": "^3.1.0"
+ },
+ "peerDependenciesMeta": {
+ "babel-plugin-macros": {
+ "optional": true
+ }
}
},
"node_modules/deep-is": {
@@ -2791,6 +6753,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/deepmerge": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/defer-to-connect": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
@@ -2838,14 +6810,24 @@
}
},
"node_modules/detect-libc": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
- "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"license": "Apache-2.0",
"engines": {
"node": ">=8"
}
},
+ "node_modules/detect-newline": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
+ "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/detect-node": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
@@ -2853,15 +6835,28 @@
"license": "MIT"
},
"node_modules/diff": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
- "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==",
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.3.1"
}
},
+ "node_modules/dir-glob": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+ "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/docker-modem": {
"version": "5.0.6",
"resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-5.0.6.tgz",
@@ -2950,9 +6945,9 @@
}
},
"node_modules/dompurify": {
- "version": "3.2.6",
- "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz",
- "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==",
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.0.tgz",
+ "integrity": "sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
@@ -3020,9 +7015,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
- "version": "1.5.213",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.213.tgz",
- "integrity": "sha512-xr9eRzSLNa4neDO0xVFrkXu3vyIzG4Ay08dApecw42Z1NbmCt+keEpXdvlYGVe0wtvY5dhW0Ay0lY0IOfsCg0Q==",
+ "version": "1.5.240",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.240.tgz",
+ "integrity": "sha512-OBwbZjWgrCOH+g6uJsA2/7Twpas2OlepS9uvByJjR2datRDuKGYeD+nP8lBBks2qnB7bGJNHDUx7c/YLaT3QMQ==",
"license": "ISC"
},
"node_modules/emitter-component": {
@@ -3033,6 +7028,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/emittery": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz",
+ "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/emittery?sponsor=1"
+ }
+ },
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
@@ -3110,9 +7118,9 @@
}
},
"node_modules/error-ex": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
- "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
+ "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
"license": "MIT",
"dependencies": {
"is-arrayish": "^0.2.1"
@@ -3163,6 +7171,48 @@
"node": ">= 0.4"
}
},
+ "node_modules/esbuild": {
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz",
+ "integrity": "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.11",
+ "@esbuild/android-arm": "0.25.11",
+ "@esbuild/android-arm64": "0.25.11",
+ "@esbuild/android-x64": "0.25.11",
+ "@esbuild/darwin-arm64": "0.25.11",
+ "@esbuild/darwin-x64": "0.25.11",
+ "@esbuild/freebsd-arm64": "0.25.11",
+ "@esbuild/freebsd-x64": "0.25.11",
+ "@esbuild/linux-arm": "0.25.11",
+ "@esbuild/linux-arm64": "0.25.11",
+ "@esbuild/linux-ia32": "0.25.11",
+ "@esbuild/linux-loong64": "0.25.11",
+ "@esbuild/linux-mips64el": "0.25.11",
+ "@esbuild/linux-ppc64": "0.25.11",
+ "@esbuild/linux-riscv64": "0.25.11",
+ "@esbuild/linux-s390x": "0.25.11",
+ "@esbuild/linux-x64": "0.25.11",
+ "@esbuild/netbsd-arm64": "0.25.11",
+ "@esbuild/netbsd-x64": "0.25.11",
+ "@esbuild/openbsd-arm64": "0.25.11",
+ "@esbuild/openbsd-x64": "0.25.11",
+ "@esbuild/openharmony-arm64": "0.25.11",
+ "@esbuild/sunos-x64": "0.25.11",
+ "@esbuild/win32-arm64": "0.25.11",
+ "@esbuild/win32-ia32": "0.25.11",
+ "@esbuild/win32-x64": "0.25.11"
+ }
+ },
"node_modules/escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
@@ -3204,25 +7254,25 @@
}
},
"node_modules/eslint": {
- "version": "9.34.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.34.0.tgz",
- "integrity": "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==",
+ "version": "9.38.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.38.0.tgz",
+ "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
- "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
- "@eslint/config-array": "^0.21.0",
- "@eslint/config-helpers": "^0.3.1",
- "@eslint/core": "^0.15.2",
+ "@eslint/config-array": "^0.21.1",
+ "@eslint/config-helpers": "^0.4.1",
+ "@eslint/core": "^0.16.0",
"@eslint/eslintrc": "^3.3.1",
- "@eslint/js": "9.34.0",
- "@eslint/plugin-kit": "^0.3.5",
+ "@eslint/js": "9.38.0",
+ "@eslint/plugin-kit": "^0.4.0",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2",
"@types/estree": "^1.0.6",
- "@types/json-schema": "^7.0.15",
"ajv": "^6.12.4",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.6",
@@ -3398,6 +7448,20 @@
"url": "https://opencollective.com/eslint"
}
},
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/esquery": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
@@ -3460,40 +7524,126 @@
"deprecated": "Use promise-toolbox/fromEvent instead",
"license": "MIT"
},
- "node_modules/express": {
- "version": "4.21.2",
- "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
- "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
+ "node_modules/execa": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+ "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+ "dev": true,
"license": "MIT",
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^6.0.0",
+ "human-signals": "^2.1.0",
+ "is-stream": "^2.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^4.0.1",
+ "onetime": "^5.1.2",
+ "signal-exit": "^3.0.3",
+ "strip-final-newline": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/execa/node_modules/get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/execa/node_modules/is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/execa/node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/exit-x": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz",
+ "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/expect": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz",
+ "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/expect-utils": "30.2.0",
+ "@jest/get-type": "30.1.0",
+ "jest-matcher-utils": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-mock": "30.2.0",
+ "jest-util": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/express": {
+ "version": "4.22.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
+ "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
+ "license": "MIT",
+ "peer": true,
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
- "body-parser": "1.20.3",
- "content-disposition": "0.5.4",
+ "body-parser": "~1.20.3",
+ "content-disposition": "~0.5.4",
"content-type": "~1.0.4",
- "cookie": "0.7.1",
- "cookie-signature": "1.0.6",
+ "cookie": "~0.7.1",
+ "cookie-signature": "~1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
- "finalhandler": "1.3.1",
- "fresh": "0.5.2",
- "http-errors": "2.0.0",
+ "finalhandler": "~1.3.1",
+ "fresh": "~0.5.2",
+ "http-errors": "~2.0.0",
"merge-descriptors": "1.0.3",
"methods": "~1.1.2",
- "on-finished": "2.4.1",
+ "on-finished": "~2.4.1",
"parseurl": "~1.3.3",
- "path-to-regexp": "0.1.12",
+ "path-to-regexp": "~0.1.12",
"proxy-addr": "~2.0.7",
- "qs": "6.13.0",
+ "qs": "~6.14.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
- "send": "0.19.0",
- "serve-static": "1.16.2",
+ "send": "~0.19.0",
+ "serve-static": "~1.16.2",
"setprototypeof": "1.2.0",
- "statuses": "2.0.1",
+ "statuses": "~2.0.1",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
@@ -3524,15 +7674,6 @@
"express": ">= 4.11"
}
},
- "node_modules/express/node_modules/cookie": {
- "version": "0.7.1",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
- "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
"node_modules/express/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -3561,6 +7702,36 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
@@ -3576,9 +7747,9 @@
"license": "MIT"
},
"node_modules/fast-xml-parser": {
- "version": "5.2.3",
- "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.3.tgz",
- "integrity": "sha512-OdCYfRqfpuLUFonTNjvd30rCBZUneHpSQkCqfaeWQ9qrKcl6XlWeDBNVwGb+INAIxRshuN2jF+BE0L6gbBO2mw==",
+ "version": "5.2.5",
+ "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz",
+ "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==",
"funding": [
{
"type": "github",
@@ -3593,6 +7764,26 @@
"fxparser": "src/cli/cli.js"
}
},
+ "node_modules/fastq": {
+ "version": "1.20.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
+ "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/fb-watchman": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
+ "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bser": "2.1.1"
+ }
+ },
"node_modules/fecha": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
@@ -3697,16 +7888,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/flat": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
- "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
- "dev": true,
- "license": "BSD-3-Clause",
- "bin": {
- "flat": "cli.js"
- }
- },
"node_modules/flat-cache": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
@@ -3831,6 +8012,13 @@
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
"license": "MIT"
},
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -3855,19 +8043,19 @@
}
},
"node_modules/gamedig": {
- "version": "5.3.1",
- "resolved": "https://registry.npmjs.org/gamedig/-/gamedig-5.3.1.tgz",
- "integrity": "sha512-RM/eCR8bAKEX+5dA9sxg4D6oQjS5t0MPsIxGquerBLSIu0f1hAVaDGC58Lp1srYwF6A1C2wYY2p9PzOrPTKf4Q==",
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/gamedig/-/gamedig-5.3.2.tgz",
+ "integrity": "sha512-R2b1LwjW783PZsHRl9M8R06UkvJwXmJ6PDKk48UukJQ9ktiwrCeAk90MAZx6nF3oA444uf7r5eHjfaYbNoSV+Q==",
"license": "MIT",
"dependencies": {
- "fast-xml-parser": "5.2.3",
+ "fast-xml-parser": "5.2.5",
"gbxremote": "0.2.1",
"got": "13.0.0",
- "iconv-lite": "0.6.3",
+ "iconv-lite": "0.7.0",
"long": "5.3.2",
"minimist": "1.2.8",
"seek-bzip": "2.0.0",
- "telnet-client": "2.2.5",
+ "telnet-client": "2.2.6",
"varint": "6.0.0"
},
"bin": {
@@ -3954,15 +8142,19 @@
}
},
"node_modules/gamedig/node_modules/iconv-lite": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
- "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz",
+ "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
"node_modules/gamedig/node_modules/p-cancelable": {
@@ -4017,6 +8209,16 @@
"node": ">=0.10"
}
},
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@@ -4050,6 +8252,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/get-package-type": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
+ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
@@ -4079,10 +8291,23 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/get-tsconfig": {
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz",
+ "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve-pkg-maps": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+ }
+ },
"node_modules/glob": {
- "version": "10.4.5",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
- "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
@@ -4149,6 +8374,27 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/globby": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
+ "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-union": "^2.1.0",
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.2.9",
+ "ignore": "^5.2.0",
+ "merge2": "^1.4.1",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@@ -4186,6 +8432,13 @@
"url": "https://github.com/sindresorhus/got?sponsor=1"
}
},
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/handlebars": {
"version": "4.7.8",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
@@ -4256,16 +8509,6 @@
"node": ">= 0.4"
}
},
- "node_modules/he": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
- "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "he": "bin/he"
- }
- },
"node_modules/helmet": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz",
@@ -4295,9 +8538,9 @@
"license": "MIT"
},
"node_modules/htmlnano": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-2.1.3.tgz",
- "integrity": "sha512-mzTUHhxdfDw80X36rv5qFvjvor7r0uCwXI5mzo8CW61tImbe5Jpvl3JzPAetVh54wUYVuoa8x3qw8LFn8B3gHQ==",
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-2.1.5.tgz",
+ "integrity": "sha512-IXffzXq1beGQN2rsr03aIPK/rVU1jR2uwHymlAIEf97Tl5WdpG50IsQ5nWGvSGQJ+x6U7S6yac9rRiFgAg4/xQ==",
"license": "MIT",
"dependencies": {
"@types/relateurl": "^0.2.33",
@@ -4430,6 +8673,16 @@
"numbered": "^1.1.0"
}
},
+ "node_modules/human-signals": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
+ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=10.17.0"
+ }
+ },
"node_modules/iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -4495,6 +8748,26 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/import-local": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
+ "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pkg-dir": "^4.2.0",
+ "resolve-cwd": "^3.0.0"
+ },
+ "bin": {
+ "import-local-fixture": "fixtures/cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
@@ -4505,6 +8778,18 @@
"node": ">=0.8.19"
}
},
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
@@ -4512,12 +8797,12 @@
"license": "ISC"
},
"node_modules/ioredis": {
- "version": "5.7.0",
- "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.7.0.tgz",
- "integrity": "sha512-NUcA93i1lukyXU+riqEyPtSEkyFq8tX90uL659J+qpCZ3rEdViB/APC58oAhIh3+bJln2hzdlZbBZsGNrlsR8g==",
+ "version": "5.8.2",
+ "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.8.2.tgz",
+ "integrity": "sha512-C6uC+kleiIMmjViJINWk80sOQw5lEzse1ZmvD+S/s8p8CWapftSaC+kocGTx6xrbrJ4WmYQGC08ffHLr6ToR6Q==",
"license": "MIT",
"dependencies": {
- "@ioredis/commands": "^1.3.0",
+ "@ioredis/commands": "1.4.0",
"cluster-key-slot": "^1.1.0",
"debug": "^4.3.4",
"denque": "^2.1.0",
@@ -4589,6 +8874,16 @@
"node": ">=8"
}
},
+ "node_modules/is-generator-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz",
+ "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
@@ -4616,16 +8911,6 @@
"node": ">=0.12.0"
}
},
- "node_modules/is-plain-obj": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
- "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/is-potential-custom-element-name": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
@@ -4644,19 +8929,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/is-unicode-supported": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
- "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
@@ -4670,16 +8942,192 @@
"license": "ISC"
},
"node_modules/isomorphic-dompurify": {
- "version": "2.26.0",
- "resolved": "https://registry.npmjs.org/isomorphic-dompurify/-/isomorphic-dompurify-2.26.0.tgz",
- "integrity": "sha512-nZmoK4wKdzPs5USq4JHBiimjdKSVAOm2T1KyDoadtMPNXYHxiENd19ou4iU/V4juFM6LVgYQnpxCYmxqNP4Obw==",
+ "version": "2.30.1",
+ "resolved": "https://registry.npmjs.org/isomorphic-dompurify/-/isomorphic-dompurify-2.30.1.tgz",
+ "integrity": "sha512-VJFbthRrns7BE+q3qSUJ5zxGNjuq4FqiaWXKCwnMoJbumnoQJoeOeOzP/oejKLPPtENckLWoDxGQiv5OkEFC+Q==",
"license": "MIT",
"dependencies": {
- "dompurify": "^3.2.6",
- "jsdom": "^26.1.0"
+ "dompurify": "^3.3.0",
+ "jsdom": "^27.0.1"
},
"engines": {
- "node": ">=18"
+ "node": ">=20.19.5"
+ }
+ },
+ "node_modules/isomorphic-dompurify/node_modules/@asamuzakjp/css-color": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.0.5.tgz",
+ "integrity": "sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@csstools/css-calc": "^2.1.4",
+ "@csstools/css-color-parser": "^3.1.0",
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4",
+ "lru-cache": "^11.2.1"
+ }
+ },
+ "node_modules/isomorphic-dompurify/node_modules/cssstyle": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.1.tgz",
+ "integrity": "sha512-g5PC9Aiph9eiczFpcgUhd9S4UUO3F+LHGRIi5NUMZ+4xtoIYbHNZwZnWA2JsFGe8OU8nl4WyaEFiZuGuxlutJQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@asamuzakjp/css-color": "^4.0.3",
+ "@csstools/css-syntax-patches-for-csstree": "^1.0.14",
+ "css-tree": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/isomorphic-dompurify/node_modules/data-urls": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz",
+ "integrity": "sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==",
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^15.0.0"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/isomorphic-dompurify/node_modules/entities": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
+ "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/isomorphic-dompurify/node_modules/jsdom": {
+ "version": "27.0.1",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.0.1.tgz",
+ "integrity": "sha512-SNSQteBL1IlV2zqhwwolaG9CwhIhTvVHWg3kTss/cLE7H/X4644mtPQqYvCfsSrGQWt9hSZcgOXX8bOZaMN+kA==",
+ "license": "MIT",
+ "dependencies": {
+ "@asamuzakjp/dom-selector": "^6.7.2",
+ "cssstyle": "^5.3.1",
+ "data-urls": "^6.0.0",
+ "decimal.js": "^10.6.0",
+ "html-encoding-sniffer": "^4.0.0",
+ "http-proxy-agent": "^7.0.2",
+ "https-proxy-agent": "^7.0.6",
+ "is-potential-custom-element-name": "^1.0.1",
+ "parse5": "^8.0.0",
+ "rrweb-cssom": "^0.8.0",
+ "saxes": "^6.0.0",
+ "symbol-tree": "^3.2.4",
+ "tough-cookie": "^6.0.0",
+ "w3c-xmlserializer": "^5.0.0",
+ "webidl-conversions": "^8.0.0",
+ "whatwg-encoding": "^3.1.1",
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^15.1.0",
+ "ws": "^8.18.3",
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "peerDependencies": {
+ "canvas": "^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "canvas": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/isomorphic-dompurify/node_modules/lru-cache": {
+ "version": "11.2.2",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz",
+ "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==",
+ "license": "ISC",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/isomorphic-dompurify/node_modules/parse5": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz",
+ "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==",
+ "license": "MIT",
+ "dependencies": {
+ "entities": "^6.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
+ "node_modules/isomorphic-dompurify/node_modules/tldts": {
+ "version": "7.0.17",
+ "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.17.tgz",
+ "integrity": "sha512-Y1KQBgDd/NUc+LfOtKS6mNsC9CCaH+m2P1RoIZy7RAPo3C3/t8X45+zgut31cRZtZ3xKPjfn3TkGTrctC2TQIQ==",
+ "license": "MIT",
+ "dependencies": {
+ "tldts-core": "^7.0.17"
+ },
+ "bin": {
+ "tldts": "bin/cli.js"
+ }
+ },
+ "node_modules/isomorphic-dompurify/node_modules/tldts-core": {
+ "version": "7.0.17",
+ "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.17.tgz",
+ "integrity": "sha512-DieYoGrP78PWKsrXr8MZwtQ7GLCUeLxihtjC1jZsW1DnvSMdKPitJSe8OSYDM2u5H6g3kWJZpePqkp43TfLh0g==",
+ "license": "MIT"
+ },
+ "node_modules/isomorphic-dompurify/node_modules/tough-cookie": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz",
+ "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "tldts": "^7.0.5"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/isomorphic-dompurify/node_modules/tr46": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz",
+ "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==",
+ "license": "MIT",
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/isomorphic-dompurify/node_modules/webidl-conversions": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz",
+ "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/isomorphic-dompurify/node_modules/whatwg-url": {
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz",
+ "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==",
+ "license": "MIT",
+ "dependencies": {
+ "tr46": "^6.0.0",
+ "webidl-conversions": "^8.0.0"
+ },
+ "engines": {
+ "node": ">=20"
}
},
"node_modules/isomorphic-unfetch": {
@@ -4720,6 +9168,23 @@
"node": ">=8"
}
},
+ "node_modules/istanbul-lib-instrument": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz",
+ "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@babel/core": "^7.23.9",
+ "@babel/parser": "^7.23.9",
+ "@istanbuljs/schema": "^0.1.3",
+ "istanbul-lib-coverage": "^3.2.0",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/istanbul-lib-report": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
@@ -4735,6 +9200,21 @@
"node": ">=10"
}
},
+ "node_modules/istanbul-lib-source-maps": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz",
+ "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.23",
+ "debug": "^4.1.1",
+ "istanbul-lib-coverage": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/istanbul-reports": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
@@ -4764,6 +9244,600 @@
"@pkgjs/parseargs": "^0.11.0"
}
},
+ "node_modules/jest": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz",
+ "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jest/core": "30.2.0",
+ "@jest/types": "30.2.0",
+ "import-local": "^3.2.0",
+ "jest-cli": "30.2.0"
+ },
+ "bin": {
+ "jest": "bin/jest.js"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-changed-files": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.2.0.tgz",
+ "integrity": "sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "execa": "^5.1.1",
+ "jest-util": "30.2.0",
+ "p-limit": "^3.1.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-circus": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.2.0.tgz",
+ "integrity": "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "30.2.0",
+ "@jest/expect": "30.2.0",
+ "@jest/test-result": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "co": "^4.6.0",
+ "dedent": "^1.6.0",
+ "is-generator-fn": "^2.1.0",
+ "jest-each": "30.2.0",
+ "jest-matcher-utils": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-runtime": "30.2.0",
+ "jest-snapshot": "30.2.0",
+ "jest-util": "30.2.0",
+ "p-limit": "^3.1.0",
+ "pretty-format": "30.2.0",
+ "pure-rand": "^7.0.0",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.6"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-cli": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.2.0.tgz",
+ "integrity": "sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/core": "30.2.0",
+ "@jest/test-result": "30.2.0",
+ "@jest/types": "30.2.0",
+ "chalk": "^4.1.2",
+ "exit-x": "^0.2.2",
+ "import-local": "^3.2.0",
+ "jest-config": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-validate": "30.2.0",
+ "yargs": "^17.7.2"
+ },
+ "bin": {
+ "jest": "bin/jest.js"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-config": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.2.0.tgz",
+ "integrity": "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.27.4",
+ "@jest/get-type": "30.1.0",
+ "@jest/pattern": "30.0.1",
+ "@jest/test-sequencer": "30.2.0",
+ "@jest/types": "30.2.0",
+ "babel-jest": "30.2.0",
+ "chalk": "^4.1.2",
+ "ci-info": "^4.2.0",
+ "deepmerge": "^4.3.1",
+ "glob": "^10.3.10",
+ "graceful-fs": "^4.2.11",
+ "jest-circus": "30.2.0",
+ "jest-docblock": "30.2.0",
+ "jest-environment-node": "30.2.0",
+ "jest-regex-util": "30.0.1",
+ "jest-resolve": "30.2.0",
+ "jest-runner": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-validate": "30.2.0",
+ "micromatch": "^4.0.8",
+ "parse-json": "^5.2.0",
+ "pretty-format": "30.2.0",
+ "slash": "^3.0.0",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "@types/node": "*",
+ "esbuild-register": ">=3.4.0",
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "esbuild-register": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-diff": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz",
+ "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/diff-sequences": "30.0.1",
+ "@jest/get-type": "30.1.0",
+ "chalk": "^4.1.2",
+ "pretty-format": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-docblock": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz",
+ "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "detect-newline": "^3.1.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-each": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.2.0.tgz",
+ "integrity": "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0",
+ "@jest/types": "30.2.0",
+ "chalk": "^4.1.2",
+ "jest-util": "30.2.0",
+ "pretty-format": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-environment-node": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.2.0.tgz",
+ "integrity": "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "30.2.0",
+ "@jest/fake-timers": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "jest-mock": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-validate": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-haste-map": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.2.0.tgz",
+ "integrity": "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "anymatch": "^3.1.3",
+ "fb-watchman": "^2.0.2",
+ "graceful-fs": "^4.2.11",
+ "jest-regex-util": "30.0.1",
+ "jest-util": "30.2.0",
+ "jest-worker": "30.2.0",
+ "micromatch": "^4.0.8",
+ "walker": "^1.0.8"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "^2.3.3"
+ }
+ },
+ "node_modules/jest-leak-detector": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz",
+ "integrity": "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0",
+ "pretty-format": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-matcher-utils": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz",
+ "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0",
+ "chalk": "^4.1.2",
+ "jest-diff": "30.2.0",
+ "pretty-format": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-message-util": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz",
+ "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@jest/types": "30.2.0",
+ "@types/stack-utils": "^2.0.3",
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "micromatch": "^4.0.8",
+ "pretty-format": "30.2.0",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.6"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-mock": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz",
+ "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "jest-util": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-pnp-resolver": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz",
+ "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "peerDependencies": {
+ "jest-resolve": "*"
+ },
+ "peerDependenciesMeta": {
+ "jest-resolve": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-regex-util": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz",
+ "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-resolve": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz",
+ "integrity": "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.2.0",
+ "jest-pnp-resolver": "^1.2.3",
+ "jest-util": "30.2.0",
+ "jest-validate": "30.2.0",
+ "slash": "^3.0.0",
+ "unrs-resolver": "^1.7.11"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-resolve-dependencies": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz",
+ "integrity": "sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "jest-regex-util": "30.0.1",
+ "jest-snapshot": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-runner": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.2.0.tgz",
+ "integrity": "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/console": "30.2.0",
+ "@jest/environment": "30.2.0",
+ "@jest/test-result": "30.2.0",
+ "@jest/transform": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "emittery": "^0.13.1",
+ "exit-x": "^0.2.2",
+ "graceful-fs": "^4.2.11",
+ "jest-docblock": "30.2.0",
+ "jest-environment-node": "30.2.0",
+ "jest-haste-map": "30.2.0",
+ "jest-leak-detector": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-resolve": "30.2.0",
+ "jest-runtime": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-watcher": "30.2.0",
+ "jest-worker": "30.2.0",
+ "p-limit": "^3.1.0",
+ "source-map-support": "0.5.13"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-runtime": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.2.0.tgz",
+ "integrity": "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "30.2.0",
+ "@jest/fake-timers": "30.2.0",
+ "@jest/globals": "30.2.0",
+ "@jest/source-map": "30.0.1",
+ "@jest/test-result": "30.2.0",
+ "@jest/transform": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "cjs-module-lexer": "^2.1.0",
+ "collect-v8-coverage": "^1.0.2",
+ "glob": "^10.3.10",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-mock": "30.2.0",
+ "jest-regex-util": "30.0.1",
+ "jest-resolve": "30.2.0",
+ "jest-snapshot": "30.2.0",
+ "jest-util": "30.2.0",
+ "slash": "^3.0.0",
+ "strip-bom": "^4.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-snapshot": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz",
+ "integrity": "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.27.4",
+ "@babel/generator": "^7.27.5",
+ "@babel/plugin-syntax-jsx": "^7.27.1",
+ "@babel/plugin-syntax-typescript": "^7.27.1",
+ "@babel/types": "^7.27.3",
+ "@jest/expect-utils": "30.2.0",
+ "@jest/get-type": "30.1.0",
+ "@jest/snapshot-utils": "30.2.0",
+ "@jest/transform": "30.2.0",
+ "@jest/types": "30.2.0",
+ "babel-preset-current-node-syntax": "^1.2.0",
+ "chalk": "^4.1.2",
+ "expect": "30.2.0",
+ "graceful-fs": "^4.2.11",
+ "jest-diff": "30.2.0",
+ "jest-matcher-utils": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-util": "30.2.0",
+ "pretty-format": "30.2.0",
+ "semver": "^7.7.2",
+ "synckit": "^0.11.8"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-util": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz",
+ "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "ci-info": "^4.2.0",
+ "graceful-fs": "^4.2.11",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-util/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/jest-validate": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.2.0.tgz",
+ "integrity": "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0",
+ "@jest/types": "30.2.0",
+ "camelcase": "^6.3.0",
+ "chalk": "^4.1.2",
+ "leven": "^3.1.0",
+ "pretty-format": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-validate/node_modules/camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/jest-watcher": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.2.0.tgz",
+ "integrity": "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/test-result": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "ansi-escapes": "^4.3.2",
+ "chalk": "^4.1.2",
+ "emittery": "^0.13.1",
+ "jest-util": "30.2.0",
+ "string-length": "^4.0.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-worker": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz",
+ "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@ungap/structured-clone": "^1.3.0",
+ "jest-util": "30.2.0",
+ "merge-stream": "^2.0.0",
+ "supports-color": "^8.1.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-worker/node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
"node_modules/jmespath": {
"version": "0.16.0",
"resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz",
@@ -4793,9 +9867,9 @@
"license": "MIT"
},
"node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
@@ -4843,6 +9917,19 @@
}
}
},
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
@@ -4869,6 +9956,19 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
@@ -4892,14 +9992,14 @@
}
},
"node_modules/juice": {
- "version": "11.0.1",
- "resolved": "https://registry.npmjs.org/juice/-/juice-11.0.1.tgz",
- "integrity": "sha512-R3KLud4l/sN9AMmFZs0QY7cugGSiKvPhGyIsufCV5nJ0MjSlngUE7k80TmFeK9I62wOXrjWBtYA1knVs2OkF8w==",
+ "version": "11.0.3",
+ "resolved": "https://registry.npmjs.org/juice/-/juice-11.0.3.tgz",
+ "integrity": "sha512-VYjPg4WylyWyLPnSiUsJ9tnnGhRZF0vn0YD8WWwaI8FhP9+1UdRMyRDbvqPOH/nBotmLKOc+FI+Oma6FwVWfSw==",
"license": "MIT",
"dependencies": {
- "cheerio": "^1.0.0",
+ "cheerio": "1.0.0",
"commander": "^12.1.0",
- "entities": "^4.5.0",
+ "entities": "^7.0.0",
"mensch": "^0.3.4",
"slick": "^1.12.2",
"web-resource-inliner": "^7.0.0"
@@ -4912,34 +10012,46 @@
}
},
"node_modules/juice/node_modules/cheerio": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.2.tgz",
- "integrity": "sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg==",
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz",
+ "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==",
"license": "MIT",
"dependencies": {
"cheerio-select": "^2.1.0",
"dom-serializer": "^2.0.0",
"domhandler": "^5.0.3",
- "domutils": "^3.2.2",
- "encoding-sniffer": "^0.2.1",
- "htmlparser2": "^10.0.0",
- "parse5": "^7.3.0",
- "parse5-htmlparser2-tree-adapter": "^7.1.0",
+ "domutils": "^3.1.0",
+ "encoding-sniffer": "^0.2.0",
+ "htmlparser2": "^9.1.0",
+ "parse5": "^7.1.2",
+ "parse5-htmlparser2-tree-adapter": "^7.0.0",
"parse5-parser-stream": "^7.1.2",
- "undici": "^7.12.0",
+ "undici": "^6.19.5",
"whatwg-mimetype": "^4.0.0"
},
"engines": {
- "node": ">=20.18.1"
+ "node": ">=18.17"
},
"funding": {
"url": "https://github.com/cheeriojs/cheerio?sponsor=1"
}
},
+ "node_modules/juice/node_modules/entities": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.0.tgz",
+ "integrity": "sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
"node_modules/juice/node_modules/htmlparser2": {
- "version": "10.0.0",
- "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz",
- "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==",
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz",
+ "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==",
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
{
@@ -4951,14 +10063,14 @@
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
- "domutils": "^3.2.1",
- "entities": "^6.0.0"
+ "domutils": "^3.1.0",
+ "entities": "^4.5.0"
}
},
"node_modules/juice/node_modules/htmlparser2/node_modules/entities": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
- "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
@@ -4967,13 +10079,6 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
- "node_modules/just-extend": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz",
- "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/jwa": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz",
@@ -4986,12 +10091,12 @@
}
},
"node_modules/jws": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
- "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz",
+ "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==",
"license": "MIT",
"dependencies": {
- "jwa": "^1.4.1",
+ "jwa": "^1.4.2",
"safe-buffer": "^5.0.1"
}
},
@@ -5019,6 +10124,16 @@
"integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==",
"license": "MIT"
},
+ "node_modules/leven": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
+ "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -5152,23 +10267,6 @@
"integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==",
"license": "MIT"
},
- "node_modules/log-symbols": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
- "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "chalk": "^4.1.0",
- "is-unicode-supported": "^0.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/logform": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz",
@@ -5192,13 +10290,6 @@
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
"license": "Apache-2.0"
},
- "node_modules/loupe": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
- "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/lowercase-keys": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz",
@@ -5218,9 +10309,9 @@
"license": "ISC"
},
"node_modules/luxon": {
- "version": "3.7.1",
- "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.1.tgz",
- "integrity": "sha512-RkRWjA926cTvz5rAb1BqyWkKbbjzCGchDUIKMCUvNi17j6f6j8uHGDV82Aqcqtzd+icoYpELmG3ksgGiFNNcNg==",
+ "version": "3.7.2",
+ "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz",
+ "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==",
"license": "MIT",
"engines": {
"node": ">=12"
@@ -5253,6 +10344,23 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/make-error": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/makeerror": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
+ "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "tmpl": "1.0.5"
+ }
+ },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -5263,12 +10371,10 @@
}
},
"node_modules/mdn-data": {
- "version": "2.0.30",
- "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
- "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
- "license": "CC0-1.0",
- "optional": true,
- "peer": true
+ "version": "2.12.2",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz",
+ "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==",
+ "license": "CC0-1.0"
},
"node_modules/media-typer": {
"version": "0.3.0",
@@ -5300,6 +10406,23 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
@@ -5309,6 +10432,20 @@
"node": ">= 0.6"
}
},
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
@@ -5351,6 +10488,16 @@
"node": ">= 0.6"
}
},
+ "node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/mimic-response": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz",
@@ -5841,81 +10988,6 @@
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
"license": "MIT"
},
- "node_modules/mocha": {
- "version": "11.1.0",
- "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.1.0.tgz",
- "integrity": "sha512-8uJR5RTC2NgpY3GrYcgpZrsEd9zKbPDpob1RezyR2upGHRQtHWofmzTMzTMSV6dru3tj5Ukt0+Vnq1qhFEEwAg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-colors": "^4.1.3",
- "browser-stdout": "^1.3.1",
- "chokidar": "^3.5.3",
- "debug": "^4.3.5",
- "diff": "^5.2.0",
- "escape-string-regexp": "^4.0.0",
- "find-up": "^5.0.0",
- "glob": "^10.4.5",
- "he": "^1.2.0",
- "js-yaml": "^4.1.0",
- "log-symbols": "^4.1.0",
- "minimatch": "^5.1.6",
- "ms": "^2.1.3",
- "serialize-javascript": "^6.0.2",
- "strip-json-comments": "^3.1.1",
- "supports-color": "^8.1.1",
- "workerpool": "^6.5.1",
- "yargs": "^17.7.2",
- "yargs-parser": "^21.1.1",
- "yargs-unparser": "^2.0.0"
- },
- "bin": {
- "_mocha": "bin/_mocha",
- "mocha": "bin/mocha.js"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/mocha/node_modules/brace-expansion": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
- "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/mocha/node_modules/minimatch": {
- "version": "5.1.6",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
- "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/mocha/node_modules/supports-color": {
- "version": "8.1.1",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
- "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/supports-color?sponsor=1"
- }
- },
"node_modules/moment": {
"version": "2.30.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
@@ -5938,14 +11010,14 @@
}
},
"node_modules/mongodb": {
- "version": "6.19.0",
- "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.19.0.tgz",
- "integrity": "sha512-H3GtYujOJdeKIMLKBT9PwlDhGrQfplABNF1G904w6r5ZXKWyv77aB0X9B+rhmaAwjtllHzaEkvi9mkGVZxs2Bw==",
+ "version": "6.20.0",
+ "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.20.0.tgz",
+ "integrity": "sha512-Tl6MEIU3K4Rq3TSHd+sZQqRBoGlFsOgNrH5ltAcFBV62Re3Fd+FcaVf8uSEQFOJ51SDowDVttBTONMfoYWrWlQ==",
"license": "Apache-2.0",
"dependencies": {
- "@mongodb-js/saslprep": "^1.1.9",
+ "@mongodb-js/saslprep": "^1.3.0",
"bson": "^6.10.4",
- "mongodb-connection-string-url": "^3.0.0"
+ "mongodb-connection-string-url": "^3.0.2"
},
"engines": {
"node": ">=16.20.1"
@@ -5994,14 +11066,14 @@
}
},
"node_modules/mongoose": {
- "version": "8.18.0",
- "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.18.0.tgz",
- "integrity": "sha512-3TixPihQKBdyaYDeJqRjzgb86KbilEH07JmzV8SoSjgoskNTpa6oTBmDxeoF9p8YnWQoz7shnCyPkSV/48y3yw==",
+ "version": "8.19.2",
+ "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.19.2.tgz",
+ "integrity": "sha512-ww2T4dBV+suCbOfG5YPwj9pLCfUVyj8FEA1D3Ux1HHqutpLxGyOYEPU06iPRBW4cKr3PJfOSYsIpHWPTkz5zig==",
"license": "MIT",
"dependencies": {
"bson": "^6.10.4",
"kareem": "2.6.3",
- "mongodb": "~6.18.0",
+ "mongodb": "~6.20.0",
"mpath": "0.9.0",
"mquery": "5.0.0",
"ms": "2.1.3",
@@ -6015,52 +11087,6 @@
"url": "https://opencollective.com/mongoose"
}
},
- "node_modules/mongoose/node_modules/mongodb": {
- "version": "6.18.0",
- "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.18.0.tgz",
- "integrity": "sha512-fO5ttN9VC8P0F5fqtQmclAkgXZxbIkYRTUi1j8JO6IYwvamkhtYDilJr35jOPELR49zqCJgXZWwCtW7B+TM8vQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@mongodb-js/saslprep": "^1.1.9",
- "bson": "^6.10.4",
- "mongodb-connection-string-url": "^3.0.0"
- },
- "engines": {
- "node": ">=16.20.1"
- },
- "peerDependencies": {
- "@aws-sdk/credential-providers": "^3.188.0",
- "@mongodb-js/zstd": "^1.1.0 || ^2.0.0",
- "gcp-metadata": "^5.2.0",
- "kerberos": "^2.0.1",
- "mongodb-client-encryption": ">=6.0.0 <7",
- "snappy": "^7.2.2",
- "socks": "^2.7.1"
- },
- "peerDependenciesMeta": {
- "@aws-sdk/credential-providers": {
- "optional": true
- },
- "@mongodb-js/zstd": {
- "optional": true
- },
- "gcp-metadata": {
- "optional": true
- },
- "kerberos": {
- "optional": true
- },
- "mongodb-client-encryption": {
- "optional": true
- },
- "snappy": {
- "optional": true
- },
- "socks": {
- "optional": true
- }
- }
- },
"node_modules/mpath": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
@@ -6138,6 +11164,20 @@
"node": ">= 6.0.0"
}
},
+ "node_modules/mylas": {
+ "version": "2.1.14",
+ "resolved": "https://registry.npmjs.org/mylas/-/mylas-2.1.14.tgz",
+ "integrity": "sha512-BzQguy9W9NJgoVn2mRWzbFrFWWztGCcng2QI9+41frfk+Athwgx3qhqhvStz7ExeUUu7Kzw427sNzHpEZNINog==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/raouldeheer"
+ }
+ },
"node_modules/nan": {
"version": "2.23.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.23.0.tgz",
@@ -6163,6 +11203,22 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
+ "node_modules/napi-postinstall": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz",
+ "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "napi-postinstall": "lib/cli.js"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/napi-postinstall"
+ }
+ },
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -6191,31 +11247,6 @@
"integrity": "sha512-kbhcj2SVVR4caaVnGLJKmlk2+f+oLkjqdKeQlmUtz6nGzOpbcobwVIeSURNgraV/v3tlmGIX82OcPCl0K6RbHQ==",
"license": "MIT"
},
- "node_modules/nise": {
- "version": "6.1.1",
- "resolved": "https://registry.npmjs.org/nise/-/nise-6.1.1.tgz",
- "integrity": "sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@sinonjs/commons": "^3.0.1",
- "@sinonjs/fake-timers": "^13.0.1",
- "@sinonjs/text-encoding": "^0.7.3",
- "just-extend": "^6.2.0",
- "path-to-regexp": "^8.1.0"
- }
- },
- "node_modules/nise/node_modules/path-to-regexp": {
- "version": "8.3.0",
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
- "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
"node_modules/node-abort-controller": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz",
@@ -6299,10 +11330,17 @@
"node-gyp-build-optional-packages-test": "build-test.js"
}
},
+ "node_modules/node-int64": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
+ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/node-releases": {
- "version": "2.0.19",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
- "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
+ "version": "2.0.26",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.26.tgz",
+ "integrity": "sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA==",
"license": "MIT"
},
"node_modules/nodemailer": {
@@ -6315,9 +11353,9 @@
}
},
"node_modules/nodemon": {
- "version": "3.1.9",
- "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz",
- "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==",
+ "version": "3.1.11",
+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz",
+ "integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -6376,9 +11414,9 @@
}
},
"node_modules/normalize-url": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.2.tgz",
- "integrity": "sha512-Ee/R3SyN4BuynXcnTaekmaVdbDAEiNrHqjQIA37mHU8G9pf7aaAD4ZX3XjBLo6rsdcxA/gtkcNYZLt30ACgynw==",
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.0.tgz",
+ "integrity": "sha512-X06Mfd/5aKsRHc0O0J5CUedwnPmnDtLF2+nq+KN9KSDlJHkPuh0JUviWjEWMe0SW/9TDdSLVPuk7L5gGTIA1/w==",
"license": "MIT",
"engines": {
"node": ">=14.16"
@@ -6387,6 +11425,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/npm-run-path": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/nth-check": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
@@ -6406,9 +11457,9 @@
"license": "MIT"
},
"node_modules/nwsapi": {
- "version": "2.2.21",
- "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.21.tgz",
- "integrity": "sha512-o6nIY3qwiSXl7/LuOU0Dmuctd34Yay0yeuZRLFmDPrrdHpXKFndPj3hM+YEPVHYC5fx2otBx4Ilc/gyYSAUaIA==",
+ "version": "2.2.22",
+ "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.22.tgz",
+ "integrity": "sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ==",
"license": "MIT"
},
"node_modules/object-assign": {
@@ -6471,6 +11522,22 @@
"fn.name": "1.x.x"
}
},
+ "node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/optionator": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -6530,18 +11597,22 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
"license": "BlueOak-1.0.0"
},
- "node_modules/papaparse": {
- "version": "5.5.3",
- "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.3.tgz",
- "integrity": "sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A==",
- "license": "MIT"
- },
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -6640,6 +11711,16 @@
"node": ">=8"
}
},
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@@ -6671,14 +11752,14 @@
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
"license": "MIT"
},
- "node_modules/pathval": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz",
- "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==",
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">= 14.16"
+ "node": ">=8"
}
},
"node_modules/picocolors": {
@@ -6708,6 +11789,98 @@
"node": ">=4.0.0"
}
},
+ "node_modules/pirates": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/pkg-dir": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
+ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "find-up": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/plimit-lit": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/plimit-lit/-/plimit-lit-1.6.1.tgz",
+ "integrity": "sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "queue-lit": "^1.5.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/postcss": {
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
@@ -6727,6 +11900,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -7154,25 +12328,6 @@
"node": ">=16"
}
},
- "node_modules/postcss-svgo/node_modules/css-tree": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz",
- "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==",
- "license": "MIT",
- "dependencies": {
- "mdn-data": "2.12.2",
- "source-map-js": "^1.0.1"
- },
- "engines": {
- "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
- }
- },
- "node_modules/postcss-svgo/node_modules/mdn-data": {
- "version": "2.12.2",
- "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz",
- "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==",
- "license": "CC0-1.0"
- },
"node_modules/postcss-svgo/node_modules/svgo": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz",
@@ -7220,9 +12375,9 @@
"license": "MIT"
},
"node_modules/posthtml": {
- "version": "0.16.6",
- "resolved": "https://registry.npmjs.org/posthtml/-/posthtml-0.16.6.tgz",
- "integrity": "sha512-JcEmHlyLK/o0uGAlj65vgg+7LIms0xKXe60lcDOTU7oVX/3LuEuLwrQpW3VJ7de5TaFKiW4kWkaIpJL42FEgxQ==",
+ "version": "0.16.7",
+ "resolved": "https://registry.npmjs.org/posthtml/-/posthtml-0.16.7.tgz",
+ "integrity": "sha512-7Hc+IvlQ7hlaIfQFZnxlRl0jnpWq2qwibORBhQYIb0QbNtuicc5ZxvKkVT71HJ4Py1wSZ/3VR1r8LfkCtoCzhw==",
"license": "MIT",
"dependencies": {
"posthtml-parser": "^0.11.0",
@@ -7364,6 +12519,34 @@
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
+ "node_modules/pretty-format": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz",
+ "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/schemas": "30.0.5",
+ "ansi-styles": "^5.2.0",
+ "react-is": "^18.3.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@@ -7439,13 +12622,30 @@
"node": ">=6"
}
},
+ "node_modules/pure-rand": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz",
+ "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/dubzzz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fast-check"
+ }
+ ],
+ "license": "MIT"
+ },
"node_modules/qs": {
- "version": "6.13.0",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
- "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
+ "version": "6.14.1",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz",
+ "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==",
"license": "BSD-3-Clause",
"dependencies": {
- "side-channel": "^1.0.6"
+ "side-channel": "^1.1.0"
},
"engines": {
"node": ">=0.6"
@@ -7454,6 +12654,37 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/queue-lit": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/queue-lit/-/queue-lit-1.5.2.tgz",
+ "integrity": "sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
"node_modules/quick-lru": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
@@ -7473,16 +12704,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/randombytes": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
- "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "safe-buffer": "^5.1.0"
- }
- },
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@@ -7493,20 +12714,56 @@
}
},
"node_modules/raw-body": {
- "version": "2.5.2",
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
- "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
+ "version": "2.5.3",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
+ "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
"license": "MIT",
"dependencies": {
- "bytes": "3.1.2",
- "http-errors": "2.0.0",
- "iconv-lite": "0.4.24",
- "unpipe": "1.0.0"
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.4.24",
+ "unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
+ "node_modules/raw-body/node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/raw-body/node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
@@ -7563,12 +12820,44 @@
"node": ">=0.10.0"
}
},
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/resolve-alpn": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz",
"integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==",
"license": "MIT"
},
+ "node_modules/resolve-cwd": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz",
+ "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve-from": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/resolve-cwd/node_modules/resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -7578,6 +12867,16 @@
"node": ">=4"
}
},
+ "node_modules/resolve-pkg-maps": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+ }
+ },
"node_modules/responselike": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz",
@@ -7593,12 +12892,47 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/rrweb-cssom": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",
"integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==",
"license": "MIT"
},
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -7635,10 +12969,13 @@
"license": "MIT"
},
"node_modules/sax": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
- "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
- "license": "ISC"
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz",
+ "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=11.0.0"
+ }
},
"node_modules/saxes": {
"version": "6.0.0",
@@ -7675,9 +13012,9 @@
}
},
"node_modules/semver": {
- "version": "7.7.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
- "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "version": "7.7.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@@ -7734,16 +13071,6 @@
"node": ">= 0.8"
}
},
- "node_modules/serialize-javascript": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
- "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "randombytes": "^2.1.0"
- }
- },
"node_modules/serve-static": {
"version": "1.16.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
@@ -7916,18 +13243,18 @@
}
},
"node_modules/simple-swizzle": {
- "version": "0.2.2",
- "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
- "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
+ "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==",
"license": "MIT",
"dependencies": {
"is-arrayish": "^0.3.1"
}
},
"node_modules/simple-swizzle/node_modules/is-arrayish": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
- "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz",
+ "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
"license": "MIT"
},
"node_modules/simple-update-notifier": {
@@ -7943,33 +13270,14 @@
"node": ">=10"
}
},
- "node_modules/sinon": {
- "version": "19.0.2",
- "resolved": "https://registry.npmjs.org/sinon/-/sinon-19.0.2.tgz",
- "integrity": "sha512-euuToqM+PjO4UgXeLETsfQiuoyPXlqFezr6YZDFwHR3t4qaX0fZUe1MfPMznTL5f8BWrVS89KduLdMUsxFCO6g==",
+ "node_modules/slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
"dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@sinonjs/commons": "^3.0.1",
- "@sinonjs/fake-timers": "^13.0.2",
- "@sinonjs/samsam": "^8.0.1",
- "diff": "^7.0.0",
- "nise": "^6.1.1",
- "supports-color": "^7.2.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/sinon"
- }
- },
- "node_modules/sinon/node_modules/diff": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz",
- "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==",
- "dev": true,
- "license": "BSD-3-Clause",
+ "license": "MIT",
"engines": {
- "node": ">=0.3.1"
+ "node": ">=8"
}
},
"node_modules/slick": {
@@ -7999,6 +13307,17 @@
"node": ">=0.10.0"
}
},
+ "node_modules/source-map-support": {
+ "version": "0.5.13",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
+ "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
"node_modules/sparse-bitfield": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
@@ -8014,6 +13333,13 @@
"integrity": "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==",
"license": "ISC"
},
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
"node_modules/ssh2": {
"version": "1.17.0",
"resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.17.0.tgz",
@@ -8046,6 +13372,29 @@
"node": "*"
}
},
+ "node_modules/stack-utils": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
+ "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "escape-string-regexp": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/stack-utils/node_modules/escape-string-regexp": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
+ "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/standard-as-callback": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
@@ -8087,6 +13436,43 @@
"safe-buffer": "~5.2.0"
}
},
+ "node_modules/string-length": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
+ "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "char-regex": "^1.0.2",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/string-length/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-length/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/string-to-stream": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string-to-stream/-/string-to-stream-1.1.1.tgz",
@@ -8193,9 +13579,9 @@
}
},
"node_modules/strip-ansi": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
- "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
+ "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
@@ -8229,6 +13615,26 @@
"node": ">=8"
}
},
+ "node_modules/strip-bom": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
+ "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-final-newline": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
@@ -8271,15 +13677,27 @@
}
},
"node_modules/super-simple-scheduler": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.4.1.tgz",
- "integrity": "sha512-jKVwxdRBXOHrUstA2pvO4jBZIA3CUinKiQ6k5C2Wn5tscoV3srppPxW0zG3VNKTCaGxo3yPn6Zaf3IDVBcAwRA==",
+ "version": "1.4.5",
+ "resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.4.5.tgz",
+ "integrity": "sha512-xHR3a+pLywY7HtwjWj/+OaANdEwAieQhckC/i0s6OoHtVJQ4+VLO1VfkKUbxp+oSROGXcaZet8PTGONVdKNUUw==",
"license": "MIT",
"dependencies": {
"uuid": "11.1.0",
"winston": "3.17.0"
}
},
+ "node_modules/super-simple-scheduler/node_modules/is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/super-simple-scheduler/node_modules/uuid": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
@@ -8293,6 +13711,28 @@
"uuid": "dist/esm/bin/uuid"
}
},
+ "node_modules/super-simple-scheduler/node_modules/winston": {
+ "version": "3.17.0",
+ "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz",
+ "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==",
+ "license": "MIT",
+ "dependencies": {
+ "@colors/colors": "^1.6.0",
+ "@dabh/diagnostics": "^2.0.2",
+ "async": "^3.2.3",
+ "is-stream": "^2.0.0",
+ "logform": "^2.7.0",
+ "one-time": "^1.0.0",
+ "readable-stream": "^3.4.0",
+ "safe-stable-stringify": "^2.3.1",
+ "stack-trace": "0.0.x",
+ "triple-beam": "^1.3.0",
+ "winston-transport": "^4.9.0"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ }
+ },
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -8312,7 +13752,6 @@
"integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==",
"license": "MIT",
"optional": true,
- "peer": true,
"dependencies": {
"@trysound/sax": "0.2.0",
"commander": "^7.2.0",
@@ -8339,15 +13778,35 @@
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
"license": "MIT",
"optional": true,
- "peer": true,
"engines": {
"node": ">= 10"
}
},
+ "node_modules/svgo/node_modules/css-tree": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
+ "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "mdn-data": "2.0.30",
+ "source-map-js": "^1.0.1"
+ },
+ "engines": {
+ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
+ }
+ },
+ "node_modules/svgo/node_modules/mdn-data": {
+ "version": "2.0.30",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
+ "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
+ "license": "CC0-1.0",
+ "optional": true
+ },
"node_modules/swagger-ui-dist": {
- "version": "5.28.1",
- "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.28.1.tgz",
- "integrity": "sha512-IvPrtNi8MvjiuDgoSmPYgg27Lvu38fnLD1OSd8Y103xXsPAqezVNnNeHnVCZ/d+CMXJblflGaIyHxAYIF3O71w==",
+ "version": "5.30.0",
+ "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.30.0.tgz",
+ "integrity": "sha512-BoiDSeT9PCtHfDYMgX5UpB/qTQy44UoSFRmzHqvhGfgxzEVPHxIW78a+HMLUIHnSFu3z63wjtbq6L6+Rto20Rw==",
"license": "Apache-2.0",
"dependencies": {
"@scarf/scarf": "=1.4.0"
@@ -8374,10 +13833,26 @@
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
"license": "MIT"
},
+ "node_modules/synckit": {
+ "version": "0.11.12",
+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz",
+ "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@pkgr/core": "^0.2.9"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/synckit"
+ }
+ },
"node_modules/tar-fs": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz",
- "integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==",
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
+ "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
"license": "MIT",
"dependencies": {
"chownr": "^1.1.1",
@@ -8403,9 +13878,9 @@
}
},
"node_modules/telnet-client": {
- "version": "2.2.5",
- "resolved": "https://registry.npmjs.org/telnet-client/-/telnet-client-2.2.5.tgz",
- "integrity": "sha512-X5xEkmKHgpCpngnH7QnkFX87UyBErauHsjzlCGVp87MbhnmHoaAeacuALGfqovHh3MXAfrpPs+g30PgyBpy8Jw==",
+ "version": "2.2.6",
+ "resolved": "https://registry.npmjs.org/telnet-client/-/telnet-client-2.2.6.tgz",
+ "integrity": "sha512-ZUYrLsPtQupQww3eSEORDVOb6ztdtKEghya6TVXPo2tg/UQq2pn5rHhvwuUvyYpbnsoqdNY1fyD1GNkXHR8dYA==",
"license": "MIT",
"dependencies": {
"net": "^1.0.2",
@@ -8481,6 +13956,13 @@
"integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==",
"license": "MIT"
},
+ "node_modules/tmpl": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
+ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -8545,12 +14027,162 @@
"node": ">= 14.0.0"
}
},
+ "node_modules/ts-jest": {
+ "version": "29.4.6",
+ "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz",
+ "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bs-logger": "^0.2.6",
+ "fast-json-stable-stringify": "^2.1.0",
+ "handlebars": "^4.7.8",
+ "json5": "^2.2.3",
+ "lodash.memoize": "^4.1.2",
+ "make-error": "^1.3.6",
+ "semver": "^7.7.3",
+ "type-fest": "^4.41.0",
+ "yargs-parser": "^21.1.1"
+ },
+ "bin": {
+ "ts-jest": "cli.js"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": ">=7.0.0-beta.0 <8",
+ "@jest/transform": "^29.0.0 || ^30.0.0",
+ "@jest/types": "^29.0.0 || ^30.0.0",
+ "babel-jest": "^29.0.0 || ^30.0.0",
+ "jest": "^29.0.0 || ^30.0.0",
+ "jest-util": "^29.0.0 || ^30.0.0",
+ "typescript": ">=4.3 <6"
+ },
+ "peerDependenciesMeta": {
+ "@babel/core": {
+ "optional": true
+ },
+ "@jest/transform": {
+ "optional": true
+ },
+ "@jest/types": {
+ "optional": true
+ },
+ "babel-jest": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jest-util": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ts-node": {
+ "version": "10.9.2",
+ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
+ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@cspotcode/source-map-support": "^0.8.0",
+ "@tsconfig/node10": "^1.0.7",
+ "@tsconfig/node12": "^1.0.7",
+ "@tsconfig/node14": "^1.0.0",
+ "@tsconfig/node16": "^1.0.2",
+ "acorn": "^8.4.1",
+ "acorn-walk": "^8.1.1",
+ "arg": "^4.1.0",
+ "create-require": "^1.1.0",
+ "diff": "^4.0.1",
+ "make-error": "^1.1.1",
+ "v8-compile-cache-lib": "^3.0.1",
+ "yn": "3.1.1"
+ },
+ "bin": {
+ "ts-node": "dist/bin.js",
+ "ts-node-cwd": "dist/bin-cwd.js",
+ "ts-node-esm": "dist/bin-esm.js",
+ "ts-node-script": "dist/bin-script.js",
+ "ts-node-transpile-only": "dist/bin-transpile.js",
+ "ts-script": "dist/bin-script-deprecated.js"
+ },
+ "peerDependencies": {
+ "@swc/core": ">=1.2.50",
+ "@swc/wasm": ">=1.2.50",
+ "@types/node": "*",
+ "typescript": ">=2.7"
+ },
+ "peerDependenciesMeta": {
+ "@swc/core": {
+ "optional": true
+ },
+ "@swc/wasm": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tsc-alias": {
+ "version": "1.8.16",
+ "resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.8.16.tgz",
+ "integrity": "sha512-QjCyu55NFyRSBAl6+MTFwplpFcnm2Pq01rR/uxfqJoLMm6X3O14KEGtaSDZpJYaE1bJBGDjD0eSuiIWPe2T58g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^3.5.3",
+ "commander": "^9.0.0",
+ "get-tsconfig": "^4.10.0",
+ "globby": "^11.0.4",
+ "mylas": "^2.1.9",
+ "normalize-path": "^3.0.0",
+ "plimit-lit": "^1.2.6"
+ },
+ "bin": {
+ "tsc-alias": "dist/bin/index.js"
+ },
+ "engines": {
+ "node": ">=16.20.2"
+ }
+ },
+ "node_modules/tsc-alias/node_modules/commander": {
+ "version": "9.5.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
+ "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || >=14"
+ }
+ },
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
+ "node_modules/tsx": {
+ "version": "4.20.5",
+ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.5.tgz",
+ "integrity": "sha512-+wKjMNU9w/EaQayHXb7WA7ZaHY6hN8WgfvHNQ3t1PnU91/7O8TcTnIhCDYTZwnt8JsO9IBqZ30Ln1r7pPF52Aw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "~0.25.0",
+ "get-tsconfig": "^4.7.5"
+ },
+ "bin": {
+ "tsx": "dist/cli.mjs"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ }
+ },
"node_modules/tweetnacl": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
@@ -8611,6 +14243,21 @@
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
"license": "MIT"
},
+ "node_modules/typescript": {
+ "version": "5.9.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
+ "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
"node_modules/uglify-js": {
"version": "3.19.3",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",
@@ -8632,18 +14279,18 @@
"license": "MIT"
},
"node_modules/undici": {
- "version": "7.15.0",
- "resolved": "https://registry.npmjs.org/undici/-/undici-7.15.0.tgz",
- "integrity": "sha512-7oZJCPvvMvTd0OlqWsIxTuItTpJBpU1tcbVl24FMn3xt3+VSunwUasmfPJRE57oNO1KsZ4PgA1xTdAX4hq8NyQ==",
+ "version": "6.22.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-6.22.0.tgz",
+ "integrity": "sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw==",
"license": "MIT",
"engines": {
- "node": ">=20.18.1"
+ "node": ">=18.17"
}
},
"node_modules/undici-types": {
- "version": "7.10.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
- "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==",
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
+ "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
"license": "MIT"
},
"node_modules/unfetch": {
@@ -8664,10 +14311,45 @@
"node": ">= 0.8"
}
},
+ "node_modules/unrs-resolver": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz",
+ "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "napi-postinstall": "^0.3.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/unrs-resolver"
+ },
+ "optionalDependencies": {
+ "@unrs/resolver-binding-android-arm-eabi": "1.11.1",
+ "@unrs/resolver-binding-android-arm64": "1.11.1",
+ "@unrs/resolver-binding-darwin-arm64": "1.11.1",
+ "@unrs/resolver-binding-darwin-x64": "1.11.1",
+ "@unrs/resolver-binding-freebsd-x64": "1.11.1",
+ "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1",
+ "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1",
+ "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-arm64-musl": "1.11.1",
+ "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1",
+ "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-x64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-x64-musl": "1.11.1",
+ "@unrs/resolver-binding-wasm32-wasi": "1.11.1",
+ "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1",
+ "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1",
+ "@unrs/resolver-binding-win32-x64-msvc": "1.11.1"
+ }
+ },
"node_modules/update-browserslist-db": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
- "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz",
+ "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==",
"funding": [
{
"type": "opencollective",
@@ -8732,6 +14414,13 @@
"uuid": "dist/bin/uuid"
}
},
+ "node_modules/v8-compile-cache-lib": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
+ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/v8-to-istanbul": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
@@ -8783,6 +14472,16 @@
"node": ">=18"
}
},
+ "node_modules/walker": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
+ "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "makeerror": "1.0.12"
+ }
+ },
"node_modules/web-resource-inliner": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/web-resource-inliner/-/web-resource-inliner-7.0.0.tgz",
@@ -8988,13 +14687,13 @@
}
},
"node_modules/winston": {
- "version": "3.17.0",
- "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz",
- "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==",
+ "version": "3.18.3",
+ "resolved": "https://registry.npmjs.org/winston/-/winston-3.18.3.tgz",
+ "integrity": "sha512-NoBZauFNNWENgsnC9YpgyYwOVrl2m58PpQ8lNHjV3kosGs7KJ7Npk9pCUE+WJlawVSe8mykWDKWFSVfs3QO9ww==",
"license": "MIT",
"dependencies": {
"@colors/colors": "^1.6.0",
- "@dabh/diagnostics": "^2.0.2",
+ "@dabh/diagnostics": "^2.0.8",
"async": "^3.2.3",
"is-stream": "^2.0.0",
"logform": "^2.7.0",
@@ -9051,13 +14750,6 @@
"integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
"license": "MIT"
},
- "node_modules/workerpool": {
- "version": "6.5.1",
- "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz",
- "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==",
- "dev": true,
- "license": "Apache-2.0"
- },
"node_modules/wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
@@ -9135,9 +14827,9 @@
}
},
"node_modules/wrap-ansi/node_modules/ansi-styles": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
- "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
"license": "MIT",
"engines": {
"node": ">=12"
@@ -9152,6 +14844,20 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
},
+ "node_modules/write-file-atomic": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz",
+ "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "imurmurhash": "^0.1.4",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
"node_modules/ws": {
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
@@ -9235,6 +14941,13 @@
"node": ">=10"
}
},
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
@@ -9262,22 +14975,6 @@
"node": ">=12"
}
},
- "node_modules/yargs-unparser": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz",
- "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "camelcase": "^6.0.0",
- "decamelize": "^4.0.0",
- "flat": "^5.0.2",
- "is-plain-obj": "^2.1.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/yargs/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@@ -9319,6 +15016,16 @@
"node": ">=8"
}
},
+ "node_modules/yn": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+ "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
diff --git a/server/package.json b/server/package.json
index d879776a8..2421aeb1c 100755
--- a/server/package.json
+++ b/server/package.json
@@ -5,8 +5,10 @@
"main": "index.js",
"type": "module",
"scripts": {
- "test": "c8 mocha",
- "dev": "nodemon src/index.js",
+ "test": "NODE_OPTIONS=--experimental-vm-modules c8 jest --runInBand",
+ "dev": "nodemon --exec tsx src/index.js",
+ "start": "node --watch ./dist/index.js",
+ "build": "tsc && tsc-alias",
"lint": "eslint .",
"lint-fix": "eslint --fix .",
"format": "prettier --write .",
@@ -15,11 +17,15 @@
"keywords": [],
"author": "",
"license": "ISC",
+ "engines": {
+ "node": ">=20"
+ },
"dependencies": {
"@pulsecron/pulse": "1.6.8",
"axios": "^1.7.2",
"bcryptjs": "3.0.2",
"bullmq": "5.41.2",
+ "cacheable-lookup": "7.0.0",
"compression": "1.8.1",
"cookie-parser": "^1.4.7",
"cors": "^2.8.5",
@@ -42,25 +48,43 @@
"mongoose": "^8.3.3",
"multer": "^1.4.5-lts.1",
"nodemailer": "^6.9.14",
- "papaparse": "^5.5.2",
"ping": "0.4.4",
"sharp": "0.33.5",
"ssl-checker": "2.0.10",
- "super-simple-scheduler": "1.4.1",
+ "super-simple-scheduler": "1.4.5",
"swagger-ui-express": "5.0.1",
"winston": "^3.13.0"
},
"devDependencies": {
"@eslint/js": "^9.17.0",
+ "@types/compression": "1.8.1",
+ "@types/cookie-parser": "1.4.10",
+ "@types/cors": "2.8.19",
+ "@types/dockerode": "^4.0.0",
+ "@types/express": "5.0.3",
+ "@types/gamedig": "^5.0.3",
+ "@types/jest": "^30.0.0",
+ "@types/jmespath": "^0.15.2",
+ "@types/jsdom": "^27.0.0",
+ "@types/jsonwebtoken": "9.0.10",
+ "@types/mjml": "^4.7.4",
+ "@types/multer": "^2.0.0",
+ "@types/nodemailer": "7.0.1",
+ "@types/papaparse": "^5.5.2",
+ "@types/ping": "0.4.4",
+ "@types/swagger-ui-express": "4.1.8",
"c8": "10.1.3",
- "chai": "5.2.0",
"eslint": "^9.17.0",
"eslint-plugin-mocha": "^10.5.0",
"esm": "3.2.25",
"globals": "^15.14.0",
- "mocha": "11.1.0",
- "nodemon": "3.1.9",
+ "jest": "^30.2.0",
+ "nodemon": "^3.1.11",
"prettier": "^3.3.3",
- "sinon": "19.0.2"
+ "ts-jest": "^29.4.6",
+ "ts-node": "^10.9.2",
+ "tsc-alias": "1.8.16",
+ "tsx": "4.20.5",
+ "typescript": "5.9.2"
}
}
diff --git a/server/scripts/generate-checks.js b/server/scripts/generate-checks.js
new file mode 100644
index 000000000..d3a33b033
--- /dev/null
+++ b/server/scripts/generate-checks.js
@@ -0,0 +1,176 @@
+import mongoose from "mongoose";
+import { MonitorModel } from "../dist/db/models/Monitor.js";
+import { CheckModel } from "../dist/db/models/Check.js";
+
+const DEFAULT_MONITOR_ID = "000000000000000000000001";
+const DEFAULT_TEAM_ID = "0000000000000000000000aa";
+const DEFAULT_USER_ID = "0000000000000000000000bb";
+const DEFAULT_MONITOR_TYPE = "http";
+const DEFAULT_TOTAL = 1_000_000;
+const DEFAULT_BATCH_SIZE = 5_000;
+
+const parseObjectId = (value, fallback) => {
+ try {
+ return new mongoose.Types.ObjectId(value || fallback);
+ } catch (error) {
+ console.warn(`Invalid ObjectId '${value}', falling back to '${fallback}'.`);
+ return new mongoose.Types.ObjectId(fallback);
+ }
+};
+
+async function ensureMonitor({ monitorId, teamId, userId, type }) {
+ const existing = await MonitorModel.findById(monitorId);
+ if (existing) {
+ return existing;
+ }
+
+ console.log(`Monitor ${monitorId.toString()} not found, creating it.`);
+ const monitor = new MonitorModel({
+ _id: monitorId,
+ userId,
+ teamId,
+ name: `Seed Monitor ${monitorId.toString()}`,
+ description: "Synthetic monitor for performance testing",
+ statusWindow: [],
+ statusWindowSize: 5,
+ statusWindowThreshold: 60,
+ type,
+ ignoreTlsErrors: false,
+ url: "https://example.com",
+ isActive: true,
+ interval: 60000,
+ alertThreshold: 5,
+ cpuAlertThreshold: 5,
+ memoryAlertThreshold: 5,
+ diskAlertThreshold: 5,
+ tempAlertThreshold: 5,
+ selectedDisks: [],
+ });
+
+ await monitor.save();
+ return monitor;
+}
+
+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 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);
+
+ console.log(`Connecting to MongoDB at ${mongoUri}`);
+ await mongoose.connect(mongoUri);
+
+ await ensureMonitor({ monitorId, teamId, userId, type: monitorType });
+
+ console.log(`Seeding ${total} checks for monitor ${monitorId.toString()} (team ${teamId.toString()}) in batches of ${batchSize}.`);
+
+ const docs = [];
+ const startTime = Date.now();
+
+ for (let i = 0; i < total; i += 1) {
+ const baseTime = Date.now() - (total - i) * 1000;
+ const createdAt = new Date(baseTime);
+ docs.push({
+ metadata: {
+ monitorId,
+ teamId,
+ type: monitorType,
+ },
+ status: i % 50 !== 0,
+ statusCode: i % 50 !== 0 ? 200 : 500,
+ responseTime: Math.floor(Math.random() * 1000),
+ message: i % 50 !== 0 ? "OK" : "Error",
+ expiry: createdAt,
+ createdAt,
+ updatedAt: createdAt,
+ timings: {
+ start: baseTime,
+ socket: baseTime,
+ lookup: baseTime,
+ connect: baseTime,
+ secureConnect: baseTime,
+ upload: baseTime,
+ response: baseTime + 40,
+ end: baseTime + 45,
+ phases: {
+ wait: 0,
+ dns: 1,
+ tcp: 2,
+ tls: 4,
+ request: 0,
+ firstByte: 30,
+ download: 5,
+ total: 45,
+ },
+ },
+ cpu: {
+ physical_core: 8,
+ logical_core: 16,
+ frequency: 3600,
+ temperature: [50 + Math.random() * 10],
+ free_percent: 40,
+ usage_percent: Math.random() * 100,
+ },
+ memory: {
+ total_bytes: 32 * 1024 ** 3,
+ available_bytes: 16 * 1024 ** 3,
+ used_bytes: 16 * 1024 ** 3,
+ usage_percent: Math.random() * 100,
+ },
+ disk: [
+ {
+ device: "/dev/sda1",
+ mountpoint: "/",
+ read_speed_bytes: Math.random() * 10_000_000,
+ write_speed_bytes: Math.random() * 10_000_000,
+ total_bytes: 512 * 1024 ** 3,
+ free_bytes: 128 * 1024 ** 3,
+ usage_percent: Math.random() * 100,
+ },
+ ],
+ host: {
+ os: "linux",
+ platform: "ubuntu",
+ kernel_version: "5.15.0",
+ },
+ net: [
+ {
+ name: "eth0",
+ bytes_sent: Math.random() * 10_000_000,
+ bytes_recv: Math.random() * 10_000_000,
+ packets_sent: Math.random() * 1_000_000,
+ packets_recv: Math.random() * 1_000_000,
+ err_in: 0,
+ err_out: 0,
+ drop_in: 0,
+ drop_out: 0,
+ fifo_in: 0,
+ fifo_out: 0,
+ },
+ ],
+ errors: i % 50 === 0 ? [{ metric: ["uptime"], err: "500" }] : [],
+ });
+
+ if (docs.length === batchSize) {
+ await CheckModel.insertMany(docs, { ordered: false });
+ console.log(`Inserted ${i + 1} / ${total}`);
+ docs.length = 0;
+ }
+ }
+
+ if (docs.length > 0) {
+ await CheckModel.insertMany(docs, { ordered: false });
+ }
+
+ await mongoose.disconnect();
+ const duration = ((Date.now() - startTime) / 1000).toFixed(2);
+ console.log(`Finished inserting ${total} checks in ${duration}s`);
+}
+
+run().catch((error) => {
+ console.error("Failed to seed checks", error);
+ process.exit(1);
+});
diff --git a/server/src/app.js b/server/src/app.ts
similarity index 65%
rename from server/src/app.js
rename to server/src/app.ts
index 9cabda6b6..04699964d 100644
--- a/server/src/app.js
+++ b/server/src/app.ts
@@ -1,33 +1,42 @@
import express from "express";
import path from "path";
-import { responseHandler } from "./middleware/responseHandler.js";
import cors from "cors";
import helmet from "helmet";
import compression from "compression";
import cookieParser from "cookie-parser";
-import languageMiddleware from "./middleware/languageMiddleware.js";
import swaggerUi from "swagger-ui-express";
-import { handleErrors } from "./middleware/handleErrors.js";
-import { setupRoutes } from "./config/routes.js";
-import { generalApiLimiter } from "./middleware/rateLimiter.js";
-import { sanitizeBody, sanitizeQuery } from "./middleware/sanitization.js";
+import { handleErrors } from "@/middleware/handleErrors.js";
+import { generalApiLimiter } from "@/middleware/rateLimiter.js";
+import { sanitizeBody, sanitizeQuery } from "@/middleware/sanitization.js";
+import { setupRoutes } from "@/config/routes.js";
+import { InitializedServices } from "@/config/services.js";
+import { InitializedControllers } from "@/config/controllers.js";
+import { EnvConfig } from "@/service/system/settingsService.js";
-export const createApp = ({ services, controllers, envSettings, frontendPath, openApiSpec }) => {
+export const createApp = ({
+ services,
+ controllers,
+ envSettings,
+ frontendPath,
+ openApiSpec,
+}: {
+ services: InitializedServices;
+ controllers: InitializedControllers;
+ envSettings: EnvConfig;
+ frontendPath: string;
+ openApiSpec: any;
+}) => {
const allowedOrigin = envSettings.clientHost;
-
const app = express();
app.use(generalApiLimiter);
// Static files
app.use(express.static(frontendPath));
- // Response handler
- app.use(responseHandler);
-
app.use(
cors({
origin: allowedOrigin,
methods: "GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS",
- allowedHeaders: "*",
+ allowedHeaders: ["Content-Type", "Authorization"],
credentials: true,
})
);
@@ -63,7 +72,6 @@ export const createApp = ({ services, controllers, envSettings, frontendPath, op
},
})
);
- app.use(languageMiddleware(services.stringService, services.translationService, services.settingsService));
// Swagger UI
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(openApiSpec));
@@ -74,7 +82,7 @@ export const createApp = ({ services, controllers, envSettings, frontendPath, op
});
// Main app routes
- setupRoutes(app, controllers);
+ setupRoutes(app, controllers, services);
// FE routes
app.get("*", (req, res) => {
diff --git a/server/src/config/controllers.js b/server/src/config/controllers.js
deleted file mode 100644
index 693e35ab8..000000000
--- a/server/src/config/controllers.js
+++ /dev/null
@@ -1,66 +0,0 @@
-import { createCommonDependencies } from "../controllers/baseController.js";
-
-// Services
-
-// Controllers
-import MonitorController from "../controllers/monitorController.js";
-import AuthController from "../controllers/authController.js";
-import SettingsController from "../controllers/settingsController.js";
-import CheckController from "../controllers/checkController.js";
-import InviteController from "../controllers/inviteController.js";
-import MaintenanceWindowController from "../controllers/maintenanceWindowController.js";
-import QueueController from "../controllers/queueController.js";
-import LogController from "../controllers/logController.js";
-import StatusPageController from "../controllers/statusPageController.js";
-import NotificationController from "../controllers/notificationController.js";
-import DiagnosticController from "../controllers/diagnosticController.js";
-
-export const initializeControllers = (services) => {
- const controllers = {};
- const commonDependencies = createCommonDependencies(services.db, services.errorService, services.logger, services.stringService);
-
- controllers.authController = new AuthController(commonDependencies, {
- settingsService: services.settingsService,
- emailService: services.emailService,
- jobQueue: services.jobQueue,
- userService: services.userService,
- });
-
- controllers.monitorController = new MonitorController(commonDependencies, {
- settingsService: services.settingsService,
- jobQueue: services.jobQueue,
- emailService: services.emailService,
- monitorService: services.monitorService,
- });
-
- controllers.settingsController = new SettingsController(commonDependencies, {
- settingsService: services.settingsService,
- emailService: services.emailService,
- });
- controllers.checkController = new CheckController(commonDependencies, {
- settingsService: services.settingsService,
- checkService: services.checkService,
- });
- controllers.inviteController = new InviteController(commonDependencies, {
- inviteService: services.inviteService,
- });
-
- controllers.maintenanceWindowController = new MaintenanceWindowController(commonDependencies, {
- settingsService: services.settingsService,
- maintenanceWindowService: services.maintenanceWindowService,
- });
- controllers.queueController = new QueueController(commonDependencies, {
- jobQueue: services.jobQueue,
- });
- controllers.logController = new LogController(commonDependencies);
- controllers.statusPageController = new StatusPageController(commonDependencies);
- controllers.notificationController = new NotificationController(commonDependencies, {
- notificationService: services.notificationService,
- statusService: services.statusService,
- });
- controllers.diagnosticController = new DiagnosticController(commonDependencies, {
- diagnosticService: services.diagnosticService,
- });
-
- return controllers;
-};
diff --git a/server/src/config/controllers.ts b/server/src/config/controllers.ts
new file mode 100644
index 000000000..7a20ed93c
--- /dev/null
+++ b/server/src/config/controllers.ts
@@ -0,0 +1,44 @@
+import MonitorController from "../controllers/monitorController.js";
+import AuthController from "../controllers/authController.js";
+import SettingsController from "../controllers/settingsController.js";
+import CheckController from "../controllers/checkController.js";
+import InviteController from "../controllers/inviteController.js";
+import MaintenanceWindowController from "../controllers/maintenanceWindowController.js";
+import QueueController from "../controllers/queueController.js";
+import LogController from "../controllers/logController.js";
+import StatusPageController from "../controllers/statusPageController.js";
+import NotificationController from "../controllers/notificationController.js";
+import DiagnosticController from "../controllers/diagnosticController.js";
+import IncidentController from "../controllers/incidentController.js";
+import type { InitializedServices } from "@/config/services.js";
+
+export interface InitializedControllers {
+ authController: AuthController;
+ monitorController: MonitorController;
+ settingsController: SettingsController;
+ checkController: CheckController;
+ inviteController: InviteController;
+ maintenanceWindowController: MaintenanceWindowController;
+ queueController: QueueController;
+ logController: LogController;
+ statusPageController: StatusPageController;
+ notificationController: NotificationController;
+ diagnosticController: DiagnosticController;
+ incidentController: IncidentController;
+}
+export const initializeControllers = (services: InitializedServices): InitializedControllers => {
+ return {
+ authController: new AuthController(services.userService),
+ monitorController: new MonitorController(services.monitorService),
+ settingsController: new SettingsController(services.settingsService, services.emailService, services.db),
+ checkController: new CheckController(services.checkService),
+ inviteController: new InviteController(services.inviteService),
+ maintenanceWindowController: new MaintenanceWindowController(services.maintenanceWindowService),
+ queueController: new QueueController(services.jobQueue),
+ logController: new LogController(services.logger),
+ statusPageController: new StatusPageController(services.statusPageService, services.monitorsRepository, services.settingsService),
+ notificationController: new NotificationController(services.notificationsService, services.monitorsRepository),
+ diagnosticController: new DiagnosticController(services.diagnosticService),
+ incidentController: new IncidentController(services.incidentService),
+ };
+};
diff --git a/server/src/config/database.js b/server/src/config/database.js
deleted file mode 100644
index e69de29bb..000000000
diff --git a/server/src/config/routes.js b/server/src/config/routes.ts
similarity index 78%
rename from server/src/config/routes.js
rename to server/src/config/routes.ts
index 7056393f9..d38ac69ae 100644
--- a/server/src/config/routes.js
+++ b/server/src/config/routes.ts
@@ -1,4 +1,4 @@
-import { verifyJWT } from "../middleware/verifyJWT.js";
+import { createVerifyJWT } from "../middleware/verifyJWT.js";
import { authApiLimiter } from "../middleware/rateLimiter.js";
import AuthRoutes from "../routes/authRoute.js";
@@ -13,18 +13,22 @@ import LogRoutes from "../routes/logRoutes.js";
import DiagnosticRoutes from "../routes/diagnosticRoute.js";
import NotificationRoutes from "../routes/notificationRoute.js";
-export const setupRoutes = (app, controllers) => {
- const authRoutes = new AuthRoutes(controllers.authController);
+import IncidentRoutes from "../routes/incidentRoute.js";
+
+export const setupRoutes = (app: any, controllers: Record, services: Record) => {
+ const verifyJWT = createVerifyJWT(services.settingsService);
+ const authRoutes = new AuthRoutes(controllers.authController, verifyJWT);
const monitorRoutes = new MonitorRoutes(controllers.monitorController);
const settingsRoutes = new SettingsRoutes(controllers.settingsController);
const checkRoutes = new CheckRoutes(controllers.checkController);
- const inviteRoutes = new InviteRoutes(controllers.inviteController);
+ const inviteRoutes = new InviteRoutes(controllers.inviteController, verifyJWT);
const maintenanceWindowRoutes = new MaintenanceWindowRoutes(controllers.maintenanceWindowController);
const queueRoutes = new QueueRoutes(controllers.queueController);
const logRoutes = new LogRoutes(controllers.logController);
- const statusPageRoutes = new StatusPageRoutes(controllers.statusPageController);
+ const statusPageRoutes = new StatusPageRoutes(controllers.statusPageController, verifyJWT);
const notificationRoutes = new NotificationRoutes(controllers.notificationController);
- const diagnosticRoutes = new DiagnosticRoutes(controllers.diagnosticController);
+ const diagnosticRoutes = new DiagnosticRoutes(controllers.diagnosticController, verifyJWT);
+ const incidentRoutes = new IncidentRoutes(controllers.incidentController);
app.use("/api/v1/auth", authApiLimiter, authRoutes.getRouter());
app.use("/api/v1/monitors", verifyJWT, monitorRoutes.getRouter());
@@ -37,4 +41,5 @@ export const setupRoutes = (app, controllers) => {
app.use("/api/v1/status-page", statusPageRoutes.getRouter());
app.use("/api/v1/notifications", verifyJWT, notificationRoutes.getRouter());
app.use("/api/v1/diagnostic", verifyJWT, diagnosticRoutes.getRouter());
+ app.use("/api/v1/incidents", verifyJWT, incidentRoutes.getRouter());
};
diff --git a/server/src/config/services.js b/server/src/config/services.js
deleted file mode 100644
index 08c2525f3..000000000
--- a/server/src/config/services.js
+++ /dev/null
@@ -1,237 +0,0 @@
-import ServiceRegistry from "../service/system/serviceRegistry.js";
-import TranslationService from "../service/system/translationService.js";
-import StringService from "../service/system/stringService.js";
-import MongoDB from "../db/mongo/MongoDB.js";
-import NetworkService from "../service/infrastructure/networkService.js";
-import EmailService from "../service/infrastructure/emailService.js";
-import BufferService from "../service/infrastructure/bufferService.js";
-import StatusService from "../service/infrastructure/statusService.js";
-import NotificationUtils from "../service/infrastructure/notificationUtils.js";
-import NotificationService from "../service/infrastructure/notificationService.js";
-import ErrorService from "../service/infrastructure/errorService.js";
-import SuperSimpleQueueHelper from "../service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js";
-import SuperSimpleQueue from "../service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js";
-import UserService from "../service/business/userService.js";
-import CheckService from "../service/business/checkService.js";
-import DiagnosticService from "../service/business/diagnosticService.js";
-import InviteService from "../service/business/inviteService.js";
-import MaintenanceWindowService from "../service/business/maintenanceWindowService.js";
-import MonitorService from "../service/business/monitorService.js";
-import papaparse from "papaparse";
-import axios from "axios";
-import got from "got";
-import ping from "ping";
-import http from "http";
-import https from "https";
-import Docker from "dockerode";
-import net from "net";
-import fs from "fs";
-import path from "path";
-import nodemailer from "nodemailer";
-import pkg from "handlebars";
-const { compile } = pkg;
-import mjml2html from "mjml";
-import jwt from "jsonwebtoken";
-import crypto from "crypto";
-import { games } from "gamedig";
-import jmespath from "jmespath";
-import { GameDig } from "gamedig";
-
-import { fileURLToPath } from "url";
-import { ObjectId } from "mongodb";
-
-// DB Modules
-import { NormalizeData, NormalizeDataUptimeDetails } from "../utils/dataUtils.js";
-import { GenerateAvatarImage } from "../utils/imageProcessing.js";
-import { ParseBoolean } from "../utils/utils.js";
-
-// Models
-import Check from "../db/models/Check.js";
-import Monitor from "../db/models/Monitor.js";
-import User from "../db/models/User.js";
-import InviteToken from "../db/models/InviteToken.js";
-import StatusPage from "../db/models/StatusPage.js";
-import Team from "../db/models/Team.js";
-import MaintenanceWindow from "../db/models/MaintenanceWindow.js";
-import MonitorStats from "../db/models/MonitorStats.js";
-import Notification from "../db/models/Notification.js";
-import RecoveryToken from "../db/models/RecoveryToken.js";
-import AppSettings from "../db/models/AppSettings.js";
-
-import InviteModule from "../db/mongo/modules/inviteModule.js";
-import CheckModule from "../db/mongo/modules/checkModule.js";
-import StatusPageModule from "../db/mongo/modules/statusPageModule.js";
-import UserModule from "../db/mongo/modules/userModule.js";
-import MaintenanceWindowModule from "../db/mongo/modules/maintenanceWindowModule.js";
-import MonitorModule from "../db/mongo/modules/monitorModule.js";
-import NotificationModule from "../db/mongo/modules/notificationModule.js";
-import RecoveryModule from "../db/mongo/modules/recoveryModule.js";
-import SettingsModule from "../db/mongo/modules/settingsModule.js";
-
-export const initializeServices = async ({ logger, envSettings, settingsService }) => {
- const serviceRegistry = new ServiceRegistry({ logger });
- ServiceRegistry.instance = serviceRegistry;
-
- const translationService = new TranslationService(logger);
- await translationService.initialize();
-
- const stringService = new StringService(translationService);
-
- // Create DB
- const checkModule = new CheckModule({ logger, Check, Monitor, User });
- const inviteModule = new InviteModule({ InviteToken, crypto, stringService });
- const statusPageModule = new StatusPageModule({ StatusPage, NormalizeData, stringService });
- const userModule = new UserModule({ User, Team, GenerateAvatarImage, ParseBoolean, stringService });
- const maintenanceWindowModule = new MaintenanceWindowModule({ MaintenanceWindow });
- const monitorModule = new MonitorModule({
- Monitor,
- MonitorStats,
- Check,
- stringService,
- fs,
- path,
- fileURLToPath,
- ObjectId,
- NormalizeData,
- NormalizeDataUptimeDetails,
- });
- const notificationModule = new NotificationModule({ Notification, Monitor });
- const recoveryModule = new RecoveryModule({ User, RecoveryToken, crypto, stringService });
- const settingsModule = new SettingsModule({ AppSettings });
-
- const db = new MongoDB({
- logger,
- envSettings,
- checkModule,
- inviteModule,
- statusPageModule,
- userModule,
- maintenanceWindowModule,
- monitorModule,
- notificationModule,
- recoveryModule,
- settingsModule,
- });
-
- await db.connect();
-
- const networkService = new NetworkService({
- axios,
- got,
- https,
- jmespath,
- GameDig,
- ping,
- logger,
- http,
- Docker,
- net,
- stringService,
- settingsService,
- });
- const emailService = new EmailService(settingsService, fs, path, compile, mjml2html, nodemailer, logger);
- const bufferService = new BufferService({ db, logger, envSettings });
- const statusService = new StatusService({ db, logger, buffer: bufferService });
-
- const notificationUtils = new NotificationUtils({
- stringService,
- emailService,
- });
-
- const notificationService = new NotificationService({
- emailService,
- db,
- logger,
- networkService,
- stringService,
- notificationUtils,
- });
-
- const errorService = new ErrorService();
-
- const superSimpleQueueHelper = new SuperSimpleQueueHelper({
- db,
- logger,
- networkService,
- statusService,
- notificationService,
- });
-
- const superSimpleQueue = await SuperSimpleQueue.create({
- envSettings,
- db,
- logger,
- helper: superSimpleQueueHelper,
- });
-
- // Business services
- const userService = new UserService({
- crypto,
- db,
- emailService,
- settingsService,
- logger,
- stringService,
- jwt,
- errorService,
- jobQueue: superSimpleQueue,
- });
- const checkService = new CheckService({
- db,
- settingsService,
- stringService,
- errorService,
- });
- const diagnosticService = new DiagnosticService();
- const inviteService = new InviteService({
- db,
- settingsService,
- emailService,
- stringService,
- errorService,
- });
- const maintenanceWindowService = new MaintenanceWindowService({
- db,
- settingsService,
- stringService,
- errorService,
- });
- const monitorService = new MonitorService({
- db,
- settingsService,
- jobQueue: superSimpleQueue,
- stringService,
- emailService,
- papaparse,
- logger,
- errorService,
- games,
- });
-
- const services = {
- settingsService,
- translationService,
- stringService,
- db,
- networkService,
- emailService,
- bufferService,
- statusService,
- notificationService,
- jobQueue: superSimpleQueue,
- userService,
- checkService,
- diagnosticService,
- inviteService,
- maintenanceWindowService,
- monitorService,
- errorService,
- logger,
- };
-
- Object.values(services).forEach((service) => {
- ServiceRegistry.register(service.serviceName, service);
- });
-
- return services;
-};
diff --git a/server/src/config/services.ts b/server/src/config/services.ts
new file mode 100644
index 000000000..06701491d
--- /dev/null
+++ b/server/src/config/services.ts
@@ -0,0 +1,292 @@
+import MongoDB from "../db/MongoDB.js";
+import NetworkService from "../service/infrastructure/networkService.js";
+import EmailService from "../service/infrastructure/emailService.js";
+import BufferService from "../service/infrastructure/bufferService.js";
+import {
+ NotificationsService,
+ StatusService,
+ WebhookProvider,
+ SlackProvider,
+ EmailProvider,
+ DiscordProvider,
+ PagerDutyProvider,
+ MatrixProvider,
+ INotificationsService,
+} from "@/service/index.js";
+import SuperSimpleQueueHelper from "../service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js";
+import SuperSimpleQueue from "../service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js";
+import UserService from "../service/business/userService.js";
+import CheckService from "../service/business/checkService.js";
+import DiagnosticService from "../service/business/diagnosticService.js";
+import InviteService from "../service/business/inviteService.js";
+import MaintenanceWindowService from "../service/business/maintenanceWindowService.js";
+import { MonitorService } from "@/service/index.js";
+import { StatusPageService, IStatusPageService } from "../service/business/statusPageService.js";
+import IncidentService from "../service/business/incidentService.js";
+import axios from "axios";
+import got from "got";
+import ping from "ping";
+import http from "http";
+import https from "https";
+import Docker from "dockerode";
+import net from "net";
+import fs from "fs";
+import path from "path";
+import nodemailer from "nodemailer";
+import pkg from "handlebars";
+const { compile } = pkg;
+import mjml2html from "mjml";
+import jwt from "jsonwebtoken";
+import crypto from "crypto";
+import { games, GameDig } from "gamedig";
+import jmespath from "jmespath";
+
+// DB Modules
+import { GenerateAvatarImage } from "../utils/imageProcessing.js";
+import { ParseBoolean } from "../utils/utils.js";
+
+// Models
+import InviteToken from "../db/models/Invite.js";
+import Team from "../db/models/Team.js";
+import MaintenanceWindow from "../db/models/MaintenanceWindow.js";
+import MonitorStats from "../db/models/MonitorStats.js";
+import NotificationModel from "../db/models/Notification.js";
+import RecoveryToken from "../db/models/RecoveryToken.js";
+
+// repositories
+import {
+ MongoMonitorsRepository,
+ MongoChecksRepository,
+ MongoMonitorStatsRepository,
+ MongoStatusPagesRepository,
+ MongoUsersRepository,
+ MongoInvitesRepository,
+ MongoRecoveryTokensRepository,
+ MongoNotificationsRepository,
+ MongoIncidentRepository,
+ MongoTeamsRepository,
+ MongoMaintenanceWindowsRepository,
+ IMonitorsRepository,
+ IChecksRepository,
+ IMonitorStatsRepository,
+ IStatusPagesRepository,
+ IUsersRepository,
+ IInvitesRepository,
+ IRecoveryTokensRepository,
+ ISettingsRepository,
+ INotificationsRepository,
+ IIncidentsRepository,
+ ITeamsRepository,
+ IMaintenanceWindowsRepository,
+} from "@/repositories/index.js";
+import { ILogger } from "@/utils/logger.js";
+import { EnvConfig } from "@/service/system/settingsService.js";
+
+export type InitializedServices = {
+ settingsService: any;
+ db: any;
+ networkService: any;
+ emailService: any;
+ bufferService: any;
+ statusService: any;
+ jobQueue: any;
+ userService: any;
+ checkService: any;
+ diagnosticService: any;
+ inviteService: any;
+ maintenanceWindowService: any;
+ monitorService: any;
+ incidentService: any;
+ logger: any;
+ notificationsService: INotificationsService;
+ statusPageService: IStatusPageService;
+
+ // Repositories
+ monitorsRepository: IMonitorsRepository;
+ checksRepository: IChecksRepository;
+ monitorStatsRepository: IMonitorStatsRepository;
+ statusPagesRepository: IStatusPagesRepository;
+ usersRepository: IUsersRepository;
+ invitesRepository: IInvitesRepository;
+ recoveryTokensRepository: IRecoveryTokensRepository;
+ settingsRepository: ISettingsRepository;
+ notificationsRepository: INotificationsRepository;
+ incidentsRepository: IIncidentsRepository;
+ teamsRepository: ITeamsRepository;
+ maintenanceWindowsRepository: IMaintenanceWindowsRepository;
+};
+
+export const initializeServices = async ({
+ logger,
+ envSettings,
+ settingsService,
+ settingsRepository,
+}: {
+ logger: ILogger;
+ envSettings: EnvConfig;
+ settingsService: any;
+ settingsRepository: ISettingsRepository;
+}): Promise => {
+ // Create DB
+
+ const db = new MongoDB(logger, envSettings);
+
+ await db.connect();
+
+ // Repositories
+ const monitorsRepository = new MongoMonitorsRepository();
+ const checksRepository = new MongoChecksRepository(logger);
+ const monitorStatsRepository = new MongoMonitorStatsRepository();
+ const statusPagesRepository = new MongoStatusPagesRepository();
+ const usersRepository = new MongoUsersRepository();
+ const invitesRepository = new MongoInvitesRepository();
+ const recoveryTokensRepository = new MongoRecoveryTokensRepository();
+ const notificationsRepository = new MongoNotificationsRepository();
+ const incidentsRepository = new MongoIncidentRepository();
+ const teamsRepository = new MongoTeamsRepository();
+ const maintenanceWindowsRepository = new MongoMaintenanceWindowsRepository();
+
+ const networkService = new NetworkService({
+ axios,
+ got,
+ https,
+ jmespath,
+ GameDig,
+ ping,
+ logger,
+ http,
+ Docker,
+ net,
+ settingsService,
+ });
+ const emailService = new EmailService(settingsService, fs, path, compile, mjml2html, nodemailer, logger);
+
+ const incidentService = new IncidentService({
+ logger,
+ incidentsRepository,
+ monitorsRepository,
+ usersRepository,
+ });
+
+ const checkService = new CheckService({
+ monitorsRepository,
+ logger,
+ checksRepository,
+ });
+
+ const bufferService = new BufferService({ logger, checkService, settingsService });
+
+ const statusService = new StatusService({ db, logger, buffer: bufferService, monitorsRepository });
+
+ const webhookProvider = new WebhookProvider(logger);
+ const slackProvider = new SlackProvider(logger);
+ const emailProvider = new EmailProvider(emailService, logger);
+ const discordProvider = new DiscordProvider(logger);
+ const pagerDutyProvider = new PagerDutyProvider(logger);
+ const matrixProvider = new MatrixProvider(logger);
+
+ const notificationsService = new NotificationsService(
+ notificationsRepository,
+ monitorsRepository,
+ webhookProvider,
+ emailProvider,
+ slackProvider,
+ discordProvider,
+ pagerDutyProvider,
+ matrixProvider,
+ logger
+ );
+
+ const superSimpleQueueHelper = new SuperSimpleQueueHelper({
+ logger,
+ networkService,
+ statusService,
+ notificationsService,
+ checkService,
+ buffer: bufferService,
+ incidentService,
+ maintenanceWindowsRepository,
+ });
+
+ const superSimpleQueue = await SuperSimpleQueue.create({
+ logger,
+ helper: superSimpleQueueHelper,
+ monitorsRepository,
+ });
+
+ // Business services
+ const userService = new UserService({
+ crypto,
+ emailService,
+ settingsService,
+ logger,
+ jwt,
+ jobQueue: superSimpleQueue,
+ monitorsRepository,
+ usersRepository,
+ invitesRepository,
+ recoveryTokensRepository,
+ settingsRepository,
+ teamsRepository,
+ });
+
+ const diagnosticService = new DiagnosticService();
+ const inviteService = new InviteService({
+ invitesRepository,
+ settingsService,
+ emailService,
+ });
+ const maintenanceWindowService = new MaintenanceWindowService({
+ monitorsRepository,
+ maintenanceWindowsRepository,
+ });
+ const monitorService = new MonitorService({
+ jobQueue: superSimpleQueue,
+ emailService,
+ logger,
+ games,
+ monitorsRepository,
+ checksRepository,
+ monitorStatsRepository,
+ statusPagesRepository,
+ });
+
+ const statusPageService = new StatusPageService(statusPagesRepository);
+
+ const services = {
+ //v1
+ settingsService,
+ db,
+ networkService,
+ emailService,
+ bufferService,
+ statusService,
+ jobQueue: superSimpleQueue,
+ userService,
+ checkService,
+ diagnosticService,
+ inviteService,
+ maintenanceWindowService,
+ monitorService,
+ incidentService,
+ logger,
+ notificationsService,
+ statusPageService,
+
+ // Repositories
+ monitorsRepository,
+ checksRepository,
+ monitorStatsRepository,
+ statusPagesRepository,
+ usersRepository,
+ invitesRepository,
+ recoveryTokensRepository,
+ settingsRepository,
+ notificationsRepository,
+ incidentsRepository,
+ teamsRepository,
+ maintenanceWindowsRepository,
+ };
+
+ return services;
+};
diff --git a/server/src/controllers/announcementsController.js b/server/src/controllers/announcementsController.js
deleted file mode 100755
index 430859791..000000000
--- a/server/src/controllers/announcementsController.js
+++ /dev/null
@@ -1,77 +0,0 @@
-import { createAnnouncementValidation } from "../validation/joi.js";
-import BaseController from "./baseController.js";
-
-const SERVICE_NAME = "announcementController";
-
-/**
- * Controller for managing announcements in the system.
- * This class handles the creation of new announcements.
- *
- * @class AnnouncementController
- */
-
-class AnnouncementController extends BaseController {
- static SERVICE_NAME = SERVICE_NAME;
- constructor(commonDependencies) {
- super(commonDependencies);
- this.createAnnouncement = this.createAnnouncement.bind(this);
- this.getAnnouncement = this.getAnnouncement.bind(this);
- }
-
- get serviceName() {
- return AnnouncementController.SERVICE_NAME;
- }
-
- /**
- * Handles the creation of a new announcement.
- *
- * @async
- * @param {Object} req - The request object, containing the announcement data in the body.
- * @param {Object} res - The response object used to send the result back to the client.
- * @param {Function} next - The next middleware function in the stack for error handling.
- *
- * @returns {Promise} A promise that resolves once the response is sent.
- */
- createAnnouncement = asyncHandler(
- async (req, res, next) => {
- await createAnnouncementValidation.validateAsync(req.body);
- const { title, message } = req.body;
- const announcementData = {
- title: title.trim(),
- message: message.trim(),
- userId: req.user._id,
- };
-
- const newAnnouncement = await this.db.createAnnouncement(announcementData);
- return res.success({
- msg: this.stringService.createAnnouncement,
- data: newAnnouncement,
- });
- },
- SERVICE_NAME,
- "createAnnouncement"
- );
-
- /**
- * Handles retrieving announcements with pagination.
- *
- * @async
- * @param {Object} res - The response object used to send the result back to the client.
- * - `data`: The list of announcements to be sent back to the client.
- * - `msg`: A message about the success of the request.
- * @param {Function} next - The next middleware function in the stack for error handling.
- */
- getAnnouncement = asyncHandler(
- async (req, res, next) => {
- const allAnnouncements = await this.db.getAnnouncements();
- return res.success({
- msg: this.stringService.getAnnouncement,
- data: allAnnouncements,
- });
- },
- SERVICE_NAME,
- "getAnnouncement"
- );
-}
-
-export default AnnouncementController;
diff --git a/server/src/controllers/authController.js b/server/src/controllers/authController.js
deleted file mode 100755
index c186e8423..000000000
--- a/server/src/controllers/authController.js
+++ /dev/null
@@ -1,461 +0,0 @@
-import BaseController from "./baseController.js";
-import {
- registrationBodyValidation,
- loginValidation,
- editUserBodyValidation,
- recoveryValidation,
- recoveryTokenBodyValidation,
- newPasswordValidation,
- getUserByIdParamValidation,
- editUserByIdParamValidation,
- editUserByIdBodyValidation,
- editSuperadminUserByIdBodyValidation,
-} from "../validation/joi.js";
-
-const SERVICE_NAME = "authController";
-
-/**
- * Authentication Controller
- *
- * Handles all authentication-related HTTP requests including user registration,
- * login, password recovery, and user management operations.
- *
- * @class AuthController
- * @description Manages user authentication and authorization operations
- */
-class AuthController extends BaseController {
- static SERVICE_NAME = SERVICE_NAME;
- /**
- * Creates an instance of AuthController.
- *
- * @param {Object} commonDependencies - Common dependencies injected into the controller
- * @param {Object} dependencies - The dependencies required by the controller
- * @param {Object} dependencies.settingsService - Service for application settings
- * @param {Object} dependencies.emailService - Service for email operations
- * @param {Object} dependencies.jobQueue - Service for job queue operations
- * @param {Object} dependencies.userService - User business logic service
- */
- constructor(commonDependencies, { settingsService, emailService, jobQueue, userService }) {
- super(commonDependencies);
- this.settingsService = settingsService;
- this.emailService = emailService;
- this.jobQueue = jobQueue;
- this.userService = userService;
- }
-
- get serviceName() {
- return AuthController.SERVICE_NAME;
- }
-
- /**
- * Registers a new user in the system.
- *
- * @async
- * @function registerUser
- * @param {Object} req - Express request object
- * @param {Object} req.body - Request body containing user registration data
- * @param {string} req.body.firstName - User's first name
- * @param {string} req.body.lastName - User's last name
- * @param {string} req.body.email - User's email address (will be converted to lowercase)
- * @param {string} req.body.password - User's password
- * @param {string} [req.body.inviteToken] - Invite token for registration (required if superadmin exists)
- * @param {string} [req.body.teamId] - Team ID (auto-assigned if superadmin)
- * @param {Array} [req.body.role] - User roles (auto-assigned if superadmin)
- * @param {Object} [req.file] - Profile image file uploaded via multer
- * @param {Object} res - Express response object
- * @returns {Promise} Success response with user data and JWT token
- * @throws {Error} 422 - Validation error if request body is invalid
- * @throws {Error} 409 - Conflict if user already exists
- * @example
- * // Register first user (becomes superadmin)
- * POST /auth/register
- * {
- * "firstName": "John",
- * "lastName": "Doe",
- * "email": "john@example.com",
- * "password": "SecurePass123!"
- * }
- *
- * // Register subsequent user (requires invite token)
- * POST /auth/register
- * {
- * "firstName": "Jane",
- * "lastName": "Smith",
- * "email": "jane@example.com",
- * "password": "SecurePass123!",
- * "inviteToken": "abc123..."
- * }
- */
- registerUser = this.asyncHandler(
- async (req, res) => {
- if (req.body?.email) {
- req.body.email = req.body.email?.toLowerCase();
- }
- await registrationBodyValidation.validateAsync(req.body);
- const { user, token } = await this.userService.registerUser(req.body, req.file);
- res.success({
- msg: this.stringService.authCreateUser,
- data: { user, token },
- });
- },
- SERVICE_NAME,
- "registerUser"
- );
-
- /**
- * Authenticates a user and returns a JWT token.
- *
- * @async
- * @function loginUser
- * @param {Object} req - Express request object
- * @param {Object} req.body - Request body containing login credentials
- * @param {string} req.body.email - User's email address (will be converted to lowercase)
- * @param {string} req.body.password - User's password
- * @param {Object} res - Express response object
- * @returns {Promise} Success response with user data and JWT token
- * @throws {Error} 422 - Validation error if request body is invalid
- * @throws {Error} 401 - Unauthorized if credentials are incorrect
- * @example
- * POST /auth/login
- * {
- * "email": "john@example.com",
- * "password": "SecurePass123!"
- * }
- */
- loginUser = this.asyncHandler(
- async (req, res) => {
- if (req.body?.email) {
- req.body.email = req.body.email?.toLowerCase();
- }
- await loginValidation.validateAsync(req.body);
- const { user, token } = await this.userService.loginUser(req.body.email, req.body.password);
-
- return res.success({
- msg: this.stringService.authLoginUser,
- data: {
- user,
- token,
- },
- });
- },
- SERVICE_NAME,
- "loginUser"
- );
-
- /**
- * Updates the current user's profile information.
- *
- * @async
- * @function editUser
- * @param {Object} req - Express request object
- * @param {Object} req.body - Request body containing user update data
- * @param {string} [req.body.firstName] - Updated first name
- * @param {string} [req.body.lastName] - Updated last name
- * @param {string} [req.body.password] - Current password (required for password change)
- * @param {string} [req.body.newPassword] - New password (required for password change)
- * @param {boolean} [req.body.deleteProfileImage] - Flag to delete profile image
- * @param {Object} [req.file] - New profile image file
- * @param {Object} req.user - Current authenticated user (from JWT)
- * @param {Object} res - Express response object
- * @returns {Promise} Success response with updated user data
- * @throws {Error} 422 - Validation error if request body is invalid
- * @throws {Error} 403 - Forbidden if current password is incorrect
- * @example
- * PUT /auth/user
- * {
- * "firstName": "John Updated",
- * "lastName": "Doe Updated"
- * }
- *
- * // Change password
- * PUT /auth/user
- * {
- * "password": "OldPass123!",
- * "newPassword": "NewPass123!"
- * }
- */
- editUser = this.asyncHandler(
- async (req, res) => {
- await editUserBodyValidation.validateAsync(req.body);
-
- const updatedUser = await this.userService.editUser(req.body, req.file, req.user);
-
- res.success({
- msg: this.stringService.authUpdateUser,
- data: updatedUser,
- });
- },
- SERVICE_NAME,
- "editUser"
- );
-
- /**
- * Checks if a superadmin account exists in the system.
- *
- * @async
- * @function checkSuperadminExists
- * @param {Object} req - Express request object
- * @param {Object} res - Express response object
- * @returns {Promise} Success response with boolean indicating superadmin existence
- * @example
- * GET /auth/users/superadmin
- * // Response: { "data": true } or { "data": false }
- */
- checkSuperadminExists = this.asyncHandler(
- async (req, res) => {
- const superAdminExists = await this.userService.checkSuperadminExists();
- return res.success({
- msg: this.stringService.authAdminExists,
- data: superAdminExists,
- });
- },
- SERVICE_NAME,
- "checkSuperadminExists"
- );
-
- /**
- * Initiates password recovery process by sending a recovery email.
- *
- * @async
- * @function requestRecovery
- * @param {Object} req - Express request object
- * @param {Object} req.body - Request body containing email
- * @param {string} req.body.email - Email address for password recovery
- * @param {Object} res - Express response object
- * @returns {Promise} Success response with message ID
- * @throws {Error} 422 - Validation error if email is invalid
- * @throws {Error} 404 - Not found if user doesn't exist
- * @example
- * POST /auth/recovery/request
- * {
- * "email": "john@example.com"
- * }
- */
- requestRecovery = this.asyncHandler(
- async (req, res) => {
- await recoveryValidation.validateAsync(req.body);
- const email = req?.body?.email;
- const msgId = await this.userService.requestRecovery(email);
- return res.success({
- msg: this.stringService.authCreateRecoveryToken,
- data: msgId,
- });
- },
- SERVICE_NAME,
- "requestRecovery"
- );
-
- /**
- * Validates a password recovery token.
- *
- * @async
- * @function validateRecovery
- * @param {Object} req - Express request object
- * @param {Object} req.body - Request body containing recovery token
- * @param {string} req.body.recoveryToken - Recovery token to validate
- * @param {Object} res - Express response object
- * @returns {Promise} Success response if token is valid
- * @throws {Error} 422 - Validation error if token format is invalid
- * @throws {Error} 400 - Bad request if token is invalid or expired
- * @example
- * POST /auth/recovery/validate
- * {
- * "recoveryToken": "abc123..."
- * }
- */
- validateRecovery = this.asyncHandler(
- async (req, res) => {
- await recoveryTokenBodyValidation.validateAsync(req.body);
- await this.userService.validateRecovery(req.body.recoveryToken);
- return res.success({
- msg: this.stringService.authVerifyRecoveryToken,
- });
- },
- SERVICE_NAME,
- "validateRecovery"
- );
-
- /**
- * Resets user password using a valid recovery token.
- *
- * @async
- * @function resetPassword
- * @param {Object} req - Express request object
- * @param {Object} req.body - Request body containing new password and recovery token
- * @param {string} req.body.password - New password
- * @param {string} req.body.recoveryToken - Valid recovery token
- * @param {Object} res - Express response object
- * @returns {Promise} Success response with user data and JWT token
- * @throws {Error} 422 - Validation error if password format is invalid
- * @throws {Error} 400 - Bad request if token is invalid or expired
- * @example
- * POST /auth/recovery/reset
- * {
- * "password": "NewSecurePass123!",
- * "recoveryToken": "abc123..."
- * }
- */
- resetPassword = this.asyncHandler(
- async (req, res) => {
- await newPasswordValidation.validateAsync(req.body);
- const { user, token } = await this.userService.resetPassword(req.body.password, req.body.recoveryToken);
- return res.success({
- msg: this.stringService.authResetPassword,
- data: { user, token },
- });
- },
- SERVICE_NAME,
- "resetPassword"
- );
-
- /**
- * Deletes the current user's account and associated data.
- *
- * @async
- * @function deleteUser
- * @param {Object} req - Express request object
- * @param {Object} req.user - Current authenticated user (from JWT)
- * @param {string} req.user._id - User ID
- * @param {string} req.user.email - User email
- * @param {string} req.user.teamId - User's team ID
- * @param {Array} req.user.role - User roles
- * @param {Object} res - Express response object
- * @returns {Promise} Success response confirming user deletion
- * @throws {Error} 400 - Bad request if user is demo user
- * @throws {Error} 404 - Not found if user doesn't exist
- * @example
- * DELETE /auth/user
- * // Requires JWT authentication
- */
- deleteUser = this.asyncHandler(
- async (req, res) => {
- await this.userService.deleteUser(req.user);
- return res.success({
- msg: this.stringService.authDeleteUser,
- });
- },
- SERVICE_NAME,
- "deleteUser"
- );
-
- /**
- * Retrieves all users in the system (admin/superadmin only).
- *
- * @async
- * @function getAllUsers
- * @param {Object} req - Express request object
- * @param {Object} res - Express response object
- * @returns {Promise} Success response with array of users
- * @throws {Error} 403 - Forbidden if user doesn't have admin/superadmin role
- * @example
- * GET /auth/users
- * // Requires JWT authentication with admin/superadmin role
- */
- getAllUsers = this.asyncHandler(
- async (req, res) => {
- const allUsers = await this.userService.getAllUsers();
- return res.success({
- msg: this.stringService.authGetAllUsers,
- data: allUsers,
- });
- },
- SERVICE_NAME,
- "getAllUsers"
- );
-
- /**
- * Retrieves a specific user by ID (superadmin only).
- *
- * @async
- * @function getUserById
- * @param {Object} req - Express request object
- * @param {Object} req.params - URL parameters
- * @param {string} req.params.userId - ID of the user to retrieve
- * @param {Object} req.user - Current authenticated user (from JWT)
- * @param {Array} req.user.role - Current user's roles
- * @param {Object} res - Express response object
- * @returns {Promise} Success response with user data
- * @throws {Error} 422 - Validation error if userId is invalid
- * @throws {Error} 403 - Forbidden if user doesn't have superadmin role
- * @throws {Error} 404 - Not found if user doesn't exist
- * @example
- * GET /auth/users/507f1f77bcf86cd799439011
- * // Requires JWT authentication with superadmin role
- */
- getUserById = this.asyncHandler(
- async (req, res) => {
- await getUserByIdParamValidation.validateAsync(req.params);
- const userId = req?.params?.userId;
- const roles = req?.user?.role;
-
- if (!userId) {
- throw new Error("No user ID in request");
- }
-
- if (!roles || roles.length === 0) {
- throw new Error("No roles in request");
- }
-
- const user = await this.userService.getUserById(roles, userId);
-
- return res.success({ msg: "ok", data: user });
- },
- SERVICE_NAME,
- "getUserById"
- );
-
- /**
- * Updates a specific user by ID (superadmin only).
- *
- * @async
- * @function editUserById
- * @param {Object} req - Express request object
- * @param {Object} req.params - URL parameters
- * @param {string} req.params.userId - ID of the user to update
- * @param {Object} req.body - Request body containing user update data
- * @param {string} [req.body.firstName] - Updated first name
- * @param {string} [req.body.lastName] - Updated last name
- * @param {Array} [req.body.role] - Updated user roles
- * @param {Object} req.user - Current authenticated user (from JWT)
- * @param {string} req.user._id - Current user's ID
- * @param {Array} req.user.role - Current user's roles
- * @param {Object} res - Express response object
- * @returns {Promise} Success response confirming user update
- * @throws {Error} 422 - Validation error if parameters or body are invalid
- * @throws {Error} 403 - Forbidden if user doesn't have superadmin role
- * @throws {Error} 404 - Not found if user doesn't exist
- * @example
- * PUT /auth/users/507f1f77bcf86cd799439011
- * {
- * "firstName": "Updated Name",
- * "role": ["admin"]
- * }
- * // Requires JWT authentication with superadmin role
- */
- editUserById = this.asyncHandler(
- async (req, res) => {
- const roles = req?.user?.role;
- if (!roles.includes("superadmin")) {
- throw createError("Unauthorized", 403);
- }
-
- const userId = req.params.userId;
- const user = { ...req.body };
-
- await editUserByIdParamValidation.validateAsync(req.params);
- // If this is superadmin self edit, allow "superadmin" role
- if (userId === req.user._id) {
- await editSuperadminUserByIdBodyValidation.validateAsync(req.body);
- } else {
- await editUserByIdBodyValidation.validateAsync(req.body);
- }
-
- await this.userService.editUserById(userId, user);
- return res.success({ msg: "ok" });
- },
- SERVICE_NAME,
- "editUserById"
- );
-}
-
-export default AuthController;
diff --git a/server/src/controllers/authController.ts b/server/src/controllers/authController.ts
new file mode 100755
index 000000000..23ea6e42e
--- /dev/null
+++ b/server/src/controllers/authController.ts
@@ -0,0 +1,237 @@
+import { Request, Response, NextFunction } from "express";
+import { AppError } from "@/utils/AppError.js";
+
+import {
+ registrationBodyValidation,
+ loginValidation,
+ editUserBodyValidation,
+ recoveryValidation,
+ recoveryTokenBodyValidation,
+ newPasswordValidation,
+ getUserByIdParamValidation,
+ editUserByIdParamValidation,
+ editUserByIdBodyValidation,
+ editSuperadminUserByIdBodyValidation,
+ editUserPasswordByIdBodyValidation,
+} from "@/validation/joi.js";
+
+const SERVICE_NAME = "authController";
+
+class AuthController {
+ static SERVICE_NAME = SERVICE_NAME;
+
+ private userService: any;
+
+ constructor(userService: any) {
+ this.userService = userService;
+ }
+
+ get serviceName() {
+ return AuthController.SERVICE_NAME;
+ }
+
+ registerUser = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const newUser = req.body.user;
+ const newUserToken = req.body.token;
+ if (newUser?.email) {
+ newUser.email = newUser.email.toLowerCase();
+ }
+ await registrationBodyValidation.validateAsync(newUser);
+
+ const { user, token } = await this.userService.registerUser(newUser, newUserToken, req.file);
+ res.status(200).json({
+ success: true,
+ msg: "User registered successfully",
+ data: { user, token },
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ loginUser = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ if (req.body?.email) {
+ req.body.email = req.body.email?.toLowerCase();
+ }
+ await loginValidation.validateAsync(req.body);
+ const { user, token } = await this.userService.loginUser(req.body.email, req.body.password);
+
+ return res.status(200).json({
+ success: true,
+ msg: "User logged in successfully",
+ data: {
+ user,
+ token,
+ },
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ editUser = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await editUserBodyValidation.validateAsync(req.body);
+
+ const updatedUser = await this.userService.editUser(req.body, req.file, req.user);
+
+ res.status(200).json({
+ success: true,
+ msg: "User updated successfully",
+ data: updatedUser,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ checkSuperadminExists = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const superAdminExists = await this.userService.checkSuperadminExists();
+ return res.status(200).json({
+ success: true,
+ msg: "Superadmin existence checked successfully",
+ data: superAdminExists,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ requestRecovery = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await recoveryValidation.validateAsync(req.body);
+ const email = req?.body?.email;
+ const msgId = await this.userService.requestRecovery(email);
+ return res.status(200).json({
+ success: true,
+ msg: "Password recovery email sent successfully",
+ data: msgId,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ validateRecovery = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await recoveryTokenBodyValidation.validateAsync(req.body);
+ await this.userService.validateRecovery(req.body.recoveryToken);
+ return res.status(200).json({
+ success: true,
+ msg: "Recovery token is valid",
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ resetPassword = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await newPasswordValidation.validateAsync(req.body);
+ const { user, token } = await this.userService.resetPassword(req.body.password, req.body.recoveryToken);
+ return res.status(200).json({
+ success: true,
+ msg: "Password has been reset successfully",
+ data: { user, token },
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ deleteUser = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await this.userService.deleteUser(req.user);
+ return res.status(200).json({
+ success: true,
+ msg: "User deleted successfully",
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getAllUsers = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const allUsers = await this.userService.getAllUsers();
+ return res.status(200).json({
+ success: true,
+ msg: "Users retrieved successfully",
+ data: allUsers,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getUserById = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await getUserByIdParamValidation.validateAsync(req.params);
+ const userId = req?.params?.userId;
+ const roles = req?.user?.role;
+
+ if (!userId) {
+ throw new Error("No user ID in request");
+ }
+
+ if (!roles || roles.length === 0) {
+ throw new Error("No roles in request");
+ }
+
+ const user = await this.userService.getUserById(roles, userId);
+
+ return res.status(200).json({ success: true, msg: "ok", data: user });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ editUserById = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const roles = req?.user?.role;
+
+ if (!roles || !roles.includes("superadmin")) {
+ throw new AppError({ message: "Unauthorized", status: 403 });
+ }
+
+ const userId = req.params.userId;
+ const user = { ...req.body };
+
+ await editUserByIdParamValidation.validateAsync(req.params);
+ // If this is superadmin self edit, allow "superadmin" role
+ if (userId === req.user?.id) {
+ await editSuperadminUserByIdBodyValidation.validateAsync(req.body);
+ } else {
+ await editUserByIdBodyValidation.validateAsync(req.body);
+ }
+
+ await this.userService.editUserById(userId, user);
+ return res.status(200).json({ success: true, msg: "ok" });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ editUserPasswordById = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const roles = req?.user?.role;
+ if (!roles || !roles.includes("superadmin")) {
+ throw new AppError({ message: "Unauthorized", status: 403 });
+ }
+
+ const userId = req.params.userId;
+ await editUserByIdParamValidation.validateAsync(req.params);
+ await editUserPasswordByIdBodyValidation.validateAsync(req.body);
+ const updatedPassword = req.body.password;
+ await this.userService.setPasswordByUserId(userId, updatedPassword);
+ return res.status(200).json({ success: true, msg: "Password reset successfully" });
+ } catch (error) {
+ next(error);
+ }
+ };
+}
+
+export default AuthController;
diff --git a/server/src/controllers/baseController.js b/server/src/controllers/baseController.js
deleted file mode 100644
index 910cf57b4..000000000
--- a/server/src/controllers/baseController.js
+++ /dev/null
@@ -1,83 +0,0 @@
-import { AppError } from "../service/infrastructure/errorService.js";
-
-export const createCommonDependencies = (db, errorService, logger, stringService) => {
- return {
- db,
- errorService,
- logger,
- stringService,
- };
-};
-
-class BaseController {
- constructor({ db, logger, errorService, ...additionalDependencies }) {
- this.db = db;
- this.logger = logger;
- this.errorService = errorService;
- Object.assign(this, additionalDependencies);
-
- this.asyncHandler = (fn, serviceName, methodName) => {
- return async (req, res, next) => {
- try {
- await fn(req, res, next);
- } catch (error) {
- // Handle validation errors
- if (error.isJoi) {
- const validationError = this.errorService.createValidationError(error.message, error.details, serviceName, methodName);
- return next(validationError);
- }
-
- if (error.name === "ValidationError") {
- const validationError = this.errorService.createValidationError("Database validation failed", error.errors, serviceName, methodName);
- return next(validationError);
- }
-
- if (error.name === "CastError") {
- const notFoundError = this.errorService.createNotFoundError(
- "Invalid resource identifier",
- { field: error.path, value: error.value },
- serviceName,
- methodName
- );
- return next(notFoundError);
- }
-
- if (error.code === "11000") {
- const conflictError = this.errorService.createConflictError("Resource already exists", {
- originalError: error.message,
- code: error.code,
- });
- conflictError.service = serviceName;
- conflictError.method = methodName;
- return next(conflictError);
- }
-
- if (error instanceof AppError) {
- error.service = error.service || serviceName;
- error.method = error.method || methodName;
- return next(error);
- }
-
- if (error.status) {
- const appError = this.errorService.createError(error.message, error.status, serviceName, methodName, {
- originalError: error.message,
- stack: error.stack,
- });
- return next(appError);
- }
-
- // For unknown errors, create a server error
- const appError = this.errorService.createServerError(error.message || "An unexpected error occurred", {
- originalError: error.message,
- stack: error.stack,
- });
- appError.service = serviceName;
- appError.method = methodName;
- appError.stack = error.stack; // Preserve original stack
- return next(appError);
- }
- };
- };
- }
-}
-export default BaseController;
diff --git a/server/src/controllers/checkController.js b/server/src/controllers/checkController.js
deleted file mode 100755
index 4069df3ff..000000000
--- a/server/src/controllers/checkController.js
+++ /dev/null
@@ -1,360 +0,0 @@
-import BaseController from "./baseController.js";
-import {
- getChecksParamValidation,
- getChecksQueryValidation,
- getTeamChecksQueryValidation,
- deleteChecksParamValidation,
- deleteChecksByTeamIdParamValidation,
- updateChecksTTLBodyValidation,
- ackCheckBodyValidation,
- ackAllChecksParamValidation,
- ackAllChecksBodyValidation,
-} from "../validation/joi.js";
-
-const SERVICE_NAME = "checkController";
-
-/**
- * Check Controller
- *
- * Handles all check-related HTTP requests including retrieving checks,
- * acknowledging checks, deleting checks, and managing check TTL settings.
- *
- * @class CheckController
- * @description Manages check operations and monitoring data
- */
-class CheckController extends BaseController {
- static SERVICE_NAME = SERVICE_NAME;
- /**
- * Creates an instance of CheckController.
- *
- * @param {Object} commonDependencies - Common dependencies injected into the controller
- * @param {Object} dependencies - The dependencies required by the controller
- * @param {Object} dependencies.settingsService - Service for application settings
- * @param {Object} dependencies.checkService - Check business logic service
- */
- constructor(commonDependencies, { settingsService, checkService }) {
- super(commonDependencies);
- this.settingsService = settingsService;
- this.checkService = checkService;
- }
-
- get serviceName() {
- return CheckController.SERVICE_NAME;
- }
-
- /**
- * Retrieves checks for a specific monitor with filtering and pagination.
- *
- * @async
- * @function getChecksByMonitor
- * @param {Object} req - Express request object
- * @param {Object} req.params - URL parameters
- * @param {string} req.params.monitorId - ID of the monitor to get checks for
- * @param {Object} req.query - Query parameters for filtering and pagination
- * @param {string} [req.query.type] - Type of checks to filter by
- * @param {string} [req.query.sortOrder] - Sort order (asc/desc)
- * @param {string} [req.query.dateRange] - Date range filter
- * @param {string} [req.query.filter] - General filter string
- * @param {boolean} [req.query.ack] - Filter by acknowledgment status
- * @param {number} [req.query.page] - Page number for pagination
- * @param {number} [req.query.rowsPerPage] - Number of rows per page
- * @param {string} [req.query.status] - Filter by check status
- * @param {Object} req.user - Current authenticated user (from JWT)
- * @param {string} req.user.teamId - User's team ID
- * @param {Object} res - Express response object
- * @returns {Promise} Success response with checks data
- * @throws {Error} 422 - Validation error if parameters are invalid
- * @throws {Error} 404 - Not found if monitor doesn't exist
- * @throws {Error} 403 - Forbidden if user doesn't have access to monitor
- * @example
- * GET /checks/monitor/507f1f77bcf86cd799439011?page=1&rowsPerPage=10&status=down
- * // Requires JWT authentication
- */
- getChecksByMonitor = this.asyncHandler(
- async (req, res) => {
- await getChecksParamValidation.validateAsync(req.params);
- await getChecksQueryValidation.validateAsync(req.query);
-
- const result = await this.checkService.getChecksByMonitor({
- monitorId: req?.params?.monitorId,
- query: req?.query,
- teamId: req?.user?.teamId,
- });
-
- return res.success({
- msg: this.stringService.checkGet,
- data: result,
- });
- },
- SERVICE_NAME,
- "getChecksByMonitor"
- );
-
- /**
- * Retrieves all checks for the current user's team with filtering and pagination.
- *
- * @async
- * @function getChecksByTeam
- * @param {Object} req - Express request object
- * @param {Object} req.query - Query parameters for filtering and pagination
- * @param {string} [req.query.sortOrder] - Sort order (asc/desc)
- * @param {string} [req.query.dateRange] - Date range filter
- * @param {string} [req.query.filter] - General filter string
- * @param {boolean} [req.query.ack] - Filter by acknowledgment status
- * @param {number} [req.query.page] - Page number for pagination
- * @param {number} [req.query.rowsPerPage] - Number of rows per page
- * @param {Object} req.user - Current authenticated user (from JWT)
- * @param {string} req.user.teamId - User's team ID
- * @param {Object} res - Express response object
- * @returns {Promise} Success response with checks data
- * @throws {Error} 422 - Validation error if query parameters are invalid
- * @example
- * GET /checks/team?page=1&rowsPerPage=20&status=down&ack=false
- * // Requires JWT authentication
- */
- getChecksByTeam = this.asyncHandler(
- async (req, res) => {
- await getTeamChecksQueryValidation.validateAsync(req.query);
- const checkData = await this.checkService.getChecksByTeam({
- teamId: req?.user?.teamId,
- query: req?.query,
- });
- return res.success({
- msg: this.stringService.checkGet,
- data: checkData,
- });
- },
- SERVICE_NAME,
- "getChecksByTeam"
- );
-
- /**
- * Retrieves a summary of checks for the current user's team.
- *
- * @async
- * @function getChecksSummaryByTeamId
- * @param {Object} req - Express request object
- * @param {Object} req.user - Current authenticated user (from JWT)
- * @param {string} req.user.teamId - User's team ID
- * @param {Object} res - Express response object
- * @returns {Promise} Success response with checks summary
- * @example
- * GET /checks/summary
- * // Requires JWT authentication
- * // Response includes counts by status, time ranges, etc.
- */
- getChecksSummaryByTeamId = this.asyncHandler(
- async (req, res) => {
- const summary = await this.checkService.getChecksSummaryByTeamId({ teamId: req?.user?.teamId });
- return res.success({
- msg: this.stringService.checkGetSummary,
- data: summary,
- });
- },
- SERVICE_NAME,
- "getChecksSummaryByTeamId"
- );
-
- /**
- * Acknowledges a specific check by ID.
- *
- * @async
- * @function ackCheck
- * @param {Object} req - Express request object
- * @param {Object} req.params - URL parameters
- * @param {string} req.params.checkId - ID of the check to acknowledge
- * @param {Object} req.body - Request body containing acknowledgment data
- * @param {boolean} req.body.ack - Acknowledgment status (true/false)
- * @param {Object} req.user - Current authenticated user (from JWT)
- * @param {string} req.user.teamId - User's team ID
- * @param {Object} res - Express response object
- * @returns {Promise} Success response with updated check data
- * @throws {Error} 422 - Validation error if request body is invalid
- * @throws {Error} 404 - Not found if check doesn't exist
- * @throws {Error} 403 - Forbidden if user doesn't have access to check
- * @example
- * PUT /checks/507f1f77bcf86cd799439011/ack
- * {
- * "ack": true
- * }
- * // Requires JWT authentication
- */
- ackCheck = this.asyncHandler(
- async (req, res) => {
- await ackCheckBodyValidation.validateAsync(req.body);
-
- const updatedCheck = await this.checkService.ackCheck({
- checkId: req?.params?.checkId,
- teamId: req?.user?.teamId,
- ack: req?.body?.ack,
- });
-
- return res.success({
- msg: this.stringService.checkUpdateStatus,
- data: updatedCheck,
- });
- },
- SERVICE_NAME,
- "ackCheck"
- );
-
- /**
- * Acknowledges all checks for a specific monitor or path.
- *
- * @async
- * @function ackAllChecks
- * @param {Object} req - Express request object
- * @param {Object} req.params - URL parameters
- * @param {string} req.params.monitorId - ID of the monitor
- * @param {string} req.params.path - Path for acknowledgment (e.g., "monitor")
- * @param {Object} req.body - Request body containing acknowledgment data
- * @param {boolean} req.body.ack - Acknowledgment status (true/false)
- * @param {Object} req.user - Current authenticated user (from JWT)
- * @param {string} req.user.teamId - User's team ID
- * @param {Object} res - Express response object
- * @returns {Promise} Success response with updated checks data
- * @throws {Error} 422 - Validation error if parameters or body are invalid
- * @throws {Error} 404 - Not found if monitor doesn't exist
- * @throws {Error} 403 - Forbidden if user doesn't have access to monitor
- * @example
- * PUT /checks/monitor/507f1f77bcf86cd799439011/ack
- * {
- * "ack": true
- * }
- * // Requires JWT authentication
- */
- ackAllChecks = this.asyncHandler(
- async (req, res) => {
- await ackAllChecksParamValidation.validateAsync(req.params);
- await ackAllChecksBodyValidation.validateAsync(req.body);
-
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw new Error("No team ID in request");
- }
-
- const updatedChecks = await this.checkService.ackAllChecks({
- monitorId: req?.params?.monitorId,
- path: req?.params?.path,
- teamId: req?.user?.teamId,
- ack: req?.body?.ack,
- });
-
- return res.success({
- msg: this.stringService.checkUpdateStatus,
- data: updatedChecks,
- });
- },
- SERVICE_NAME,
- "ackAllChecks"
- );
-
- /**
- * Deletes all checks for a specific monitor.
- *
- * @async
- * @function deleteChecks
- * @param {Object} req - Express request object
- * @param {Object} req.params - URL parameters
- * @param {string} req.params.monitorId - ID of the monitor whose checks to delete
- * @param {Object} req.user - Current authenticated user (from JWT)
- * @param {string} req.user.teamId - User's team ID
- * @param {Object} res - Express response object
- * @returns {Promise} Success response with deletion count
- * @throws {Error} 422 - Validation error if monitorId is invalid
- * @throws {Error} 404 - Not found if monitor doesn't exist
- * @throws {Error} 403 - Forbidden if user doesn't have access to monitor
- * @example
- * DELETE /checks/monitor/507f1f77bcf86cd799439011
- * // Requires JWT authentication
- * // Response: { "data": { "deletedCount": 150 } }
- */
- deleteChecks = this.asyncHandler(
- async (req, res) => {
- await deleteChecksParamValidation.validateAsync(req.params);
-
- const deletedCount = await this.checkService.deleteChecks({
- monitorId: req.params.monitorId,
- teamId: req?.user?.teamId,
- });
-
- return res.success({
- msg: this.stringService.checkDelete,
- data: { deletedCount },
- });
- },
- SERVICE_NAME,
- "deleteChecks"
- );
-
- /**
- * Deletes all checks for the current user's team.
- *
- * @async
- * @function deleteChecksByTeamId
- * @param {Object} req - Express request object
- * @param {Object} req.user - Current authenticated user (from JWT)
- * @param {string} req.user.teamId - User's team ID
- * @param {Object} res - Express response object
- * @returns {Promise} Success response with deletion count
- * @throws {Error} 422 - Validation error if parameters are invalid
- * @example
- * DELETE /checks/team
- * // Requires JWT authentication
- * // Response: { "data": { "deletedCount": 1250 } }
- */
- deleteChecksByTeamId = this.asyncHandler(
- async (req, res) => {
- await deleteChecksByTeamIdParamValidation.validateAsync(req.params);
-
- const deletedCount = await this.checkService.deleteChecksByTeamId({ teamId: req?.user?.teamId });
-
- return res.success({
- msg: this.stringService.checkDelete,
- data: { deletedCount },
- });
- },
- SERVICE_NAME,
- "deleteChecksByTeamId"
- );
-
- /**
- * Updates the TTL (Time To Live) setting for checks in the current user's team.
- *
- * @async
- * @function updateChecksTTL
- * @param {Object} req - Express request object
- * @param {Object} req.body - Request body containing TTL data
- * @param {number} req.body.ttl - TTL value in days
- * @param {Object} req.user - Current authenticated user (from JWT)
- * @param {string} req.user.teamId - User's team ID
- * @param {Object} res - Express response object
- * @returns {Promise} Success response confirming TTL update
- * @throws {Error} 422 - Validation error if TTL value is invalid
- * @example
- * PUT /checks/ttl
- * {
- * "ttl": 30
- * }
- * // Requires JWT authentication
- * // Sets check TTL to 30 days
- */
- updateChecksTTL = this.asyncHandler(
- async (req, res) => {
- await updateChecksTTLBodyValidation.validateAsync(req.body);
-
- await this.checkService.updateChecksTTL({
- teamId: req?.user?.teamId,
- ttl: req?.body?.ttl,
- });
-
- return res.success({
- msg: this.stringService.checkUpdateTTL,
- });
- },
- SERVICE_NAME,
- "updateChecksTtl"
- );
-}
-
-export default CheckController;
diff --git a/server/src/controllers/checkController.ts b/server/src/controllers/checkController.ts
new file mode 100755
index 000000000..8277df3e7
--- /dev/null
+++ b/server/src/controllers/checkController.ts
@@ -0,0 +1,134 @@
+import { Request, Response, NextFunction } from "express";
+
+import {
+ getChecksParamValidation,
+ getChecksQueryValidation,
+ getTeamChecksQueryValidation,
+ deleteChecksParamValidation,
+ deleteChecksByTeamIdParamValidation,
+ updateChecksTTLBodyValidation,
+ ackCheckBodyValidation,
+ ackAllChecksParamValidation,
+ ackAllChecksBodyValidation,
+} from "@/validation/joi.js";
+
+const SERVICE_NAME = "checkController";
+
+class CheckController {
+ static SERVICE_NAME = SERVICE_NAME;
+
+ private checkService: any;
+ constructor(checkService: any) {
+ this.checkService = checkService;
+ }
+
+ get serviceName() {
+ return CheckController.SERVICE_NAME;
+ }
+
+ getChecksByMonitor = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await getChecksParamValidation.validateAsync(req.params);
+ await getChecksQueryValidation.validateAsync(req.query);
+
+ const result = await this.checkService.getChecksByMonitor({
+ monitorId: req?.params?.monitorId,
+ query: req?.query,
+ teamId: req?.user?.teamId,
+ });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Checks retrieved successfully",
+ data: result,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getChecksByTeam = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await getTeamChecksQueryValidation.validateAsync(req.query);
+ const checkData = await this.checkService.getChecksByTeam({
+ teamId: req?.user?.teamId,
+ query: req?.query,
+ });
+ return res.status(200).json({
+ success: true,
+ msg: "Team checks retrieved successfully",
+ data: checkData,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getChecksSummaryByTeamId = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const summary = await this.checkService.getChecksSummaryByTeamId({ teamId: req?.user?.teamId });
+ return res.status(200).json({
+ success: true,
+ msg: "Checks summary retrieved successfully",
+ data: summary,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ deleteChecks = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await deleteChecksParamValidation.validateAsync(req.params);
+
+ const deletedCount = await this.checkService.deleteChecks({
+ monitorId: req.params.monitorId,
+ teamId: req?.user?.teamId,
+ });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Checks deleted successfully",
+ data: { deletedCount },
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ deleteChecksByTeamId = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await deleteChecksByTeamIdParamValidation.validateAsync(req.params);
+
+ const deletedCount = await this.checkService.deleteChecksByTeamId({ teamId: req?.user?.teamId });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Checks deleted successfully",
+ data: { deletedCount },
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ updateChecksTTL = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await updateChecksTTLBodyValidation.validateAsync(req.body);
+
+ await this.checkService.updateChecksTTL({
+ teamId: req?.user?.teamId,
+ ttl: req?.body?.ttl,
+ });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Checks TTL updated successfully",
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+}
+
+export default CheckController;
diff --git a/server/src/controllers/controllerUtils.js b/server/src/controllers/controllerUtils.js
deleted file mode 100755
index f21ba154b..000000000
--- a/server/src/controllers/controllerUtils.js
+++ /dev/null
@@ -1,12 +0,0 @@
-const fetchMonitorCertificate = async (sslChecker, monitor) => {
- const monitorUrl = new URL(monitor.url);
- const hostname = monitorUrl.hostname;
- const cert = await sslChecker(hostname);
- // Throw an error if no cert or if cert.validTo is not present
- if (cert?.validTo === null || cert?.validTo === undefined) {
- throw new Error("Certificate not found");
- }
- return cert;
-};
-
-export { fetchMonitorCertificate };
diff --git a/server/src/controllers/controllerUtils.ts b/server/src/controllers/controllerUtils.ts
new file mode 100755
index 000000000..e6c8767fa
--- /dev/null
+++ b/server/src/controllers/controllerUtils.ts
@@ -0,0 +1,119 @@
+import { AppError } from "@/utils/AppError.js";
+import { type MonitorType, MonitorTypes } from "@/types/index.js";
+
+const fetchMonitorCertificate = async (sslChecker: any, monitor: any): Promise => {
+ const monitorUrl = new URL(monitor.url);
+ const hostname = monitorUrl.hostname;
+ const cert = await sslChecker(hostname);
+ // Throw an error if no cert or if cert.validTo is not present
+ if (cert?.validTo === null || cert?.validTo === undefined) {
+ throw new Error("Certificate not found");
+ }
+ return cert;
+};
+const requireString = (value: unknown, fieldName: string): string => {
+ if (typeof value === "string" && value.trim().length > 0) {
+ return value;
+ }
+ throw new AppError({ message: `${fieldName} is required`, status: 400 });
+};
+
+const optionalString = (value: unknown, fieldName: string): string | undefined => {
+ if (value === undefined) {
+ return undefined;
+ }
+ if (typeof value === "string") {
+ return value;
+ }
+ throw new AppError({ message: `${fieldName} must be a string`, status: 400 });
+};
+
+const optionalNumber = (value: unknown, fieldName: string): number | undefined => {
+ if (value === undefined) {
+ return undefined;
+ }
+ if (typeof value === "number" && Number.isFinite(value)) {
+ return value;
+ }
+ if (typeof value === "string" && value.trim() !== "") {
+ const parsed = Number(value);
+ if (!Number.isNaN(parsed)) {
+ return parsed;
+ }
+ }
+ throw new AppError({ message: `${fieldName} must be a number`, status: 400 });
+};
+
+const optionalBoolean = (value: unknown, fieldName: string): boolean | undefined => {
+ if (value === undefined) {
+ return undefined;
+ }
+ if (typeof value === "boolean") {
+ return value;
+ }
+ if (typeof value === "string") {
+ if (value === "true") {
+ return true;
+ }
+ if (value === "false") {
+ return false;
+ }
+ }
+ throw new AppError({ message: `${fieldName} must be a boolean`, status: 400 });
+};
+
+const parseMonitorTypeFilter = (value: unknown): MonitorType | MonitorType[] | undefined => {
+ const parseSingle = (input: unknown): MonitorType => {
+ if (typeof input !== "string") {
+ throw new AppError({ message: "Monitor type must be a string", status: 400 });
+ }
+ if (!MonitorTypes.includes(input as MonitorType)) {
+ throw new AppError({ message: `Invalid monitor type: ${input}`, status: 400 });
+ }
+ return input as MonitorType;
+ };
+
+ if (value === undefined) {
+ return undefined;
+ }
+ if (Array.isArray(value)) {
+ return value.map((entry) => parseSingle(entry));
+ }
+ return parseSingle(value);
+};
+
+const parseSortOrder = (value: unknown): "asc" | "desc" | undefined => {
+ if (value === undefined) {
+ return undefined;
+ }
+ if (value === "asc" || value === "desc") {
+ return value;
+ }
+ throw new AppError({ message: "order must be either 'asc' or 'desc'", status: 400 });
+};
+
+const requireTeamId = (teamId?: string): string => {
+ if (!teamId) {
+ throw new AppError({ message: "Team ID is required", status: 400 });
+ }
+ return teamId;
+};
+
+const requireUserId = (userId?: string): string => {
+ if (!userId) {
+ throw new AppError({ message: "User ID is required", status: 400 });
+ }
+ return userId;
+};
+
+export {
+ fetchMonitorCertificate,
+ requireString,
+ optionalString,
+ optionalNumber,
+ optionalBoolean,
+ parseMonitorTypeFilter,
+ parseSortOrder,
+ requireTeamId,
+ requireUserId,
+};
diff --git a/server/src/controllers/diagnosticController.js b/server/src/controllers/diagnosticController.js
deleted file mode 100755
index 0723a9f6c..000000000
--- a/server/src/controllers/diagnosticController.js
+++ /dev/null
@@ -1,61 +0,0 @@
-const SERVICE_NAME = "diagnosticController";
-import BaseController from "./baseController.js";
-/**
- * Diagnostic Controller
- *
- * Handles system diagnostic and monitoring requests including system statistics,
- * performance metrics, and health checks.
- *
- * @class DiagnosticController
- * @description Manages system diagnostics and performance monitoring
- */
-class DiagnosticController extends BaseController {
- static SERVICE_NAME = SERVICE_NAME;
- /**
- * Creates an instance of DiagnosticController.
- * @param {Object} commonDependencies - Common dependencies injected into the controller
- * @param {Object} dependencies - The dependencies required by the controller
- * @param {Object} dependencies.diagnosticService - Service for system diagnostics and monitoring
- */
- constructor(commonDependencies, { diagnosticService }) {
- super(commonDependencies);
- this.diagnosticService = diagnosticService;
- }
-
- get serviceName() {
- return DiagnosticController.SERVICE_NAME;
- }
- /**
- * Retrieves comprehensive system statistics and performance metrics.
- *
- * @async
- * @function getSystemStats
- * @param {Object} req - Express request object
- * @param {Object} res - Express response object
- * @returns {Promise} Success response with system diagnostics data
- * @description Returns system performance metrics, memory usage, CPU statistics,
- * and other diagnostic information useful for monitoring system health.
- * @example
- * GET /diagnostics/stats
- * // Response includes:
- * // - Memory usage (heap, external, arrayBuffers)
- * // - CPU usage statistics
- * // - System uptime
- * // - Performance metrics
- * // - Database connection status
- * // - Active processes/connections
- */
- getSystemStats = this.asyncHandler(
- async (req, res) => {
- const diagnostics = await this.diagnosticService.getSystemStats();
- return res.success({
- msg: "OK",
- data: diagnostics,
- });
- },
- SERVICE_NAME,
- "getSystemStats"
- );
-}
-
-export default DiagnosticController;
diff --git a/server/src/controllers/diagnosticController.ts b/server/src/controllers/diagnosticController.ts
new file mode 100755
index 000000000..b4d59ceaf
--- /dev/null
+++ b/server/src/controllers/diagnosticController.ts
@@ -0,0 +1,31 @@
+import { Request, Response, NextFunction } from "express";
+
+const SERVICE_NAME = "diagnosticController";
+class DiagnosticController {
+ static SERVICE_NAME = SERVICE_NAME;
+
+ private diagnosticService: any;
+
+ constructor(diagnosticService: any) {
+ this.diagnosticService = diagnosticService;
+ }
+
+ get serviceName() {
+ return DiagnosticController.SERVICE_NAME;
+ }
+
+ getSystemStats = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const diagnostics = await this.diagnosticService.getSystemStats();
+ return res.status(200).json({
+ success: true,
+ msg: "OK",
+ data: diagnostics,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+}
+
+export default DiagnosticController;
diff --git a/server/src/controllers/incidentController.ts b/server/src/controllers/incidentController.ts
new file mode 100644
index 000000000..72e6498f9
--- /dev/null
+++ b/server/src/controllers/incidentController.ts
@@ -0,0 +1,107 @@
+import { AppError } from "@/utils/AppError.js";
+import { Request, Response, NextFunction } from "express";
+import { requireTeamId } from "./controllerUtils.js";
+
+const SERVICE_NAME = "incidentController";
+
+class IncidentController {
+ static SERVICE_NAME = SERVICE_NAME;
+
+ private incidentService: any;
+ constructor(incidentService: any) {
+ this.incidentService = incidentService;
+ }
+
+ get serviceName() {
+ return IncidentController.SERVICE_NAME;
+ }
+
+ getIncidentsByTeam = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const { sortOrder, dateRange, page, rowsPerPage, status, monitorId, resolutionType } = req.query || {};
+
+ const teamId = requireTeamId(req.user?.teamId);
+ const result = await this.incidentService.getIncidentsByTeam(
+ teamId,
+ sortOrder,
+ dateRange,
+ page,
+ rowsPerPage,
+ status,
+ monitorId,
+ resolutionType
+ );
+
+ return res.status(200).json({
+ success: true,
+ msg: "Incidents retrieved successfully",
+ data: result,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getIncidentSummary = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const teamId = req.user?.teamId;
+
+ if (!teamId) {
+ throw new AppError({ message: "Team ID is required", service: SERVICE_NAME, status: 400 });
+ }
+
+ const limit = req.query.limit ? parseInt(req.query.limit as string, 10) : 10;
+
+ const summary = await this.incidentService.getIncidentSummary(req?.user?.teamId, limit);
+
+ return res.status(200).json({
+ success: true,
+ msg: "Incident summary retrieved successfully",
+ data: summary,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getIncidentById = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const teamId = requireTeamId(req.user?.teamId);
+ const incidentId = req.params.incidentId;
+ if (!incidentId) {
+ throw new AppError({ message: "Incident ID is required", service: SERVICE_NAME, status: 400 });
+ }
+
+ const incident = await this.incidentService.getIncidentById(incidentId, teamId);
+
+ return res.status(200).json({
+ success: true,
+ msg: "Incident retrieved successfully",
+ data: incident,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ resolveIncidentManually = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const resolvedIncident = await this.incidentService.resolveIncident(
+ req?.params?.incidentId,
+ req?.user?.id,
+ req?.user?.teamId,
+ req?.body?.comment
+ );
+
+ return res.status(200).json({
+ success: true,
+ msg: "Incident resolved successfully",
+ data: resolvedIncident,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+}
+
+export default IncidentController;
diff --git a/server/src/controllers/inviteController.js b/server/src/controllers/inviteController.js
deleted file mode 100755
index a377234c0..000000000
--- a/server/src/controllers/inviteController.js
+++ /dev/null
@@ -1,101 +0,0 @@
-import { inviteBodyValidation, inviteVerificationBodyValidation } from "../validation/joi.js";
-import BaseController from "./baseController.js";
-const SERVICE_NAME = "inviteController";
-
-/**
- * Controller for handling user invitation operations
- * Manages invite token generation, email sending, and token verification
- */
-class InviteController extends BaseController {
- static SERVICE_NAME = SERVICE_NAME;
- /**
- * Creates a new InviteController instance
- * @param {Object} commonDependencies - Common dependencies injected into the controller
- * @param {Object} dependencies.inviteService - Service for invite-related operations
- */
- constructor(commonDependencies, { inviteService }) {
- super(commonDependencies);
- this.inviteService = inviteService;
- }
-
- get serviceName() {
- return InviteController.SERVICE_NAME;
- }
-
- /**
- * Generates an invite token for a user invitation
- * @param {Object} req - Express request object
- * @param {Object} req.body - Request body containing invite details
- * @param {Object} req.user - Authenticated user object
- * @param {string} req.user.teamId - Team ID of the authenticated user
- * @param {Object} res - Express response object
- * @returns {Promise} Response with invite token data
- */
- getInviteToken = this.asyncHandler(
- async (req, res) => {
- const invite = req.body;
- const teamId = req?.user?.teamId;
- invite.teamId = teamId;
- await inviteBodyValidation.validateAsync(invite);
- const inviteToken = await this.inviteService.getInviteToken({ invite, teamId });
- return res.success({
- msg: this.stringService.inviteIssued,
- data: inviteToken,
- });
- },
- SERVICE_NAME,
- "getInviteToken"
- );
-
- /**
- * Sends an invitation email to a user
- * @param {Object} req - Express request object
- * @param {Object} req.body - Request body containing invite details
- * @param {Object} req.user - Authenticated user object
- * @param {string} req.user.teamId - Team ID of the authenticated user
- * @param {string} req.user.firstName - First name of the authenticated user
- * @param {Object} res - Express response object
- * @returns {Promise} Response with invite token data
- */
- sendInviteEmail = this.asyncHandler(
- async (req, res) => {
- const inviteRequest = req.body;
- inviteRequest.teamId = req?.user?.teamId;
- await inviteBodyValidation.validateAsync(inviteRequest);
-
- const inviteToken = await this.inviteService.sendInviteEmail({
- inviteRequest,
- firstName: req?.user?.firstName,
- });
- return res.success({
- msg: this.stringService.inviteIssued,
- data: inviteToken,
- });
- },
- SERVICE_NAME,
- "sendInviteEmail"
- );
-
- /**
- * Verifies an invite token and returns invite details
- * @param {Object} req - Express request object
- * @param {Object} req.body - Request body containing the invite token
- * @param {string} req.body.token - The invite token to verify
- * @param {Object} res - Express response object
- * @returns {Promise} Response with verified invite data
- */
- verifyInviteToken = this.asyncHandler(
- async (req, res) => {
- await inviteVerificationBodyValidation.validateAsync(req.body);
- const invite = await this.inviteService.verifyInviteToken({ inviteToken: req?.body?.token });
- return res.success({
- msg: this.stringService.inviteVerified,
- data: invite,
- });
- },
- SERVICE_NAME,
- "verifyInviteToken"
- );
-}
-
-export default InviteController;
diff --git a/server/src/controllers/inviteController.ts b/server/src/controllers/inviteController.ts
new file mode 100755
index 000000000..c3d63aa80
--- /dev/null
+++ b/server/src/controllers/inviteController.ts
@@ -0,0 +1,68 @@
+import { Request, Response, NextFunction } from "express";
+import { inviteBodyValidation, inviteVerificationBodyValidation } from "@/validation/joi.js";
+const SERVICE_NAME = "inviteController";
+
+class InviteController {
+ static SERVICE_NAME = SERVICE_NAME;
+ private inviteService: any;
+ constructor(inviteService: any) {
+ this.inviteService = inviteService;
+ }
+
+ get serviceName() {
+ return InviteController.SERVICE_NAME;
+ }
+
+ getInviteToken = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const invite = req.body;
+ const teamId = req?.user?.teamId;
+ invite.teamId = teamId;
+ await inviteBodyValidation.validateAsync(invite);
+ const inviteToken = await this.inviteService.getInviteToken({ invite, teamId });
+ return res.status(200).json({
+ success: true,
+ msg: "Invite token generated successfully",
+ data: inviteToken,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ sendInviteEmail = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const inviteRequest = req.body;
+ inviteRequest.teamId = req?.user?.teamId;
+ await inviteBodyValidation.validateAsync(inviteRequest);
+
+ const inviteToken = await this.inviteService.sendInviteEmail({
+ inviteRequest,
+ firstName: req?.user?.firstName,
+ });
+ return res.status(200).json({
+ success: true,
+ msg: "Invite issued successfully",
+ data: inviteToken,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ verifyInviteToken = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await inviteVerificationBodyValidation.validateAsync(req.body);
+ const invite = await this.inviteService.verifyInviteToken({ inviteToken: req?.body?.token });
+ return res.status(200).json({
+ success: true,
+ msg: "Invite verified successfully",
+ data: invite,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+}
+
+export default InviteController;
diff --git a/server/src/controllers/logController.js b/server/src/controllers/logController.js
deleted file mode 100644
index 3537246bb..000000000
--- a/server/src/controllers/logController.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import BaseController from "./baseController.js";
-const SERVICE_NAME = "LogController";
-
-class LogController extends BaseController {
- static SERVICE_NAME = SERVICE_NAME;
- constructor(commonDependencies) {
- super(commonDependencies);
- }
-
- get serviceName() {
- return LogController.SERVICE_NAME;
- }
-
- getLogs = this.asyncHandler(
- async (req, res) => {
- const logs = await this.logger.getLogs();
- res.success({
- msg: "Logs fetched successfully",
- data: logs,
- });
- },
- SERVICE_NAME,
- "getLogs"
- );
-}
-export default LogController;
diff --git a/server/src/controllers/logController.ts b/server/src/controllers/logController.ts
new file mode 100644
index 000000000..d99de1e9c
--- /dev/null
+++ b/server/src/controllers/logController.ts
@@ -0,0 +1,28 @@
+import { Request, Response, NextFunction } from "express";
+
+const SERVICE_NAME = "LogController";
+
+class LogController {
+ static SERVICE_NAME = SERVICE_NAME;
+ private logger: any;
+ constructor(logger: any) {
+ this.logger = logger;
+ }
+ get serviceName() {
+ return LogController.SERVICE_NAME;
+ }
+
+ getLogs = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const logs = await this.logger.getLogs();
+ res.status(200).json({
+ success: true,
+ msg: "Logs fetched successfully",
+ data: logs,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+}
+export default LogController;
diff --git a/server/src/controllers/maintenanceWindowController.js b/server/src/controllers/maintenanceWindowController.js
deleted file mode 100755
index 2fdb1ee15..000000000
--- a/server/src/controllers/maintenanceWindowController.js
+++ /dev/null
@@ -1,146 +0,0 @@
-import {
- createMaintenanceWindowBodyValidation,
- editMaintenanceWindowByIdParamValidation,
- editMaintenanceByIdWindowBodyValidation,
- getMaintenanceWindowByIdParamValidation,
- getMaintenanceWindowsByMonitorIdParamValidation,
- getMaintenanceWindowsByTeamIdQueryValidation,
- deleteMaintenanceWindowByIdParamValidation,
-} from "../validation/joi.js";
-import BaseController from "./baseController.js";
-
-const SERVICE_NAME = "maintenanceWindowController";
-
-class MaintenanceWindowController extends BaseController {
- static SERVICE_NAME = SERVICE_NAME;
- constructor(commonDependencies, { settingsService, maintenanceWindowService }) {
- super(commonDependencies);
- this.settingsService = settingsService;
- this.maintenanceWindowService = maintenanceWindowService;
- }
-
- get serviceName() {
- return MaintenanceWindowController.SERVICE_NAME;
- }
-
- createMaintenanceWindows = this.asyncHandler(
- async (req, res) => {
- await createMaintenanceWindowBodyValidation.validateAsync(req.body);
-
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- await this.maintenanceWindowService.createMaintenanceWindow({ teamId, body: req.body });
-
- return res.success({
- msg: this.stringService.maintenanceWindowCreate,
- });
- },
- SERVICE_NAME,
- "createMaintenanceWindows"
- );
-
- getMaintenanceWindowById = this.asyncHandler(
- async (req, res) => {
- await getMaintenanceWindowByIdParamValidation.validateAsync(req.params);
-
- const teamId = req.user.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const maintenanceWindow = await this.maintenanceWindowService.getMaintenanceWindowById({ id: req.params.id, teamId });
-
- return res.success({
- msg: this.stringService.maintenanceWindowGetById,
- data: maintenanceWindow,
- });
- },
- SERVICE_NAME,
- "getMaintenanceWindowById"
- );
-
- getMaintenanceWindowsByTeamId = this.asyncHandler(
- async (req, res) => {
- await getMaintenanceWindowsByTeamIdQueryValidation.validateAsync(req.query);
-
- const teamId = req?.user?.teamId;
-
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const maintenanceWindows = await this.maintenanceWindowService.getMaintenanceWindowsByTeamId({ teamId, query: req.query });
-
- return res.success({
- msg: this.stringService.maintenanceWindowGetByTeam,
- data: maintenanceWindows,
- });
- },
- SERVICE_NAME,
- "getMaintenanceWindowsByTeamId"
- );
-
- getMaintenanceWindowsByMonitorId = this.asyncHandler(
- async (req, res) => {
- await getMaintenanceWindowsByMonitorIdParamValidation.validateAsync(req.params);
-
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const maintenanceWindows = await this.maintenanceWindowService.getMaintenanceWindowsByMonitorId({ monitorId: req.params.monitorId, teamId });
-
- return res.success({
- msg: this.stringService.maintenanceWindowGetByUser,
- data: maintenanceWindows,
- });
- },
- SERVICE_NAME,
- "getMaintenanceWindowsByMonitorId"
- );
-
- deleteMaintenanceWindow = this.asyncHandler(
- async (req, res) => {
- await deleteMaintenanceWindowByIdParamValidation.validateAsync(req.params);
-
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- await this.maintenanceWindowService.deleteMaintenanceWindow({ id: req.params.id, teamId });
-
- return res.success({
- msg: this.stringService.maintenanceWindowDelete,
- });
- },
- SERVICE_NAME,
- "deleteMaintenanceWindow"
- );
-
- editMaintenanceWindow = this.asyncHandler(
- async (req, res) => {
- await editMaintenanceWindowByIdParamValidation.validateAsync(req.params);
- await editMaintenanceByIdWindowBodyValidation.validateAsync(req.body);
-
- const teamId = req.user.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const editedMaintenanceWindow = await this.maintenanceWindowService.editMaintenanceWindow({ id: req.params.id, body: req.body, teamId });
- return res.success({
- msg: this.stringService.maintenanceWindowEdit,
- data: editedMaintenanceWindow,
- });
- },
- SERVICE_NAME,
- "editMaintenanceWindow"
- );
-}
-
-export default MaintenanceWindowController;
diff --git a/server/src/controllers/maintenanceWindowController.ts b/server/src/controllers/maintenanceWindowController.ts
new file mode 100644
index 000000000..0cc9610ea
--- /dev/null
+++ b/server/src/controllers/maintenanceWindowController.ts
@@ -0,0 +1,130 @@
+import { Request, Response, NextFunction } from "express";
+import {
+ createMaintenanceWindowBodyValidation,
+ editMaintenanceWindowByIdParamValidation,
+ editMaintenanceByIdWindowBodyValidation,
+ getMaintenanceWindowByIdParamValidation,
+ getMaintenanceWindowsByMonitorIdParamValidation,
+ getMaintenanceWindowsByTeamIdQueryValidation,
+ deleteMaintenanceWindowByIdParamValidation,
+} from "@/validation/joi.js";
+import { requireTeamId } from "@/controllers/controllerUtils.js";
+
+const SERVICE_NAME = "maintenanceWindowController";
+
+class MaintenanceWindowController {
+ static SERVICE_NAME = SERVICE_NAME;
+ private maintenanceWindowService: any;
+ constructor(maintenanceWindowService: any) {
+ this.maintenanceWindowService = maintenanceWindowService;
+ }
+
+ get serviceName() {
+ return MaintenanceWindowController.SERVICE_NAME;
+ }
+
+ createMaintenanceWindows = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await createMaintenanceWindowBodyValidation.validateAsync(req.body);
+ const teamId = requireTeamId(req?.user?.teamId);
+
+ await this.maintenanceWindowService.createMaintenanceWindow({ teamId, body: req.body });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Maintenance window created successfully",
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+ getMaintenanceWindowById = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await getMaintenanceWindowByIdParamValidation.validateAsync(req.params);
+
+ const teamId = requireTeamId(req.user?.teamId);
+
+ const maintenanceWindow = await this.maintenanceWindowService.getMaintenanceWindowById({ id: req.params.id, teamId });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Maintenance window fetched successfully",
+ data: maintenanceWindow,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getMaintenanceWindowsByTeamId = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await getMaintenanceWindowsByTeamIdQueryValidation.validateAsync(req.query);
+
+ const teamId = requireTeamId(req?.user?.teamId);
+
+ const maintenanceWindows = await this.maintenanceWindowService.getMaintenanceWindowsByTeamId({ teamId, query: req.query });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Maintenance windows fetched successfully",
+ data: maintenanceWindows,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getMaintenanceWindowsByMonitorId = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await getMaintenanceWindowsByMonitorIdParamValidation.validateAsync(req.params);
+
+ const teamId = requireTeamId(req?.user?.teamId);
+
+ const maintenanceWindows = await this.maintenanceWindowService.getMaintenanceWindowsByMonitorId({ monitorId: req.params.monitorId, teamId });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Maintenance windows fetched successfully",
+ data: maintenanceWindows,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+ deleteMaintenanceWindow = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await deleteMaintenanceWindowByIdParamValidation.validateAsync(req.params);
+
+ const teamId = requireTeamId(req?.user?.teamId);
+
+ await this.maintenanceWindowService.deleteMaintenanceWindow({ id: req.params.id, teamId });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Maintenance window deleted successfully",
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ editMaintenanceWindow = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await editMaintenanceWindowByIdParamValidation.validateAsync(req.params);
+ await editMaintenanceByIdWindowBodyValidation.validateAsync(req.body);
+
+ const teamId = requireTeamId(req.user?.teamId);
+
+ const editedMaintenanceWindow = await this.maintenanceWindowService.editMaintenanceWindow({ id: req.params.id, body: req.body, teamId });
+ return res.status(200).json({
+ success: true,
+ msg: "Maintenance window edited successfully",
+ data: editedMaintenanceWindow,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+}
+
+export default MaintenanceWindowController;
diff --git a/server/src/controllers/monitorController.js b/server/src/controllers/monitorController.js
deleted file mode 100755
index 0f40a4a01..000000000
--- a/server/src/controllers/monitorController.js
+++ /dev/null
@@ -1,457 +0,0 @@
-import {
- getMonitorByIdParamValidation,
- getMonitorByIdQueryValidation,
- getMonitorsByTeamIdParamValidation,
- getMonitorsByTeamIdQueryValidation,
- createMonitorBodyValidation,
- editMonitorBodyValidation,
- pauseMonitorParamValidation,
- getMonitorStatsByIdParamValidation,
- getMonitorStatsByIdQueryValidation,
- getCertificateParamValidation,
- getHardwareDetailsByIdParamValidation,
- getHardwareDetailsByIdQueryValidation,
-} from "../validation/joi.js";
-import sslChecker from "ssl-checker";
-import { fetchMonitorCertificate } from "./controllerUtils.js";
-import BaseController from "./baseController.js";
-
-const SERVICE_NAME = "monitorController";
-class MonitorController extends BaseController {
- static SERVICE_NAME = SERVICE_NAME;
- constructor(commonDependencies, { settingsService, jobQueue, emailService, monitorService }) {
- super(commonDependencies);
- this.settingsService = settingsService;
- this.jobQueue = jobQueue;
- this.emailService = emailService;
- this.monitorService = monitorService;
- }
-
- get serviceName() {
- return MonitorController.SERVICE_NAME;
- }
-
- async verifyTeamAccess(teamId, monitorId) {
- const monitor = await this.db.monitorModule.getMonitorById(monitorId);
- if (!monitor.teamId.equals(teamId)) {
- throw this.errorService.createAuthorizationError();
- }
- }
-
- getAllMonitors = this.asyncHandler(
- async (req, res) => {
- const monitors = await this.monitorService.getAllMonitors();
- return res.success({
- msg: this.stringService.monitorGetAll,
- data: monitors,
- });
- },
- SERVICE_NAME,
- "getAllMonitors"
- );
-
- getUptimeDetailsById = this.asyncHandler(
- async (req, res) => {
- const monitorId = req?.params?.monitorId;
- const dateRange = req?.query?.dateRange;
- const normalize = req?.query?.normalize;
-
- const teamId = req?.user?.teamId;
-
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const data = await this.monitorService.getUptimeDetailsById({
- teamId,
- monitorId,
- dateRange,
- normalize,
- });
- return res.success({
- msg: this.stringService.monitorGetByIdSuccess,
- data: data,
- });
- },
- SERVICE_NAME,
- "getUptimeDetailsById"
- );
-
- getMonitorStatsById = this.asyncHandler(
- async (req, res) => {
- await getMonitorStatsByIdParamValidation.validateAsync(req.params);
- await getMonitorStatsByIdQueryValidation.validateAsync(req.query);
-
- let { limit, sortOrder, dateRange, numToDisplay, normalize } = req.query;
- const monitorId = req?.params?.monitorId;
-
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const monitorStats = await this.monitorService.getMonitorStatsById({
- teamId,
- monitorId,
- limit,
- sortOrder,
- dateRange,
- numToDisplay,
- normalize,
- });
-
- return res.success({
- msg: this.stringService.monitorStatsById,
- data: monitorStats,
- });
- },
- SERVICE_NAME,
- "getMonitorStatsById"
- );
-
- /**
- * Get hardware details for a specific monitor by ID
- * @async
- * @param {Express.Request} req - Express request object containing monitorId in params
- * @param {Express.Response} res - Express response object
- * @param {Express.NextFunction} next - Express next middleware function
- * @returns {Promise}
- * @throws {Error} - Throws error if monitor not found or other database errors
- */
- getHardwareDetailsById = this.asyncHandler(
- async (req, res) => {
- await getHardwareDetailsByIdParamValidation.validateAsync(req.params);
- await getHardwareDetailsByIdQueryValidation.validateAsync(req.query);
-
- const monitorId = req?.params?.monitorId;
- const dateRange = req?.query?.dateRange;
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const monitor = await this.monitorService.getHardwareDetailsById({
- teamId,
- monitorId,
- dateRange,
- });
-
- return res.success({
- msg: this.stringService.monitorGetByIdSuccess,
- data: monitor,
- });
- },
- SERVICE_NAME,
- "getHardwareDetailsById"
- );
-
- getMonitorCertificate = this.asyncHandler(
- async (req, res) => {
- await getCertificateParamValidation.validateAsync(req.params);
-
- const { monitorId } = req.params;
- const monitor = await this.db.monitorModule.getMonitorById(monitorId);
- const certificate = await fetchMonitorCertificate(sslChecker, monitor);
-
- return res.success({
- msg: this.stringService.monitorCertificate,
- data: {
- certificateDate: new Date(certificate.validTo),
- },
- });
- },
- SERVICE_NAME,
- "getMonitorCertificate"
- );
-
- getMonitorById = this.asyncHandler(
- async (req, res) => {
- await getMonitorByIdParamValidation.validateAsync(req.params);
- await getMonitorByIdQueryValidation.validateAsync(req.query);
-
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const monitor = await this.monitorService.getMonitorById({ teamId, monitorId: req?.params?.monitorId });
-
- return res.success({
- msg: this.stringService.monitorGetByIdSuccess,
- data: monitor,
- });
- },
- SERVICE_NAME,
- "getMonitorById"
- );
-
- createMonitor = this.asyncHandler(
- async (req, res) => {
- await createMonitorBodyValidation.validateAsync(req.body);
-
- const userId = req?.user?._id;
- const teamId = req?.user?.teamId;
-
- const monitor = await this.monitorService.createMonitor({ teamId, userId, body: req.body });
-
- return res.success({
- msg: this.stringService.monitorCreate,
- data: monitor,
- });
- },
- SERVICE_NAME,
- "createMonitor"
- );
-
- createBulkMonitors = this.asyncHandler(
- async (req, res) => {
- if (!req.file) {
- throw this.errorService.createBadRequestError("No file uploaded");
- }
-
- if (!req.file.mimetype.includes("csv")) {
- throw this.errorService.createBadRequestError("File is not a CSV");
- }
-
- if (req.file.size === 0) {
- throw this.errorService.createBadRequestError("File is empty");
- }
-
- const userId = req?.user?._id;
- const teamId = req?.user?.teamId;
-
- if (!userId || !teamId) {
- throw this.errorService.createBadRequestError("Missing userId or teamId");
- }
-
- const fileData = req?.file?.buffer?.toString("utf-8");
- if (!fileData) {
- throw this.errorService.createBadRequestError("Cannot get file from buffer");
- }
-
- const monitors = await this.monitorService.createBulkMonitors({ fileData, userId, teamId });
-
- return res.success({
- msg: this.stringService.bulkMonitorsCreate,
- data: monitors,
- });
- },
- SERVICE_NAME,
- "createBulkMonitors"
- );
-
- deleteMonitor = this.asyncHandler(
- async (req, res) => {
- await getMonitorByIdParamValidation.validateAsync(req.params);
- const monitorId = req.params.monitorId;
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const deletedMonitor = await this.monitorService.deleteMonitor({ teamId, monitorId });
-
- return res.success({ msg: this.stringService.monitorDelete, data: deletedMonitor });
- },
- SERVICE_NAME,
- "deleteMonitor"
- );
-
- deleteAllMonitors = this.asyncHandler(
- async (req, res) => {
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const deletedCount = await this.monitorService.deleteAllMonitors({ teamId });
-
- return res.success({ msg: `Deleted ${deletedCount} monitors` });
- },
- SERVICE_NAME,
- "deleteAllMonitors"
- );
-
- editMonitor = this.asyncHandler(
- async (req, res) => {
- await getMonitorByIdParamValidation.validateAsync(req.params);
- await editMonitorBodyValidation.validateAsync(req.body);
- const monitorId = req?.params?.monitorId;
-
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const editedMonitor = await this.monitorService.editMonitor({ teamId, monitorId, body: req.body });
-
- return res.success({
- msg: this.stringService.monitorEdit,
- data: editedMonitor,
- });
- },
- SERVICE_NAME,
- "editMonitor"
- );
-
- pauseMonitor = this.asyncHandler(
- async (req, res) => {
- await pauseMonitorParamValidation.validateAsync(req.params);
-
- const monitorId = req.params.monitorId;
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const monitor = await this.monitorService.pauseMonitor({ teamId, monitorId });
-
- return res.success({
- msg: monitor.isActive ? this.stringService.monitorResume : this.stringService.monitorPause,
- data: monitor,
- });
- },
- SERVICE_NAME,
- "pauseMonitor"
- );
-
- addDemoMonitors = this.asyncHandler(
- async (req, res) => {
- const { _id, teamId } = req.user;
- const demoMonitors = await this.monitorService.addDemoMonitors({ userId: _id, teamId });
-
- return res.success({
- msg: this.stringService.monitorDemoAdded,
- data: demoMonitors?.length ?? 0,
- });
- },
- SERVICE_NAME,
- "addDemoMonitors"
- );
-
- sendTestEmail = this.asyncHandler(
- async (req, res) => {
- const { to } = req.body;
- if (!to || typeof to !== "string") {
- throw this.errorService.createBadRequestError(this.stringService.errorForValidEmailAddress);
- }
-
- const messageId = await this.monitorService.sendTestEmail({ to });
- return res.success({
- msg: this.stringService.sendTestEmail,
- data: { messageId },
- });
- },
- SERVICE_NAME,
- "sendTestEmail"
- );
-
- getMonitorsByTeamId = this.asyncHandler(
- async (req, res) => {
- await getMonitorsByTeamIdParamValidation.validateAsync(req.params);
- await getMonitorsByTeamIdQueryValidation.validateAsync(req.query);
-
- let { limit, type, page, rowsPerPage, filter, field, order } = req.query;
- const teamId = req?.user?.teamId;
-
- const monitors = await this.monitorService.getMonitorsByTeamId({ teamId, limit, type, page, rowsPerPage, filter, field, order });
-
- return res.success({
- msg: this.stringService.monitorGetByTeamId,
- data: monitors,
- });
- },
- SERVICE_NAME,
- "getMonitorsByTeamId"
- );
-
- getMonitorsAndSummaryByTeamId = this.asyncHandler(
- async (req, res) => {
- await getMonitorsByTeamIdParamValidation.validateAsync(req.params);
- await getMonitorsByTeamIdQueryValidation.validateAsync(req.query);
-
- const explain = req?.query?.explain;
- const type = req?.query?.type;
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const result = await this.monitorService.getMonitorsAndSummaryByTeamId({ teamId, type, explain });
-
- return res.success({
- msg: "OK", // TODO
- data: result,
- });
- },
- SERVICE_NAME,
- "getMonitorsAndSummaryByTeamId"
- );
-
- getMonitorsWithChecksByTeamId = this.asyncHandler(
- async (req, res) => {
- await getMonitorsByTeamIdParamValidation.validateAsync(req.params);
- await getMonitorsByTeamIdQueryValidation.validateAsync(req.query);
-
- const explain = req?.query?.explain;
- let { limit, type, page, rowsPerPage, filter, field, order } = req.query;
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const monitors = await this.monitorService.getMonitorsWithChecksByTeamId({
- teamId,
- limit,
- type,
- page,
- rowsPerPage,
- filter,
- field,
- order,
- explain,
- });
-
- return res.success({
- msg: "OK",
- data: monitors,
- });
- },
- SERVICE_NAME,
- "getMonitorsWithChecksByTeamId"
- );
-
- exportMonitorsToCSV = this.asyncHandler(
- async (req, res) => {
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const csv = await this.monitorService.exportMonitorsToCSV({ teamId });
-
- return res.file({
- data: csv,
- headers: {
- "Content-Type": "text/csv",
- "Content-Disposition": "attachment; filename=monitors.csv",
- },
- });
- },
- SERVICE_NAME,
- "exportMonitorsToCSV"
- );
-
- getAllGames = this.asyncHandler(
- async (req, res) => {
- return res.success({
- msg: "OK",
- data: this.monitorService.getAllGames(),
- });
- },
- SERVICE_NAME,
- "getAllGames"
- );
-}
-
-export default MonitorController;
diff --git a/server/src/controllers/monitorController.ts b/server/src/controllers/monitorController.ts
new file mode 100644
index 000000000..30ceabbc5
--- /dev/null
+++ b/server/src/controllers/monitorController.ts
@@ -0,0 +1,410 @@
+import { Request, Response, NextFunction } from "express";
+
+import {
+ getMonitorByIdParamValidation,
+ getMonitorByIdQueryValidation,
+ getMonitorsByTeamIdParamValidation,
+ getMonitorsByTeamIdQueryValidation,
+ getMonitorsWithChecksQueryValidation,
+ createMonitorBodyValidation,
+ editMonitorBodyValidation,
+ pauseMonitorParamValidation,
+ getCertificateParamValidation,
+ getHardwareDetailsByIdParamValidation,
+ getHardwareDetailsByIdQueryValidation,
+} from "@/validation/joi.js";
+import sslChecker from "ssl-checker";
+import {
+ fetchMonitorCertificate,
+ requireString,
+ optionalString,
+ optionalNumber,
+ optionalBoolean,
+ parseMonitorTypeFilter,
+ parseSortOrder,
+ requireTeamId,
+} from "./controllerUtils.js";
+import { AppError } from "@/utils/AppError.js";
+import { IMonitorService } from "@/service/index.js";
+
+const SERVICE_NAME = "monitorController";
+class MonitorController {
+ static SERVICE_NAME = SERVICE_NAME;
+
+ private monitorService: IMonitorService;
+
+ constructor(monitorService: IMonitorService) {
+ this.monitorService = monitorService;
+ }
+
+ get serviceName() {
+ return MonitorController.SERVICE_NAME;
+ }
+
+ getMonitorCertificate = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await getCertificateParamValidation.validateAsync(req.params);
+ const teamId = requireTeamId(req?.user?.teamId);
+ const monitorId = requireString(req.params?.monitorId, "Monitor ID");
+ const monitor = await this.monitorService.getMonitorById({ teamId, monitorId });
+ const certificate = await fetchMonitorCertificate(sslChecker, monitor);
+
+ return res.status(200).json({
+ success: true,
+ msg: "SSL certificate retrieved successfully",
+ data: {
+ certificateDate: new Date(certificate.validTo),
+ },
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getUptimeDetailsById = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
+ const dateRange = requireString(req?.query?.dateRange, "dateRange");
+ const normalize = optionalBoolean(req?.query?.normalize, "normalize");
+ const teamId = requireTeamId(req?.user?.teamId);
+
+ const data = await this.monitorService.getUptimeDetailsById({
+ teamId,
+ monitorId,
+ dateRange,
+ normalize,
+ });
+ return res.status(200).json({
+ success: true,
+ msg: "Uptime details retrieved successfully",
+ data: data,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getHardwareDetailsById = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await getHardwareDetailsByIdParamValidation.validateAsync(req.params);
+ await getHardwareDetailsByIdQueryValidation.validateAsync(req.query);
+
+ const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
+ const dateRange = optionalString(req?.query?.dateRange, "dateRange") || "recent";
+ const teamId = requireTeamId(req?.user?.teamId);
+
+ const monitor = await this.monitorService.getHardwareDetailsById({
+ teamId,
+ monitorId,
+ dateRange,
+ });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Hardware details retrieved successfully",
+ data: monitor,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+ getPageSpeedDetailsById = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await getHardwareDetailsByIdParamValidation.validateAsync(req.params);
+ await getHardwareDetailsByIdQueryValidation.validateAsync(req.query);
+
+ const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
+ const dateRange = requireString(req?.query?.dateRange, "dateRange");
+ const teamId = requireTeamId(req?.user?.teamId);
+
+ const data = await this.monitorService.getPageSpeedDetailsById({
+ teamId,
+ monitorId,
+ dateRange,
+ });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Page speed details retrieved successfully",
+ data,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getMonitorById = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await getMonitorByIdParamValidation.validateAsync(req.params);
+ await getMonitorByIdQueryValidation.validateAsync(req.query);
+
+ const teamId = requireTeamId(req?.user?.teamId);
+ const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
+
+ const monitor = await this.monitorService.getMonitorById({ teamId, monitorId });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Monitor retrieved successfully",
+ data: monitor,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ createMonitor = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await createMonitorBodyValidation.validateAsync(req.body);
+
+ const userId = requireString(req?.user?.id, "User ID");
+ const teamId = requireTeamId(req?.user?.teamId);
+
+ const monitor = await this.monitorService.createMonitor(teamId, userId, req.body);
+
+ return res.status(200).json({
+ success: true,
+ msg: "Monitor created successfully",
+ data: monitor,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ createBulkMonitors = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const userId = req.user?.id;
+ if (!userId) {
+ throw new AppError({ message: "User ID is required", status: 400 });
+ }
+
+ const teamId = requireTeamId(req?.user?.teamId);
+
+ const data = req.body;
+ const monitors = await this.monitorService.createBulkMonitors(data, userId, teamId);
+
+ return res.status(200).json({
+ success: true,
+ msg: "Bulk monitors created successfully",
+ data: monitors,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ deleteMonitor = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await getMonitorByIdParamValidation.validateAsync(req.params);
+ const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
+ const teamId = requireTeamId(req?.user?.teamId);
+
+ const deletedMonitor = await this.monitorService.deleteMonitor({ teamId, monitorId });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Monitor deleted successfully",
+ data: deletedMonitor,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ deleteAllMonitors = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const teamId = requireTeamId(req?.user?.teamId);
+
+ const deletedCount = await this.monitorService.deleteAllMonitors({ teamId });
+
+ return res.status(200).json({
+ success: true,
+ msg: `Deleted ${deletedCount} monitors`,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ editMonitor = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await getMonitorByIdParamValidation.validateAsync(req.params);
+ await editMonitorBodyValidation.validateAsync(req.body);
+ const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
+ const teamId = requireTeamId(req?.user?.teamId);
+
+ const editedMonitor = await this.monitorService.editMonitor({ teamId, monitorId, body: req.body });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Monitor edited successfully",
+ data: editedMonitor,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ pauseMonitor = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await pauseMonitorParamValidation.validateAsync(req.params);
+
+ const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
+ const teamId = requireTeamId(req?.user?.teamId);
+
+ const monitor = await this.monitorService.pauseMonitor({ teamId, monitorId });
+
+ return res.status(200).json({
+ success: true,
+ msg: monitor.isActive ? "Monitor resumed successfully" : "Monitor paused successfully",
+ data: monitor,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ addDemoMonitors = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const id = requireString(req?.user?.id, "User ID");
+ const teamId = requireTeamId(req?.user?.teamId);
+ const demoMonitors = await this.monitorService.addDemoMonitors({ userId: id, teamId });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Demo monitors added successfully",
+ data: demoMonitors?.length ?? 0,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ sendTestEmail = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const { to } = req.body;
+ if (!to || typeof to !== "string") {
+ throw new AppError({ message: "Invalid 'to' email address", status: 400 });
+ }
+
+ const messageId = await this.monitorService.sendTestEmail({ to });
+ return res.status(200).json({
+ success: true,
+ msg: "Test email sent successfully",
+ data: { messageId },
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getMonitorsByTeamId = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await getMonitorsByTeamIdParamValidation.validateAsync(req.params);
+ await getMonitorsByTeamIdQueryValidation.validateAsync(req.query);
+
+ const teamId = requireTeamId(req?.user?.teamId);
+ const type = parseMonitorTypeFilter(req.query?.type);
+ const filter = optionalString(req.query?.filter, "filter");
+
+ const monitors = await this.monitorService.getMonitorsByTeamId({ teamId, type, filter });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Monitors retrieved successfully",
+ data: monitors,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getMonitorsWithChecksByTeamId = async (req: Request, res: Response, next: NextFunction) => {
+ 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");
+ const rowsPerPage = optionalNumber(req?.query?.rowsPerPage, "rowsPerPage");
+ const filter = optionalString(req?.query?.filter, "filter");
+ const field = optionalString(req?.query?.field, "field");
+ const order = parseSortOrder(req?.query?.order);
+ const type = parseMonitorTypeFilter(req?.query?.type);
+ const teamId = requireTeamId(req?.user?.teamId);
+
+ const monitors = await this.monitorService.getMonitorsWithChecksByTeamId({
+ teamId,
+ limit,
+ type,
+ page,
+ rowsPerPage,
+ filter,
+ field,
+ order,
+ explain,
+ });
+
+ return res.status(200).json({
+ msg: "Monitors retrieved successfully",
+ data: monitors,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ exportMonitorsToJSON = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const teamId = req?.user?.teamId;
+ if (!teamId) {
+ throw new AppError({ message: "Team ID is required", status: 400 });
+ }
+
+ const json = await this.monitorService.exportMonitorsToJSON({ teamId });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Monitors exported successfully",
+ data: json,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getAllGames = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const games = this.monitorService.getAllGames();
+ return res.status(200).json({
+ success: true,
+ msg: "Supported games retrieved successfully",
+ data: games,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getGroupsByTeamId = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const teamId = req?.user?.teamId;
+ if (!teamId) {
+ throw new AppError({ message: "Team ID is required", status: 400 });
+ }
+
+ const groups = await this.monitorService.getGroupsByTeamId({ teamId });
+
+ return res.status(200).json({
+ msg: "Groups retrieved successfully",
+ data: groups,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+}
+
+export default MonitorController;
diff --git a/server/src/controllers/notificationController.js b/server/src/controllers/notificationController.js
deleted file mode 100755
index e00dd5b6f..000000000
--- a/server/src/controllers/notificationController.js
+++ /dev/null
@@ -1,180 +0,0 @@
-import { createNotificationBodyValidation } from "../validation/joi.js";
-import BaseController from "./baseController.js";
-
-const SERVICE_NAME = "NotificationController";
-
-class NotificationController extends BaseController {
- static SERVICE_NAME = SERVICE_NAME;
- constructor(commonDependencies, { notificationService, statusService }) {
- super(commonDependencies);
- this.notificationService = notificationService;
- this.statusService = statusService;
- }
-
- get serviceName() {
- return NotificationController.SERVICE_NAME;
- }
-
- testNotification = this.asyncHandler(
- async (req, res) => {
- const notification = req.body;
-
- const success = await this.notificationService.sendTestNotification(notification);
-
- if (!success) {
- throw this.errorService.createServerError("Sending notification failed");
- }
-
- return res.success({
- msg: "Notification sent successfully",
- });
- },
- SERVICE_NAME,
- "testNotification"
- );
-
- createNotification = this.asyncHandler(
- async (req, res) => {
- await createNotificationBodyValidation.validateAsync(req.body, {
- abortEarly: false,
- });
-
- const body = req.body;
-
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const userId = req?.user?._id;
- if (!userId) {
- throw this.errorService.createBadRequestError("User ID is required");
- }
- body.userId = userId;
- body.teamId = teamId;
-
- const notification = await this.db.notificationModule.createNotification(body);
- return res.success({
- msg: "Notification created successfully",
- data: notification,
- });
- },
- SERVICE_NAME,
- "createNotification"
- );
-
- getNotificationsByTeamId = this.asyncHandler(
- async (req, res) => {
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const notifications = await this.db.notificationModule.getNotificationsByTeamId(teamId);
-
- return res.success({
- msg: "Notifications fetched successfully",
- data: notifications,
- });
- },
- SERVICE_NAME,
- "getNotificationsByTeamId"
- );
-
- deleteNotification = this.asyncHandler(
- async (req, res) => {
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const notification = await this.db.notificationModule.getNotificationById(req.params.id);
- if (!notification.teamId.equals(teamId)) {
- throw this.errorService.createAuthorizationError();
- }
-
- await this.db.notificationModule.deleteNotificationById(req.params.id);
- return res.success({
- msg: "Notification deleted successfully",
- });
- },
- SERVICE_NAME,
- "deleteNotification"
- );
-
- getNotificationById = this.asyncHandler(
- async (req, res) => {
- const notification = await this.db.notificationModule.getNotificationById(req.params.id);
-
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- if (!notification.teamId.equals(teamId)) {
- throw this.errorService.createAuthorizationError();
- }
- return res.success({
- msg: "Notification fetched successfully",
- data: notification,
- });
- },
- SERVICE_NAME,
- "getNotificationById"
- );
-
- editNotification = this.asyncHandler(
- async (req, res) => {
- await createNotificationBodyValidation.validateAsync(req.body, {
- abortEarly: false,
- });
-
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const notification = await this.db.notificationModule.getNotificationById(req.params.id);
-
- if (!notification.teamId.equals(teamId)) {
- throw this.errorService.createAuthorizationError();
- }
-
- const editedNotification = await this.db.notificationModule.editNotification(req.params.id, req.body);
- return res.success({
- msg: "Notification updated successfully",
- data: editedNotification,
- });
- },
- SERVICE_NAME,
- "editNotification"
- );
-
- testAllNotifications = this.asyncHandler(
- async (req, res) => {
- const monitorId = req.body.monitorId;
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const monitor = await this.db.monitorModule.getMonitorById(monitorId);
-
- if (!monitor.teamId.equals(teamId)) {
- throw this.errorService.createAuthorizationError();
- }
-
- const notifications = monitor.notifications;
- if (notifications.length === 0) throw this.errorService.createBadRequestError("No notifications");
- const result = await this.notificationService.testAllNotifications(notifications);
- if (!result) throw this.errorService.createServerError("Failed to send all notifications");
- return res.success({
- msg: "All notifications sent successfully",
- });
- },
- SERVICE_NAME,
- "testAllNotifications"
- );
-}
-
-export default NotificationController;
diff --git a/server/src/controllers/notificationController.ts b/server/src/controllers/notificationController.ts
new file mode 100644
index 000000000..1aa10a82c
--- /dev/null
+++ b/server/src/controllers/notificationController.ts
@@ -0,0 +1,188 @@
+import { Request, Response, NextFunction } from "express";
+
+import { Notification } from "@/types/index.js";
+import { createNotificationBodyValidation } from "@/validation/joi.js";
+import { AppError } from "@/utils/AppError.js";
+import { IMonitorsRepository } from "@/repositories/index.js";
+import { INotificationsService } from "@/service/index.js";
+import { requireTeamId } from "./controllerUtils.js";
+
+const SERVICE_NAME = "NotificationController";
+
+class NotificationController {
+ static SERVICE_NAME = SERVICE_NAME;
+ private notificationsService: INotificationsService;
+ private monitorsRepository: IMonitorsRepository;
+ constructor(notificationsService: INotificationsService, monitorsRepository: IMonitorsRepository) {
+ this.notificationsService = notificationsService;
+ this.monitorsRepository = monitorsRepository;
+ }
+
+ get serviceName() {
+ return NotificationController.SERVICE_NAME;
+ }
+
+ testNotification = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const notification: Notification = req.body;
+ const success = await this.notificationsService.sendTestNotification(notification);
+
+ if (!success) {
+ throw new AppError({ message: "Sending notification failed", status: 500 });
+ }
+
+ return res.status(200).json({
+ success: true,
+ msg: "Notification sent successfully",
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ createNotification = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await createNotificationBodyValidation.validateAsync(req.body, {
+ abortEarly: false,
+ });
+
+ const body = req.body;
+
+ const teamId = req?.user?.teamId;
+ if (!teamId) {
+ throw new AppError({ message: "Team ID is required", status: 400 });
+ }
+
+ const userId = req?.user?.id;
+ if (!userId) {
+ throw new AppError({ message: "User ID is required", status: 400 });
+ }
+ body.userId = userId;
+ body.teamId = teamId;
+
+ const notification = await this.notificationsService.createNotification(body);
+ return res.status(200).json({
+ success: true,
+ msg: "Notification created successfully",
+ data: notification,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getNotificationsByTeamId = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const teamId = req?.user?.teamId;
+ if (!teamId) {
+ throw new AppError({ message: "Team ID is required", status: 400 });
+ }
+
+ const notifications = await this.notificationsService.findNotificationsByTeamId(teamId);
+
+ return res.status(200).json({
+ success: true,
+ msg: "Notifications fetched successfully",
+ data: notifications,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ deleteNotification = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const teamId = req?.user?.teamId;
+ if (!teamId) {
+ throw new AppError({ message: "Team ID is required", status: 400 });
+ }
+
+ const notificationId = req.params.id;
+ if (!notificationId) {
+ throw new AppError({ message: "Notification ID is required", status: 400 });
+ }
+
+ await this.notificationsService.deleteById(notificationId, teamId);
+ return res.status(200).json({
+ success: true,
+ msg: "Notification deleted successfully",
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getNotificationById = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const teamId = requireTeamId(req.user?.teamId);
+ const notificationId = req.params.id;
+ if (!notificationId) {
+ throw new AppError({ message: "Notification ID is required", status: 400 });
+ }
+
+ const notification = await this.notificationsService.findById(notificationId, teamId);
+
+ return res.status(200).json({
+ success: true,
+ msg: "Notification fetched successfully",
+ data: notification,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ editNotification = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await createNotificationBodyValidation.validateAsync(req.body, {
+ abortEarly: false,
+ });
+
+ const teamId = requireTeamId(req.user?.teamId);
+ const notificationId = req.params.id;
+ if (!notificationId) {
+ throw new AppError({ message: "Notification ID is required", status: 400 });
+ }
+ const editedNotification = await this.notificationsService.updateById(notificationId, teamId, req.body);
+ return res.status(200).json({
+ success: true,
+ msg: "Notification updated successfully",
+ data: editedNotification,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ testAllNotifications = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const monitorId = req.body.monitorId;
+ const teamId = req?.user?.teamId;
+ if (!teamId) {
+ throw new AppError({ message: "Team ID is required", status: 400 });
+ }
+
+ const monitor = await this.monitorsRepository.findById(monitorId, teamId);
+
+ const notifications = monitor.notifications;
+ if (notifications.length === 0) {
+ throw new AppError({ message: "No notifications", status: 400 });
+ }
+
+ const result = await this.notificationsService.testAllNotifications(notifications);
+
+ if (!result) {
+ throw new AppError({ message: "Failed to send all notifications", status: 500 });
+ }
+
+ return res.status(200).json({
+ success: true,
+ msg: "All notifications sent successfully",
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+}
+
+export default NotificationController;
diff --git a/server/src/controllers/queueController.js b/server/src/controllers/queueController.js
deleted file mode 100755
index d2c3fd06f..000000000
--- a/server/src/controllers/queueController.js
+++ /dev/null
@@ -1,87 +0,0 @@
-import BaseController from "./baseController.js";
-const SERVICE_NAME = "JobQueueController";
-
-class JobQueueController extends BaseController {
- static SERVICE_NAME = SERVICE_NAME;
- constructor(commonDependencies, { jobQueue }) {
- super(commonDependencies);
- this.jobQueue = jobQueue;
- }
-
- get serviceName() {
- return JobQueueController.SERVICE_NAME;
- }
-
- getMetrics = this.asyncHandler(
- async (req, res) => {
- const metrics = await this.jobQueue.getMetrics();
- res.success({
- msg: this.stringService.queueGetMetrics,
- data: metrics,
- });
- },
- SERVICE_NAME,
- "getMetrics"
- );
-
- getJobs = this.asyncHandler(
- async (req, res) => {
- const jobs = await this.jobQueue.getJobs();
- return res.success({
- msg: this.stringService.queueGetJobs,
- data: jobs,
- });
- },
- SERVICE_NAME,
- "getJobs"
- );
-
- getAllMetrics = this.asyncHandler(
- async (req, res) => {
- const jobs = await this.jobQueue.getJobs();
- const metrics = await this.jobQueue.getMetrics();
- return res.success({
- msg: this.stringService.queueGetAllMetrics,
- data: { jobs, metrics },
- });
- },
- SERVICE_NAME,
- "getAllMetrics"
- );
-
- addJob = this.asyncHandler(
- async (req, res) => {
- await this.jobQueue.addJob(Math.random().toString(36).substring(7));
- return res.success({
- msg: this.stringService.queueAddJob,
- });
- },
- SERVICE_NAME,
- "addJob"
- );
-
- flushQueue = this.asyncHandler(
- async (req, res) => {
- const result = await this.jobQueue.flushQueues();
- return res.success({
- msg: this.stringService.jobQueueFlush,
- data: result,
- });
- },
- SERVICE_NAME,
- "flushQueue"
- );
-
- checkQueueHealth = this.asyncHandler(
- async (req, res) => {
- const stuckQueues = await this.jobQueue.checkQueueHealth();
- return res.success({
- msg: this.stringService.queueHealthCheck,
- data: stuckQueues,
- });
- },
- SERVICE_NAME,
- "checkQueueHealth"
- );
-}
-export default JobQueueController;
diff --git a/server/src/controllers/queueController.ts b/server/src/controllers/queueController.ts
new file mode 100755
index 000000000..9885bab87
--- /dev/null
+++ b/server/src/controllers/queueController.ts
@@ -0,0 +1,94 @@
+import { Request, Response, NextFunction } from "express";
+
+const SERVICE_NAME = "JobQueueController";
+
+class JobQueueController {
+ static SERVICE_NAME = SERVICE_NAME;
+ private jobQueue: any;
+ constructor(jobQueue: any) {
+ this.jobQueue = jobQueue;
+ }
+
+ get serviceName() {
+ return JobQueueController.SERVICE_NAME;
+ }
+
+ getMetrics = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const metrics = await this.jobQueue.getMetrics();
+ res.status(200).json({
+ success: true,
+ msg: "Queue metrics fetched successfully",
+ data: metrics,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getJobs = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const jobs = await this.jobQueue.getJobs();
+ return res.status(200).json({
+ success: true,
+ msg: "Queue jobs fetched successfully",
+ data: jobs,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getAllMetrics = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const jobs = await this.jobQueue.getJobs();
+ const metrics = await this.jobQueue.getMetrics();
+ return res.status(200).json({
+ success: true,
+ msg: "Queue metrics fetched successfully",
+ data: { jobs, metrics },
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ addJob = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await this.jobQueue.addJob(Math.random().toString(36).substring(7));
+ return res.status(200).json({
+ success: true,
+ msg: "Job added to queue successfully",
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ flushQueue = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const result = await this.jobQueue.flushQueues();
+ return res.status(200).json({
+ success: true,
+ msg: "Queue flushed successfully",
+ data: result,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ checkQueueHealth = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const stuckQueues = await this.jobQueue.checkQueueHealth();
+ return res.status(200).json({
+ success: true,
+ msg: "Queue health checked successfully",
+ data: stuckQueues,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+}
+export default JobQueueController;
diff --git a/server/src/controllers/settingsController.js b/server/src/controllers/settingsController.ts
old mode 100755
new mode 100644
similarity index 57%
rename from server/src/controllers/settingsController.js
rename to server/src/controllers/settingsController.ts
index 7da724d23..0a01daa0c
--- a/server/src/controllers/settingsController.js
+++ b/server/src/controllers/settingsController.ts
@@ -1,28 +1,32 @@
-import { updateAppSettingsBodyValidation } from "../validation/joi.js";
-import { sendTestEmailBodyValidation } from "../validation/joi.js";
-import BaseController from "./baseController.js";
+import { Request, Response, NextFunction } from "express";
+import { sendTestEmailBodyValidation, updateAppSettingsBodyValidation } from "@/validation/joi.js";
+import { AppError } from "@/utils/AppError.js";
const SERVICE_NAME = "SettingsController";
-class SettingsController extends BaseController {
+class SettingsController {
static SERVICE_NAME = SERVICE_NAME;
- constructor(commonDependencies, { settingsService, emailService }) {
- super(commonDependencies);
+ private settingsService: any;
+ private emailService: any;
+ private db: any;
+ constructor(settingsService: any, emailService: any, db: any) {
this.settingsService = settingsService;
this.emailService = emailService;
+ this.db = db;
}
get serviceName() {
return SettingsController.SERVICE_NAME;
}
- buildAppSettings = (dbSettings) => {
+ buildAppSettings = (dbSettings: any) => {
const sanitizedSettings = { ...dbSettings };
delete sanitizedSettings.version;
-
+ delete sanitizedSettings.jwtSecret;
const returnSettings = {
pagespeedKeySet: false,
emailPasswordSet: false,
+ settings: null,
};
if (typeof sanitizedSettings.pagespeedApiKey !== "undefined") {
@@ -37,37 +41,35 @@ class SettingsController extends BaseController {
return returnSettings;
};
- getAppSettings = this.asyncHandler(
- async (req, res) => {
+ getAppSettings = async (req: Request, res: Response, next: NextFunction) => {
+ try {
const dbSettings = await this.settingsService.getDBSettings();
const returnSettings = this.buildAppSettings(dbSettings);
- return res.success({
- msg: this.stringService.getAppSettings,
+ return res.status(200).json({
+ success: true,
+ msg: "App settings fetched successfully",
data: returnSettings,
});
- },
- SERVICE_NAME,
- "getAppSettings"
- );
+ } catch (error) {
+ next(error);
+ }
+ };
- updateAppSettings = this.asyncHandler(
- async (req, res) => {
- await updateAppSettingsBodyValidation.validateAsync(req.body);
+ updateAppSettings = async (req: Request, res: Response, next: NextFunction) => {
+ await updateAppSettingsBodyValidation.validateAsync(req.body);
- const updatedSettings = await this.db.settingsModule.updateAppSettings(req.body);
- const returnSettings = this.buildAppSettings(updatedSettings);
- return res.success({
- msg: this.stringService.updateAppSettings,
- data: returnSettings,
- });
- },
- SERVICE_NAME,
- "updateAppSettings"
- );
+ const updatedSettings = await this.settingsService.updateDbSettings(req.body);
+ const returnSettings = this.buildAppSettings(updatedSettings);
+ return res.status(200).json({
+ success: true,
+ msg: "App settings updated successfully",
+ data: returnSettings,
+ });
+ };
- sendTestEmail = this.asyncHandler(
- async (req, res) => {
+ sendTestEmail = async (req: Request, res: Response, next: NextFunction) => {
+ try {
await sendTestEmailBodyValidation.validateAsync(req.body);
const {
@@ -86,7 +88,7 @@ class SettingsController extends BaseController {
systemEmailTLSServername,
} = req.body;
- const subject = this.stringService.testEmailSubject;
+ const subject = "This is a test email from Checkmate";
const context = { testName: "Monitoring System" };
const html = await this.emailService.buildEmail("testEmailTemplate", context);
@@ -106,17 +108,18 @@ class SettingsController extends BaseController {
});
if (!messageId) {
- throw this.errorService.createServerError("Failed to send test email.");
+ throw new AppError({ message: "Failed to send test email.", status: 500 });
}
- return res.success({
- msg: this.stringService.sendTestEmail,
+ return res.status(200).json({
+ success: true,
+ msg: "Test email sent successfully",
data: { messageId },
});
- },
- SERVICE_NAME,
- "sendTestEmail"
- );
+ } catch (error) {
+ next(error);
+ }
+ };
}
export default SettingsController;
diff --git a/server/src/controllers/statusPageController.js b/server/src/controllers/statusPageController.js
deleted file mode 100755
index a46688590..000000000
--- a/server/src/controllers/statusPageController.js
+++ /dev/null
@@ -1,108 +0,0 @@
-import { createStatusPageBodyValidation, getStatusPageParamValidation, getStatusPageQueryValidation, imageValidation } from "../validation/joi.js";
-import BaseController from "./baseController.js";
-
-const SERVICE_NAME = "statusPageController";
-
-class StatusPageController extends BaseController {
- static SERVICE_NAME = SERVICE_NAME;
- constructor(commonDependencies) {
- super(commonDependencies);
- }
-
- get serviceName() {
- return StatusPageController.SERVICE_NAME;
- }
-
- createStatusPage = this.asyncHandler(
- async (req, res) => {
- await createStatusPageBodyValidation.validateAsync(req.body);
- await imageValidation.validateAsync(req.file);
-
- const { _id, teamId } = req.user;
- const statusPage = await this.db.statusPageModule.createStatusPage({
- statusPageData: req.body,
- image: req.file,
- userId: _id,
- teamId,
- });
- return res.success({
- msg: this.stringService.statusPageCreate,
- data: statusPage,
- });
- },
- SERVICE_NAME,
- "createStatusPage"
- );
-
- updateStatusPage = this.asyncHandler(
- async (req, res) => {
- await createStatusPageBodyValidation.validateAsync(req.body);
- await imageValidation.validateAsync(req.file);
-
- const statusPage = await this.db.statusPageModule.updateStatusPage(req.body, req.file);
- if (statusPage === null) {
- throw this.errorService.createNotFoundError(this.stringService.statusPageNotFound);
- }
- return res.success({
- msg: this.stringService.statusPageUpdate,
- data: statusPage,
- });
- },
- SERVICE_NAME,
- "updateStatusPage"
- );
-
- getStatusPage = this.asyncHandler(
- async (req, res) => {
- const statusPage = await this.db.statusPageModule.getStatusPage();
- return res.success({
- msg: this.stringService.statusPageByUrl,
- data: statusPage,
- });
- },
- SERVICE_NAME,
- "getStatusPage"
- );
-
- getStatusPageByUrl = this.asyncHandler(
- async (req, res) => {
- await getStatusPageParamValidation.validateAsync(req.params);
- await getStatusPageQueryValidation.validateAsync(req.query);
-
- const statusPage = await this.db.statusPageModule.getStatusPageByUrl(req.params.url);
- return res.success({
- msg: this.stringService.statusPageByUrl,
- data: statusPage,
- });
- },
- SERVICE_NAME,
- "getStatusPageByUrl"
- );
-
- getStatusPagesByTeamId = this.asyncHandler(
- async (req, res) => {
- const teamId = req.user.teamId;
- const statusPages = await this.db.statusPageModule.getStatusPagesByTeamId(teamId);
-
- return res.success({
- msg: this.stringService.statusPageByTeamId,
- data: statusPages,
- });
- },
- SERVICE_NAME,
- "getStatusPagesByTeamId"
- );
-
- deleteStatusPage = this.asyncHandler(
- async (req, res) => {
- await this.db.statusPageModule.deleteStatusPage(req.params.url);
- return res.success({
- msg: this.stringService.statusPageDelete,
- });
- },
- SERVICE_NAME,
- "deleteStatusPage"
- );
-}
-
-export default StatusPageController;
diff --git a/server/src/controllers/statusPageController.ts b/server/src/controllers/statusPageController.ts
new file mode 100644
index 000000000..3501a6592
--- /dev/null
+++ b/server/src/controllers/statusPageController.ts
@@ -0,0 +1,136 @@
+import { Request, Response, NextFunction } from "express";
+
+import { createStatusPageBodyValidation, getStatusPageParamValidation, getStatusPageQueryValidation, imageValidation } from "@/validation/joi.js";
+import { AppError } from "@/utils/AppError.js";
+import { requireTeamId, requireUserId } from "@/controllers/controllerUtils.js";
+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";
+
+class StatusPageController {
+ static SERVICE_NAME = SERVICE_NAME;
+ private statusPageService: IStatusPageService;
+ private monitorsRepository: IMonitorsRepository;
+ private settingsService: ISettingsService;
+ constructor(statusPageService: IStatusPageService, monitorsRepository: IMonitorsRepository, settingsService: ISettingsService) {
+ this.statusPageService = statusPageService;
+ this.monitorsRepository = monitorsRepository;
+ this.settingsService = settingsService;
+ }
+
+ get serviceName() {
+ return StatusPageController.SERVICE_NAME;
+ }
+
+ createStatusPage = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await createStatusPageBodyValidation.validateAsync(req.body);
+ await imageValidation.validateAsync(req.file);
+
+ const teamId = requireTeamId(req?.user?.teamId);
+ const userId = requireUserId(req?.user?.id);
+ const statusPage = await this.statusPageService.createStatusPage(userId, teamId, req.file, req.body);
+
+ return res.status(200).json({
+ success: true,
+ msg: "Status page created successfully",
+ data: statusPage,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ updateStatusPage = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await createStatusPageBodyValidation.validateAsync(req.body);
+ await imageValidation.validateAsync(req.file);
+ const teamId = requireTeamId(req?.user?.teamId);
+ const statusPageId = req.params.id;
+ if (!statusPageId) {
+ throw new AppError({ message: "Status page ID is required", status: 400 });
+ }
+ const statusPage = await this.statusPageService.updateStatusPage(statusPageId, teamId, req.file, req.body);
+ if (statusPage === null) {
+ throw new AppError({ message: "Status page not found", status: 404 });
+ }
+ return res.status(200).json({
+ success: true,
+ msg: "Status page updated successfully",
+ data: statusPage,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getStatusPageByUrl = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await getStatusPageParamValidation.validateAsync(req.params);
+ await getStatusPageQueryValidation.validateAsync(req.query);
+
+ if (!req.params.url) {
+ throw new AppError({ message: "Status page URL is required", status: 400 });
+ }
+
+ const statusPage = await this.statusPageService.getStatusPageByUrl(req.params.url);
+ const settings = await this.settingsService.getDBSettings();
+ const showURL = settings.showURL;
+
+ const monitors = await this.monitorsRepository.findByIdsWithChecks(statusPage.monitors);
+ const normalizedMonitors = monitors.map((monitor) => {
+ const normalizedChecks = NormalizeData(monitor.checks, 10, 100);
+ if (!showURL) {
+ const { url, port, secret, notifications, ...rest } = monitor;
+ return { ...rest, checks: normalizedChecks };
+ }
+ return { ...monitor, checks: normalizedChecks };
+ });
+ return res.status(200).json({
+ success: true,
+ msg: "Status page retrieved successfully",
+ data: { statusPage, monitors: normalizedMonitors },
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+ getStatusPagesByTeamId = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const teamId = requireTeamId(req.user?.teamId);
+ const statusPages = await this.statusPageService.getStatusPagesByTeamId(teamId);
+
+ return res.status(200).json({
+ success: true,
+ msg: "Status pages retrieved successfully",
+ data: statusPages,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ deleteStatusPage = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const statusPageId = req.params.id;
+ if (!statusPageId) {
+ throw new AppError({ message: "Status page ID is required", status: 400 });
+ }
+ const teamId = requireTeamId(req.user?.teamId);
+ await this.statusPageService.deleteStatusPage(statusPageId, teamId);
+ return res.status(200).json({
+ success: true,
+ msg: "Status page deleted successfully",
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+}
+
+export default StatusPageController;
diff --git a/server/src/db/mongo/MongoDB.js b/server/src/db/MongoDB.ts
similarity index 53%
rename from server/src/db/mongo/MongoDB.js
rename to server/src/db/MongoDB.ts
index 74fcbd89f..d62f4bbad 100755
--- a/server/src/db/mongo/MongoDB.js
+++ b/server/src/db/MongoDB.ts
@@ -1,39 +1,18 @@
import mongoose from "mongoose";
-import AppSettings from "../models/AppSettings.js";
+import AppSettings from "./models/AppSettings.js";
import { runMigrations } from "./migration/index.js";
+import { ILogger } from "@/utils/logger.js";
+import { EnvConfig } from "@/service/system/settingsService.js";
+const SERVICE_NAME = "MongoDB";
class MongoDB {
- static SERVICE_NAME = "MongoDB";
+ static SERVICE_NAME = SERVICE_NAME;
- constructor({
- logger,
- envSettings,
- checkModule,
- inviteModule,
- statusPageModule,
- userModule,
- hardwareCheckModule,
- maintenanceWindowModule,
- monitorModule,
- networkCheckModule,
- notificationModule,
- pageSpeedCheckModule,
- recoveryModule,
- settingsModule,
- }) {
+ private logger: ILogger;
+ private envSettings: EnvConfig;
+
+ constructor(logger: ILogger, envSettings: EnvConfig) {
this.logger = logger;
this.envSettings = envSettings;
- this.userModule = userModule;
- this.inviteModule = inviteModule;
- this.recoveryModule = recoveryModule;
- this.pageSpeedCheckModule = pageSpeedCheckModule;
- this.hardwareCheckModule = hardwareCheckModule;
- this.checkModule = checkModule;
- this.maintenanceWindowModule = maintenanceWindowModule;
- this.monitorModule = monitorModule;
- this.notificationModule = notificationModule;
- this.settingsModule = settingsModule;
- this.statusPageModule = statusPageModule;
- this.networkCheckModule = networkCheckModule;
}
get serviceName() {
@@ -62,15 +41,15 @@ class MongoDB {
this.logger.info({
message: "Connected to MongoDB",
- service: this.SERVICE_NAME,
+ service: SERVICE_NAME,
method: "connect",
});
await runMigrations();
- } catch (error) {
+ } catch (error: any) {
this.logger.error({
message: error.message,
- service: this.SERVICE_NAME,
+ service: SERVICE_NAME,
method: "connect",
stack: error.stack,
});
@@ -80,14 +59,14 @@ class MongoDB {
disconnect = async () => {
try {
- this.logger.info({ message: "Disconnecting from MongoDB" });
+ this.logger.info({ message: "Disconnecting from MongoDB", service: SERVICE_NAME, method: "disconnect" });
await mongoose.disconnect();
- this.logger.info({ message: "Disconnected from MongoDB" });
+ this.logger.info({ message: "Disconnected from MongoDB", service: SERVICE_NAME, method: "disconnect" });
return;
- } catch (error) {
+ } catch (error: any) {
this.logger.error({
message: error.message,
- service: this.SERVICE_NAME,
+ service: SERVICE_NAME,
method: "disconnect",
stack: error.stack,
});
diff --git a/server/src/db/migration/0001_migrateStatusWindowThreshold.ts b/server/src/db/migration/0001_migrateStatusWindowThreshold.ts
new file mode 100644
index 000000000..d9dd12aa1
--- /dev/null
+++ b/server/src/db/migration/0001_migrateStatusWindowThreshold.ts
@@ -0,0 +1,10 @@
+import Monitor from "@/db/models/Monitor.js";
+async function migrateStatusWindowThreshold() {
+ const monitors = await Monitor.find({ statusWindowThreshold: { $lt: 1 } });
+ for (const monitor of monitors) {
+ monitor.statusWindowThreshold = monitor.statusWindowThreshold * 100;
+ await monitor.save();
+ }
+}
+
+export { migrateStatusWindowThreshold };
diff --git a/server/src/db/migration/0002_convertChecksToTimeSeries.ts b/server/src/db/migration/0002_convertChecksToTimeSeries.ts
new file mode 100644
index 000000000..222d210b0
--- /dev/null
+++ b/server/src/db/migration/0002_convertChecksToTimeSeries.ts
@@ -0,0 +1,184 @@
+import mongoose from "mongoose";
+import { BulkWriteResult } from "mongodb";
+
+const CHECKS_COLLECTION = "checks";
+const BACKUP_COLLECTION = "checks_backup";
+const FAILED_DOCS_COLLECTION = "checks_migration_failed";
+const BATCH_SIZE = 1000;
+
+interface MigrationStats {
+ totalSource: number;
+ totalMigrated: number;
+ totalFailed: number;
+}
+
+const getDb = () => {
+ const db = mongoose.connection.db;
+ if (!db) {
+ throw new Error("Database connection is not initialized");
+ }
+ return db;
+};
+
+const restoreFromBackup = async () => {
+ const db = getDb();
+ const backupExists = await db.listCollections({ name: BACKUP_COLLECTION }).toArray();
+ if (backupExists.length === 0) {
+ throw new Error("Cannot restore: backup collection does not exist");
+ }
+
+ const checksExists = await db.listCollections({ name: CHECKS_COLLECTION }).toArray();
+ if (checksExists.length > 0) {
+ await db.collection(CHECKS_COLLECTION).drop();
+ }
+
+ await db.collection(BACKUP_COLLECTION).rename(CHECKS_COLLECTION);
+};
+
+const backupAndDropExistingCollection = async () => {
+ const db = getDb();
+ const collections = await db.listCollections({ name: CHECKS_COLLECTION }).toArray();
+ if (collections.length === 0) {
+ return false;
+ }
+ const backupExists = await db.listCollections({ name: BACKUP_COLLECTION }).toArray();
+ if (backupExists.length > 0) {
+ throw new Error(`Backup collection "${BACKUP_COLLECTION}" already exists. ` + `Please remove it manually before running migration.`);
+ }
+
+ await db
+ .collection(CHECKS_COLLECTION)
+ .aggregate([{ $match: {} }, { $out: BACKUP_COLLECTION }])
+ .toArray();
+ await db.collection(CHECKS_COLLECTION).drop();
+ return true;
+};
+
+const createTimeSeriesCollection = async (backedUp: boolean) => {
+ const db = getDb();
+ try {
+ const existing = await db.listCollections({ name: CHECKS_COLLECTION }).toArray();
+ if (existing.length === 0) {
+ await db.createCollection(CHECKS_COLLECTION, {
+ timeseries: {
+ timeField: "createdAt",
+ metaField: "metadata",
+ granularity: "seconds",
+ },
+ });
+ }
+
+ await db
+ .collection(CHECKS_COLLECTION)
+ .createIndexes([
+ { key: { createdAt: 1 } },
+ { key: { "metadata.monitorId": 1, createdAt: 1 } },
+ { key: { "metadata.monitorId": 1, createdAt: -1 } },
+ { key: { "metadata.teamId": 1, createdAt: -1 } },
+ { key: { "metadata.monitorId": 1, "metadata.type": 1, createdAt: -1 } },
+ { key: { "metadata.teamId": 1, status: 1, createdAt: -1 } },
+ ]);
+ } catch (error) {
+ if (backedUp) {
+ await restoreFromBackup();
+ }
+ throw error;
+ }
+};
+
+const validateDocument = (doc: any): boolean => {
+ if (!doc.createdAt) {
+ return false;
+ }
+ if (!doc.monitorId || !doc.teamId || !doc.type) {
+ return false;
+ }
+ return true;
+};
+
+const storeFailedDocument = async (doc: any, reason: string) => {
+ const db = getDb();
+ await db.collection(FAILED_DOCS_COLLECTION).insertOne({
+ originalDoc: doc,
+ reason,
+ failedAt: new Date(),
+ });
+};
+
+const migrateBackupData = async (backedUp: boolean): Promise => {
+ const db = getDb();
+ const backupExists = await db.listCollections({ name: BACKUP_COLLECTION }).toArray();
+ if (backupExists.length === 0) {
+ return { totalSource: 0, totalMigrated: 0, totalFailed: 0 };
+ }
+
+ const stats: MigrationStats = {
+ totalSource: 0,
+ totalMigrated: 0,
+ totalFailed: 0,
+ };
+
+ try {
+ const source = db.collection(BACKUP_COLLECTION);
+ const target = db.collection(CHECKS_COLLECTION);
+
+ stats.totalSource = await source.countDocuments();
+
+ const cursor = source.find().addCursorFlag("noCursorTimeout", true);
+ const operations: any[] = [];
+ const invalidDocs: any[] = [];
+
+ while (await cursor.hasNext()) {
+ const doc = await cursor.next();
+ if (!doc) {
+ continue;
+ }
+
+ if (!validateDocument(doc)) {
+ invalidDocs.push(doc);
+ continue;
+ }
+
+ const { _id, monitorId, teamId, type, ...rest } = doc;
+ const metadata = { monitorId, teamId, type };
+ operations.push({ insertOne: { document: { ...rest, metadata } } });
+
+ if (operations.length >= BATCH_SIZE) {
+ const result: BulkWriteResult = await target.bulkWrite(operations, { ordered: false });
+ stats.totalMigrated += result.insertedCount;
+ operations.length = 0;
+ }
+ }
+
+ if (operations.length) {
+ const result: BulkWriteResult = await target.bulkWrite(operations, { ordered: false });
+ stats.totalMigrated += result.insertedCount;
+ }
+
+ for (const doc of invalidDocs) {
+ await storeFailedDocument(doc, "Missing required fields (createdAt, monitorId, teamId, or type)");
+ stats.totalFailed++;
+ }
+
+ await cursor.close();
+
+ return stats;
+ } catch (error) {
+ if (backedUp) {
+ await restoreFromBackup();
+ }
+ throw error;
+ }
+};
+
+export const convertChecksToTimeSeries = async () => {
+ const backedUp = await backupAndDropExistingCollection();
+ await createTimeSeriesCollection(backedUp);
+ if (backedUp) {
+ const stats = await migrateBackupData(backedUp);
+ console.log(
+ `Migration completed: ${stats.totalMigrated} documents migrated, ` +
+ `${stats.totalFailed} documents failed (stored in ${FAILED_DOCS_COLLECTION})`
+ );
+ }
+};
diff --git a/server/src/db/migration/index.ts b/server/src/db/migration/index.ts
new file mode 100644
index 000000000..c8e9e7c75
--- /dev/null
+++ b/server/src/db/migration/index.ts
@@ -0,0 +1,47 @@
+import { migrateStatusWindowThreshold } from "./0001_migrateStatusWindowThreshold.js";
+import { convertChecksToTimeSeries } from "./0002_convertChecksToTimeSeries.js";
+import MigrationModel from "../models/Migration.js";
+
+type MigrationEntry = {
+ name: string;
+ execute: () => Promise;
+};
+
+const migrations: MigrationEntry[] = [
+ { name: "0001_migrateStatusWindowThreshold", execute: migrateStatusWindowThreshold },
+ { name: "0002_convertChecksToTimeSeries", execute: convertChecksToTimeSeries },
+];
+
+const runMigrations = async (logger?: { info: Function; error: Function }) => {
+ try {
+ logger?.info({ message: "Running migrations", service: "Migrations" });
+ for (const migration of migrations) {
+ const exists = await MigrationModel.findOne({ name: migration.name, status: "completed" });
+ if (exists) {
+ logger?.info({ message: `Skipping ${migration.name}`, service: "Migrations" });
+ continue;
+ }
+
+ try {
+ await migration.execute();
+ await MigrationModel.findOneAndUpdate(
+ { name: migration.name },
+ { status: "completed", completedAt: new Date(), error: undefined },
+ { upsert: true }
+ );
+ logger?.info({ message: `Completed ${migration.name}`, service: "Migrations" });
+ } catch (error) {
+ const err = error as Error;
+ await MigrationModel.findOneAndUpdate({ name: migration.name }, { status: "failed", error: err?.message }, { upsert: true });
+ throw error;
+ }
+ }
+ logger?.info({ message: "Migrations completed", service: "Migrations" });
+ } catch (error) {
+ const err = error as Error;
+ logger?.error({ message: "Migration failed", service: "Migrations", details: err?.message, stack: err?.stack });
+ throw error;
+ }
+};
+
+export { runMigrations };
diff --git a/server/src/db/models/AppSettings.js b/server/src/db/models/AppSettings.js
deleted file mode 100755
index 2313d638c..000000000
--- a/server/src/db/models/AppSettings.js
+++ /dev/null
@@ -1,80 +0,0 @@
-import mongoose from "mongoose";
-
-const AppSettingsSchema = mongoose.Schema(
- {
- checkTTL: {
- type: Number,
- default: 30,
- },
- language: {
- type: String,
- default: "gb",
- },
- pagespeedApiKey: {
- type: String,
- },
- systemEmailHost: {
- type: String,
- },
- systemEmailPort: {
- type: Number,
- },
- systemEmailAddress: {
- type: String,
- },
- systemEmailPassword: {
- type: String,
- },
- systemEmailUser: {
- type: String,
- },
- systemEmailConnectionHost: {
- type: String,
- default: "localhost",
- },
- systemEmailTLSServername: {
- type: String,
- },
- systemEmailSecure: {
- type: Boolean,
- default: false,
- },
- systemEmailPool: {
- type: Boolean,
- default: false,
- },
- systemEmailIgnoreTLS: {
- type: Boolean,
- default: false,
- },
- systemEmailRequireTLS: {
- type: Boolean,
- default: false,
- },
- systemEmailRejectUnauthorized: {
- type: Boolean,
- default: true,
- },
- singleton: {
- type: Boolean,
- required: true,
- unique: true,
- default: true,
- },
- version: {
- type: Number,
- default: 1,
- },
- globalThresholds: {
- cpu: { type: Number },
- memory: { type: Number },
- disk: { type: Number },
- temperature: { type: Number },
- },
- },
- {
- timestamps: true,
- }
-);
-
-export default mongoose.model("AppSettings", AppSettingsSchema);
diff --git a/server/src/db/models/AppSettings.ts b/server/src/db/models/AppSettings.ts
new file mode 100644
index 000000000..9eeaa640a
--- /dev/null
+++ b/server/src/db/models/AppSettings.ts
@@ -0,0 +1,50 @@
+import { Schema, model, type Types } from "mongoose";
+import type { Settings, SettingsThresholds } from "@/types/settings.js";
+
+interface AppSettingsDocument extends Omit {
+ _id: Types.ObjectId;
+ createdAt: Date;
+ updatedAt: Date;
+}
+
+const thresholdsSchema = new Schema(
+ {
+ cpu: { type: Number },
+ memory: { type: Number },
+ disk: { type: Number },
+ temperature: { type: Number },
+ },
+ { _id: false }
+);
+
+const AppSettingsSchema = new Schema(
+ {
+ checkTTL: { type: Number, default: 30 },
+ language: { type: String, default: "gb" },
+ jwtSecret: { type: String },
+ pagespeedApiKey: { type: String },
+ systemEmailHost: { type: String },
+ systemEmailPort: { type: Number },
+ systemEmailAddress: { type: String },
+ systemEmailPassword: { type: String },
+ systemEmailUser: { type: String },
+ systemEmailConnectionHost: { type: String, default: "localhost" },
+ systemEmailTLSServername: { type: String },
+ systemEmailSecure: { type: Boolean, default: false },
+ systemEmailPool: { type: Boolean, default: false },
+ systemEmailIgnoreTLS: { type: Boolean, default: false },
+ systemEmailRequireTLS: { type: Boolean, default: false },
+ systemEmailRejectUnauthorized: { type: Boolean, default: true },
+ showURL: { type: Boolean, default: false },
+ singleton: { type: Boolean, required: true, unique: true, default: true },
+ version: { type: Number, default: 1 },
+ globalThresholds: { type: thresholdsSchema },
+ },
+ { timestamps: true }
+);
+
+const AppSettingsModel = model("AppSettings", AppSettingsSchema);
+
+export type { AppSettingsDocument };
+export { AppSettingsModel };
+export default AppSettingsModel;
diff --git a/server/src/db/models/Check.js b/server/src/db/models/Check.js
deleted file mode 100755
index 789cebaa5..000000000
--- a/server/src/db/models/Check.js
+++ /dev/null
@@ -1,176 +0,0 @@
-import mongoose from "mongoose";
-
-const cpuSchema = mongoose.Schema({
- physical_core: { type: Number, default: 0 },
- logical_core: { type: Number, default: 0 },
- frequency: { type: Number, default: 0 },
- temperature: { type: [Number], default: [] },
- free_percent: { type: Number, default: 0 },
- usage_percent: { type: Number, default: 0 },
-});
-
-const memorySchema = mongoose.Schema({
- total_bytes: { type: Number, default: 0 },
- available_bytes: { type: Number, default: 0 },
- used_bytes: { type: Number, default: 0 },
- usage_percent: { type: Number, default: 0 },
-});
-
-const diskSchema = mongoose.Schema({
- read_speed_bytes: { type: Number, default: 0 },
- write_speed_bytes: { type: Number, default: 0 },
- total_bytes: { type: Number, default: 0 },
- free_bytes: { type: Number, default: 0 },
- usage_percent: { type: Number, default: 0 },
-});
-
-const hostSchema = mongoose.Schema({
- os: { type: String, default: "" },
- platform: { type: String, default: "" },
- kernel_version: { type: String, default: "" },
-});
-
-const errorSchema = mongoose.Schema({
- metric: { type: [String], default: [] },
- err: { type: String, default: "" },
-});
-
-const captureSchema = mongoose.Schema({
- version: { type: String, default: "" },
- mode: { type: String, default: "" },
-});
-
-const networkInterfaceSchema = mongoose.Schema({
- name: { type: String },
- bytes_sent: { type: Number, default: 0 },
- bytes_recv: { type: Number, default: 0 },
- packets_sent: { type: Number, default: 0 },
- packets_recv: { type: Number, default: 0 },
- err_in: { type: Number, default: 0 },
- err_out: { type: Number, default: 0 },
- drop_in: { type: Number, default: 0 },
- drop_out: { type: Number, default: 0 },
- fifo_in: { type: Number, default: 0 },
- fifo_out: { type: Number, default: 0 },
-});
-
-const CheckSchema = new mongoose.Schema(
- {
- // Common fields
- monitorId: {
- type: mongoose.Schema.Types.ObjectId,
- ref: "Monitor",
- immutable: true,
- index: true,
- },
-
- teamId: {
- type: mongoose.Schema.Types.ObjectId,
- ref: "Team",
- immutable: true,
- index: true,
- },
- type: {
- type: String,
- enum: ["http", "ping", "pagespeed", "hardware", "docker", "port", "game"],
- required: true,
- index: true,
- },
-
- status: {
- type: Boolean,
- index: true,
- },
-
- responseTime: {
- type: Number,
- },
-
- timings: {
- type: Object,
- default: {},
- },
-
- statusCode: {
- type: Number,
- index: true,
- },
-
- message: {
- type: String,
- },
-
- expiry: {
- type: Date,
- default: Date.now,
- expires: 60 * 60 * 24 * 30, // 30 days
- },
-
- ack: {
- type: Boolean,
- default: false,
- },
-
- ackAt: {
- type: Date,
- },
-
- // Hardware fields
- cpu: {
- type: cpuSchema,
- default: () => ({}),
- },
- memory: {
- type: memorySchema,
- default: () => ({}),
- },
- disk: {
- type: [diskSchema],
- default: () => [],
- },
- host: {
- type: hostSchema,
- default: () => ({}),
- },
-
- errors: {
- type: [errorSchema],
- default: () => [],
- },
-
- capture: {
- type: captureSchema,
- default: () => ({}),
- },
-
- net: {
- type: [networkInterfaceSchema],
- default: () => [],
- },
-
- // PageSpeed fields
- accessibility: {
- type: Number,
- },
- bestPractices: {
- type: Number,
- },
- seo: {
- type: Number,
- },
- performance: {
- type: Number,
- },
- audits: {
- type: Object,
- },
- },
- { timestamps: true }
-);
-
-CheckSchema.index({ updatedAt: 1 });
-CheckSchema.index({ monitorId: 1, updatedAt: 1 });
-CheckSchema.index({ monitorId: 1, updatedAt: -1 });
-CheckSchema.index({ teamId: 1, updatedAt: -1 });
-
-export default mongoose.model("Check", CheckSchema);
diff --git a/server/src/db/models/Check.ts b/server/src/db/models/Check.ts
new file mode 100644
index 000000000..4d9c499de
--- /dev/null
+++ b/server/src/db/models/Check.ts
@@ -0,0 +1,298 @@
+import { Schema, model, Types } from "mongoose";
+import { MonitorTypes, type MonitorType } from "@/types/monitor.js";
+import type {
+ Check,
+ CheckAudits,
+ CheckCaptureInfo,
+ CheckCpuInfo,
+ CheckDiskInfo,
+ CheckErrorInfo,
+ CheckHostInfo,
+ CheckMemoryInfo,
+ CheckMetadata,
+ CheckNetworkInterfaceInfo,
+ GotTimings,
+ ILighthouseAudit,
+} from "@/types/check.js";
+
+type CheckMetadataDocument = Omit & {
+ monitorId: Types.ObjectId;
+ teamId: Types.ObjectId;
+ type: MonitorType;
+};
+
+type CheckDocumentBase = Omit & {
+ metadata: CheckMetadataDocument;
+ expiry: Date;
+ ackAt?: Date | null;
+ createdAt: Date;
+ updatedAt: Date;
+ __v: number;
+};
+
+interface CheckDocument extends CheckDocumentBase {
+ _id: Types.ObjectId;
+}
+
+const timingPhasesSchema = new Schema(
+ {
+ wait: { type: Number, default: 0 },
+ dns: { type: Number, default: 0 },
+ tcp: { type: Number, default: 0 },
+ tls: { type: Number, default: 0 },
+ request: { type: Number, default: 0 },
+ firstByte: { type: Number, default: 0 },
+ download: { type: Number, default: 0 },
+ total: { type: Number, default: 0 },
+ },
+ { _id: false }
+);
+
+const timingsSchema = new Schema(
+ {
+ start: { type: Number, default: 0 },
+ socket: { type: Number, default: 0 },
+ lookup: { type: Number, default: 0 },
+ connect: { type: Number, default: 0 },
+ secureConnect: { type: Number, default: 0 },
+ upload: { type: Number, default: 0 },
+ response: { type: Number, default: 0 },
+ end: { type: Number, default: 0 },
+ phases: {
+ type: timingPhasesSchema,
+ default: () => ({}),
+ },
+ },
+ { _id: false }
+);
+
+const cpuSchema = new Schema(
+ {
+ physical_core: { type: Number, default: 0 },
+ logical_core: { type: Number, default: 0 },
+ frequency: { type: Number, default: 0 },
+ temperature: { type: [Number], default: [] },
+ free_percent: { type: Number, default: 0 },
+ usage_percent: { type: Number, default: 0 },
+ },
+ { _id: false }
+);
+
+const memorySchema = new Schema(
+ {
+ total_bytes: { type: Number, default: 0 },
+ available_bytes: { type: Number, default: 0 },
+ used_bytes: { type: Number, default: 0 },
+ usage_percent: { type: Number, default: 0 },
+ },
+ { _id: false }
+);
+
+const diskSchema = new Schema(
+ {
+ device: { type: String, default: "" },
+ mountpoint: { type: String, default: "" },
+ read_speed_bytes: { type: Number, default: 0 },
+ write_speed_bytes: { type: Number, default: 0 },
+ total_bytes: { type: Number, default: 0 },
+ free_bytes: { type: Number, default: 0 },
+ usage_percent: { type: Number, default: 0 },
+ },
+ { _id: false }
+);
+
+const hostSchema = new Schema(
+ {
+ os: { type: String, default: "" },
+ platform: { type: String, default: "" },
+ kernel_version: { type: String, default: "" },
+ },
+ { _id: false }
+);
+
+const errorSchema = new Schema(
+ {
+ metric: { type: [String], default: [] },
+ err: { type: String, default: "" },
+ },
+ { _id: false }
+);
+
+const captureSchema = new Schema(
+ {
+ version: { type: String, default: "" },
+ mode: { type: String, default: "" },
+ },
+ { _id: false }
+);
+
+const networkInterfaceSchema = new Schema(
+ {
+ name: { type: String, default: "" },
+ bytes_sent: { type: Number, default: 0 },
+ bytes_recv: { type: Number, default: 0 },
+ packets_sent: { type: Number, default: 0 },
+ packets_recv: { type: Number, default: 0 },
+ err_in: { type: Number, default: 0 },
+ err_out: { type: Number, default: 0 },
+ drop_in: { type: Number, default: 0 },
+ drop_out: { type: Number, default: 0 },
+ fifo_in: { type: Number, default: 0 },
+ fifo_out: { type: Number, default: 0 },
+ },
+ { _id: false }
+);
+
+const lighthouseAuditSchema = new Schema(
+ {
+ id: { type: String },
+ title: { type: String },
+ score: { type: Number },
+ displayValue: { type: String },
+ numericValue: { type: Number },
+ numericUnit: { type: String },
+ },
+ { _id: false }
+);
+
+const auditsSchema = new Schema(
+ {
+ cls: { type: lighthouseAuditSchema, default: undefined },
+ si: { type: lighthouseAuditSchema, default: undefined },
+ fcp: { type: lighthouseAuditSchema, default: undefined },
+ lcp: { type: lighthouseAuditSchema, default: undefined },
+ tbt: { type: lighthouseAuditSchema, default: undefined },
+ },
+ { _id: false }
+);
+
+const metadataSchema = new Schema(
+ {
+ monitorId: {
+ type: Schema.Types.ObjectId,
+ ref: "Monitor",
+ required: true,
+ immutable: true,
+ index: true,
+ },
+ teamId: {
+ type: Schema.Types.ObjectId,
+ ref: "Team",
+ required: true,
+ immutable: true,
+ index: true,
+ },
+ type: {
+ type: String,
+ enum: MonitorTypes,
+ required: true,
+ index: true,
+ },
+ },
+ { _id: false }
+);
+
+const CheckSchema = new Schema(
+ {
+ metadata: {
+ type: metadataSchema,
+ required: true,
+ },
+ status: {
+ type: Boolean,
+ index: true,
+ },
+ responseTime: {
+ type: Number,
+ },
+ timings: {
+ type: timingsSchema,
+ default: undefined,
+ },
+ statusCode: {
+ type: Number,
+ index: true,
+ },
+ message: {
+ type: String,
+ },
+ expiry: {
+ type: Date,
+ default: Date.now,
+ },
+ ack: {
+ type: Boolean,
+ default: false,
+ },
+ ackAt: {
+ type: Date,
+ default: undefined,
+ },
+ cpu: {
+ type: cpuSchema,
+ default: () => ({}),
+ },
+ memory: {
+ type: memorySchema,
+ default: () => ({}),
+ },
+ disk: {
+ type: [diskSchema],
+ default: () => [],
+ },
+ host: {
+ type: hostSchema,
+ default: () => ({}),
+ },
+ errors: {
+ type: [errorSchema],
+ default: () => [],
+ },
+ capture: {
+ type: captureSchema,
+ default: () => ({}),
+ },
+ net: {
+ type: [networkInterfaceSchema],
+ default: () => [],
+ },
+ accessibility: {
+ type: Number,
+ },
+ bestPractices: {
+ type: Number,
+ },
+ seo: {
+ type: Number,
+ },
+ performance: {
+ type: Number,
+ },
+ audits: {
+ type: auditsSchema,
+ default: undefined,
+ },
+ },
+ {
+ timestamps: true,
+ strict: false,
+ timeseries: {
+ timeField: "createdAt",
+ metaField: "metadata",
+ granularity: "seconds",
+ },
+ }
+);
+
+CheckSchema.index({ "metadata.monitorId": 1, createdAt: -1 });
+CheckSchema.index({ "metadata.monitorId": 1, createdAt: 1 });
+CheckSchema.index({ createdAt: 1 });
+CheckSchema.index({ "metadata.teamId": 1, createdAt: -1 });
+CheckSchema.index({ "metadata.monitorId": 1, "metadata.type": 1, createdAt: -1 });
+CheckSchema.index({ "metadata.teamId": 1, status: 1, createdAt: -1 });
+
+const CheckModel = model("Check", CheckSchema);
+
+export type { CheckDocument, CheckMetadataDocument };
+export { CheckModel };
+export default CheckModel;
diff --git a/server/src/db/models/Incident.ts b/server/src/db/models/Incident.ts
new file mode 100644
index 000000000..8de1c7c2c
--- /dev/null
+++ b/server/src/db/models/Incident.ts
@@ -0,0 +1,86 @@
+import { Schema, model, type Types } from "mongoose";
+import { IncidentResolutionTypes, type Incident } from "@/types/incident.js";
+
+type IncidentDocumentBase = Omit & {
+ monitorId: Types.ObjectId;
+ teamId: Types.ObjectId;
+ resolvedBy?: Types.ObjectId | null;
+ startTime: Date;
+ endTime: Date | null;
+ createdAt: Date;
+ updatedAt: Date;
+};
+
+export interface IncidentDocument extends IncidentDocumentBase {
+ _id: Types.ObjectId;
+}
+
+const IncidentSchema = new Schema(
+ {
+ monitorId: {
+ type: Schema.Types.ObjectId,
+ ref: "Monitor",
+ required: true,
+ immutable: true,
+ index: true,
+ },
+ teamId: {
+ type: Schema.Types.ObjectId,
+ ref: "Team",
+ required: true,
+ immutable: true,
+ index: true,
+ },
+ startTime: {
+ type: Date,
+ immutable: true,
+ required: true,
+ },
+ endTime: {
+ type: Date,
+ default: null,
+ },
+ status: {
+ type: Boolean,
+ default: true,
+ index: true,
+ },
+ message: {
+ type: String,
+ default: null,
+ },
+ statusCode: {
+ type: Number,
+ default: null,
+ index: true,
+ },
+ resolutionType: {
+ type: String,
+ enum: IncidentResolutionTypes,
+ default: null,
+ },
+ resolvedBy: {
+ type: Schema.Types.ObjectId,
+ ref: "User",
+ default: null,
+ },
+ comment: {
+ type: String,
+ default: null,
+ },
+ },
+ { timestamps: true }
+);
+
+IncidentSchema.index({ monitorId: 1, status: 1 });
+IncidentSchema.index({ teamId: 1, status: 1 });
+IncidentSchema.index({ teamId: 1, startTime: -1 });
+IncidentSchema.index({ status: 1, startTime: -1 });
+IncidentSchema.index({ resolutionType: 1, status: 1 });
+IncidentSchema.index({ resolvedBy: 1, status: 1 });
+IncidentSchema.index({ createdAt: -1 });
+
+const IncidentModel = model("Incident", IncidentSchema);
+
+export { IncidentModel };
+export default IncidentModel;
diff --git a/server/src/db/models/Invite.ts b/server/src/db/models/Invite.ts
new file mode 100755
index 000000000..25ddd9a72
--- /dev/null
+++ b/server/src/db/models/Invite.ts
@@ -0,0 +1,30 @@
+import { Schema, model, type Types } from "mongoose";
+import type { Invite } from "@/types/invite.js";
+
+type InviteDocumentBase = Omit & {
+ teamId: Types.ObjectId;
+ expiry: Date;
+};
+
+interface InviteDocument extends InviteDocumentBase {
+ _id: Types.ObjectId;
+ createdAt: Date;
+ updatedAt: Date;
+}
+
+const InviteSchema = new Schema(
+ {
+ email: { type: String, required: true, unique: true },
+ teamId: { type: Schema.Types.ObjectId, ref: "Team", immutable: true, required: true },
+ role: { type: [String], required: true, default: ["user"] },
+ token: { type: String, required: true },
+ expiry: { type: Date, default: Date.now, expires: 3600 },
+ },
+ { timestamps: true }
+);
+
+const InviteModel = model("Invite", InviteSchema);
+
+export type { InviteDocument };
+export { InviteModel };
+export default InviteModel;
diff --git a/server/src/db/models/InviteToken.js b/server/src/db/models/InviteToken.js
deleted file mode 100755
index 079600647..000000000
--- a/server/src/db/models/InviteToken.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import mongoose from "mongoose";
-const InviteTokenSchema = mongoose.Schema(
- {
- email: {
- type: String,
- required: true,
- unique: true,
- },
- teamId: {
- type: mongoose.Schema.Types.ObjectId,
- ref: "Team",
- immutable: true,
- required: true,
- },
- role: {
- type: Array,
- required: true,
- default: ["user"],
- },
- token: {
- type: String,
- required: true,
- },
- expiry: {
- type: Date,
- default: Date.now,
- expires: 3600,
- },
- },
- {
- timestamps: true,
- }
-);
-
-export default mongoose.model("InviteToken", InviteTokenSchema);
diff --git a/server/src/db/models/MaintenanceWindow.js b/server/src/db/models/MaintenanceWindow.js
deleted file mode 100755
index b68abb413..000000000
--- a/server/src/db/models/MaintenanceWindow.js
+++ /dev/null
@@ -1,68 +0,0 @@
-import mongoose from "mongoose";
-/**
- * MaintenanceWindow Schema
- * @module MaintenanceWindow
- * @typedef {Object} MaintenanceWindow
- * @property {mongoose.Schema.Types.ObjectId} monitorId - The ID of the monitor. This is a reference to the Monitor model and is immutable.
- * @property {Boolean} active - Indicates whether the maintenance window is active.
- * @property {Number} repeat - Indicates how often this maintenance window should repeat.
- * @property {Date} start - The start date and time of the maintenance window.
- * @property {Date} end - The end date and time of the maintenance window.
- * @property {Date} expiry - The expiry date and time of the maintenance window. This is used for MongoDB's TTL index to automatically delete the document at this time. This field is set to the same value as `end` when `oneTime` is `true`.
- *
- * @example
- *
- * let maintenanceWindow = new MaintenanceWindow({
- * monitorId: monitorId,
- * active: active,
- * repeat: repeat,
- * start: start,
- * end: end,
- * });
- *
- * if (repeat === 0) {
- * maintenanceWindow.expiry = end;
- * }
- *
- */
-
-const MaintenanceWindow = mongoose.Schema(
- {
- monitorId: {
- type: mongoose.Schema.Types.ObjectId,
- ref: "Monitor",
- immutable: true,
- },
- teamId: {
- type: mongoose.Schema.Types.ObjectId,
- ref: "Team",
- immutable: true,
- },
- active: {
- type: Boolean,
- default: true,
- },
- name: {
- type: String,
- },
- repeat: {
- type: Number,
- },
- start: {
- type: Date,
- },
- end: {
- type: Date,
- },
- expiry: {
- type: Date,
- index: { expires: "0s" },
- },
- },
-
- {
- timestamps: true,
- }
-);
-
-export default mongoose.model("MaintenanceWindow", MaintenanceWindow);
diff --git a/server/src/db/models/MaintenanceWindow.ts b/server/src/db/models/MaintenanceWindow.ts
new file mode 100644
index 000000000..b68de206e
--- /dev/null
+++ b/server/src/db/models/MaintenanceWindow.ts
@@ -0,0 +1,60 @@
+import { Schema, model, type Types } from "mongoose";
+import type { MaintenanceWindow } from "@/types/maintenanceWindow.js";
+
+type MaintenanceWindowDocumentBase = Omit & {
+ monitorId: Types.ObjectId;
+ teamId: Types.ObjectId;
+ start: Date;
+ end: Date;
+ expiry?: Date;
+ createdAt: Date;
+ updatedAt: Date;
+};
+
+interface MaintenanceWindowDocument extends MaintenanceWindowDocumentBase {
+ _id: Types.ObjectId;
+}
+
+const MaintenanceWindowSchema = new Schema(
+ {
+ monitorId: {
+ type: Schema.Types.ObjectId,
+ ref: "Monitor",
+ immutable: true,
+ },
+ teamId: {
+ type: Schema.Types.ObjectId,
+ ref: "Team",
+ immutable: true,
+ },
+ active: {
+ type: Boolean,
+ default: true,
+ },
+ name: {
+ type: String,
+ },
+ repeat: {
+ type: Number,
+ },
+ start: {
+ type: Date,
+ },
+ end: {
+ type: Date,
+ },
+ expiry: {
+ type: Date,
+ index: { expires: "0s" },
+ },
+ },
+ {
+ timestamps: true,
+ }
+);
+
+const MaintenanceWindowModel = model("MaintenanceWindow", MaintenanceWindowSchema);
+
+export type { MaintenanceWindowDocument };
+export { MaintenanceWindowModel };
+export default MaintenanceWindowModel;
diff --git a/server/src/db/models/Migration.ts b/server/src/db/models/Migration.ts
new file mode 100644
index 000000000..ba928d910
--- /dev/null
+++ b/server/src/db/models/Migration.ts
@@ -0,0 +1,13 @@
+import mongoose from "mongoose";
+
+const MigrationSchema = new mongoose.Schema(
+ {
+ name: { type: String, required: true, unique: true },
+ status: { type: String, enum: ["completed", "failed"], default: "completed" },
+ completedAt: { type: Date },
+ error: { type: String },
+ },
+ { timestamps: true }
+);
+
+export default mongoose.model("Migration", MigrationSchema);
diff --git a/server/src/db/models/Monitor.js b/server/src/db/models/Monitor.ts
old mode 100755
new mode 100644
similarity index 62%
rename from server/src/db/models/Monitor.js
rename to server/src/db/models/Monitor.ts
index 0e12d3c26..17565a2e4
--- a/server/src/db/models/Monitor.js
+++ b/server/src/db/models/Monitor.ts
@@ -1,18 +1,49 @@
-import mongoose from "mongoose";
+import { Schema, model, Types, type UpdateQuery } from "mongoose";
+import type { Monitor, MonitorMatchMethod, MonitorThresholds } 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";
-const MonitorSchema = mongoose.Schema(
+type MonitorDocumentBase = Omit<
+ Monitor,
+ "id" | "userId" | "teamId" | "notifications" | "selectedDisks" | "statusWindow" | "createdAt" | "updatedAt"
+> & {
+ statusWindow: boolean[];
+ notifications: Types.ObjectId[];
+ selectedDisks: string[];
+ matchMethod?: MonitorMatchMethod;
+ thresholds?: MonitorThresholds;
+};
+
+interface MonitorDocument extends MonitorDocumentBase {
+ _id: Types.ObjectId;
+ userId: Types.ObjectId;
+ teamId: Types.ObjectId;
+ createdAt: Date;
+ updatedAt: Date;
+}
+
+const thresholdsSchema = new Schema(
+ {
+ usage_cpu: { type: Number },
+ usage_memory: { type: Number },
+ usage_disk: { type: Number },
+ usage_temperature: { type: Number },
+ },
+ { _id: false }
+);
+
+const MonitorSchema = new Schema(
{
userId: {
- type: mongoose.Schema.Types.ObjectId,
+ type: Schema.Types.ObjectId,
ref: "User",
immutable: true,
required: true,
},
teamId: {
- type: mongoose.Schema.Types.ObjectId,
+ type: Schema.Types.ObjectId,
ref: "Team",
immutable: true,
required: true,
@@ -43,7 +74,7 @@ const MonitorSchema = mongoose.Schema(
type: {
type: String,
required: true,
- enum: ["http", "ping", "pagespeed", "hardware", "docker", "port", "game"],
+ enum: MonitorTypes,
},
ignoreTlsErrors: {
type: Boolean,
@@ -71,7 +102,6 @@ const MonitorSchema = mongoose.Schema(
default: true,
},
interval: {
- // in milliseconds
type: Number,
default: 60000,
},
@@ -81,7 +111,7 @@ const MonitorSchema = mongoose.Schema(
},
notifications: [
{
- type: mongoose.Schema.Types.ObjectId,
+ type: Schema.Types.ObjectId,
ref: "Notification",
},
],
@@ -89,13 +119,7 @@ const MonitorSchema = mongoose.Schema(
type: String,
},
thresholds: {
- type: {
- usage_cpu: { type: Number },
- usage_memory: { type: Number },
- usage_disk: { type: Number },
- usage_temperature: { type: Number },
- },
- _id: false,
+ type: thresholdsSchema,
},
alertThreshold: {
type: Number,
@@ -125,48 +149,28 @@ const MonitorSchema = mongoose.Schema(
return this.alertThreshold;
},
},
+ selectedDisks: {
+ type: [String],
+ default: [],
+ },
gameId: {
type: String,
},
+ group: {
+ type: String,
+ trim: true,
+ maxLength: 50,
+ default: null,
+ set(value: string | null) {
+ return value && value.trim() ? value.trim() : null;
+ },
+ },
},
{
timestamps: true,
}
);
-MonitorSchema.pre("findOneAndDelete", async function (next) {
- // Delete checks and stats
- try {
- const doc = await this.model.findOne(this.getFilter());
-
- if (!doc) {
- throw new Error("Monitor not found");
- }
-
- await Check.deleteMany({ monitorId: doc._id });
-
- // Deal with status pages
- await StatusPage.updateMany({ monitors: doc?._id }, { $pull: { monitors: doc?._id } });
-
- await MonitorStats.deleteMany({ monitorId: doc?._id.toString() });
- next();
- } catch (error) {
- next(error);
- }
-});
-
-MonitorSchema.pre("deleteMany", async function (next) {
- const filter = this.getFilter();
- const monitors = await this.model.find(filter).select(["_id", "type"]).lean();
-
- for (const monitor of monitors) {
- await Check.deleteMany({ monitorId: monitor._id });
- await StatusPage.updateMany({ monitors: monitor._id }, { $pull: { monitors: monitor._id } });
- await MonitorStats.deleteMany({ monitorId: monitor._id.toString() });
- }
- next();
-});
-
MonitorSchema.pre("save", function (next) {
if (!this.cpuAlertThreshold || this.isModified("alertThreshold")) {
this.cpuAlertThreshold = this.alertThreshold;
@@ -184,8 +188,8 @@ MonitorSchema.pre("save", function (next) {
});
MonitorSchema.pre("findOneAndUpdate", function (next) {
- const update = this.getUpdate();
- if (update.alertThreshold) {
+ const update = this.getUpdate() as UpdateQuery | null;
+ if (update && !Array.isArray(update) && update.alertThreshold !== undefined) {
update.cpuAlertThreshold = update.alertThreshold;
update.memoryAlertThreshold = update.alertThreshold;
update.diskAlertThreshold = update.alertThreshold;
@@ -196,4 +200,8 @@ MonitorSchema.pre("findOneAndUpdate", function (next) {
MonitorSchema.index({ teamId: 1, type: 1 });
-export default mongoose.model("Monitor", MonitorSchema);
+const MonitorModel = model("Monitor", MonitorSchema);
+
+export type { MonitorDocument };
+export { MonitorModel };
+export default MonitorModel;
diff --git a/server/src/db/models/MonitorStats.js b/server/src/db/models/MonitorStats.js
deleted file mode 100755
index c081c8a4f..000000000
--- a/server/src/db/models/MonitorStats.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import mongoose from "mongoose";
-
-const MonitorStatsSchema = new mongoose.Schema(
- {
- monitorId: {
- type: mongoose.Schema.Types.ObjectId,
- ref: "Monitor",
- immutable: true,
- index: true,
- },
- avgResponseTime: {
- type: Number,
- default: 0,
- },
- totalChecks: {
- type: Number,
- default: 0,
- },
- totalUpChecks: {
- type: Number,
- default: 0,
- },
- totalDownChecks: {
- type: Number,
- default: 0,
- },
- uptimePercentage: {
- type: Number,
- default: 0,
- },
- lastCheckTimestamp: {
- type: Number,
- default: 0,
- },
- lastResponseTime: {
- type: Number,
- default: 0,
- },
- timeOfLastFailure: {
- type: Number,
- default: 0,
- },
- },
- { timestamps: true }
-);
-
-const MonitorStats = mongoose.model("MonitorStats", MonitorStatsSchema);
-
-export default MonitorStats;
diff --git a/server/src/db/models/MonitorStats.ts b/server/src/db/models/MonitorStats.ts
new file mode 100755
index 000000000..d1d49ca9f
--- /dev/null
+++ b/server/src/db/models/MonitorStats.ts
@@ -0,0 +1,63 @@
+import { Schema, model, type Types } from "mongoose";
+import type { MonitorStats as MonitorStatsEntity } from "@/types/monitorStats.js";
+
+type MonitorStatsDocumentBase = Omit & {
+ monitorId: Types.ObjectId;
+};
+
+interface MonitorStatsDocument extends MonitorStatsDocumentBase {
+ _id: Types.ObjectId;
+ createdAt: Date;
+ updatedAt: Date;
+}
+
+const MonitorStatsSchema = new Schema(
+ {
+ monitorId: {
+ type: Schema.Types.ObjectId,
+ ref: "Monitor",
+ immutable: true,
+ index: true,
+ required: true,
+ },
+ avgResponseTime: {
+ type: Number,
+ default: 0,
+ },
+ totalChecks: {
+ type: Number,
+ default: 0,
+ },
+ totalUpChecks: {
+ type: Number,
+ default: 0,
+ },
+ totalDownChecks: {
+ type: Number,
+ default: 0,
+ },
+ uptimePercentage: {
+ type: Number,
+ default: 0,
+ },
+ lastCheckTimestamp: {
+ type: Number,
+ default: 0,
+ },
+ lastResponseTime: {
+ type: Number,
+ default: 0,
+ },
+ timeOfLastFailure: {
+ type: Number,
+ default: undefined,
+ },
+ },
+ { timestamps: true }
+);
+
+const MonitorStatsModel = model("MonitorStats", MonitorStatsSchema);
+
+export type { MonitorStatsDocument };
+export { MonitorStatsModel };
+export default MonitorStatsModel;
diff --git a/server/src/db/models/Notification.js b/server/src/db/models/Notification.js
deleted file mode 100755
index a6a3c7c09..000000000
--- a/server/src/db/models/Notification.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import mongoose from "mongoose";
-
-const NotificationSchema = mongoose.Schema(
- {
- userId: {
- type: mongoose.Schema.Types.ObjectId,
- ref: "User",
- immutable: true,
- required: true,
- },
- teamId: {
- type: mongoose.Schema.Types.ObjectId,
- ref: "Team",
- immutable: true,
- required: true,
- },
- type: {
- type: String,
- enum: ["email", "slack", "discord", "webhook", "pager_duty"],
- },
- notificationName: {
- type: String,
- required: true,
- },
- address: {
- type: String,
- },
- phone: {
- type: String,
- },
- },
- {
- timestamps: true,
- }
-);
-
-NotificationSchema.pre("save", function (next) {
- if (!this.cpuAlertThreshold || this.isModified("alertThreshold")) {
- this.cpuAlertThreshold = this.alertThreshold;
- }
- if (!this.memoryAlertThreshold || this.isModified("alertThreshold")) {
- this.memoryAlertThreshold = this.alertThreshold;
- }
- if (!this.diskAlertThreshold || this.isModified("alertThreshold")) {
- this.diskAlertThreshold = this.alertThreshold;
- }
- if (!this.tempAlertThreshold || this.isModified("alertThreshold")) {
- this.tempAlertThreshold = this.alertThreshold;
- }
- next();
-});
-
-NotificationSchema.pre("findOneAndUpdate", function (next) {
- const update = this.getUpdate();
- if (update.alertThreshold) {
- update.cpuAlertThreshold = update.alertThreshold;
- update.memoryAlertThreshold = update.alertThreshold;
- update.diskAlertThreshold = update.alertThreshold;
- update.tempAlertThreshold = update.alertThreshold;
- }
- next();
-});
-
-export default mongoose.model("Notification", NotificationSchema);
diff --git a/server/src/db/models/Notification.ts b/server/src/db/models/Notification.ts
new file mode 100755
index 000000000..97e2d43fb
--- /dev/null
+++ b/server/src/db/models/Notification.ts
@@ -0,0 +1,50 @@
+import { Schema, model, type Types } from "mongoose";
+import type { Notification, NotificationChannel } from "@/types/notification.js";
+
+interface NotificationDocument extends Omit {
+ _id: Types.ObjectId;
+ userId: Types.ObjectId;
+ teamId: Types.ObjectId;
+ createdAt: Date;
+ updatedAt: Date;
+}
+
+const NotificationSchema = new Schema