) => {
+ e.stopPropagation();
+ setAnchorEl(null);
+ };
+
+ return (
+
+
+
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/Arrows/ArrowLeft.tsx b/client/src/Components/v2/Arrows/ArrowLeft.tsx
new file mode 100644
index 000000000..01d1342c8
--- /dev/null
+++ b/client/src/Components/v2/Arrows/ArrowLeft.tsx
@@ -0,0 +1,36 @@
+import LeftArrow from "@/assets/icons/left-arrow.svg?react";
+import LeftArrowDouble from "@/assets/icons/left-arrow-double.svg?react";
+import LeftArrowLong from "@/assets/icons/left-arrow-long.svg?react";
+
+export const ArrowLeft = ({
+ type,
+ color = "#667085",
+ ...props
+}: {
+ type?: string;
+ color?: string | undefined;
+ [key: string]: any;
+}) => {
+ if (type === "double") {
+ return (
+
+ );
+ } else if (type === "long") {
+ return (
+
+ );
+ } else {
+ return (
+
+ );
+ }
+};
diff --git a/client/src/Components/v2/Arrows/ArrowRight.tsx b/client/src/Components/v2/Arrows/ArrowRight.tsx
new file mode 100644
index 000000000..c73165f16
--- /dev/null
+++ b/client/src/Components/v2/Arrows/ArrowRight.tsx
@@ -0,0 +1,28 @@
+import RightArrow from "@/assets/icons/right-arrow.svg?react";
+import RightArrowDouble from "@/assets/icons/right-arrow-double.svg?react";
+
+export const ArrowRight = ({
+ type,
+ color = "#667085",
+ ...props
+}: {
+ type?: string;
+ color?: string | undefined;
+ [key: string]: any;
+}) => {
+ if (type === "double") {
+ return (
+
+ );
+ } else {
+ return (
+
+ );
+ }
+};
diff --git a/client/src/Components/v2/Auth/AuthBasePage.tsx b/client/src/Components/v2/Auth/AuthBasePage.tsx
new file mode 100644
index 000000000..ba4230a6d
--- /dev/null
+++ b/client/src/Components/v2/Auth/AuthBasePage.tsx
@@ -0,0 +1,48 @@
+import Stack from "@mui/material/Stack";
+import Box from "@mui/material/Box";
+import { HeaderAuth } from "@/Components/v2/Auth";
+import Logo from "@/assets/icons/checkmate-icon.svg?react";
+
+import type { StackProps } from "@mui/material/Stack";
+import { useTheme } from "@mui/material/styles";
+import { Typography } from "@mui/material";
+
+interface AuthBasePageProps extends StackProps {
+ title?: string;
+ subtitle?: string;
+ children: React.ReactNode;
+}
+
+export const AuthBasePage: React.FC = ({
+ children,
+ title,
+ subtitle,
+ ...props
+}) => {
+ const theme = useTheme();
+ return (
+
+
+
+
+
+
+ {title}
+ {subtitle}
+ {children}
+
+
+ );
+};
diff --git a/client/src/Components/v2/Auth/HeaderAuth.jsx b/client/src/Components/v2/Auth/HeaderAuth.jsx
new file mode 100644
index 000000000..4e437cd7e
--- /dev/null
+++ b/client/src/Components/v2/Auth/HeaderAuth.jsx
@@ -0,0 +1,26 @@
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import Logo from "@/assets/icons/checkmate-icon.svg?react";
+
+import { useTheme } from "@mui/material/styles";
+import { useTranslation } from "react-i18next";
+import { LanguageSelector, ThemeSwitch } from "@/Components/v2/Inputs";
+
+export const HeaderAuth = () => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+ return (
+
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/Auth/index.jsx b/client/src/Components/v2/Auth/index.jsx
new file mode 100644
index 000000000..84b742113
--- /dev/null
+++ b/client/src/Components/v2/Auth/index.jsx
@@ -0,0 +1,2 @@
+export { HeaderAuth } from "./HeaderAuth";
+export { AuthBasePage } from "./AuthBasePage";
diff --git a/client/src/Components/v2/DesignElements/BaseBox.tsx b/client/src/Components/v2/DesignElements/BaseBox.tsx
new file mode 100644
index 000000000..14c4af1c4
--- /dev/null
+++ b/client/src/Components/v2/DesignElements/BaseBox.tsx
@@ -0,0 +1,23 @@
+import Box from "@mui/material/Box";
+import { useTheme } from "@mui/material/styles";
+import type { SxProps } from "@mui/material/styles";
+
+type BaseBoxProps = React.PropsWithChildren<{ sx?: SxProps }>;
+
+export const BaseBox: React.FC = ({ children, sx }) => {
+ const theme = useTheme();
+ return (
+
+ {children}
+
+ );
+};
diff --git a/client/src/Components/v2/DesignElements/BasePage.tsx b/client/src/Components/v2/DesignElements/BasePage.tsx
new file mode 100644
index 000000000..f458cbb0a
--- /dev/null
+++ b/client/src/Components/v2/DesignElements/BasePage.tsx
@@ -0,0 +1,80 @@
+import Stack from "@mui/material/Stack";
+import { ErrorFallback, EmptyFallback } from "./Fallback";
+
+import type { StackProps } from "@mui/material/Stack";
+import { useTheme } from "@mui/material/styles";
+import { useTranslation } from "react-i18next";
+interface BasePageProps extends StackProps {
+ children: React.ReactNode;
+}
+
+export const BasePage: React.FC = ({
+ children,
+ ...props
+}: {
+ children: React.ReactNode;
+}) => {
+ const theme = useTheme();
+ return (
+
+ {children}
+
+ );
+};
+
+interface BasePageWithStatesProps extends StackProps {
+ loading: boolean;
+ error: any;
+ items: any[];
+ page: string;
+ actionLink?: string;
+ children: React.ReactNode;
+}
+
+const isEmpty = (items: any[]) => {
+ if (!items) return true;
+ if (Array.isArray(items) && items.length === 0) return true;
+ return false;
+};
+
+export const BasePageWithStates: React.FC = ({
+ loading,
+ error,
+ items,
+ page,
+ actionLink,
+ children,
+ ...props
+}: BasePageWithStatesProps) => {
+ const { t } = useTranslation();
+
+ if (loading) {
+ return null;
+ }
+
+ if (error) {
+ return (
+
+ );
+ }
+
+ if (isEmpty(items)) {
+ return (
+
+ );
+ }
+
+ return {children};
+};
diff --git a/client/src/Components/v2/DesignElements/BulletPointCheck.tsx b/client/src/Components/v2/DesignElements/BulletPointCheck.tsx
new file mode 100644
index 000000000..03147e011
--- /dev/null
+++ b/client/src/Components/v2/DesignElements/BulletPointCheck.tsx
@@ -0,0 +1,45 @@
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import CheckOutlined from "@/assets/icons/check-outlined.svg?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.primary.contrastTextSecondary,
+ };
+
+ return (
+
+
+
+ {text}
+
+
+ );
+};
diff --git a/client/src/Components/v2/DesignElements/Dot.tsx b/client/src/Components/v2/DesignElements/Dot.tsx
new file mode 100644
index 000000000..ca3884cfb
--- /dev/null
+++ b/client/src/Components/v2/DesignElements/Dot.tsx
@@ -0,0 +1,23 @@
+export const Dot = ({
+ color = "gray",
+ size = "4px",
+ style,
+}: {
+ color?: string;
+ size?: string;
+ style?: React.CSSProperties;
+}) => {
+ return (
+
+ );
+};
diff --git a/client/src/Components/v2/DesignElements/Fallback.tsx b/client/src/Components/v2/DesignElements/Fallback.tsx
new file mode 100644
index 000000000..f7b48a113
--- /dev/null
+++ b/client/src/Components/v2/DesignElements/Fallback.tsx
@@ -0,0 +1,158 @@
+import Box from "@mui/material/Box";
+import Stack from "@mui/material/Stack";
+import OutputAnimation from "@/assets/Animations/output.gif";
+import DarkmodeOutput from "@/assets/Animations/darkmodeOutput.gif";
+import Typography from "@mui/material/Typography";
+import { BulletPointCheck } from "@/Components/v2/DesignElements";
+import { Button } from "@/Components/v2/Inputs";
+
+import { useNavigate } from "react-router";
+import { useMediaQuery } from "@mui/material";
+import { useTheme } from "@mui/material/styles";
+import { useSelector } from "react-redux";
+
+import type { BoxProps } from "@mui/material";
+
+interface BaseFallbackProps extends BoxProps {
+ children: React.ReactNode;
+}
+
+export const BaseFallback: React.FC = ({ children, ...props }) => {
+ const theme = useTheme();
+ const mode = useSelector((state: any) => state.ui.mode);
+ 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 = ({
+ page,
+ title,
+ bullets,
+ actionButtonText,
+ actionLink,
+}: {
+ page: string;
+ title: string;
+ bullets: any;
+ actionButtonText: string;
+ actionLink: string;
+}) => {
+ const theme = useTheme();
+ const navigate = useNavigate();
+ return (
+
+
+
+ {title}
+
+
+ {bullets?.map((bullet: string, index: number) => (
+
+ ))}
+
+
+
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/DesignElements/PulseDot.tsx b/client/src/Components/v2/DesignElements/PulseDot.tsx
new file mode 100644
index 000000000..a785b5a0d
--- /dev/null
+++ b/client/src/Components/v2/DesignElements/PulseDot.tsx
@@ -0,0 +1,44 @@
+import Stack from "@mui/material/Stack";
+import Box from "@mui/material/Box";
+import { useTheme } from "@mui/material/styles";
+export const PulseDot = ({ color }: { color: string }) => {
+ const theme = useTheme();
+ return (
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/DesignElements/SplitBox.tsx b/client/src/Components/v2/DesignElements/SplitBox.tsx
new file mode 100644
index 000000000..f5a09d8f5
--- /dev/null
+++ b/client/src/Components/v2/DesignElements/SplitBox.tsx
@@ -0,0 +1,68 @@
+import { Fragment } from "react";
+import Stack from "@mui/material/Stack";
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import { useTheme } from "@mui/material/styles";
+import { useMediaQuery } from "@mui/material";
+export const SplitBox = ({
+ left,
+ right,
+}: {
+ left: React.ReactNode;
+ right: React.ReactNode;
+}) => {
+ const theme = useTheme();
+ const isSmall = useMediaQuery(theme.breakpoints.down("md"));
+ return (
+
+
+ {left}
+
+
+ {right}
+
+
+ );
+};
+
+export const ConfigBox = ({
+ title,
+ subtitle,
+ rightContent,
+}: {
+ title: string;
+ subtitle: string;
+ rightContent: React.ReactNode;
+}) => {
+ return (
+
+
+ {title}
+
+ {subtitle}
+
+ }
+ right={rightContent}
+ />
+ );
+};
diff --git a/client/src/Components/v2/DesignElements/StatBox.tsx b/client/src/Components/v2/DesignElements/StatBox.tsx
new file mode 100644
index 000000000..3450444b2
--- /dev/null
+++ b/client/src/Components/v2/DesignElements/StatBox.tsx
@@ -0,0 +1,57 @@
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import { useTheme } from "@mui/material/styles";
+import { useMediaQuery } from "@mui/material";
+import type { PaletteKey } from "@/Utils/Theme/v2/theme";
+import { BaseBox } from "@/Components/v2/DesignElements";
+
+type GradientBox = React.PropsWithChildren<{ palette?: PaletteKey }>;
+
+export const GradientBox: React.FC = ({ children, palette }) => {
+ const theme = useTheme();
+ const isSmall = useMediaQuery(theme.breakpoints.down("md"));
+ const bg = palette
+ ? `linear-gradient(to bottom right, ${theme.palette[palette].main} 30%, ${theme.palette[palette].lowContrast} 70%)`
+ : `linear-gradient(340deg, ${theme.palette.tertiary.main} 10%, ${theme.palette.primary.main} 45%)`;
+
+ return (
+
+ {children}
+
+ );
+};
+
+type StatBoxProps = React.PropsWithChildren<{
+ title: string;
+ subtitle: string;
+ palette?: PaletteKey;
+}>;
+
+export const StatBox: React.FC = ({
+ title,
+ subtitle,
+ palette,
+ children,
+}) => {
+ const theme = useTheme();
+ const textColor = palette ? theme.palette[palette].contrastText : "inherit";
+
+ return (
+
+
+ {title}
+ {subtitle}
+ {children}
+
+
+ );
+};
diff --git a/client/src/Components/v2/DesignElements/StatusBox.tsx b/client/src/Components/v2/DesignElements/StatusBox.tsx
new file mode 100644
index 000000000..8cd5cfbc1
--- /dev/null
+++ b/client/src/Components/v2/DesignElements/StatusBox.tsx
@@ -0,0 +1,100 @@
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import Box from "@mui/material/Box";
+import { BaseBox } from "@/Components/v2/DesignElements";
+import Background from "@/assets/Images/background-grid.svg?react";
+import { useTranslation } from "react-i18next";
+
+import { useTheme } from "@mui/material/styles";
+
+type StatusBoxProps = React.PropsWithChildren<{}>;
+
+export const BGBox: React.FC = ({ children }) => {
+ 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 (
+
+ );
+};
diff --git a/client/src/Components/v2/DesignElements/StatusLabel.tsx b/client/src/Components/v2/DesignElements/StatusLabel.tsx
new file mode 100644
index 000000000..e4d288e63
--- /dev/null
+++ b/client/src/Components/v2/DesignElements/StatusLabel.tsx
@@ -0,0 +1,41 @@
+import Box from "@mui/material/Box";
+import { BaseBox } from "@/Components/v2/DesignElements";
+import type { MonitorStatus } from "@/Types/Monitor";
+
+import { getStatusPalette } from "@/Utils/v2/MonitorUtils";
+import { useTheme } from "@mui/material/styles";
+
+export const StatusLabel = ({
+ status,
+ isActive,
+}: {
+ status: MonitorStatus;
+ isActive?: boolean;
+}) => {
+ const theme = useTheme();
+ const palette = getStatusPalette(status);
+ const transformedText = status.charAt(0).toUpperCase() + status.slice(1).toLowerCase();
+
+ return (
+
+
+ {isActive === false ? "Paused" : transformedText}
+
+ );
+};
diff --git a/client/src/Components/v2/DesignElements/Table.tsx b/client/src/Components/v2/DesignElements/Table.tsx
new file mode 100644
index 000000000..bf75fd9bf
--- /dev/null
+++ b/client/src/Components/v2/DesignElements/Table.tsx
@@ -0,0 +1,215 @@
+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 IconButton from "@mui/material/IconButton";
+import LastPageIcon from "@mui/icons-material/LastPage";
+import FirstPageIcon from "@mui/icons-material/FirstPage";
+import KeyboardArrowLeft from "@mui/icons-material/KeyboardArrowLeft";
+import KeyboardArrowRight from "@mui/icons-material/KeyboardArrowRight";
+
+import Box from "@mui/material/Box";
+import TablePagination from "@mui/material/TablePagination";
+import type { TablePaginationProps } from "@mui/material/TablePagination";
+
+import { useTheme } from "@mui/material/styles";
+import { useMediaQuery } from "@mui/material";
+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;
+};
+
+export function DataTable<
+ T extends {
+ id?: string | number;
+ _id?: string | number;
+ onRowClick?: (row: T) => void;
+ },
+>({ headers, data, onRowClick }: DataTableProps) {
+ const theme = useTheme();
+ if (data.length === 0 || headers.length === 0) return No data
;
+ return (
+
+
+
+
+ {headers.map((header, idx) => {
+ return (
+
+ {header.content}
+
+ );
+ })}
+
+
+
+ {data.map((row) => {
+ const key = row.id || row._id || Math.random();
+
+ return (
+ (onRowClick ? onRowClick(row) : null)}
+ >
+ {headers.map((header, index) => {
+ return (
+ header.onClick!(e, row) : undefined
+ }
+ >
+ {header.render(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" ? : }
+
+
+ );
+}
+
+export const Pagination: React.FC = ({ ...props }) => {
+ const isSmall = useMediaQuery((theme: any) => theme.breakpoints.down("sm"));
+ const theme = useTheme();
+ return (
+
+ );
+};
diff --git a/client/src/Components/v2/DesignElements/index.tsx b/client/src/Components/v2/DesignElements/index.tsx
new file mode 100644
index 000000000..3574daffb
--- /dev/null
+++ b/client/src/Components/v2/DesignElements/index.tsx
@@ -0,0 +1,9 @@
+export { SplitBox as HorizontalSplitBox, ConfigBox } from "./SplitBox";
+export { BasePage, BasePageWithStates } from "./BasePage";
+export { BGBox, UpStatusBox, DownStatusBox, PausedStatusBox } from "./StatusBox";
+export { DataTable as Table, Pagination } from "./Table";
+export { GradientBox, StatBox } from "./StatBox";
+export { BaseBox } from "./BaseBox";
+export { StatusLabel } from "./StatusLabel";
+export { BaseFallback, ErrorFallback, EmptyFallback } from "./Fallback";
+export { BulletPointCheck } from "./BulletPointCheck";
diff --git a/client/src/Components/v2/Inputs/AutoComplete.tsx b/client/src/Components/v2/Inputs/AutoComplete.tsx
new file mode 100644
index 000000000..73afa365a
--- /dev/null
+++ b/client/src/Components/v2/Inputs/AutoComplete.tsx
@@ -0,0 +1,48 @@
+import Autocomplete from "@mui/material/Autocomplete";
+import type { AutocompleteProps } from "@mui/material/Autocomplete";
+import { TextInput } from "@/Components/v2/Inputs/TextInput";
+import { CheckboxInput } from "@/Components/v2/Inputs/Checkbox";
+import ListItem from "@mui/material/ListItem";
+import { useTheme } from "@mui/material/styles";
+
+type AutoCompleteInputProps = Omit<
+ AutocompleteProps,
+ "renderInput"
+> & {
+ renderInput?: AutocompleteProps["renderInput"];
+};
+
+export const AutoCompleteInput: React.FC = ({ ...props }) => {
+ const theme = useTheme();
+ return (
+ (
+
+ )}
+ getOptionKey={(option) => option._id}
+ renderTags={() => null}
+ renderOption={(props, option, { selected }) => {
+ const { key, ...optionProps } = props;
+ return (
+
+
+ {option.name}
+
+ );
+ }}
+ sx={{
+ "&.MuiAutocomplete-root .MuiAutocomplete-input": {
+ padding: `0 ${theme.spacing(5)}`,
+ },
+ }}
+ />
+ );
+};
diff --git a/client/src/Components/v2/Inputs/Button.tsx b/client/src/Components/v2/Inputs/Button.tsx
new file mode 100644
index 000000000..2f28e8eda
--- /dev/null
+++ b/client/src/Components/v2/Inputs/Button.tsx
@@ -0,0 +1,11 @@
+import Button from "@mui/material/Button";
+import type { ButtonProps } from "@mui/material/Button";
+
+export const ButtonInput: React.FC = ({ sx, ...props }) => {
+ return (
+
+ );
+};
diff --git a/client/src/Components/v2/Inputs/ButtonGroup.tsx b/client/src/Components/v2/Inputs/ButtonGroup.tsx
new file mode 100644
index 000000000..3d8acd8cc
--- /dev/null
+++ b/client/src/Components/v2/Inputs/ButtonGroup.tsx
@@ -0,0 +1,13 @@
+import ButtonGroup from "@mui/material/ButtonGroup";
+import type { ButtonGroupProps } from "@mui/material/ButtonGroup";
+export const ButtonGroupInput: React.FC = ({
+ orientation,
+ ...props
+}) => {
+ 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..bc2cedd03
--- /dev/null
+++ b/client/src/Components/v2/Inputs/Checkbox.tsx
@@ -0,0 +1,23 @@
+import Checkbox from "@mui/material/Checkbox";
+import type { CheckboxProps } from "@mui/material/Checkbox";
+import CheckboxOutline from "@/assets/icons/checkbox-outline.svg?react";
+import CheckboxFilled from "@/assets/icons/checkbox-filled.svg?react";
+import { useTheme } from "@mui/material/styles";
+type CheckboxInputProps = CheckboxProps & {
+ label?: string;
+};
+
+export const CheckboxInput: React.FC = ({ label, ...props }) => {
+ const theme = useTheme();
+ return (
+ }
+ checkedIcon={}
+ sx={{
+ "&:hover": { backgroundColor: "transparent" },
+ "& svg": { width: theme.spacing(8), height: theme.spacing(8) },
+ }}
+ />
+ );
+};
diff --git a/client/src/Components/v2/Inputs/LanguageSelector.tsx b/client/src/Components/v2/Inputs/LanguageSelector.tsx
new file mode 100644
index 000000000..f7953c873
--- /dev/null
+++ b/client/src/Components/v2/Inputs/LanguageSelector.tsx
@@ -0,0 +1,64 @@
+import "flag-icons/css/flag-icons.min.css";
+import { Select } from "@/Components/v2/Inputs";
+import MenuItem from "@mui/material/MenuItem";
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+
+import { useTranslation } from "react-i18next";
+import { useTheme } from "@mui/material/styles";
+import { useDispatch } from "react-redux";
+import { useSelector } from "react-redux";
+import { setLanguage } from "@/Features/UI/uiSlice";
+import type { SelectChangeEvent } from "@mui/material/Select";
+
+export const LanguageSelector = () => {
+ const { i18n } = useTranslation();
+ const theme = useTheme();
+ const dispatch = useDispatch();
+ const language = useSelector((state: any) => state.ui.language);
+ const languages = Object.keys(i18n.options.resources || {});
+ const languageMap: Record = {
+ cs: "cz",
+ ja: "jp",
+ uk: "ua",
+ vi: "vn",
+ };
+
+ const handleChange = (event: SelectChangeEvent) => {
+ const newLang = event.target.value;
+ dispatch(setLanguage(newLang));
+ };
+
+ const languagesForDisplay = languages.map((l) => {
+ let formattedLanguage = l === "en" ? "gb" : l;
+ formattedLanguage = formattedLanguage.includes("-")
+ ? formattedLanguage.split("-")[1].toLowerCase()
+ : formattedLanguage;
+ formattedLanguage = languageMap[formattedLanguage] || formattedLanguage;
+ const flag = formattedLanguage ? `fi fi-${formattedLanguage}` : null;
+
+ return (
+
+ );
+ });
+
+ return (
+
+ );
+};
diff --git a/client/src/Components/v2/Inputs/RadioInput.tsx b/client/src/Components/v2/Inputs/RadioInput.tsx
new file mode 100644
index 000000000..d903a7b58
--- /dev/null
+++ b/client/src/Components/v2/Inputs/RadioInput.tsx
@@ -0,0 +1,66 @@
+import Radio from "@mui/material/Radio";
+import type { RadioProps } from "@mui/material/Radio";
+import { useTheme } from "@mui/material/styles";
+import RadioChecked from "@/assets/icons/radio-checked.svg?react";
+import FormControlLabel from "@mui/material/FormControlLabel";
+import Typography from "@mui/material/Typography";
+
+interface RadioInputProps extends RadioProps {}
+
+export const RadioInput: React.FC = ({ ...props }) => {
+ const theme = useTheme();
+ return (
+ }
+ sx={{
+ color: "transparent",
+ boxShadow: `inset 0 0 0 1px ${theme.palette.secondary.main}`,
+ "&:not(.Mui-checked)": {
+ boxShadow: `inset 0 0 0 1px ${theme.palette.primary.contrastText}70`, // Use theme text color for the outline
+ },
+ mt: theme.spacing(0.5),
+ padding: 0,
+ "& .MuiSvgIcon-root": {
+ fontSize: 16,
+ },
+ }}
+ />
+ );
+};
+
+export const RadioWithDescription: React.FC<
+ RadioInputProps & { label: string; description: string }
+> = ({ label, description, ...props }) => {
+ const theme = useTheme();
+ return (
+ }
+ label={
+ <>
+ {label}
+
+ {description}
+
+ >
+ }
+ sx={{
+ alignItems: "flex-start",
+ p: theme.spacing(2.5),
+ m: theme.spacing(-2.5),
+ borderRadius: theme.shape.borderRadius,
+
+ "&:hover": {
+ backgroundColor: theme.palette.tertiary.main,
+ },
+ "& .MuiButtonBase-root": {
+ p: 0,
+ mr: theme.spacing(6),
+ },
+ }}
+ />
+ );
+};
diff --git a/client/src/Components/v2/Inputs/Select.tsx b/client/src/Components/v2/Inputs/Select.tsx
new file mode 100644
index 000000000..54aacf68a
--- /dev/null
+++ b/client/src/Components/v2/Inputs/Select.tsx
@@ -0,0 +1,88 @@
+import { Typography, Select } from "@mui/material";
+import MenuItem from "@mui/material/MenuItem";
+import type { SelectProps } from "@mui/material/Select";
+import { useTheme } from "@mui/material/styles";
+import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
+
+export const SelectInput: React.FC = ({ ...props }) => {
+ const theme = useTheme();
+ return (
+
+ );
+};
+
+type ItemTypes = string | number;
+interface SelectItem {
+ _id: ItemTypes;
+ name: string;
+}
+export type CustomSelectProps = SelectProps & {
+ items: SelectItem[];
+ placeholder?: string;
+ isHidden?: boolean;
+ hasError?: boolean;
+};
+
+export const SelectFromItems: React.FC = ({
+ items,
+ placeholder,
+ isHidden = false,
+ hasError = false,
+ ...props
+}) => {
+ return (
+ {
+ if (!selected) {
+ return (
+
+ {placeholder ?? ""}
+
+ );
+ }
+ const selectedItem = items.find((item) => item._id === selected);
+ const displayName = selectedItem ? selectedItem.name : placeholder;
+ return (
+
+ {displayName}
+
+ );
+ }}
+ {...props}
+ >
+ {items.map((item) => (
+
+ ))}
+
+ );
+};
+
+SelectInput.displayName = "SelectInput";
+SelectFromItems.displayName = "SelectFromItems";
diff --git a/client/src/Components/v2/Inputs/TextInput.tsx b/client/src/Components/v2/Inputs/TextInput.tsx
new file mode 100644
index 000000000..4cd792059
--- /dev/null
+++ b/client/src/Components/v2/Inputs/TextInput.tsx
@@ -0,0 +1,22 @@
+import { forwardRef } from "react";
+import TextField from "@mui/material/TextField";
+import type { TextFieldProps } from "@mui/material";
+import { typographyLevels } from "@/Utils/Theme/v2/palette";
+
+export const TextInput = forwardRef(
+ function TextInput(props, ref) {
+ return (
+
+ );
+ }
+);
+TextInput.displayName = "TextInput";
diff --git a/client/src/Components/v2/Inputs/TextLink.tsx b/client/src/Components/v2/Inputs/TextLink.tsx
new file mode 100644
index 000000000..78525f837
--- /dev/null
+++ b/client/src/Components/v2/Inputs/TextLink.tsx
@@ -0,0 +1,37 @@
+import Typography from "@mui/material/Typography";
+import Stack from "@mui/material/Stack";
+import Link from "@mui/material/Link";
+import { Link as RouterLink } from "react-router-dom";
+
+import { useTheme } from "@mui/material/styles";
+
+export const TextLink = ({
+ text,
+ linkText,
+ href,
+ target = "_self",
+}: {
+ text: string;
+ linkText: string;
+ href: string;
+ target?: string;
+}) => {
+ const theme = useTheme();
+
+ return (
+
+ {text}
+
+ {linkText}
+
+
+ );
+};
diff --git a/client/src/Components/v2/Inputs/ThemeSwitch.tsx b/client/src/Components/v2/Inputs/ThemeSwitch.tsx
new file mode 100644
index 000000000..fb4bb6d81
--- /dev/null
+++ b/client/src/Components/v2/Inputs/ThemeSwitch.tsx
@@ -0,0 +1,135 @@
+import { useTheme } from "@mui/material/styles";
+import { useDispatch, useSelector } from "react-redux";
+import { setMode } from "@/Features/UI/uiSlice.js";
+import { useTranslation } from "react-i18next";
+import IconButton from "@mui/material/IconButton";
+
+const SunAndMoonIcon = () => {
+ const theme = useTheme();
+
+ return (
+
+ );
+};
+
+export const ThemeSwitch = ({
+ width = 48,
+ height = 48,
+}: {
+ width?: number;
+ height?: number;
+}) => {
+ const mode = useSelector((state: any) => state.ui.mode);
+ const dispatch = useDispatch();
+ const { t } = useTranslation();
+
+ const handleChange = () => {
+ dispatch(setMode(mode === "light" ? "dark" : "light"));
+ };
+
+ 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..ccf17f630
--- /dev/null
+++ b/client/src/Components/v2/Inputs/index.tsx
@@ -0,0 +1,7 @@
+export { ButtonInput as Button } from "./Button";
+export { ButtonGroupInput as ButtonGroup } from "./ButtonGroup";
+export { TextInput } from "./TextInput";
+export { SelectInput as Select } from "./Select";
+export { LanguageSelector } from "./LanguageSelector";
+export { ThemeSwitch } from "./ThemeSwitch";
+export { TextLink } from "./TextLink";
diff --git a/client/src/Components/v2/Layouts/RootLayout.tsx b/client/src/Components/v2/Layouts/RootLayout.tsx
new file mode 100644
index 000000000..4237494b1
--- /dev/null
+++ b/client/src/Components/v2/Layouts/RootLayout.tsx
@@ -0,0 +1,23 @@
+import { Outlet } from "react-router";
+import Stack from "@mui/material/Stack";
+import { SideBar } from "@/Components/v2/Layouts/Sidebar";
+import { useTheme } from "@mui/material/styles";
+const RootLayout = () => {
+ const theme = useTheme();
+ return (
+
+
+
+
+
+
+ );
+};
+
+export default RootLayout;
diff --git a/client/src/Components/v2/Layouts/Sidebar/BottomControls.tsx b/client/src/Components/v2/Layouts/Sidebar/BottomControls.tsx
new file mode 100644
index 000000000..a07ccde6c
--- /dev/null
+++ b/client/src/Components/v2/Layouts/Sidebar/BottomControls.tsx
@@ -0,0 +1,16 @@
+import { useTheme } from "@mui/material/styles";
+
+import Stack from "@mui/material/Stack";
+
+export const BottomControls = ({}) => {
+ const theme = useTheme();
+ return (
+
+ );
+};
diff --git a/client/src/Components/v2/Layouts/Sidebar/CollapseButton.tsx b/client/src/Components/v2/Layouts/Sidebar/CollapseButton.tsx
new file mode 100644
index 000000000..564c41591
--- /dev/null
+++ b/client/src/Components/v2/Layouts/Sidebar/CollapseButton.tsx
@@ -0,0 +1,50 @@
+import IconButton from "@mui/material/IconButton";
+import { ArrowRight } from "@/Components/v2/Arrows/ArrowRight";
+import { ArrowLeft } from "@/Components/v2/Arrows/ArrowLeft";
+import { useTheme } from "@mui/material/styles";
+import { useDispatch } from "react-redux";
+import { toggleSidebar } from "../../../../Features/UI/uiSlice.js";
+
+export const CollapseButton = ({ collapsed }: { collapsed: boolean }) => {
+ const theme = useTheme();
+ const dispatch = useDispatch();
+ const arrowIcon = collapsed ? (
+
+ ) : (
+
+ );
+
+ return (
+ {
+ dispatch(toggleSidebar());
+ }}
+ >
+ {arrowIcon}
+
+ );
+};
diff --git a/client/src/Components/v2/Layouts/Sidebar/Logo.tsx b/client/src/Components/v2/Layouts/Sidebar/Logo.tsx
new file mode 100644
index 000000000..6d190b99d
--- /dev/null
+++ b/client/src/Components/v2/Layouts/Sidebar/Logo.tsx
@@ -0,0 +1,59 @@
+import Stack from "@mui/material/Stack";
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import { useTheme } from "@mui/material/styles";
+import { useNavigate } from "react-router";
+import { useTranslation } from "react-i18next";
+
+export const Logo = ({ collapsed }: { collapsed: boolean }) => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+ const navigate = useNavigate();
+
+ return (
+ navigate("/")}
+ sx={{ cursor: "pointer" }}
+ >
+
+ C
+
+
+ {" "}
+
+ {t("common.appName")}
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/Layouts/Sidebar/Menu.tsx b/client/src/Components/v2/Layouts/Sidebar/Menu.tsx
new file mode 100644
index 000000000..f0f99f33a
--- /dev/null
+++ b/client/src/Components/v2/Layouts/Sidebar/Menu.tsx
@@ -0,0 +1,58 @@
+// import Notifications from "@/assets/icons/notifications.svg?react";
+import Monitors from "@/assets/icons/monitors.svg?react";
+// import PageSpeed from "@/assets/icons/page-speed.svg?react";
+// import Integrations from "@/assets/icons/integrations.svg?react";
+// import Incidents from "@/assets/icons/incidents.svg?react";
+// import StatusPages from "@/assets/icons/status-pages.svg?react";
+// import Maintenance from "@/assets/icons/maintenance.svg?react";
+// import Logs from "@/assets/icons/logs.svg?react";
+// import Settings from "@/assets/icons/settings.svg?react";
+import Support from "@/assets/icons/support.svg?react";
+import Discussions from "@/assets/icons/discussions.svg?react";
+import Docs from "@/assets/icons/docs.svg?react";
+import ChangeLog from "@/assets/icons/changeLog.svg?react";
+
+export const getMenu = (t: Function) => [
+ { name: t("menu.uptime"), path: "v2/uptime", icon: },
+ // { name: t("menu.pagespeed"), path: "pagespeed", icon: },
+
+ // { name: t("menu.infrastructure"), path: "infrastructure", icon: },
+ // {
+ // name: t("menu.notifications"),
+ // path: "notifications",
+ // icon: ,
+ // },
+ // { name: t("menu.incidents"), path: "incidents", icon: },
+
+ // { name: t("menu.statusPages"), path: "status", icon: },
+ // { name: t("menu.maintenance"), path: "maintenance", icon: },
+ // { name: t("menu.logs"), path: "logs", icon: },
+
+ // {
+ // name: t("menu.settings"),
+ // icon: ,
+ // path: "settings",
+ // },
+];
+
+export const getBottomMenu = (t: Function) => [
+ { name: t("menu.support"), path: "support", icon: , url: "invite" },
+ {
+ name: t("menu.discussions"),
+ path: "discussions",
+ icon: ,
+ url: "https://github.com/bluewave-labs/checkmate/discussions",
+ },
+ {
+ name: t("menu.docs"),
+ path: "docs",
+ icon: ,
+ url: "https://bluewavelabs.gitbook.io/checkmate",
+ },
+ {
+ name: t("menu.changelog"),
+ path: "changelog",
+ icon: ,
+ url: "https://github.com/bluewave-labs/checkmate/releases",
+ },
+];
diff --git a/client/src/Components/v2/Layouts/Sidebar/NavItem.tsx b/client/src/Components/v2/Layouts/Sidebar/NavItem.tsx
new file mode 100644
index 000000000..8f7f5d68c
--- /dev/null
+++ b/client/src/Components/v2/Layouts/Sidebar/NavItem.tsx
@@ -0,0 +1,103 @@
+import Tooltip from "@mui/material/Tooltip";
+import ListItemButton from "@mui/material/ListItemButton";
+import ListItemIcon from "@mui/material/ListItemIcon";
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import { useTheme } from "@mui/material/styles";
+
+export interface NavData {
+ name: string;
+ icon: JSX.Element;
+}
+
+export const NavItem = ({
+ item,
+ collapsed,
+ selected,
+ onClick,
+}: {
+ item: NavData;
+ collapsed: boolean;
+ selected: boolean;
+ onClick: (event: React.MouseEvent) => void;
+}) => {
+ const theme = useTheme();
+ const iconStroke = selected
+ ? theme.palette.primary.contrastText
+ : theme.palette.primary.contrastTextTertiary;
+
+ const buttonBgColor = selected ? theme.palette.secondary.main : "transparent";
+ const buttonBgHoverColor = selected
+ ? theme.palette.secondary.main
+ : theme.palette.tertiary.main;
+ const fontWeight = selected ? 600 : 400;
+ return (
+
+
+
+ {item.icon}
+
+
+
+ {item.name}
+
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/Layouts/Sidebar/index.tsx b/client/src/Components/v2/Layouts/Sidebar/index.tsx
new file mode 100644
index 000000000..a17b9fa13
--- /dev/null
+++ b/client/src/Components/v2/Layouts/Sidebar/index.tsx
@@ -0,0 +1,106 @@
+import { useEffect } from "react";
+import { useSelector, useDispatch } from "react-redux";
+import { setCollapsed } from "@/Features/UI/uiSlice";
+import useMediaQuery from "@mui/material/useMediaQuery";
+import { useTheme } from "@mui/material/styles";
+import { useTranslation } from "react-i18next";
+import { useNavigate, useLocation } from "react-router-dom";
+
+import { CollapseButton } from "@/Components/v2/Layouts/Sidebar/CollapseButton";
+import Stack from "@mui/material/Stack";
+import List from "@mui/material/List";
+import Divider from "@mui/material/Divider";
+import { Logo } from "@/Components/v2/Layouts/Sidebar/Logo";
+import { getMenu, getBottomMenu } from "@/Components/v2/Layouts/Sidebar/Menu";
+import { NavItem } from "@/Components/v2/Layouts/Sidebar/NavItem";
+import { BottomControls } from "@/Components/v2/Layouts/Sidebar/BottomControls";
+
+export const COLLAPSED_WIDTH = 64;
+export const EXPANDED_WIDTH = 250;
+
+export const SideBar = () => {
+ const theme = useTheme();
+ const isSmall = useMediaQuery(theme.breakpoints.down("md"));
+ const dispatch = useDispatch();
+ const collapsed = useSelector((state: any) => state.ui.sidebar.collapsed);
+ const { t } = useTranslation();
+ const navigate = useNavigate();
+ const location = useLocation();
+ const menu = getMenu(t);
+ const bottomMenu = getBottomMenu(t);
+
+ useEffect(() => {
+ dispatch(setCollapsed({ collapsed: isSmall }));
+ }, [isSmall]);
+
+ return (
+
+
+
+
+ {menu.map((item) => {
+ const selected = location.pathname.startsWith(`/${item.path}`);
+ return (
+ navigate(`/${item.path}`)}
+ />
+ );
+ })}
+
+
+ {bottomMenu.map((item) => {
+ const selected = location.pathname.startsWith(`/${item.path}`);
+
+ return (
+ {
+ if (item.url) {
+ window.open(item.url, "_blank", "noreferrer");
+ } else {
+ navigate(`/${item.path}`);
+ }
+ }}
+ />
+ );
+ })}
+
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/Monitors/ChartAvgResponse.tsx b/client/src/Components/v2/Monitors/ChartAvgResponse.tsx
new file mode 100644
index 000000000..e9b85cb43
--- /dev/null
+++ b/client/src/Components/v2/Monitors/ChartAvgResponse.tsx
@@ -0,0 +1,84 @@
+import { BaseChart } from "./HistogramStatus";
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import AverageResponseIcon from "@/assets/icons/average-response-icon.svg?react";
+import { Cell, RadialBarChart, RadialBar, ResponsiveContainer } from "recharts";
+
+import { getResponseTimeColor } from "@/Utils/v2/MonitorUtils";
+import { useTheme } from "@mui/material/styles";
+
+export const ChartAvgResponse = ({ avg, max }: { avg: number; max: number }) => {
+ 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={"Average response time"}
+ >
+
+
+
+
+ |
+ |
+
+
+
+
+ Low
+ High
+
+
+
+ {msg[palette]}
+
+ {`${avg?.toFixed()}ms`}
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/Monitors/ChartResponseTime.tsx b/client/src/Components/v2/Monitors/ChartResponseTime.tsx
new file mode 100644
index 000000000..3c1c93f01
--- /dev/null
+++ b/client/src/Components/v2/Monitors/ChartResponseTime.tsx
@@ -0,0 +1,163 @@
+import { BaseChart } from "./HistogramStatus";
+import { BaseBox } from "../DesignElements";
+import ResponseTimeIcon from "@/assets/icons/response-time-icon.svg?react";
+import { normalizeResponseTimes } from "@/Utils/v2/DataUtils";
+import {
+ AreaChart,
+ Area,
+ XAxis,
+ Tooltip,
+ CartesianGrid,
+ ResponsiveContainer,
+ Text,
+} from "recharts";
+import Typography from "@mui/material/Typography";
+
+import {
+ formatDateWithTz,
+ tickDateFormatLookup,
+ tooltipDateFormatLookup,
+} from "@/Utils/v2/TimeUtils";
+import { useTheme } from "@mui/material/styles";
+import type { GroupedCheck } from "@/Types/Check";
+import { useSelector } from "react-redux";
+
+type XTickProps = {
+ x: number;
+ y: number;
+ payload: { value: any };
+ range: string;
+};
+
+const XTick: React.FC = ({ x, y, payload, range }) => {
+ const format = tickDateFormatLookup(range);
+ const theme = useTheme();
+ const uiTimezone = useSelector((state: any) => state.ui.timezone);
+ return (
+
+ {formatDateWithTz(payload?.value, format, uiTimezone)}
+
+ );
+};
+
+type ResponseTimeToolTipProps = {
+ active?: boolean | undefined;
+ payload?: any[];
+ label?: string;
+ range: string;
+ theme: any;
+ uiTimezone: string;
+};
+
+const ResponseTimeToolTip: React.FC = ({
+ active,
+ payload,
+ label,
+ range,
+ theme,
+ uiTimezone,
+}) => {
+ 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(label, format, uiTimezone)}
+ Response time: {responseTime} ms
+
+ );
+};
+
+export const ChartResponseTime = ({
+ checks,
+ range,
+}: {
+ checks: GroupedCheck[];
+ range: string;
+}) => {
+ const theme = useTheme();
+ const uiTimezone = useSelector((state: any) => state.ui.timezone);
+ const normalized = normalizeResponseTimes(
+ checks,
+ "avgResponseTime"
+ );
+ return (
+ }
+ title="Response times"
+ >
+
+
+
+
+
+
+
+
+
+ (
+
+ )}
+ />
+
+ (
+
+ )}
+ />
+
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/Monitors/HeaderControls.tsx b/client/src/Components/v2/Monitors/HeaderControls.tsx
new file mode 100644
index 000000000..c6bfa7f81
--- /dev/null
+++ b/client/src/Components/v2/Monitors/HeaderControls.tsx
@@ -0,0 +1,67 @@
+import Stack from "@mui/material/Stack";
+import { MonitorStatus } from "@/Components/v2/Monitors/MonitorStatus";
+import { ButtonGroup, Button } from "@/Components/v2/Inputs";
+import SettingsOutlinedIcon from "@mui/icons-material/SettingsOutlined";
+import PauseOutlinedIcon from "@mui/icons-material/PauseOutlined";
+import PlayArrowOutlinedIcon from "@mui/icons-material/PlayArrowOutlined";
+import EmailIcon from "@mui/icons-material/Email";
+import BugReportOutlinedIcon from "@mui/icons-material/BugReportOutlined";
+
+import { useMediaQuery } from "@mui/material";
+import { useTranslation } from "react-i18next";
+import { useTheme } from "@mui/material/styles";
+
+import type { IMonitor } from "@/Types/Monitor";
+
+export const HeaderControls = ({
+ monitor,
+ patch,
+ isPatching,
+ refetch,
+}: {
+ monitor: IMonitor;
+ patch: Function;
+ isPatching: boolean;
+ refetch: Function;
+}) => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+ const isSmall = useMediaQuery(theme.breakpoints.down("md"));
+
+ return (
+
+
+
+
+ }>{t("sendTestNotifications")}
+ }>{t("menu.incidents")}
+
+ }>{t("configure")}
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/Monitors/HeaderCreate.tsx b/client/src/Components/v2/Monitors/HeaderCreate.tsx
new file mode 100644
index 000000000..46b17c8a5
--- /dev/null
+++ b/client/src/Components/v2/Monitors/HeaderCreate.tsx
@@ -0,0 +1,35 @@
+import Stack from "@mui/material/Stack";
+import { Button } from "@/Components/v2/Inputs";
+import { useTheme } from "@mui/material/styles";
+import { useTranslation } from "react-i18next";
+import { useNavigate } from "react-router";
+export const HeaderCreate = ({
+ label,
+ isLoading,
+ path,
+}: {
+ label?: string;
+ isLoading: boolean;
+ path: string;
+}) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+ const navigate = useNavigate();
+ return (
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/Monitors/HeaderRange.tsx b/client/src/Components/v2/Monitors/HeaderRange.tsx
new file mode 100644
index 000000000..21ce01fe2
--- /dev/null
+++ b/client/src/Components/v2/Monitors/HeaderRange.tsx
@@ -0,0 +1,62 @@
+import Stack from "@mui/material/Stack";
+import { ButtonGroup, Button } from "@/Components/v2/Inputs";
+import { useTheme } from "@mui/material/styles";
+import Typography from "@mui/material/Typography";
+import { useMediaQuery } from "@mui/material";
+export const HeaderRange = ({
+ range,
+ setRange,
+ loading,
+}: {
+ range: string;
+ setRange: Function;
+ loading: boolean;
+}) => {
+ const theme = useTheme();
+ const isSmall = useMediaQuery(theme.breakpoints.down("md"));
+ return (
+
+ {`Showing statistics for past ${range}`}
+
+
+
+
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/Monitors/HistogramResponseTime.tsx b/client/src/Components/v2/Monitors/HistogramResponseTime.tsx
new file mode 100644
index 000000000..aa1996e2a
--- /dev/null
+++ b/client/src/Components/v2/Monitors/HistogramResponseTime.tsx
@@ -0,0 +1,87 @@
+import Stack from "@mui/material/Stack";
+import Box from "@mui/material/Box";
+import { useTheme } from "@mui/material/styles";
+import type { Check } from "@/Types/Check";
+import { HistogramResponseTimeTooltip } from "@/Components/v2/Monitors/HistogramResponseTimeTooltip";
+import { normalizeResponseTimes } from "@/Utils/v2/DataUtils";
+
+export const HistogramResponseTime = ({ checks }: { checks: Check[] }) => {
+ const normalChecks = normalizeResponseTimes(checks, "responseTime");
+ let data = Array();
+
+ if (!normalChecks || normalChecks.length === 0) {
+ return null;
+ }
+ if (normalChecks.length !== 25) {
+ const placeholders = Array(25 - normalChecks.length).fill("placeholder");
+ data = [...normalChecks, ...placeholders];
+ } else {
+ data = normalChecks;
+ }
+
+ const theme = useTheme();
+
+ return (
+ event.stopPropagation()}
+ sx={{
+ cursor: "default",
+ }}
+ >
+ {data.map((check, index) => {
+ if (check === "placeholder") {
+ return (
+
+ );
+ } else {
+ return (
+
+
+
+
+
+ );
+ }
+ })}
+
+ );
+};
diff --git a/client/src/Components/v2/Monitors/HistogramResponseTimeTooltip.tsx b/client/src/Components/v2/Monitors/HistogramResponseTimeTooltip.tsx
new file mode 100644
index 000000000..d8c0263f0
--- /dev/null
+++ b/client/src/Components/v2/Monitors/HistogramResponseTimeTooltip.tsx
@@ -0,0 +1,29 @@
+import Stack from "@mui/material/Stack";
+import Tooltip from "@mui/material/Tooltip";
+import Typography from "@mui/material/Typography";
+import { formatDateWithTz } from "@/Utils/v2/TimeUtils";
+import { useSelector } from "react-redux";
+import type { LatestCheck } from "@/Types/Check";
+
+export const HistogramResponseTimeTooltip: React.FC<{
+ children: React.ReactElement;
+ check: LatestCheck;
+}> = ({ children, check }) => {
+ const uiTimezone = useSelector((state: any) => state.ui.timezone);
+ return (
+
+
+ {formatDateWithTz(check?.checkedAt, "ddd, MMMM D, YYYY, HH:mm A", uiTimezone)}
+
+ {check?.responseTime && (
+ Response Time: {check.responseTime} ms
+ )}
+
+ }
+ >
+ {children}
+
+ );
+};
diff --git a/client/src/Components/v2/Monitors/HistogramStatus.tsx b/client/src/Components/v2/Monitors/HistogramStatus.tsx
new file mode 100644
index 000000000..bc6675bc3
--- /dev/null
+++ b/client/src/Components/v2/Monitors/HistogramStatus.tsx
@@ -0,0 +1,218 @@
+import Stack from "@mui/material/Stack";
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import { BaseBox } from "@/Components/v2/DesignElements";
+import { ResponsiveContainer, BarChart, XAxis, Bar, Cell } from "recharts";
+import UptimeIcon from "@/assets/icons/uptime-icon.svg?react";
+import IncidentsIcon from "@/assets/icons/incidents.svg?react";
+
+import type { GroupedCheck } from "@/Types/Check";
+import type { MonitorStatus } from "@/Types/Monitor";
+
+import { normalizeResponseTimes } from "@/Utils/v2/DataUtils";
+import { useState } from "react";
+import { formatDateWithTz } from "@/Utils/v2/TimeUtils";
+import { useSelector } from "react-redux";
+import { useTheme } from "@mui/material/styles";
+import { getResponseTimeColor } from "@/Utils/v2/MonitorUtils";
+
+const XLabel = ({
+ p1,
+ p2,
+ range,
+}: {
+ p1: GroupedCheck;
+ p2: GroupedCheck;
+ range: string;
+}) => {
+ const theme = useTheme();
+ const uiTimezone = useSelector((state: any) => state.ui.timezone);
+ const dateFormat = range === "day" ? "MMM D, h:mm A" : "MMM D";
+ return (
+ <>
+
+ {formatDateWithTz(p1._id, dateFormat, uiTimezone)}
+
+
+ {formatDateWithTz(p2._id, dateFormat, uiTimezone)}
+
+ >
+ );
+};
+
+type BaseChartProps = React.PropsWithChildren<{
+ icon: React.ReactNode;
+ title: string;
+}>;
+
+export const BaseChart: React.FC = ({ children, icon, title }) => {
+ const theme = useTheme();
+
+ return (
+
+
+
+
+ {icon}
+
+ {title}
+
+ {children}
+
+
+ );
+};
+
+export const HistogramStatus = ({
+ checks,
+ status,
+ range,
+ title,
+}: {
+ checks: GroupedCheck[];
+ status: MonitorStatus;
+ range: string;
+ title: string;
+}) => {
+ const uiTimezone = useSelector((state: any) => state.ui.timezone);
+
+ const icon = status === "up" ? : ;
+ const theme = useTheme();
+ const [idx, setIdx] = useState(null);
+ const dateFormat = range === "1d" || range === "2h" ? "MMM D, h A" : "MMM D";
+ const normalChecks = normalizeResponseTimes(checks, "avgResponseTime");
+
+ if (normalChecks.length === 0) {
+ return (
+
+
+
+ {status === "up" ? "No checks yet" : "Great, no downtime yet!"}
+
+
+
+ );
+ }
+
+ const totalChecks = normalChecks.reduce((count, check) => {
+ return count + check.count;
+ }, 0);
+
+ return (
+
+
+
+
+ Total checks
+ {idx ? (
+
+ {normalChecks[idx].count}
+
+ {formatDateWithTz(normalChecks[idx]._id, dateFormat, uiTimezone)}
+
+
+ ) : (
+ {totalChecks}
+ )}
+
+
+
+
+
+ }
+ />
+
+ {normalChecks?.map((groupedCheck, idx) => {
+ const fillColor = getResponseTimeColor(groupedCheck.normalResponseTime);
+ return (
+ setIdx(idx)}
+ onMouseLeave={() => setIdx(null)}
+ key={groupedCheck._id}
+ fill={theme.palette[fillColor].main}
+ />
+ );
+ })}
+ |
+
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/Monitors/MonitorStatus.tsx b/client/src/Components/v2/Monitors/MonitorStatus.tsx
new file mode 100644
index 000000000..f329e6bb8
--- /dev/null
+++ b/client/src/Components/v2/Monitors/MonitorStatus.tsx
@@ -0,0 +1,60 @@
+import type { IMonitor } from "@/Types/Monitor";
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import { PulseDot } from "@/Components/v2/DesignElements/PulseDot";
+import { Dot } from "@/Components/v2/DesignElements/Dot";
+import { getStatusColor, formatUrl } from "@/Utils/v2/MonitorUtils";
+import { useTheme } from "@mui/material/styles";
+import prettyMilliseconds from "pretty-ms";
+import { typographyLevels } from "@/Utils/Theme/v2/palette";
+import { useMediaQuery } from "@mui/material";
+export const MonitorStatus = ({ monitor }: { monitor: IMonitor }) => {
+ 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/Monitors/index.tsx b/client/src/Components/v2/Monitors/index.tsx
new file mode 100644
index 000000000..3b0acbd20
--- /dev/null
+++ b/client/src/Components/v2/Monitors/index.tsx
@@ -0,0 +1 @@
+export { HeaderCreate } from "./HeaderCreate";
diff --git a/client/src/Features/Auth/v2AuthSlice.ts b/client/src/Features/Auth/v2AuthSlice.ts
new file mode 100644
index 000000000..28a18b659
--- /dev/null
+++ b/client/src/Features/Auth/v2AuthSlice.ts
@@ -0,0 +1,19 @@
+import { createSlice } from "@reduxjs/toolkit";
+
+const initialState = {
+ isAuthenticated: false,
+};
+
+const v2AuthSlice = createSlice({
+ name: "v2Auth",
+ initialState,
+ reducers: {
+ setIsAuthenticated: (state, action) => {
+ const { authenticated } = action.payload;
+ state.isAuthenticated = authenticated;
+ },
+ },
+});
+
+export default v2AuthSlice.reducer;
+export const { setIsAuthenticated } = v2AuthSlice.actions;
diff --git a/client/src/Features/UI/uiSlice.js b/client/src/Features/UI/uiSlice.js
index 45ba267b0..63f7ae90e 100644
--- a/client/src/Features/UI/uiSlice.js
+++ b/client/src/Features/UI/uiSlice.js
@@ -47,6 +47,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;
},
@@ -73,6 +77,7 @@ export default uiSlice.reducer;
export const {
setRowsPerPage,
toggleSidebar,
+ setCollapsed,
setMode,
setShowURL,
setGreeting,
diff --git a/client/src/Hooks/checkHooks.js b/client/src/Hooks/v1/checkHooks.js
similarity index 97%
rename from client/src/Hooks/checkHooks.js
rename to client/src/Hooks/v1/checkHooks.js
index bdb1a73b7..96fd1fe1c 100644
--- a/client/src/Hooks/checkHooks.js
+++ b/client/src/Hooks/v1/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 = ({
diff --git a/client/src/Hooks/inviteHooks.js b/client/src/Hooks/v1/inviteHooks.js
similarity index 97%
rename from client/src/Hooks/inviteHooks.js
rename to client/src/Hooks/v1/inviteHooks.js
index fb577272a..b6a0851af 100644
--- a/client/src/Hooks/inviteHooks.js
+++ b/client/src/Hooks/v1/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/v1/logHooks.js
similarity index 96%
rename from client/src/Hooks/logHooks.js
rename to client/src/Hooks/v1/logHooks.js
index 989870eea..ff4d13872 100644
--- a/client/src/Hooks/logHooks.js
+++ b/client/src/Hooks/v1/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/v1/monitorHooks.js
similarity index 98%
rename from client/src/Hooks/monitorHooks.js
rename to client/src/Hooks/v1/monitorHooks.js
index eb1e7acdc..e6b4b5261 100644
--- a/client/src/Hooks/monitorHooks.js
+++ b/client/src/Hooks/v1/monitorHooks.js
@@ -1,8 +1,8 @@
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";
diff --git a/client/src/Hooks/settingsHooks.js b/client/src/Hooks/v1/settingsHooks.js
similarity index 95%
rename from client/src/Hooks/settingsHooks.js
rename to client/src/Hooks/v1/settingsHooks.js
index 98a21c1fa..787e46ff9 100644
--- a/client/src/Hooks/settingsHooks.js
+++ b/client/src/Hooks/v1/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 }) => {
diff --git a/client/src/Hooks/useIsAdmin.js b/client/src/Hooks/v1/useIsAdmin.js
similarity index 100%
rename from client/src/Hooks/useIsAdmin.js
rename to client/src/Hooks/v1/useIsAdmin.js
diff --git a/client/src/Hooks/useMonitorUtils.js b/client/src/Hooks/v1/useMonitorUtils.js
similarity index 100%
rename from client/src/Hooks/useMonitorUtils.js
rename to client/src/Hooks/v1/useMonitorUtils.js
diff --git a/client/src/Hooks/useNotifications.js b/client/src/Hooks/v1/useNotifications.js
similarity index 96%
rename from client/src/Hooks/useNotifications.js
rename to client/src/Hooks/v1/useNotifications.js
index 539725958..d1b4a6652 100644
--- a/client/src/Hooks/useNotifications.js
+++ b/client/src/Hooks/v1/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/v1/Notifications/utils.js";
const useCreateNotification = () => {
const navigate = useNavigate();
diff --git a/client/src/Hooks/useSendTestEmail.js b/client/src/Hooks/v1/useSendTestEmail.js
similarity index 93%
rename from client/src/Hooks/useSendTestEmail.js
rename to client/src/Hooks/v1/useSendTestEmail.js
index e280b1f99..0be77954a 100644
--- a/client/src/Hooks/useSendTestEmail.js
+++ b/client/src/Hooks/v1/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/userHooks.js b/client/src/Hooks/v1/userHooks.js
similarity index 68%
rename from client/src/Hooks/userHooks.js
rename to client/src/Hooks/v1/userHooks.js
index 8be32a082..f9c454450 100644
--- a/client/src/Hooks/userHooks.js
+++ b/client/src/Hooks/v1/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/Hooks/v2/UseApi.tsx b/client/src/Hooks/v2/UseApi.tsx
new file mode 100644
index 000000000..2ee37c0da
--- /dev/null
+++ b/client/src/Hooks/v2/UseApi.tsx
@@ -0,0 +1,90 @@
+import { useState } from "react";
+import useSWR from "swr";
+import type { SWRConfiguration } from "swr";
+import type { AxiosRequestConfig } from "axios";
+import { get, post, patch } from "@/Utils/v2/ApiClient"; // your axios wrapper
+
+export type ApiResponse = {
+ message: string;
+ data: any;
+};
+
+// Generic fetcher for GET requests
+const fetcher = async (url: string, config?: AxiosRequestConfig) => {
+ const res = await get(url, config);
+ return res.data;
+};
+
+export const useGet = (
+ url: string,
+ axiosConfig?: AxiosRequestConfig,
+ swrConfig?: SWRConfiguration
+) => {
+ const { data, error, isLoading, isValidating, mutate } = useSWR(
+ url,
+ (url) => fetcher(url, axiosConfig),
+ swrConfig
+ );
+
+ return {
+ response: data ?? null,
+ loading: isLoading,
+ isValidating,
+ error: error?.message ?? null,
+ refetch: mutate,
+ };
+};
+
+export const usePost = () => {
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ const postFn = async (
+ endpoint: string,
+ body: B,
+ config?: AxiosRequestConfig
+ ): Promise => {
+ setLoading(true);
+ setError(null);
+
+ try {
+ const res = await post(endpoint, body, config);
+ return res.data;
+ } catch (err: any) {
+ const errMsg = err?.response?.data?.msg || err.message || "An error occurred";
+ 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 patchFn = async (
+ endpoint: string,
+ body?: B,
+ config?: AxiosRequestConfig
+ ): Promise => {
+ setLoading(true);
+ setError(null);
+
+ try {
+ const res = await patch(endpoint, body, config);
+ return res.data;
+ } catch (err: any) {
+ const errMsg = err?.response?.data?.msg || err.message || "An error occurred";
+ setError(errMsg);
+ return null;
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return { patch: patchFn, loading, error };
+};
diff --git a/client/src/Hooks/v2/useInitMonitorForm.tsx b/client/src/Hooks/v2/useInitMonitorForm.tsx
new file mode 100644
index 000000000..2e98b3753
--- /dev/null
+++ b/client/src/Hooks/v2/useInitMonitorForm.tsx
@@ -0,0 +1,17 @@
+import { monitorSchema } from "@/Validation/v2/zod";
+import { z } from "zod";
+export const useInitForm = ({
+ initialData,
+}: {
+ initialData: Partial> | undefined;
+}) => {
+ const defaults: z.infer = {
+ type: initialData?.type || "http",
+ url: initialData?.url || "",
+ n: initialData?.n || 3,
+ notificationChannels: initialData?.notificationChannels || [],
+ name: initialData?.name || "",
+ interval: initialData?.interval || "1 minute",
+ };
+ return { defaults };
+};
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..cc284a675
--- /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/v1/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/v1/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 (
+ <>
+
+
+
+ }
+ sx={{ mb: theme.spacing(5) }}
+ />
+
+ }
+ sx={{ mb: theme.spacing(5) }}
+ />
+
+
+
+
+
+
+ >
+ );
+};
+
+ChangePasswordModal.propTypes = {
+ isSaving: PropTypes.bool.isRequired,
+ isLoading: PropTypes.bool.isRequired,
+ changePassword: PropTypes.func.isRequired,
+};
+
+export default ChangePasswordModal;
diff --git a/client/src/Pages/Uptime/Create/index.css b/client/src/Pages/Uptime/Create/index.css
deleted file mode 100644
index e69de29bb..000000000
diff --git a/client/src/Pages/About/index.jsx b/client/src/Pages/v1/About/index.jsx
similarity index 68%
rename from client/src/Pages/About/index.jsx
rename to client/src/Pages/v1/About/index.jsx
index 97040b2e3..ff7bb6c6b 100644
--- a/client/src/Pages/About/index.jsx
+++ b/client/src/Pages/v1/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/v1/Account/EditUser/hooks/editUser.js
similarity index 91%
rename from client/src/Pages/Account/EditUser/hooks/editUser.js
rename to client/src/Pages/v1/Account/EditUser/hooks/editUser.js
index 2ffa914a0..00660aead 100644
--- a/client/src/Pages/Account/EditUser/hooks/editUser.js
+++ b/client/src/Pages/v1/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/v1/Account/EditUser/index.jsx
similarity index 70%
rename from client/src/Pages/Account/EditUser/index.jsx
rename to client/src/Pages/v1/Account/EditUser/index.jsx
index e1eeead9d..674a522b8 100644
--- a/client/src/Pages/Account/EditUser/index.jsx
+++ b/client/src/Pages/v1/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/v1/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}
/>
-
+
-
+ {!isSameUser && (
+
+ )}
+
);
diff --git a/client/src/Pages/Account/components/AddMemberMenu/index.jsx b/client/src/Pages/v1/Account/components/AddMemberMenu/index.jsx
similarity index 100%
rename from client/src/Pages/Account/components/AddMemberMenu/index.jsx
rename to client/src/Pages/v1/Account/components/AddMemberMenu/index.jsx
diff --git a/client/src/Pages/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx b/client/src/Pages/v1/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx
similarity index 93%
rename from client/src/Pages/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx
rename to client/src/Pages/v1/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx
index e42c504b2..58db019a3 100644
--- a/client/src/Pages/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx
+++ b/client/src/Pages/v1/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/v1/Account/components/AddTeamMember/index.jsx
similarity index 92%
rename from client/src/Pages/Account/components/AddTeamMember/index.jsx
rename to client/src/Pages/v1/Account/components/AddTeamMember/index.jsx
index 2df8fb226..219658b63 100644
--- a/client/src/Pages/Account/components/AddTeamMember/index.jsx
+++ b/client/src/Pages/v1/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/v1/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/PasswordPanel.jsx b/client/src/Pages/v1/Account/components/PasswordPanel.jsx
similarity index 92%
rename from client/src/Pages/Account/components/PasswordPanel.jsx
rename to client/src/Pages/v1/Account/components/PasswordPanel.jsx
index dabd67706..ca2af9205 100644
--- a/client/src/Pages/Account/components/PasswordPanel.jsx
+++ b/client/src/Pages/v1/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 = {
diff --git a/client/src/Pages/Account/components/ProfilePanel.jsx b/client/src/Pages/v1/Account/components/ProfilePanel.jsx
similarity index 94%
rename from client/src/Pages/Account/components/ProfilePanel.jsx
rename to client/src/Pages/v1/Account/components/ProfilePanel.jsx
index 7c73e7aa3..2035861a2 100644
--- a/client/src/Pages/Account/components/ProfilePanel.jsx
+++ b/client/src/Pages/v1/Account/components/ProfilePanel.jsx
@@ -2,16 +2,20 @@ 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";
/**
diff --git a/client/src/Pages/Account/components/RoleTable/index.jsx b/client/src/Pages/v1/Account/components/RoleTable/index.jsx
similarity index 88%
rename from client/src/Pages/Account/components/RoleTable/index.jsx
rename to client/src/Pages/v1/Account/components/RoleTable/index.jsx
index 9d6ba3a16..02dcaaf0d 100644
--- a/client/src/Pages/Account/components/RoleTable/index.jsx
+++ b/client/src/Pages/v1/Account/components/RoleTable/index.jsx
@@ -1,9 +1,9 @@
import Typography from "@mui/material/Typography";
-import DataTable from "../../../../Components/Table";
+import DataTable from "@/Components/v1/Table/index.jsx";
import DeleteOutlineRoundedIcon from "@mui/icons-material/DeleteOutlineRounded";
import { useTranslation } from "react-i18next";
-import { ROLES } from "../../../../Utils/roleUtils";
+import { ROLES } from "../../../../../Utils/roleUtils.js";
const RoleTable = ({ roles, handleDeleteRole }) => {
const { t } = useTranslation();
diff --git a/client/src/Pages/Account/components/TeamPanel.jsx b/client/src/Pages/v1/Account/components/TeamPanel.jsx
similarity index 92%
rename from client/src/Pages/Account/components/TeamPanel.jsx
rename to client/src/Pages/v1/Account/components/TeamPanel.jsx
index fc1768642..02918eb30 100644
--- a/client/src/Pages/Account/components/TeamPanel.jsx
+++ b/client/src/Pages/v1/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/v1/inviteHooks.js";
import { useNavigate } from "react-router-dom";
-import { useIsSuperAdmin } from "../../../Hooks/useIsAdmin";
-import AddMemberMenu from "./AddMemberMenu";
+import { useIsSuperAdmin } from "../../../../Hooks/v1/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,
diff --git a/client/src/Pages/Account/index.jsx b/client/src/Pages/v1/Account/index.jsx
similarity index 91%
rename from client/src/Pages/Account/index.jsx
rename to client/src/Pages/v1/Account/index.jsx
index 37c64aca6..ae16ff9d7 100644
--- a/client/src/Pages/Account/index.jsx
+++ b/client/src/Pages/v1/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/v1/Auth/CheckEmail.jsx
similarity index 93%
rename from client/src/Pages/Auth/CheckEmail.jsx
rename to client/src/Pages/v1/Auth/CheckEmail.jsx
index e1f5e43ed..c12a84804 100644
--- a/client/src/Pages/Auth/CheckEmail.jsx
+++ b/client/src/Pages/v1/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 EmailIcon from "../../../assets/icons/email.svg?react";
+import Logo from "../../../assets/icons/checkmate-icon.svg?react";
+import IconBox from "@/Components/v1/IconBox/index.jsx";
import "./index.css";
const CheckEmail = () => {
diff --git a/client/src/Pages/Auth/ForgotPassword.jsx b/client/src/Pages/v1/Auth/ForgotPassword.jsx
similarity index 91%
rename from client/src/Pages/Auth/ForgotPassword.jsx
rename to client/src/Pages/v1/Auth/ForgotPassword.jsx
index 2a887756e..8cbd85d8a 100644
--- a/client/src/Pages/Auth/ForgotPassword.jsx
+++ b/client/src/Pages/v1/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 Key from "../../../assets/icons/key.svg?react";
+import Background from "../../../assets/Images/background-grid.svg?react";
+import IconBox from "@/Components/v1/IconBox/index.jsx";
import { Trans, useTranslation } from "react-i18next";
import "./index.css";
diff --git a/client/src/Pages/Auth/Login/hooks/useLoadingSubmit.jsx b/client/src/Pages/v1/Auth/Login/hooks/useLoadingSubmit.jsx
similarity index 100%
rename from client/src/Pages/Auth/Login/hooks/useLoadingSubmit.jsx
rename to client/src/Pages/v1/Auth/Login/hooks/useLoadingSubmit.jsx
diff --git a/client/src/Pages/Auth/Login/hooks/useLoginForm.jsx b/client/src/Pages/v1/Auth/Login/hooks/useLoginForm.jsx
similarity index 100%
rename from client/src/Pages/Auth/Login/hooks/useLoginForm.jsx
rename to client/src/Pages/v1/Auth/Login/hooks/useLoginForm.jsx
diff --git a/client/src/Pages/Auth/Login/hooks/useLoginSubmit.jsx b/client/src/Pages/v1/Auth/Login/hooks/useLoginSubmit.jsx
similarity index 87%
rename from client/src/Pages/Auth/Login/hooks/useLoginSubmit.jsx
rename to client/src/Pages/v1/Auth/Login/hooks/useLoginSubmit.jsx
index 6bc5716ff..0eed9bd07 100644
--- a/client/src/Pages/Auth/Login/hooks/useLoginSubmit.jsx
+++ b/client/src/Pages/v1/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/v1/Auth/Login/hooks/useValidateLoginForm.jsx
similarity index 90%
rename from client/src/Pages/Auth/Login/hooks/useValidateLoginForm.jsx
rename to client/src/Pages/v1/Auth/Login/hooks/useValidateLoginForm.jsx
index e5d045be8..ca9bef879 100644
--- a/client/src/Pages/Auth/Login/hooks/useValidateLoginForm.jsx
+++ b/client/src/Pages/v1/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/v1/Auth/Login/index.jsx
similarity index 82%
rename from client/src/Pages/Auth/Login/index.jsx
rename to client/src/Pages/v1/Auth/Login/index.jsx
index d06ecef6d..7f5dcb5e4 100644
--- a/client/src/Pages/Auth/Login/index.jsx
+++ b/client/src/Pages/v1/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/v1/Auth/NewPasswordConfirmed.jsx
similarity index 90%
rename from client/src/Pages/Auth/NewPasswordConfirmed.jsx
rename to client/src/Pages/v1/Auth/NewPasswordConfirmed.jsx
index 7ace77abc..629494098 100644
--- a/client/src/Pages/Auth/NewPasswordConfirmed.jsx
+++ b/client/src/Pages/v1/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 ConfirmIcon from "../../../assets/icons/check-outlined.svg?react";
+import Logo from "../../../assets/icons/checkmate-icon.svg?react";
+import IconBox from "@/Components/v1/IconBox/index.jsx";
import { Trans, useTranslation } from "react-i18next";
import "./index.css";
diff --git a/client/src/Pages/Auth/Register/index.jsx b/client/src/Pages/v1/Auth/Register/index.jsx
similarity index 94%
rename from client/src/Pages/Auth/Register/index.jsx
rename to client/src/Pages/v1/Auth/Register/index.jsx
index 2b45d2d15..d6515b85c 100644
--- a/client/src/Pages/Auth/Register/index.jsx
+++ b/client/src/Pages/v1/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) => {
diff --git a/client/src/Pages/Auth/SetNewPassword.jsx b/client/src/Pages/v1/Auth/SetNewPassword.jsx
similarity index 91%
rename from client/src/Pages/Auth/SetNewPassword.jsx
rename to client/src/Pages/v1/Auth/SetNewPassword.jsx
index b4899f47c..45f3351f3 100644
--- a/client/src/Pages/Auth/SetNewPassword.jsx
+++ b/client/src/Pages/v1/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 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 "./index.css";
-import { useValidatePassword } from "./hooks/useValidatePassword";
+import { useValidatePassword } from "./hooks/useValidatePassword.jsx";
import { Trans, useTranslation } from "react-i18next";
const SetNewPassword = () => {
diff --git a/client/src/Pages/Auth/components/AuthHeader.jsx b/client/src/Pages/v1/Auth/components/AuthHeader.jsx
similarity index 82%
rename from client/src/Pages/Auth/components/AuthHeader.jsx
rename to client/src/Pages/v1/Auth/components/AuthHeader.jsx
index ce454c065..a34e83d6a 100644
--- a/client/src/Pages/Auth/components/AuthHeader.jsx
+++ b/client/src/Pages/v1/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/v1/Auth/components/AuthPageWrapper.jsx
similarity index 92%
rename from client/src/Pages/Auth/components/AuthPageWrapper.jsx
rename to client/src/Pages/v1/Auth/components/AuthPageWrapper.jsx
index b83be19c5..7e358e6ab 100644
--- a/client/src/Pages/Auth/components/AuthPageWrapper.jsx
+++ b/client/src/Pages/v1/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/v1/Auth/components/PasswordTooltip.jsx
similarity index 98%
rename from client/src/Pages/Auth/components/PasswordTooltip.jsx
rename to client/src/Pages/v1/Auth/components/PasswordTooltip.jsx
index bc2f28ec7..2881eb001 100644
--- a/client/src/Pages/Auth/components/PasswordTooltip.jsx
+++ b/client/src/Pages/v1/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";
diff --git a/client/src/Pages/Auth/hooks/usePasswordFeedback.jsx b/client/src/Pages/v1/Auth/hooks/usePasswordFeedback.jsx
similarity index 96%
rename from client/src/Pages/Auth/hooks/usePasswordFeedback.jsx
rename to client/src/Pages/v1/Auth/hooks/usePasswordFeedback.jsx
index 4159e6166..3c199e25f 100644
--- a/client/src/Pages/Auth/hooks/usePasswordFeedback.jsx
+++ b/client/src/Pages/v1/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/v1/Auth/hooks/useValidatePassword.jsx
similarity index 96%
rename from client/src/Pages/Auth/hooks/useValidatePassword.jsx
rename to client/src/Pages/v1/Auth/hooks/useValidatePassword.jsx
index 102d8d73e..231b8eeb8 100644
--- a/client/src/Pages/Auth/hooks/useValidatePassword.jsx
+++ b/client/src/Pages/v1/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/Auth/index.css b/client/src/Pages/v1/Auth/index.css
similarity index 100%
rename from client/src/Pages/Auth/index.css
rename to client/src/Pages/v1/Auth/index.css
diff --git a/client/src/Pages/Incidents/Components/IncidentTable/index.jsx b/client/src/Pages/v1/Incidents/Components/IncidentTable/index.jsx
similarity index 89%
rename from client/src/Pages/Incidents/Components/IncidentTable/index.jsx
rename to client/src/Pages/v1/Incidents/Components/IncidentTable/index.jsx
index df9174290..df457c0e2 100644
--- a/client/src/Pages/Incidents/Components/IncidentTable/index.jsx
+++ b/client/src/Pages/v1/Incidents/Components/IncidentTable/index.jsx
@@ -1,26 +1,26 @@
//Components
import Stack from "@mui/material/Stack";
-import DataTable from "../../../../Components/Table";
+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/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 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/timeUtils";
+import { formatDateWithTz } from "../../../../../Utils/timeUtils.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 { useFetchChecksTeam } from "../../../../../Hooks/v1/checkHooks.js";
+import { useFetchChecksByMonitor } from "../../../../../Hooks/v1/checkHooks.js";
+import { useResolveIncident } from "../../../../../Hooks/v1/checkHooks.js";
import { Button, Typography, useTheme } from "@mui/material";
import { lighten } from "@mui/material/styles";
diff --git a/client/src/Pages/Incidents/Components/OptionsHeader/index.jsx b/client/src/Pages/v1/Incidents/Components/OptionsHeader/index.jsx
similarity index 97%
rename from client/src/Pages/Incidents/Components/OptionsHeader/index.jsx
rename to client/src/Pages/v1/Incidents/Components/OptionsHeader/index.jsx
index 259880223..c16f54f18 100644
--- a/client/src/Pages/Incidents/Components/OptionsHeader/index.jsx
+++ b/client/src/Pages/v1/Incidents/Components/OptionsHeader/index.jsx
@@ -1,11 +1,11 @@
// 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 SkeletonLayout from "./skeleton.jsx";
import { useTranslation } from "react-i18next";
const OptionsHeader = ({
diff --git a/client/src/Pages/Incidents/Components/OptionsHeader/skeleton.jsx b/client/src/Pages/v1/Incidents/Components/OptionsHeader/skeleton.jsx
similarity index 100%
rename from client/src/Pages/Incidents/Components/OptionsHeader/skeleton.jsx
rename to client/src/Pages/v1/Incidents/Components/OptionsHeader/skeleton.jsx
diff --git a/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx b/client/src/Pages/v1/Incidents/Components/StatusBoxes/StatusBox.jsx
similarity index 87%
rename from client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx
rename to client/src/Pages/v1/Incidents/Components/StatusBoxes/StatusBox.jsx
index 7b20e3d51..cd7078e6c 100644
--- a/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx
+++ b/client/src/Pages/v1/Incidents/Components/StatusBoxes/StatusBox.jsx
@@ -1,11 +1,11 @@
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 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";
const StatusBox = ({ title, value, status }) => {
const theme = useTheme();
diff --git a/client/src/Pages/Incidents/Components/StatusBoxes/index.jsx b/client/src/Pages/v1/Incidents/Components/StatusBoxes/index.jsx
similarity index 87%
rename from client/src/Pages/Incidents/Components/StatusBoxes/index.jsx
rename to client/src/Pages/v1/Incidents/Components/StatusBoxes/index.jsx
index 6ba2f1820..bb993111a 100644
--- a/client/src/Pages/Incidents/Components/StatusBoxes/index.jsx
+++ b/client/src/Pages/v1/Incidents/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 }) => {
>
{
diff --git a/client/src/Pages/Infrastructure/Create/Components/MonitorStatusHeader.jsx b/client/src/Pages/v1/Infrastructure/Create/Components/MonitorStatusHeader.jsx
similarity index 92%
rename from client/src/Pages/Infrastructure/Create/Components/MonitorStatusHeader.jsx
rename to client/src/Pages/v1/Infrastructure/Create/Components/MonitorStatusHeader.jsx
index 2ed48bf64..570f38a7e 100644
--- a/client/src/Pages/Infrastructure/Create/Components/MonitorStatusHeader.jsx
+++ b/client/src/Pages/v1/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/v1/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/v1/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx
similarity index 100%
rename from client/src/Pages/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx
rename to client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx
diff --git a/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx b/client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx
similarity index 96%
rename from client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx
rename to client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx
index 9b3ee0d04..8a97486c7 100644
--- a/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx
+++ b/client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx
@@ -1,4 +1,7 @@
-import { useCreateMonitor, useUpdateMonitor } from "../../../../Hooks/monitorHooks";
+import {
+ useCreateMonitor,
+ useUpdateMonitor,
+} from "../../../../../Hooks/v1/monitorHooks.js";
const useInfrastructureSubmit = () => {
const [createMonitor, isCreating] = useCreateMonitor();
const [updateMonitor, isUpdating] = useUpdateMonitor();
diff --git a/client/src/Pages/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx b/client/src/Pages/v1/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx
similarity index 85%
rename from client/src/Pages/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx
rename to client/src/Pages/v1/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx
index 080f441c4..73097d0d8 100644
--- a/client/src/Pages/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx
+++ b/client/src/Pages/v1/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/v1/Infrastructure/Create/index.jsx
similarity index 92%
rename from client/src/Pages/Infrastructure/Create/index.jsx
rename to client/src/Pages/v1/Infrastructure/Create/index.jsx
index a9d16c926..d4fd859ec 100644
--- a/client/src/Pages/Infrastructure/Create/index.jsx
+++ b/client/src/Pages/v1/Infrastructure/Create/index.jsx
@@ -1,18 +1,18 @@
//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";
// Utils
-import NotificationsConfig from "../../../Components/NotificationConfig";
-import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications";
+import NotificationsConfig from "@/Components/v1/NotificationConfig/index.jsx";
+import { useGetNotificationsByTeamId } from "../../../../Hooks/v1/useNotifications.js";
import { useParams } from "react-router-dom";
import { useState, useEffect } from "react";
import { useTheme } from "@emotion/react";
@@ -22,10 +22,10 @@ import {
useFetchGlobalSettings,
useFetchHardwareMonitorById,
usePauseMonitor,
-} from "../../../Hooks/monitorHooks";
-import useInfrastructureMonitorForm from "./hooks/useInfrastructureMonitorForm";
-import useValidateInfrastructureForm from "./hooks/useValidateInfrastructureForm";
-import useInfrastructureSubmit from "./hooks/useInfrastructureSubmit";
+} from "../../../../Hooks/v1/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();
diff --git a/client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx
similarity index 80%
rename from client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx
rename to client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx
index 9544bcf2a..ee100bb0e 100644
--- a/client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx
+++ b/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx
@@ -1,10 +1,10 @@
// Components
import { Typography } from "@mui/material";
-import BaseContainer from "../BaseContainer";
-import AreaChart from "../../../../../Components/Charts/AreaChart";
+import BaseContainer from "../BaseContainer/index.jsx";
+import AreaChart from "@/Components/v1/Charts/AreaChart/index.jsx";
// Utils
import { useTheme } from "@emotion/react";
-import { useHardwareUtils } from "../../Hooks/useHardwareUtils";
+import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx";
const InfraAreaChart = ({ config }) => {
const theme = useTheme();
const { getDimensions } = useHardwareUtils();
diff --git a/client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/index.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/index.jsx
similarity index 93%
rename from client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/index.jsx
rename to client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/index.jsx
index 0eb3aa36d..3465683d9 100644
--- a/client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/index.jsx
+++ b/client/src/Pages/v1/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/AreaChartBoxes/skeleton.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/skeleton.jsx
similarity index 100%
rename from client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/skeleton.jsx
rename to client/src/Pages/v1/Infrastructure/Details/Components/AreaChartBoxes/skeleton.jsx
diff --git a/client/src/Pages/Infrastructure/Details/Components/BaseContainer/index.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/BaseContainer/index.jsx
similarity index 94%
rename from client/src/Pages/Infrastructure/Details/Components/BaseContainer/index.jsx
rename to client/src/Pages/v1/Infrastructure/Details/Components/BaseContainer/index.jsx
index 246769751..dd73d1a65 100644
--- a/client/src/Pages/Infrastructure/Details/Components/BaseContainer/index.jsx
+++ b/client/src/Pages/v1/Infrastructure/Details/Components/BaseContainer/index.jsx
@@ -11,7 +11,7 @@ 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 = {} }) => {
diff --git a/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx
similarity index 93%
rename from client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx
rename to client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx
index 70d36bd00..cd61b7109 100644
--- a/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx
+++ b/client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx
@@ -1,6 +1,6 @@
// 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";
diff --git a/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/index.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/index.jsx
similarity index 93%
rename from client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/index.jsx
rename to client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/index.jsx
index 3c1c68f8b..432881e7e 100644
--- a/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/index.jsx
+++ b/client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/index.jsx
@@ -1,11 +1,11 @@
// Components
import { Stack } from "@mui/material";
-import Gauge from "./Gauge";
-import SkeletonLayout from "./skeleton";
+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";
diff --git a/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx
similarity index 100%
rename from client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx
rename to client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx
diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx
similarity index 93%
rename from client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx
rename to client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx
index 9157a3432..d9f93311c 100644
--- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx
+++ b/client/src/Pages/v1/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/v1/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx
similarity index 91%
rename from client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx
rename to client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx
index 98ba90dae..0747dfcd1 100644
--- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx
+++ b/client/src/Pages/v1/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/v1/Infrastructure/Details/Components/NetworkStats/index.jsx
similarity index 91%
rename from client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx
rename to client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/index.jsx
index 41d2b0a87..78db333e6 100644
--- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx
+++ b/client/src/Pages/v1/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/NetworkStats/skeleton.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/skeleton.jsx
similarity index 100%
rename from client/src/Pages/Infrastructure/Details/Components/NetworkStats/skeleton.jsx
rename to client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/skeleton.jsx
diff --git a/client/src/Pages/Infrastructure/Details/Components/StatusBoxes/index.jsx b/client/src/Pages/v1/Infrastructure/Details/Components/StatusBoxes/index.jsx
similarity index 90%
rename from client/src/Pages/Infrastructure/Details/Components/StatusBoxes/index.jsx
rename to client/src/Pages/v1/Infrastructure/Details/Components/StatusBoxes/index.jsx
index 6c11e7c82..7f09e82d5 100644
--- a/client/src/Pages/Infrastructure/Details/Components/StatusBoxes/index.jsx
+++ b/client/src/Pages/v1/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/v1/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/v1/Infrastructure/Details/Hooks/useHardwareUtils.jsx
similarity index 100%
rename from client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx
rename to client/src/Pages/v1/Infrastructure/Details/Hooks/useHardwareUtils.jsx
diff --git a/client/src/Pages/Infrastructure/Details/index.jsx b/client/src/Pages/v1/Infrastructure/Details/index.jsx
similarity index 79%
rename from client/src/Pages/Infrastructure/Details/index.jsx
rename to client/src/Pages/v1/Infrastructure/Details/index.jsx
index a16f3906b..f9c51c27d 100644
--- a/client/src/Pages/Infrastructure/Details/index.jsx
+++ b/client/src/Pages/v1/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/v1/useIsAdmin.js";
+import { useFetchHardwareMonitorById } from "../../../../Hooks/v1/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/v1/Infrastructure/Monitors/Components/Filters/index.jsx
similarity index 97%
rename from client/src/Pages/Infrastructure/Monitors/Components/Filters/index.jsx
rename to client/src/Pages/v1/Infrastructure/Monitors/Components/Filters/index.jsx
index 3c2511c2f..70bf502af 100644
--- a/client/src/Pages/Infrastructure/Monitors/Components/Filters/index.jsx
+++ b/client/src/Pages/v1/Infrastructure/Monitors/Components/Filters/index.jsx
@@ -1,6 +1,6 @@
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";
diff --git a/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx b/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx
similarity index 87%
rename from client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx
rename to client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx
index 7ee603ef7..cc2cd5f98 100644
--- a/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx
+++ b/client/src/Pages/v1/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 CPUChipIcon from "../../../../../../assets/icons/cpu-chip.svg?react";
+import CustomGauge from "@/Components/v1/Charts/CustomGauge/index.jsx";
// Utils
import { useTheme } from "@emotion/react";
-import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils";
+import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js";
import { useNavigate } from "react-router-dom";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
diff --git a/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx b/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx
similarity index 93%
rename from client/src/Pages/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx
rename to client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx
index 147f1c297..d3af34d00 100644
--- a/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx
+++ b/client/src/Pages/v1/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 Settings from "../../../../../../assets/icons/settings-bold.svg?react";
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/v1/monitorHooks.js";
import { useTranslation } from "react-i18next";
/**
diff --git a/client/src/Pages/Infrastructure/Monitors/index.jsx b/client/src/Pages/v1/Infrastructure/Monitors/index.jsx
similarity index 81%
rename from client/src/Pages/Infrastructure/Monitors/index.jsx
rename to client/src/Pages/v1/Infrastructure/Monitors/index.jsx
index bfd2270fe..6c3a657ab 100644
--- a/client/src/Pages/Infrastructure/Monitors/index.jsx
+++ b/client/src/Pages/v1/Infrastructure/Monitors/index.jsx
@@ -1,21 +1,21 @@
// Components
import { Stack } from "@mui/material";
-import Breadcrumbs from "../../../Components/Breadcrumbs";
-import MonitorCountHeader from "../../../Components/MonitorCountHeader";
-import MonitorCreateHeader from "../../../Components/MonitorCreateHeader";
-import MonitorsTable from "./Components/MonitorsTable";
-import Pagination from "../../..//Components/Table/TablePagination";
-import PageStateWrapper from "../../../Components/PageStateWrapper";
-import Filter from "./Components/Filters";
-import SearchComponent from "../../Uptime/Monitors/Components/SearchComponent";
+import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
+import MonitorCountHeader from "@/Components/v1/MonitorCountHeader/index.jsx";
+import MonitorCreateHeader from "@/Components/v1/MonitorCreateHeader/index.jsx";
+import MonitorsTable from "./Components/MonitorsTable/index.jsx";
+import Pagination from "@/Components/v1/Table/TablePagination/index.jsx";
+import PageStateWrapper from "@/Components/v1/PageStateWrapper/index.jsx";
+import Filter from "./Components/Filters/index.jsx";
+import SearchComponent from "../../Uptime/Monitors/Components/SearchComponent/index.jsx";
// Utils
import { useTheme } from "@emotion/react";
import { useEffect, useState } from "react";
-import { useIsAdmin } from "../../../Hooks/useIsAdmin";
+import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js";
import { useTranslation } from "react-i18next";
-import { useFetchMonitorsByTeamId } from "../../../Hooks/monitorHooks";
+import { useFetchMonitorsByTeamId } from "../../../../Hooks/v1/monitorHooks.js";
import { useDispatch, useSelector } from "react-redux";
-import { setRowsPerPage } from "../../../Features/UI/uiSlice";
+import { setRowsPerPage } from "../../../../Features/UI/uiSlice.js";
// Constants
const TYPES = ["hardware"];
const BREADCRUMBS = [{ name: `infrastructure`, path: "/infrastructure" }];
diff --git a/client/src/Pages/Integrations/index.css b/client/src/Pages/v1/Integrations/index.css
similarity index 100%
rename from client/src/Pages/Integrations/index.css
rename to client/src/Pages/v1/Integrations/index.css
diff --git a/client/src/Pages/Integrations/index.jsx b/client/src/Pages/v1/Integrations/index.jsx
similarity index 94%
rename from client/src/Pages/Integrations/index.jsx
rename to client/src/Pages/v1/Integrations/index.jsx
index fe0faa5cf..dde6b52ee 100644
--- a/client/src/Pages/Integrations/index.jsx
+++ b/client/src/Pages/v1/Integrations/index.jsx
@@ -1,9 +1,9 @@
import PropTypes from "prop-types";
import { Stack, Typography, Grid, Button } from "@mui/material";
import { useTheme } from "@emotion/react";
-import Discord from "../../assets/icons/discord-icon.svg?react";
-import Slack from "../../assets/icons/slack-icon.svg?react";
-import Zapier from "../../assets/icons/zapier-icon.svg?react";
+import Discord from "../../../assets/icons/discord-icon.svg?react";
+import Slack from "../../../assets/icons/slack-icon.svg?react";
+import Zapier from "../../../assets/icons/zapier-icon.svg?react";
import { useTranslation } from "react-i18next";
import "./index.css";
diff --git a/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx b/client/src/Pages/v1/Logs/Diagnostics/components/gauges/index.jsx
similarity index 96%
rename from client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx
rename to client/src/Pages/v1/Logs/Diagnostics/components/gauges/index.jsx
index 5bb227b7c..b38837cf2 100644
--- a/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx
+++ b/client/src/Pages/v1/Logs/Diagnostics/components/gauges/index.jsx
@@ -1,11 +1,11 @@
import Stack from "@mui/material/Stack";
-import CustomGauge from "../../../../../Components/Charts/CustomGauge";
+import CustomGauge from "@/Components/v1/Charts/CustomGauge/index.jsx";
import Typography from "@mui/material/Typography";
// Utils
import { useTheme } from "@emotion/react";
import PropTypes from "prop-types";
-import { getPercentage, formatBytes } from "../../utils/utils";
+import { getPercentage, formatBytes } from "../../utils/utils.js";
import { useTranslation } from "react-i18next";
import { Box } from "@mui/material";
diff --git a/client/src/Pages/Logs/Diagnostics/components/stats/index.jsx b/client/src/Pages/v1/Logs/Diagnostics/components/stats/index.jsx
similarity index 94%
rename from client/src/Pages/Logs/Diagnostics/components/stats/index.jsx
rename to client/src/Pages/v1/Logs/Diagnostics/components/stats/index.jsx
index 103d1c698..b99f34506 100644
--- a/client/src/Pages/Logs/Diagnostics/components/stats/index.jsx
+++ b/client/src/Pages/v1/Logs/Diagnostics/components/stats/index.jsx
@@ -7,8 +7,8 @@ import CircularProgress from "@mui/material/CircularProgress";
import { useTheme } from "@emotion/react";
import PropTypes from "prop-types";
-import { getHumanReadableDuration } from "../../../../../Utils/timeUtils";
-import { formatBytes } from "../../utils/utils";
+import { getHumanReadableDuration } from "../../../../../../Utils/timeUtils.js";
+import { formatBytes } from "../../utils/utils.js";
import { useTranslation } from "react-i18next";
const StatsCard = ({ title, value, unit = "", isLoading }) => {
diff --git a/client/src/Pages/Logs/Diagnostics/index.jsx b/client/src/Pages/v1/Logs/Diagnostics/index.jsx
similarity index 83%
rename from client/src/Pages/Logs/Diagnostics/index.jsx
rename to client/src/Pages/v1/Logs/Diagnostics/index.jsx
index 72ee47f48..54feee516 100644
--- a/client/src/Pages/Logs/Diagnostics/index.jsx
+++ b/client/src/Pages/v1/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/v1/logHooks.js";
+import { getHumanReadableDuration } from "../../../../Utils/timeUtils.js";
+import { formatBytes, getPercentage } from "./utils/utils.js";
const Diagnostics = () => {
// Local state
diff --git a/client/src/Pages/Logs/Diagnostics/utils/utils.js b/client/src/Pages/v1/Logs/Diagnostics/utils/utils.js
similarity index 100%
rename from client/src/Pages/Logs/Diagnostics/utils/utils.js
rename to client/src/Pages/v1/Logs/Diagnostics/utils/utils.js
diff --git a/client/src/Pages/Logs/Logs/index.jsx b/client/src/Pages/v1/Logs/Logs/index.jsx
similarity index 95%
rename from client/src/Pages/Logs/Logs/index.jsx
rename to client/src/Pages/v1/Logs/Logs/index.jsx
index b44b3fd1a..64c4cb97a 100644
--- a/client/src/Pages/Logs/Logs/index.jsx
+++ b/client/src/Pages/v1/Logs/Logs/index.jsx
@@ -1,10 +1,10 @@
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 { useFetchLogs } from "../../../Hooks/logHooks";
+import { useFetchLogs } from "../../../../Hooks/v1/logHooks.js";
import { useTheme } from "@emotion/react";
import { useState } from "react";
import { useTranslation } from "react-i18next";
diff --git a/client/src/Pages/Logs/Queue/components/FailedJobTable/index.jsx b/client/src/Pages/v1/Logs/Queue/components/FailedJobTable/index.jsx
similarity index 93%
rename from client/src/Pages/Logs/Queue/components/FailedJobTable/index.jsx
rename to client/src/Pages/v1/Logs/Queue/components/FailedJobTable/index.jsx
index 94fc2f5c3..743f52639 100644
--- a/client/src/Pages/Logs/Queue/components/FailedJobTable/index.jsx
+++ b/client/src/Pages/v1/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/monitorUtils.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/v1/Logs/Queue/components/JobTable/index.jsx
similarity index 93%
rename from client/src/Pages/Logs/Queue/components/JobTable/index.jsx
rename to client/src/Pages/v1/Logs/Queue/components/JobTable/index.jsx
index fab85ec5d..7d309e845 100644
--- a/client/src/Pages/Logs/Queue/components/JobTable/index.jsx
+++ b/client/src/Pages/v1/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/monitorUtils.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/v1/Logs/Queue/components/Metrics/index.jsx
similarity index 86%
rename from client/src/Pages/Logs/Queue/components/Metrics/index.jsx
rename to client/src/Pages/v1/Logs/Queue/components/Metrics/index.jsx
index b0112fa99..c4f4389c3 100644
--- a/client/src/Pages/Logs/Queue/components/Metrics/index.jsx
+++ b/client/src/Pages/v1/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/v1/Logs/Queue/components/MetricsTable/index.jsx
similarity index 95%
rename from client/src/Pages/Logs/Queue/components/MetricsTable/index.jsx
rename to client/src/Pages/v1/Logs/Queue/components/MetricsTable/index.jsx
index 204b90035..d96f37a20 100644
--- a/client/src/Pages/Logs/Queue/components/MetricsTable/index.jsx
+++ b/client/src/Pages/v1/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/v1/Logs/Queue/index.jsx
similarity index 86%
rename from client/src/Pages/Logs/Queue/index.jsx
rename to client/src/Pages/v1/Logs/Queue/index.jsx
index fde9073fc..10eca4d3b 100644
--- a/client/src/Pages/Logs/Queue/index.jsx
+++ b/client/src/Pages/v1/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/v1/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/v1/Logs/index.jsx
similarity index 84%
rename from client/src/Pages/Logs/index.jsx
rename to client/src/Pages/v1/Logs/index.jsx
index 79c89f282..e5bdf623d 100644
--- a/client/src/Pages/Logs/index.jsx
+++ b/client/src/Pages/v1/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/v1/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx
similarity index 93%
rename from client/src/Pages/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx
rename to client/src/Pages/v1/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx
index 1627ef3d5..8d4c0ae53 100644
--- a/client/src/Pages/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx
+++ b/client/src/Pages/v1/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/v1/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx
similarity index 100%
rename from client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx
rename to client/src/Pages/v1/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx
diff --git a/client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx b/client/src/Pages/v1/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx
similarity index 94%
rename from client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx
rename to client/src/Pages/v1/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx
index e792734ff..14920d81c 100644
--- a/client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx
+++ b/client/src/Pages/v1/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/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx
similarity index 93%
rename from client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx
rename to client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx
index 84333a14f..a75cbe206 100644
--- a/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx
+++ b/client/src/Pages/v1/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/timeUtils.js";
const useMaintenanceActions = () => {
const MS_LOOKUP = {
diff --git a/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx b/client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx
similarity index 95%
rename from client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx
rename to client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx
index 8ba9b5e80..d21906e41 100644
--- a/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx
+++ b/client/src/Pages/v1/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/timeUtils.js";
const useMaintenanceData = () => {
const REVERSE_REPEAT_LOOKUP = {
0: "none",
diff --git a/client/src/Pages/Maintenance/CreateMaintenance/index.css b/client/src/Pages/v1/Maintenance/CreateMaintenance/index.css
similarity index 100%
rename from client/src/Pages/Maintenance/CreateMaintenance/index.css
rename to client/src/Pages/v1/Maintenance/CreateMaintenance/index.css
diff --git a/client/src/Pages/Maintenance/CreateMaintenance/index.jsx b/client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx
similarity index 93%
rename from client/src/Pages/Maintenance/CreateMaintenance/index.jsx
rename to client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx
index 9dbf9d4aa..d0e7b03cd 100644
--- a/client/src/Pages/Maintenance/CreateMaintenance/index.jsx
+++ b/client/src/Pages/v1/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 CalendarIcon from "../../../../assets/icons/calendar.svg?react";
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";
diff --git a/client/src/Pages/Maintenance/MaintenanceTable/ActionsMenu/index.jsx b/client/src/Pages/v1/Maintenance/MaintenanceTable/ActionsMenu/index.jsx
similarity index 92%
rename from client/src/Pages/Maintenance/MaintenanceTable/ActionsMenu/index.jsx
rename to client/src/Pages/v1/Maintenance/MaintenanceTable/ActionsMenu/index.jsx
index aa1c48433..b2654da49 100644
--- a/client/src/Pages/Maintenance/MaintenanceTable/ActionsMenu/index.jsx
+++ b/client/src/Pages/v1/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 Settings from "../../../../../assets/icons/settings-bold.svg?react";
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;
diff --git a/client/src/Pages/Maintenance/MaintenanceTable/index.jsx b/client/src/Pages/v1/Maintenance/MaintenanceTable/index.jsx
similarity index 93%
rename from client/src/Pages/Maintenance/MaintenanceTable/index.jsx
rename to client/src/Pages/v1/Maintenance/MaintenanceTable/index.jsx
index bb5842e9f..f1d44f944 100644
--- a/client/src/Pages/Maintenance/MaintenanceTable/index.jsx
+++ b/client/src/Pages/v1/Maintenance/MaintenanceTable/index.jsx
@@ -1,15 +1,15 @@
import PropTypes from "prop-types";
import { Box } from "@mui/material";
-import DataTable from "../../../Components/Table";
-import Pagination from "../../../Components/Table/TablePagination";
+import DataTable from "@/Components/v1/Table/index.jsx";
+import Pagination from "@/Components/v1/Table/TablePagination/index.jsx";
import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded";
import ArrowUpwardRoundedIcon from "@mui/icons-material/ArrowUpwardRounded";
-import ActionsMenu from "./ActionsMenu";
+import ActionsMenu from "./ActionsMenu/index.jsx";
import { memo } from "react";
import { useDispatch, useSelector } from "react-redux";
-import { formatDurationRounded } from "../../../Utils/timeUtils";
-import { StatusLabel } from "../../../Components/Label";
-import { setRowsPerPage } from "../../../Features/UI/uiSlice";
+import { formatDurationRounded } from "../../../../Utils/timeUtils.js";
+import { StatusLabel } from "@/Components/v1/Label/index.jsx";
+import { setRowsPerPage } from "../../../../Features/UI/uiSlice.js";
import { useTranslation } from "react-i18next";
import { useTheme } from "@emotion/react";
import { useNavigate } from "react-router-dom";
diff --git a/client/src/Components/ProgressStepper/index.css b/client/src/Pages/v1/Maintenance/index.css
similarity index 100%
rename from client/src/Components/ProgressStepper/index.css
rename to client/src/Pages/v1/Maintenance/index.css
diff --git a/client/src/Pages/Maintenance/index.jsx b/client/src/Pages/v1/Maintenance/index.jsx
similarity index 91%
rename from client/src/Pages/Maintenance/index.jsx
rename to client/src/Pages/v1/Maintenance/index.jsx
index ee142f524..cba0e9f18 100644
--- a/client/src/Pages/Maintenance/index.jsx
+++ b/client/src/Pages/v1/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/v1/NotFound/index.jsx
similarity index 96%
rename from client/src/Pages/NotFound/index.jsx
rename to client/src/Pages/v1/NotFound/index.jsx
index fdae92cb5..48825c9f6 100644
--- a/client/src/Pages/NotFound/index.jsx
+++ b/client/src/Pages/v1/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/v1/Notifications/components/ActionMenu.jsx
similarity index 100%
rename from client/src/Pages/Notifications/components/ActionMenu.jsx
rename to client/src/Pages/v1/Notifications/components/ActionMenu.jsx
diff --git a/client/src/Pages/Notifications/create/index.jsx b/client/src/Pages/v1/Notifications/create/index.jsx
similarity index 76%
rename from client/src/Pages/Notifications/create/index.jsx
rename to client/src/Pages/v1/Notifications/create/index.jsx
index 6d8afe665..b1c50493c 100644
--- a/client/src/Pages/Notifications/create/index.jsx
+++ b/client/src/Pages/v1/Notifications/create/index.jsx
@@ -1,13 +1,13 @@
// Components
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
-import Breadcrumbs from "../../../Components/Breadcrumbs";
+import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
import Button from "@mui/material/Button";
-import ConfigBox from "../../../Components/ConfigBox";
+import ConfigBox from "@/Components/v1/ConfigBox/index.jsx";
import Box from "@mui/material/Box";
-import Select from "../../../Components/Inputs/Select";
-import TextInput from "../../../Components/Inputs/TextInput";
-import Dialog from "../../../Components/Dialog";
+import Select from "@/Components/v1/Inputs/Select/index.jsx";
+import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
+import Dialog from "@/Components/v1/Dialog/index.jsx";
// Utils
import { useState } from "react";
@@ -18,9 +18,9 @@ import {
useEditNotification,
useTestNotification,
useDeleteNotification,
-} from "../../../Hooks/useNotifications";
-import { notificationValidation } from "../../../Validation/validation";
-import { createToast } from "../../../Utils/toastUtils";
+} from "../../../../Hooks/v1/useNotifications.js";
+import { notificationValidation } from "../../../../Validation/validation.js";
+import { createToast } from "../../../../Utils/toastUtils.jsx";
import { useTranslation } from "react-i18next";
import { useParams, useNavigate } from "react-router-dom";
import {
@@ -29,7 +29,7 @@ import {
DESCRIPTION_MAP,
LABEL_MAP,
PLACEHOLDER_MAP,
-} from "../utils";
+} from "../utils.js";
// Setup
@@ -223,18 +223,54 @@ const CreateNotifications = () => {
{t(DESCRIPTION_MAP[type])}
-
+ {type === "matrix" ? (
+ <>
+
+
+
+ >
+ ) : (
+
+ )}
-
-
+ {" "}
{
diff --git a/client/src/Pages/Notifications/utils.js b/client/src/Pages/v1/Notifications/utils.js
similarity index 84%
rename from client/src/Pages/Notifications/utils.js
rename to client/src/Pages/v1/Notifications/utils.js
index 0813f6868..2a3d32f4d 100644
--- a/client/src/Pages/Notifications/utils.js
+++ b/client/src/Pages/v1/Notifications/utils.js
@@ -4,6 +4,7 @@ export const NOTIFICATION_TYPES = [
{ _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/Maintenance/index.css b/client/src/Pages/v1/PageSpeed/Create/index.css
similarity index 100%
rename from client/src/Pages/Maintenance/index.css
rename to client/src/Pages/v1/PageSpeed/Create/index.css
diff --git a/client/src/Pages/PageSpeed/Create/index.jsx b/client/src/Pages/v1/PageSpeed/Create/index.jsx
similarity index 92%
rename from client/src/Pages/PageSpeed/Create/index.jsx
rename to client/src/Pages/v1/PageSpeed/Create/index.jsx
index 46bc7f977..cb856bd37 100644
--- a/client/src/Pages/PageSpeed/Create/index.jsx
+++ b/client/src/Pages/v1/PageSpeed/Create/index.jsx
@@ -1,37 +1,37 @@
// 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 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 PauseCircleOutlineIcon from "@mui/icons-material/PauseCircleOutline";
-import Breadcrumbs from "../../../Components/Breadcrumbs";
-import PulseDot from "../../../Components/Animated/PulseDot";
+import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
+import PulseDot from "@/Components/v1/Animated/PulseDot.jsx";
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 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/monitorUtils.js";
import { useTranslation } from "react-i18next";
-import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications";
+import { useGetNotificationsByTeamId } from "../../../../Hooks/v1/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/v1/useMonitorUtils.js";
import {
useCreateMonitor,
useFetchMonitorById,
useDeleteMonitor,
useUpdateMonitor,
usePauseMonitor,
-} from "../../../Hooks/monitorHooks";
+} from "../../../../Hooks/v1/monitorHooks.js";
const PageSpeedSetup = () => {
const { monitorId } = useParams();
diff --git a/client/src/Pages/PageSpeed/Create/skeleton.jsx b/client/src/Pages/v1/PageSpeed/Create/skeleton.jsx
similarity index 100%
rename from client/src/Pages/PageSpeed/Create/skeleton.jsx
rename to client/src/Pages/v1/PageSpeed/Create/skeleton.jsx
diff --git a/client/src/Pages/PageSpeed/Details/Components/Charts/AreaChart.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChart.jsx
similarity index 99%
rename from client/src/Pages/PageSpeed/Details/Components/Charts/AreaChart.jsx
rename to client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChart.jsx
index 40fec0ab6..5c79cc06c 100644
--- a/client/src/Pages/PageSpeed/Details/Components/Charts/AreaChart.jsx
+++ b/client/src/Pages/v1/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/timeUtils.js";
import { useSelector } from "react-redux";
const config = {
diff --git a/client/src/Pages/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx
similarity index 85%
rename from client/src/Pages/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx
rename to client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx
index 72811fa2a..c0da1600f 100644
--- a/client/src/Pages/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx
+++ b/client/src/Pages/v1/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 MetricsIcon from "../../../../../../assets/icons/ruler-icon.svg?react";
+import LegendBox from "@/Components/v1/Charts/LegendBox/index.jsx";
import { useTranslation } from "react-i18next";
import { useTheme } from "@emotion/react";
diff --git a/client/src/Pages/PageSpeed/Details/Components/Charts/PieChart.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChart.jsx
similarity index 100%
rename from client/src/Pages/PageSpeed/Details/Components/Charts/PieChart.jsx
rename to client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChart.jsx
diff --git a/client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChartLegend.jsx
similarity index 93%
rename from client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx
rename to client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChartLegend.jsx
index 18ca0733c..1297bd699 100644
--- a/client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx
+++ b/client/src/Pages/v1/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 SpeedometerIcon from "../../../../../../assets/icons/speedometer-icon.svg?react";
import PropTypes from "prop-types";
const PieChartLegend = ({ audits }) => {
diff --git a/client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx
similarity index 76%
rename from client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx
rename to client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx
index 582648e23..1281a396e 100644
--- a/client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx
+++ b/client/src/Pages/v1/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 ScoreIcon from "../../../../../../assets/icons/monitor-graph-line.svg?react";
import { Stack } from "@mui/material";
import PropTypes from "prop-types";
diff --git a/client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx
similarity index 100%
rename from client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx
rename to client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx
diff --git a/client/src/Pages/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx
similarity index 79%
rename from client/src/Pages/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx
rename to client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx
index cb79198e9..ff265317c 100644
--- a/client/src/Pages/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx
+++ b/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx
@@ -1,7 +1,7 @@
-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/timeUtils.js";
import { useTranslation } from "react-i18next";
const PageSpeedStatusBoxes = ({ shouldRender, monitor }) => {
diff --git a/client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/index.jsx
similarity index 78%
rename from client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx
rename to client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/index.jsx
index 2f809ebbb..27f9e885b 100644
--- a/client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx
+++ b/client/src/Pages/v1/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 PerformanceIcon from "../../../../../../assets/icons/performance-report.svg?react";
+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 }) => {
diff --git a/client/src/Pages/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx b/client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx
similarity index 100%
rename from client/src/Pages/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx
rename to client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx
diff --git a/client/src/Pages/PageSpeed/Details/index.jsx b/client/src/Pages/v1/PageSpeed/Details/index.jsx
similarity index 81%
rename from client/src/Pages/PageSpeed/Details/index.jsx
rename to client/src/Pages/v1/PageSpeed/Details/index.jsx
index cfc799b36..836f0ca37 100644
--- a/client/src/Pages/PageSpeed/Details/index.jsx
+++ b/client/src/Pages/v1/PageSpeed/Details/index.jsx
@@ -1,17 +1,17 @@
// 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/v1/useIsAdmin.js";
import { useParams } from "react-router-dom";
-import { useFetchStatsByMonitorId } from "../../../Hooks/monitorHooks";
+import { useFetchStatsByMonitorId } from "../../../../Hooks/v1/monitorHooks.js";
import { useState } from "react";
import { useTranslation } from "react-i18next";
// Constants
diff --git a/client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx b/client/src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx
similarity index 95%
rename from client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx
rename to client/src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx
index 94d6b137b..4e551526b 100644
--- a/client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx
+++ b/client/src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx
@@ -1,17 +1,20 @@
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 PageSpeedIcon from "../../../../../../assets/icons/page-speed.svg?react";
+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/timeUtils.js";
+import { useMonitorUtils } from "../../../../../../Hooks/v1/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
diff --git a/client/src/Pages/PageSpeed/Monitors/Components/MonitorGrid/index.jsx b/client/src/Pages/v1/PageSpeed/Monitors/Components/MonitorGrid/index.jsx
similarity index 88%
rename from client/src/Pages/PageSpeed/Monitors/Components/MonitorGrid/index.jsx
rename to client/src/Pages/v1/PageSpeed/Monitors/Components/MonitorGrid/index.jsx
index 6266ebdc9..6b0a800fb 100644
--- a/client/src/Pages/PageSpeed/Monitors/Components/MonitorGrid/index.jsx
+++ b/client/src/Pages/v1/PageSpeed/Monitors/Components/MonitorGrid/index.jsx
@@ -1,5 +1,5 @@
import { Grid, Grid2 } from "@mui/material";
-import Card from "../Card";
+import Card from "../Card/index.jsx";
const MonitorGrid = ({ shouldRender, monitors }) => {
return (
diff --git a/client/src/Pages/PageSpeed/Monitors/index.jsx b/client/src/Pages/v1/PageSpeed/Monitors/index.jsx
similarity index 70%
rename from client/src/Pages/PageSpeed/Monitors/index.jsx
rename to client/src/Pages/v1/PageSpeed/Monitors/index.jsx
index b5b13ba6e..7485a72c9 100644
--- a/client/src/Pages/PageSpeed/Monitors/index.jsx
+++ b/client/src/Pages/v1/PageSpeed/Monitors/index.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/v1/useIsAdmin.js";
+import { useFetchMonitorsByTeamId } from "../../../../Hooks/v1/monitorHooks.js";
+import { useFetchSettings } from "../../../../Hooks/v1/settingsHooks.js";
// Constants
const BREADCRUMBS = [{ name: `pagespeed`, path: "/pagespeed" }];
const TYPES = ["pagespeed"];
diff --git a/client/src/Pages/ServerUnreachable.jsx b/client/src/Pages/v1/ServerUnreachable.jsx
similarity index 90%
rename from client/src/Pages/ServerUnreachable.jsx
rename to client/src/Pages/v1/ServerUnreachable.jsx
index 466633144..8230f473b 100644
--- a/client/src/Pages/ServerUnreachable.jsx
+++ b/client/src/Pages/v1/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/SettingsAbout.jsx b/client/src/Pages/v1/Settings/SettingsAbout.jsx
similarity index 89%
rename from client/src/Pages/Settings/SettingsAbout.jsx
rename to client/src/Pages/v1/Settings/SettingsAbout.jsx
index a80351393..6a8125af3 100644
--- a/client/src/Pages/Settings/SettingsAbout.jsx
+++ b/client/src/Pages/v1/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/v1/Settings/SettingsDemoMonitors.jsx
similarity index 95%
rename from client/src/Pages/Settings/SettingsDemoMonitors.jsx
rename to client/src/Pages/v1/Settings/SettingsDemoMonitors.jsx
index 13e93421e..47e699851 100644
--- a/client/src/Pages/Settings/SettingsDemoMonitors.jsx
+++ b/client/src/Pages/v1/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/v1/Settings/SettingsEmail.jsx
similarity index 95%
rename from client/src/Pages/Settings/SettingsEmail.jsx
rename to client/src/Pages/v1/Settings/SettingsEmail.jsx
index 1f86b1984..7b5ad175e 100644
--- a/client/src/Pages/Settings/SettingsEmail.jsx
+++ b/client/src/Pages/v1/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/v1/useSendTestEmail.js";
+import { createToast } from "../../../Utils/toastUtils.jsx";
const SettingsEmail = ({
isAdmin,
diff --git a/client/src/Pages/Settings/SettingsGlobalThresholds.jsx b/client/src/Pages/v1/Settings/SettingsGlobalThresholds.jsx
similarity index 94%
rename from client/src/Pages/Settings/SettingsGlobalThresholds.jsx
rename to client/src/Pages/v1/Settings/SettingsGlobalThresholds.jsx
index acc101ab4..ea58478e2 100644
--- a/client/src/Pages/Settings/SettingsGlobalThresholds.jsx
+++ b/client/src/Pages/v1/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/v1/Settings/SettingsPagespeed.jsx
similarity index 91%
rename from client/src/Pages/Settings/SettingsPagespeed.jsx
rename to client/src/Pages/v1/Settings/SettingsPagespeed.jsx
index 157c75dac..b0d5d6441 100644
--- a/client/src/Pages/Settings/SettingsPagespeed.jsx
+++ b/client/src/Pages/v1/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/v1/Settings/SettingsStats.jsx
similarity index 92%
rename from client/src/Pages/Settings/SettingsStats.jsx
rename to client/src/Pages/v1/Settings/SettingsStats.jsx
index 740ac3a37..b68006ab2 100644
--- a/client/src/Pages/Settings/SettingsStats.jsx
+++ b/client/src/Pages/v1/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";
diff --git a/client/src/Pages/Settings/SettingsTimeZone.jsx b/client/src/Pages/v1/Settings/SettingsTimeZone.jsx
similarity index 90%
rename from client/src/Pages/Settings/SettingsTimeZone.jsx
rename to client/src/Pages/v1/Settings/SettingsTimeZone.jsx
index ee5cf48de..f684c871d 100644
--- a/client/src/Pages/Settings/SettingsTimeZone.jsx
+++ b/client/src/Pages/v1/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/v1/Settings/SettingsUI.jsx
similarity index 92%
rename from client/src/Pages/Settings/SettingsUI.jsx
rename to client/src/Pages/v1/Settings/SettingsUI.jsx
index aab5f863d..7129e775b 100644
--- a/client/src/Pages/Settings/SettingsUI.jsx
+++ b/client/src/Pages/v1/Settings/SettingsUI.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/SettingsURL.jsx b/client/src/Pages/v1/Settings/SettingsURL.jsx
similarity index 90%
rename from client/src/Pages/Settings/SettingsURL.jsx
rename to client/src/Pages/v1/Settings/SettingsURL.jsx
index 24c49cad1..c52dcf317 100644
--- a/client/src/Pages/Settings/SettingsURL.jsx
+++ b/client/src/Pages/v1/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/v1/Settings/index.jsx
similarity index 88%
rename from client/src/Pages/Settings/index.jsx
rename to client/src/Pages/v1/Settings/index.jsx
index 92f58b4c0..2732e331a 100644
--- a/client/src/Pages/Settings/index.jsx
+++ b/client/src/Pages/v1/Settings/index.jsx
@@ -1,31 +1,36 @@
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 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,
+} 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/v1/settingsHooks.js";
+import { useIsAdmin } from "../../../Hooks/v1/useIsAdmin.js";
import {
useAddDemoMonitors,
useDeleteAllMonitors,
useDeleteMonitorStats,
-} from "../../Hooks/monitorHooks";
+} from "../../../Hooks/v1/monitorHooks.js";
// Constants
const BREADCRUMBS = [{ name: `Settings`, path: "/settings" }];
diff --git a/client/src/Pages/StatusPage/Create/Components/MonitorList/index.jsx b/client/src/Pages/v1/StatusPage/Create/Components/MonitorList/index.jsx
similarity index 100%
rename from client/src/Pages/StatusPage/Create/Components/MonitorList/index.jsx
rename to client/src/Pages/v1/StatusPage/Create/Components/MonitorList/index.jsx
diff --git a/client/src/Pages/StatusPage/Create/Components/Progress/index.jsx b/client/src/Pages/v1/StatusPage/Create/Components/Progress/index.jsx
similarity index 84%
rename from client/src/Pages/StatusPage/Create/Components/Progress/index.jsx
rename to client/src/Pages/v1/StatusPage/Create/Components/Progress/index.jsx
index 14c4ce0f5..232a12f1d 100644
--- a/client/src/Pages/StatusPage/Create/Components/Progress/index.jsx
+++ b/client/src/Pages/v1/StatusPage/Create/Components/Progress/index.jsx
@@ -1,8 +1,8 @@
import { Button, Box } from "@mui/material";
-import ProgressUpload from "../../../../../Components/ProgressBars";
+import ProgressUpload from "@/Components/v1/ProgressBars/index.jsx";
import ImageIcon from "@mui/icons-material/Image";
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) {
diff --git a/client/src/Pages/StatusPage/Create/Components/Skeleton/index.jsx b/client/src/Pages/v1/StatusPage/Create/Components/Skeleton/index.jsx
similarity index 100%
rename from client/src/Pages/StatusPage/Create/Components/Skeleton/index.jsx
rename to client/src/Pages/v1/StatusPage/Create/Components/Skeleton/index.jsx
diff --git a/client/src/Pages/StatusPage/Create/Components/Tabs/ConfigStack.jsx b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/ConfigStack.jsx
similarity index 93%
rename from client/src/Pages/StatusPage/Create/Components/Tabs/ConfigStack.jsx
rename to client/src/Pages/v1/StatusPage/Create/Components/Tabs/ConfigStack.jsx
index e313f0e85..f31c456ef 100644
--- a/client/src/Pages/StatusPage/Create/Components/Tabs/ConfigStack.jsx
+++ b/client/src/Pages/v1/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/v1/StatusPage/Create/Components/Tabs/Content.jsx
similarity index 91%
rename from client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx
rename to client/src/Pages/v1/StatusPage/Create/Components/Tabs/Content.jsx
index 327a2fb0a..d11825e69 100644
--- a/client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx
+++ b/client/src/Pages/v1/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,
diff --git a/client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx
similarity index 89%
rename from client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx
rename to client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx
index dd4345158..a668a9db8 100644
--- a/client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx
+++ b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx
@@ -1,17 +1,17 @@
// 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";
-import timezones from "../../../../../Utils/timezones.json";
+import timezones from "../../../../../../Utils/timezones.json";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
import { useMemo, useState, useCallback } from "react";
diff --git a/client/src/Pages/StatusPage/Create/Components/Tabs/index.jsx b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/index.jsx
similarity index 93%
rename from client/src/Pages/StatusPage/Create/Components/Tabs/index.jsx
rename to client/src/Pages/v1/StatusPage/Create/Components/Tabs/index.jsx
index 60d86e1c9..e8669c474 100644
--- a/client/src/Pages/StatusPage/Create/Components/Tabs/index.jsx
+++ b/client/src/Pages/v1/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/v1/StatusPage/Create/Hooks/useCreateStatusPage.jsx
similarity index 83%
rename from client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx
rename to client/src/Pages/v1/StatusPage/Create/Hooks/useCreateStatusPage.jsx
index a2ac5f55d..808598052 100644
--- a/client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx
+++ b/client/src/Pages/v1/StatusPage/Create/Hooks/useCreateStatusPage.jsx
@@ -1,6 +1,6 @@
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 [isLoading, setIsLoading] = useState(false);
diff --git a/client/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx b/client/src/Pages/v1/StatusPage/Create/Hooks/useMonitorsFetch.jsx
similarity index 88%
rename from client/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx
rename to client/src/Pages/v1/StatusPage/Create/Hooks/useMonitorsFetch.jsx
index f380bed9a..2a7db202a 100644
--- a/client/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx
+++ b/client/src/Pages/v1/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);
diff --git a/client/src/Pages/StatusPage/Create/index.jsx b/client/src/Pages/v1/StatusPage/Create/index.jsx
similarity index 93%
rename from client/src/Pages/StatusPage/Create/index.jsx
rename to client/src/Pages/v1/StatusPage/Create/index.jsx
index d4cbc0fd5..d9fafed70 100644
--- a/client/src/Pages/StatusPage/Create/index.jsx
+++ b/client/src/Pages/v1/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"],
diff --git a/client/src/Pages/StatusPage/Status/Components/AdminLink/index.jsx b/client/src/Pages/v1/StatusPage/Status/Components/AdminLink/index.jsx
similarity index 100%
rename from client/src/Pages/StatusPage/Status/Components/AdminLink/index.jsx
rename to client/src/Pages/v1/StatusPage/Status/Components/AdminLink/index.jsx
diff --git a/client/src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx b/client/src/Pages/v1/StatusPage/Status/Components/ControlsHeader/index.jsx
similarity index 93%
rename from client/src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx
rename to client/src/Pages/v1/StatusPage/Status/Components/ControlsHeader/index.jsx
index beac3b0d2..c56aa40cc 100644
--- a/client/src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx
+++ b/client/src/Pages/v1/StatusPage/Status/Components/ControlsHeader/index.jsx
@@ -1,8 +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 Image from "@/Components/v1/Image/index.jsx";
+import SettingsIcon from "../../../../../../assets/icons/settings-bold.svg?react";
+import ThemeSwitch from "@/Components/v1/ThemeSwitch/index.jsx";
import ArrowOutwardIcon from "@mui/icons-material/ArrowOutward";
//Utils
import { useTheme } from "@mui/material/styles";
diff --git a/client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx b/client/src/Pages/v1/StatusPage/Status/Components/MonitorsList/index.jsx
similarity index 84%
rename from client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx
rename to client/src/Pages/v1/StatusPage/Status/Components/MonitorsList/index.jsx
index 545974c57..59ad4d522 100644
--- a/client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx
+++ b/client/src/Pages/v1/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/v1/useMonitorUtils.js";
import PropTypes from "prop-types";
import { useSelector } from "react-redux";
diff --git a/client/src/Pages/StatusPage/Status/Components/Skeleton/index.jsx b/client/src/Pages/v1/StatusPage/Status/Components/Skeleton/index.jsx
similarity index 100%
rename from client/src/Pages/StatusPage/Status/Components/Skeleton/index.jsx
rename to client/src/Pages/v1/StatusPage/Status/Components/Skeleton/index.jsx
diff --git a/client/src/Pages/StatusPage/Status/Components/StatusBar/index.jsx b/client/src/Pages/v1/StatusPage/Status/Components/StatusBar/index.jsx
similarity index 100%
rename from client/src/Pages/StatusPage/Status/Components/StatusBar/index.jsx
rename to client/src/Pages/v1/StatusPage/Status/Components/StatusBar/index.jsx
diff --git a/client/src/Pages/StatusPage/Status/Hooks/useStatusPageDelete.jsx b/client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageDelete.jsx
similarity index 89%
rename from client/src/Pages/StatusPage/Status/Hooks/useStatusPageDelete.jsx
rename to client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageDelete.jsx
index 2c39c70b3..038a771ba 100644
--- a/client/src/Pages/StatusPage/Status/Hooks/useStatusPageDelete.jsx
+++ b/client/src/Pages/v1/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/v1/StatusPage/Status/Hooks/useStatusPageFetch.jsx
similarity index 87%
rename from client/src/Pages/StatusPage/Status/Hooks/useStatusPageFetch.jsx
rename to client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageFetch.jsx
index ae9c7b579..506082887 100644
--- a/client/src/Pages/StatusPage/Status/Hooks/useStatusPageFetch.jsx
+++ b/client/src/Pages/v1/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/v1/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/v1/StatusPage/Status/index.jsx
similarity index 85%
rename from client/src/Pages/StatusPage/Status/index.jsx
rename to client/src/Pages/v1/StatusPage/Status/index.jsx
index a77f2a380..e700bbbd9 100644
--- a/client/src/Pages/StatusPage/Status/index.jsx
+++ b/client/src/Pages/v1/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/v1/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/v1/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx
similarity index 94%
rename from client/src/Pages/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx
rename to client/src/Pages/v1/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx
index 04d7a6cfb..1afe25409 100644
--- a/client/src/Pages/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx
+++ b/client/src/Pages/v1/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx
@@ -1,7 +1,7 @@
-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 { StatusLabel } from "@/Components/v1/Label/index.jsx";
import ArrowOutwardIcon from "@mui/icons-material/ArrowOutward";
import { Stack, Typography } from "@mui/material";
import { useTranslation } from "react-i18next";
diff --git a/client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx b/client/src/Pages/v1/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx
similarity index 86%
rename from client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx
rename to client/src/Pages/v1/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx
index b236368c8..89df1b84d 100644
--- a/client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx
+++ b/client/src/Pages/v1/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);
diff --git a/client/src/Pages/StatusPage/StatusPages/index.jsx b/client/src/Pages/v1/StatusPage/StatusPages/index.jsx
similarity index 71%
rename from client/src/Pages/StatusPage/StatusPages/index.jsx
rename to client/src/Pages/v1/StatusPage/StatusPages/index.jsx
index fa26c2b81..3c60fe950 100644
--- a/client/src/Pages/StatusPage/StatusPages/index.jsx
+++ b/client/src/Pages/v1/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/v1/useIsAdmin.js";
const BREADCRUMBS = [{ name: `Status Pages`, path: "" }];
const StatusPages = () => {
diff --git a/client/src/Pages/Uptime/BulkImport/Upload.jsx b/client/src/Pages/v1/Uptime/BulkImport/Upload.jsx
similarity index 100%
rename from client/src/Pages/Uptime/BulkImport/Upload.jsx
rename to client/src/Pages/v1/Uptime/BulkImport/Upload.jsx
diff --git a/client/src/Pages/Uptime/BulkImport/index.jsx b/client/src/Pages/v1/Uptime/BulkImport/index.jsx
similarity index 89%
rename from client/src/Pages/Uptime/BulkImport/index.jsx
rename to client/src/Pages/v1/Uptime/BulkImport/index.jsx
index 9490b4eab..9f3efcd67 100644
--- a/client/src/Pages/Uptime/BulkImport/index.jsx
+++ b/client/src/Pages/v1/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/v1/monitorHooks.js";
const BulkImport = () => {
const theme = useTheme();
diff --git a/client/src/Pages/PageSpeed/Create/index.css b/client/src/Pages/v1/Uptime/Create/index.css
similarity index 100%
rename from client/src/Pages/PageSpeed/Create/index.css
rename to client/src/Pages/v1/Uptime/Create/index.css
diff --git a/client/src/Pages/Uptime/Create/index.jsx b/client/src/Pages/v1/Uptime/Create/index.jsx
similarity index 95%
rename from client/src/Pages/Uptime/Create/index.jsx
rename to client/src/Pages/v1/Uptime/Create/index.jsx
index 3f905fa11..202f64d85 100644
--- a/client/src/Pages/Uptime/Create/index.jsx
+++ b/client/src/Pages/v1/Uptime/Create/index.jsx
@@ -9,31 +9,31 @@ 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 { monitorValidation } from "../../../../Validation/validation.js";
+import { createToast } from "../../../../Utils/toastUtils.jsx";
import {
PauseOutlined as PauseOutlinedIcon,
PlayArrowOutlined as PlayArrowOutlinedIcon,
} from "@mui/icons-material";
-import { useMonitorUtils } from "../../../Hooks/useMonitorUtils";
-import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications";
+import { useMonitorUtils } from "../../../../Hooks/v1/useMonitorUtils.js";
+import { useGetNotificationsByTeamId } from "../../../../Hooks/v1/useNotifications.js";
import { useParams } from "react-router-dom";
import {
useCreateMonitor,
@@ -42,7 +42,7 @@ import {
usePauseMonitor,
useFetchMonitorById,
useFetchMonitorGames,
-} from "../../../Hooks/monitorHooks";
+} from "../../../../Hooks/v1/monitorHooks.js";
/**
* Create page renders monitor creation or configuration views.
diff --git a/client/src/Pages/Uptime/Create/skeleton.jsx b/client/src/Pages/v1/Uptime/Create/skeleton.jsx
similarity index 100%
rename from client/src/Pages/Uptime/Create/skeleton.jsx
rename to client/src/Pages/v1/Uptime/Create/skeleton.jsx
diff --git a/client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx b/client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/index.jsx
similarity index 86%
rename from client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx
rename to client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/index.jsx
index b60a4dd04..45f35a42e 100644
--- a/client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx
+++ b/client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/index.jsx
@@ -1,15 +1,15 @@
// 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";
+import ChartBox from "@/Components/v1/Charts/ChartBox/index.jsx";
+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.jsx";
+import DownBarChart from "../Charts/DownBarChart.jsx";
+import ResponseGaugeChart from "../Charts/ResponseGaugeChart.jsx";
+import SkeletonLayout from "./skeleton.jsx";
// Utils
-import { formatDateWithTz } from "../../../../../Utils/timeUtils";
+import { formatDateWithTz } from "../../../../../../Utils/timeUtils.js";
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { useState } from "react";
diff --git a/client/src/Pages/Uptime/Details/Components/ChartBoxes/skeleton.jsx b/client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/skeleton.jsx
similarity index 100%
rename from client/src/Pages/Uptime/Details/Components/ChartBoxes/skeleton.jsx
rename to client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/skeleton.jsx
diff --git a/client/src/Pages/Uptime/Details/Components/Charts/CustomLabels.jsx b/client/src/Pages/v1/Uptime/Details/Components/Charts/CustomLabels.jsx
similarity index 93%
rename from client/src/Pages/Uptime/Details/Components/Charts/CustomLabels.jsx
rename to client/src/Pages/v1/Uptime/Details/Components/Charts/CustomLabels.jsx
index 8df33953f..c9c1ff58d 100644
--- a/client/src/Pages/Uptime/Details/Components/Charts/CustomLabels.jsx
+++ b/client/src/Pages/v1/Uptime/Details/Components/Charts/CustomLabels.jsx
@@ -1,6 +1,6 @@
import PropTypes from "prop-types";
import { useSelector } from "react-redux";
-import { formatDateWithTz } from "../../../../../Utils/timeUtils";
+import { formatDateWithTz } from "../../../../../../Utils/timeUtils.js";
const CustomLabels = ({ x, width, height, firstDataPoint, lastDataPoint, type }) => {
const uiTimezone = useSelector((state) => state.ui.timezone);
diff --git a/client/src/Pages/Uptime/Details/Components/Charts/DownBarChart.jsx b/client/src/Pages/v1/Uptime/Details/Components/Charts/DownBarChart.jsx
similarity index 97%
rename from client/src/Pages/Uptime/Details/Components/Charts/DownBarChart.jsx
rename to client/src/Pages/v1/Uptime/Details/Components/Charts/DownBarChart.jsx
index 662abc831..c5e08ecca 100644
--- a/client/src/Pages/Uptime/Details/Components/Charts/DownBarChart.jsx
+++ b/client/src/Pages/v1/Uptime/Details/Components/Charts/DownBarChart.jsx
@@ -2,7 +2,7 @@ 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";
+import CustomLabels from "./CustomLabels.jsx";
const DownBarChart = memo(({ groupedDownChecks = [], type, onBarHover }) => {
const theme = useTheme();
diff --git a/client/src/Pages/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx b/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx
similarity index 100%
rename from client/src/Pages/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx
rename to client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx
diff --git a/client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx b/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChart.jsx
similarity index 62%
rename from client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx
rename to client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChart.jsx
index e2d8dea80..daa7b4f53 100644
--- a/client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx
+++ b/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChart.jsx
@@ -1,7 +1,7 @@
-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 ChartBox from "@/Components/v1/Charts/ChartBox/index.jsx";
+import MonitorDetailsAreaChart from "@/Components/v1/Charts/MonitorDetailsAreaChart/index.jsx";
+import ResponseTimeIcon from "../../../../../../assets/icons/response-time-icon.svg?react";
+import SkeletonLayout from "./ResponseTimeChartSkeleton.jsx";
import PropTypes from "prop-types";
const ResponseTImeChart = ({ isLoading = false, groupedChecks = [], dateRange }) => {
diff --git a/client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx b/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx
similarity index 100%
rename from client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx
rename to client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx
diff --git a/client/src/Pages/Uptime/Details/Components/Charts/UpBarChart.jsx b/client/src/Pages/v1/Uptime/Details/Components/Charts/UpBarChart.jsx
similarity index 98%
rename from client/src/Pages/Uptime/Details/Components/Charts/UpBarChart.jsx
rename to client/src/Pages/v1/Uptime/Details/Components/Charts/UpBarChart.jsx
index 317147b74..8f1af8912 100644
--- a/client/src/Pages/Uptime/Details/Components/Charts/UpBarChart.jsx
+++ b/client/src/Pages/v1/Uptime/Details/Components/Charts/UpBarChart.jsx
@@ -2,7 +2,7 @@ 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";
+import CustomLabels from "./CustomLabels.jsx";
const getThemeColor = (responseTime) => {
if (responseTime < 200) {
diff --git a/client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx b/client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx
similarity index 78%
rename from client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx
rename to client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx
index 4d482aa07..948489ac1 100644
--- a/client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx
+++ b/client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx
@@ -1,12 +1,12 @@
-import ChartBox from "../../../../../Components/Charts/ChartBox";
+import ChartBox from "@/Components/v1/Charts/ChartBox/index.jsx";
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 HistoryIcon from "../../../../../../assets/icons/history-icon.svg?react";
+import Table from "@/Components/v1/Table/index.jsx";
+import TablePagination from "@/Components/v1/Table/TablePagination/index.jsx";
+import { StatusLabel } from "@/Components/v1/Label/index.jsx";
import { useTranslation } from "react-i18next";
-import { formatDateWithTz } from "../../../../../Utils/timeUtils";
-import SkeletonLayout from "./skeleton";
+import { formatDateWithTz } from "../../../../../../Utils/timeUtils.js";
+import SkeletonLayout from "./skeleton.jsx";
const ResponseTable = ({
isLoading = false,
checks = [],
diff --git a/client/src/Pages/Uptime/Details/Components/ResponseTable/skeleton.jsx b/client/src/Pages/v1/Uptime/Details/Components/ResponseTable/skeleton.jsx
similarity index 100%
rename from client/src/Pages/Uptime/Details/Components/ResponseTable/skeleton.jsx
rename to client/src/Pages/v1/Uptime/Details/Components/ResponseTable/skeleton.jsx
diff --git a/client/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx b/client/src/Pages/v1/Uptime/Details/Components/UptimeStatusBoxes/index.jsx
similarity index 86%
rename from client/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx
rename to client/src/Pages/v1/Uptime/Details/Components/UptimeStatusBoxes/index.jsx
index abeb251fe..a7b971839 100644
--- a/client/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx
+++ b/client/src/Pages/v1/Uptime/Details/Components/UptimeStatusBoxes/index.jsx
@@ -1,11 +1,11 @@
-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 PropTypes from "prop-types";
-import { getHumanReadableDuration } from "../../../../../Utils/timeUtils";
+import { getHumanReadableDuration } from "../../../../../../Utils/timeUtils.js";
import { useTheme } from "@mui/material/styles";
import { Typography } from "@mui/material";
-import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils";
+import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js";
const UptimeStatusBoxes = ({
isLoading = false,
diff --git a/client/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx b/client/src/Pages/v1/Uptime/Details/Hooks/useCertificateFetch.jsx
similarity index 84%
rename from client/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx
rename to client/src/Pages/v1/Uptime/Details/Hooks/useCertificateFetch.jsx
index ad0db6f70..f85111f97 100644
--- a/client/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx
+++ b/client/src/Pages/v1/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/timeUtils.js";
const useCertificateFetch = ({
monitor,
diff --git a/client/src/Pages/Uptime/Details/index.jsx b/client/src/Pages/v1/Uptime/Details/index.jsx
similarity index 84%
rename from client/src/Pages/Uptime/Details/index.jsx
rename to client/src/Pages/v1/Uptime/Details/index.jsx
index c72a5756f..f5e283495 100644
--- a/client/src/Pages/Uptime/Details/index.jsx
+++ b/client/src/Pages/v1/Uptime/Details/index.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/v1/useIsAdmin.js";
+import { useFetchUptimeMonitorById } from "../../../../Hooks/v1/monitorHooks.js";
+import useCertificateFetch from "./Hooks/useCertificateFetch.jsx";
+import { useFetchChecksByMonitor } from "../../../../Hooks/v1/checkHooks.js";
import { useTranslation } from "react-i18next";
// Constants
diff --git a/client/src/Pages/Uptime/Monitors/Components/Filter/index.jsx b/client/src/Pages/v1/Uptime/Monitors/Components/Filter/index.jsx
similarity index 98%
rename from client/src/Pages/Uptime/Monitors/Components/Filter/index.jsx
rename to client/src/Pages/v1/Uptime/Monitors/Components/Filter/index.jsx
index 0efb06b6c..b9a6824c1 100644
--- a/client/src/Pages/Uptime/Monitors/Components/Filter/index.jsx
+++ b/client/src/Pages/v1/Uptime/Monitors/Components/Filter/index.jsx
@@ -1,6 +1,6 @@
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";
diff --git a/client/src/Pages/Uptime/Monitors/Components/LoadingSpinner/index.jsx b/client/src/Pages/v1/Uptime/Monitors/Components/LoadingSpinner/index.jsx
similarity index 100%
rename from client/src/Pages/Uptime/Monitors/Components/LoadingSpinner/index.jsx
rename to client/src/Pages/v1/Uptime/Monitors/Components/LoadingSpinner/index.jsx
diff --git a/client/src/Pages/Uptime/Monitors/Components/SearchComponent/index.jsx b/client/src/Pages/v1/Uptime/Monitors/Components/SearchComponent/index.jsx
similarity index 90%
rename from client/src/Pages/Uptime/Monitors/Components/SearchComponent/index.jsx
rename to client/src/Pages/v1/Uptime/Monitors/Components/SearchComponent/index.jsx
index 0c6eec854..64c546a8a 100644
--- a/client/src/Pages/Uptime/Monitors/Components/SearchComponent/index.jsx
+++ b/client/src/Pages/v1/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/Skeleton/index.jsx b/client/src/Pages/v1/Uptime/Monitors/Components/Skeleton/index.jsx
similarity index 100%
rename from client/src/Pages/Uptime/Monitors/Components/Skeleton/index.jsx
rename to client/src/Pages/v1/Uptime/Monitors/Components/Skeleton/index.jsx
diff --git a/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/index.jsx b/client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/index.jsx
similarity index 91%
rename from client/src/Pages/Uptime/Monitors/Components/StatusBoxes/index.jsx
rename to client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/index.jsx
index 9cd29c4ca..5cba9e3b1 100644
--- a/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/index.jsx
+++ b/client/src/Pages/v1/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/skeleton.jsx b/client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/skeleton.jsx
similarity index 100%
rename from client/src/Pages/Uptime/Monitors/Components/StatusBoxes/skeleton.jsx
rename to client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/skeleton.jsx
diff --git a/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx b/client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx
similarity index 90%
rename from client/src/Pages/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx
rename to client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx
index 78d555409..c745e7935 100644
--- a/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx
+++ b/client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx
@@ -1,9 +1,9 @@
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 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";
const StatusBox = ({ title, value, status }) => {
const theme = useTheme();
diff --git a/client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx b/client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx
similarity index 91%
rename from client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx
rename to client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx
index 63a40f85a..9cf2d247d 100644
--- a/client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx
+++ b/client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx
@@ -1,19 +1,19 @@
// Components
import { Box, Stack } from "@mui/material";
-import DataTable from "../../../../../Components/Table";
+import DataTable from "@/Components/v1/Table/index.jsx";
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 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/v1/useMonitorUtils.js";
import { useNavigate } from "react-router-dom";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
diff --git a/client/src/Pages/Uptime/Monitors/Hooks/useDebounce.jsx b/client/src/Pages/v1/Uptime/Monitors/Hooks/useDebounce.jsx
similarity index 100%
rename from client/src/Pages/Uptime/Monitors/Hooks/useDebounce.jsx
rename to client/src/Pages/v1/Uptime/Monitors/Hooks/useDebounce.jsx
diff --git a/client/src/Pages/Uptime/Monitors/index.jsx b/client/src/Pages/v1/Uptime/Monitors/index.jsx
similarity index 86%
rename from client/src/Pages/Uptime/Monitors/index.jsx
rename to client/src/Pages/v1/Uptime/Monitors/index.jsx
index 7f28c1cd7..cacfc23b4 100644
--- a/client/src/Pages/Uptime/Monitors/index.jsx
+++ b/client/src/Pages/v1/Uptime/Monitors/index.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/v1/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";
+} from "../../../../Hooks/v1/monitorHooks.js";
import { useTranslation } from "react-i18next";
const TYPES = ["http", "ping", "docker", "port", "game"];
diff --git a/client/src/Pages/v2/Auth/Login.tsx b/client/src/Pages/v2/Auth/Login.tsx
new file mode 100644
index 000000000..79ed2329f
--- /dev/null
+++ b/client/src/Pages/v2/Auth/Login.tsx
@@ -0,0 +1,136 @@
+import { AuthBasePage } from "@/Components/v2/Auth";
+import { Button } from "@/Components/v2/Inputs";
+import Stack from "@mui/material/Stack";
+import { TextInput, TextLink } from "@/Components/v2/Inputs";
+
+import type { ApiResponse } from "@/Hooks/v2/UseApi";
+
+import { zodResolver } from "@hookform/resolvers/zod";
+import { usePost } from "@/Hooks/v2/UseApi";
+import { useNavigate } from "react-router";
+import { useTheme } from "@mui/material/styles";
+import { useDispatch } from "react-redux";
+import { setIsAuthenticated } from "@/Features/Auth/v2AuthSlice";
+import { useTranslation } from "react-i18next";
+import { z } from "zod";
+import { useForm, Controller } from "react-hook-form";
+
+const schema = z.object({
+ email: z.email("Invalid email address"),
+ password: z.string().min(6, "Password must be at least 6 characters"),
+});
+
+type FormData = z.infer;
+
+const Login = () => {
+ const { t } = useTranslation();
+ const dispatch = useDispatch();
+ const theme = useTheme();
+ const { post, loading } = usePost();
+ const navigate = useNavigate();
+
+ const {
+ handleSubmit,
+ control,
+ formState: { errors },
+ } = useForm({
+ resolver: zodResolver(schema),
+ defaultValues: {
+ email: "",
+ password: "",
+ },
+ });
+
+ const onSubmit = async (data: FormData) => {
+ const result = await post("/auth/login", data);
+ if (result) {
+ dispatch(setIsAuthenticated({ authenticated: true }));
+ navigate("/v2/uptime");
+ } else {
+ dispatch(setIsAuthenticated({ authenticated: false }));
+ }
+ };
+
+ return (
+
+
+
+ (
+
+ )}
+ />
+ (
+
+ )}
+ />
+
+
+
+
+
+
+ );
+};
+
+export default Login;
diff --git a/client/src/Pages/v2/Auth/Register.tsx b/client/src/Pages/v2/Auth/Register.tsx
new file mode 100644
index 000000000..d4565c66c
--- /dev/null
+++ b/client/src/Pages/v2/Auth/Register.tsx
@@ -0,0 +1,177 @@
+import { AuthBasePage } from "@/Components/v2/Auth";
+import { TextInput } from "@/Components/v2/Inputs";
+import { Button } from "@/Components/v2/Inputs";
+import Typography from "@mui/material/Typography";
+import Stack from "@mui/material/Stack";
+
+import type { ApiResponse } from "@/Hooks/v2/UseApi";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { z } from "zod";
+import { useTranslation } from "react-i18next";
+import { useForm, Controller } from "react-hook-form";
+import { useTheme } from "@mui/material/styles";
+import { usePost } from "@/Hooks/v2/UseApi";
+import { useNavigate } from "react-router";
+
+const schema = z
+ .object({
+ email: z.email({ message: "Invalid email address" }),
+ firstName: z.string().min(1, { message: "First Name is required" }),
+ lastName: z.string().min(1, { message: "Last Name is required" }),
+ password: z.string().min(6, { message: "Password must be at least 6 characters" }),
+ confirmPassword: z
+ .string()
+ .min(6, { message: "Confirm Password must be at least 6 characters" }),
+ })
+ .refine((data) => data.password === data.confirmPassword, {
+ path: ["confirmPassword"],
+ message: "Passwords must match",
+ });
+
+type FormData = z.infer;
+
+const Register = () => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+ const navigate = useNavigate();
+ const { post, loading, error } = usePost();
+
+ const {
+ handleSubmit,
+ control,
+ formState: { errors },
+ } = useForm({
+ resolver: zodResolver(schema),
+ defaultValues: {
+ email: "",
+ password: "",
+ },
+ });
+
+ const onSubmit = async (data: FormData) => {
+ const result = await post("/auth/register", data);
+ if (result) {
+ navigate("/v2/uptime");
+ } else {
+ console.error("Login failed:", error);
+ }
+ };
+
+ return (
+
+
+
+ (
+
+ )}
+ />
+ (
+
+ )}
+ />
+ (
+
+ )}
+ />
+ (
+
+ )}
+ />
+ (
+
+ )}
+ />
+
+ {error && {error}}
+
+
+
+ );
+};
+
+export default Register;
diff --git a/client/src/Pages/v2/Uptime/CheckTable.tsx b/client/src/Pages/v2/Uptime/CheckTable.tsx
new file mode 100644
index 000000000..c2811c453
--- /dev/null
+++ b/client/src/Pages/v2/Uptime/CheckTable.tsx
@@ -0,0 +1,93 @@
+import { Table, Pagination } from "@/Components/v2/DesignElements";
+import { StatusLabel } from "@/Components/v2/DesignElements";
+import Box from "@mui/material/Box";
+
+import type { Header } from "@/Components/v2/DesignElements/Table";
+import type { Check } from "@/Types/Check";
+import type { ApiResponse } from "@/Hooks/v2/UseApi";
+import type { MonitorStatus } from "@/Types/Monitor";
+
+import { useState } from "react";
+import { useTranslation } from "react-i18next";
+import { useGet } from "@/Hooks/v2/UseApi";
+import { formatDateWithTz } from "@/Utils/v2/TimeUtils";
+import { useSelector } from "react-redux";
+const getHeaders = (t: Function, uiTimezone: string) => {
+ const headers: Header[] = [
+ {
+ id: "status",
+ content: t("status"),
+ render: (row) => {
+ return ;
+ },
+ },
+ {
+ id: "date",
+ content: t("date&Time"),
+ render: (row) => {
+ return formatDateWithTz(row.createdAt, "ddd, MMMM D, YYYY, HH:mm A", uiTimezone);
+ },
+ },
+ {
+ id: "statusCode",
+ content: t("statusCode"),
+ render: (row) => {
+ return row.httpStatusCode || "N/A";
+ },
+ },
+ ];
+ return headers;
+};
+
+export const CheckTable = ({ monitorId }: { monitorId: string }) => {
+ const [page, setPage] = useState(0);
+ const [rowsPerPage, setRowsPerPage] = useState(5);
+ const { t } = useTranslation();
+ const uiTimezone = useSelector((state: any) => state.ui.timezone);
+ const headers = getHeaders(t, uiTimezone);
+
+ const { response, error } = useGet(
+ `/monitors/${monitorId}/checks?page=${page}&rowsPerPage=${rowsPerPage}`,
+ {},
+ { keepPreviousData: true }
+ );
+
+ const checks = response?.data?.checks || [];
+ const count = response?.data?.count || 0;
+
+ 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);
+ };
+
+ if (error) {
+ console.error(error);
+ }
+
+ return (
+
+
+
+
+ );
+};
diff --git a/client/src/Pages/v2/Uptime/Configure.tsx b/client/src/Pages/v2/Uptime/Configure.tsx
new file mode 100644
index 000000000..dc014669b
--- /dev/null
+++ b/client/src/Pages/v2/Uptime/Configure.tsx
@@ -0,0 +1,43 @@
+import { monitorSchema } from "@/Validation/v2/zod";
+import { useGet, usePost } from "@/Hooks/v2/UseApi";
+import { UptimeForm } from "@/Pages/v2/Uptime/UptimeForm";
+
+import { useParams } from "react-router";
+import type { ApiResponse } from "@/Hooks/v2/UseApi";
+import humanInterval from "human-interval";
+import { z } from "zod";
+
+export const UptimeConfigurePage = () => {
+ type FormValues = z.infer;
+ type SubmitValues = Omit & { interval: number | undefined };
+
+ const { id } = useParams();
+ const { response } = useGet("/notification-channels");
+ const { response: monitorResponse } = useGet(`/monitors/${id}`);
+ const monitor = monitorResponse?.data || null;
+ const notificationOptions = response?.data ?? [];
+
+ const { post, loading, error } = usePost();
+
+ const onSubmit = async (data: FormValues) => {
+ let interval = humanInterval(data.interval);
+ if (!interval) interval = 60000;
+ const submitData = { ...data, interval };
+ const result = await post("/monitors", submitData);
+ if (result) {
+ console.log(result);
+ } else {
+ console.error(error);
+ }
+ };
+ return (
+
+ );
+};
diff --git a/client/src/Pages/v2/Uptime/Create.tsx b/client/src/Pages/v2/Uptime/Create.tsx
new file mode 100644
index 000000000..38b1511f4
--- /dev/null
+++ b/client/src/Pages/v2/Uptime/Create.tsx
@@ -0,0 +1,262 @@
+import Stack from "@mui/material/Stack";
+import { TextInput } from "@/Components/v2/Inputs/TextInput";
+import { AutoCompleteInput } from "@/Components/v2/Inputs/AutoComplete";
+import { ConfigBox, BasePage } from "@/Components/v2/DesignElements";
+import RadioGroup from "@mui/material/RadioGroup";
+import FormControl from "@mui/material/FormControl";
+import { RadioWithDescription } from "@/Components/v2/Inputs/RadioInput";
+import { Button } from "@/Components/v2/Inputs";
+import DeleteOutlineRoundedIcon from "@mui/icons-material/DeleteOutlineRounded";
+import { Typography } from "@mui/material";
+import humanInterval from "human-interval";
+
+import { useTranslation } from "react-i18next";
+import { useForm, Controller, useWatch } from "react-hook-form";
+import { z } from "zod";
+import { monitorSchema } from "@/Validation/v2/zod";
+import { useTheme } from "@mui/material/styles";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { useGet, usePost } from "@/Hooks/v2/UseApi";
+import type { ApiResponse } from "@/Hooks/v2/UseApi";
+
+const UptimeCreatePage = () => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+
+ type FormValues = z.infer;
+ type SubmitValues = Omit & { interval: number | undefined };
+
+ const {
+ handleSubmit,
+ control,
+ setValue,
+ formState: { errors },
+ } = useForm({
+ resolver: zodResolver(monitorSchema) as any,
+ defaultValues: {
+ type: "http",
+ url: "",
+ n: 3,
+ notificationChannels: [],
+ name: "",
+ interval: "1 minute",
+ },
+ mode: "onChange",
+ });
+ const { response } = useGet("/notification-channels");
+ const { post, loading, error } = usePost();
+ const selectedType = useWatch({
+ control,
+ name: "type",
+ });
+ const notificationChannels = useWatch({
+ control,
+ name: "notificationChannels",
+ });
+
+ const onSubmit = async (data: FormValues) => {
+ let interval = humanInterval(data.interval);
+ if (!interval) interval = 60000;
+ const submitData = { ...data, interval };
+ const result = await post("/monitors", submitData);
+ if (result) {
+ console.log(result);
+ } else {
+ console.error(error);
+ }
+ };
+
+ const notificationOptions = response?.data ?? [];
+ return (
+
+ }
+ />
+ (
+ {
+ const target = e.target as HTMLInputElement;
+ field.onChange(target.valueAsNumber);
+ }}
+ />
+ )}
+ />
+ }
+ />
+
+ (
+ option.name}
+ value={notificationOptions.filter((o: any) =>
+ (field.value || []).includes(o._id)
+ )}
+ onChange={(_, newValue) => {
+ field.onChange(newValue.map((o: any) => o._id));
+ }}
+ />
+ )}
+ />
+
+ {notificationChannels.map((notificationId) => {
+ const option = notificationOptions.find(
+ (o: any) => o._id === notificationId
+ );
+ if (!option) return null;
+ return (
+
+ {option.name}
+ {
+ const updated = notificationChannels.filter(
+ (id) => id !== notificationId
+ );
+ setValue("notificationChannels", updated);
+ }}
+ sx={{ cursor: "pointer" }}
+ />
+
+ );
+ })}
+
+
+ }
+ />
+ (
+
+ )}
+ />
+ }
+ />
+
+
+
+
+
+ );
+};
+
+export default UptimeCreatePage;
diff --git a/client/src/Pages/v2/Uptime/Details.tsx b/client/src/Pages/v2/Uptime/Details.tsx
new file mode 100644
index 000000000..5515e7b32
--- /dev/null
+++ b/client/src/Pages/v2/Uptime/Details.tsx
@@ -0,0 +1,157 @@
+import { BasePage } from "@/Components/v2/DesignElements";
+import { HeaderControls } from "@/Components/v2/Monitors/HeaderControls";
+import Stack from "@mui/material/Stack";
+import { StatBox } from "@/Components/v2/DesignElements";
+import { HistogramStatus } from "@/Components/v2/Monitors/HistogramStatus";
+import { ChartAvgResponse } from "@/Components/v2/Monitors/ChartAvgResponse";
+import { ChartResponseTime } from "@/Components/v2/Monitors/ChartResponseTime";
+import { HeaderRange } from "@/Components/v2/Monitors/HeaderRange";
+import { CheckTable } from "@/Pages/v2/Uptime/CheckTable";
+
+import type { IMonitor } from "@/Types/Monitor";
+import { useMediaQuery } from "@mui/material";
+import { useTheme } from "@mui/material/styles";
+import { useParams } from "react-router";
+import { useGet, usePatch, type ApiResponse } from "@/Hooks/v2/UseApi";
+import { useState } from "react";
+import { getStatusPalette } from "@/Utils/v2/MonitorUtils";
+import prettyMilliseconds from "pretty-ms";
+
+const UptimeDetailsPage = () => {
+ const { id } = useParams();
+ const theme = useTheme();
+ const isSmall = useMediaQuery(theme.breakpoints.down("md"));
+
+ // Local state
+ const [range, setRange] = useState("2h");
+
+ const { response, isValidating, error, refetch } = useGet(
+ `/monitors/${id}?embedChecks=true&range=${range}`,
+
+ {},
+ { refreshInterval: 30000, keepPreviousData: true }
+ );
+
+ const {
+ response: upResponse,
+ isValidating: upIsValidating,
+ error: upError,
+ } = useGet(
+ `/monitors/${id}?embedChecks=true&range=${range}&status=up`,
+ {},
+ { keepPreviousData: true }
+ );
+
+ const {
+ response: downResponse,
+ error: downError,
+ isValidating: downIsValidating,
+ } = useGet(
+ `/monitors/${id}?embedChecks=true&range=${range}&status=down`,
+ {},
+ { keepPreviousData: true }
+ );
+
+ const { patch, loading: isPatching, error: postError } = usePatch();
+
+ const monitor: IMonitor = response?.data?.monitor;
+
+ if (!monitor) {
+ return null;
+ }
+
+ const stats = response?.data?.stats || null;
+ const avgResponseTime = stats?.avgResponseTime || 0;
+ const maxResponseTime = stats?.maxResponseTime || 0;
+
+ const streakDuration = stats?.currentStreakStartedAt
+ ? Date.now() - stats?.currentStreakStartedAt
+ : 0;
+
+ const lastChecked = stats?.lastCheckTimestamp
+ ? Date.now() - stats?.lastCheckTimestamp
+ : -1;
+
+ const checks = response?.data?.checks || [];
+ const upChecks = upResponse?.data?.checks ? [...upResponse.data.checks].reverse() : [];
+ const downChecks = downResponse?.data?.checks
+ ? [...downResponse.data.checks].reverse()
+ : [];
+
+ const palette = getStatusPalette(monitor?.status);
+
+ if (error || upError || downError || postError) {
+ console.error("Error fetching monitor data:", {
+ error,
+ upError,
+ downError,
+ postError,
+ });
+ }
+
+ return (
+
+
+
+
+ = 0
+ ? `${prettyMilliseconds(lastChecked, { secondsDecimalDigits: 0 })} ago`
+ : "N/A"
+ }
+ />
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default UptimeDetailsPage;
diff --git a/client/src/Pages/v2/Uptime/MonitorTable.tsx b/client/src/Pages/v2/Uptime/MonitorTable.tsx
new file mode 100644
index 000000000..209784309
--- /dev/null
+++ b/client/src/Pages/v2/Uptime/MonitorTable.tsx
@@ -0,0 +1,158 @@
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import { Table } from "@/Components/v2/DesignElements";
+import { HistogramResponseTime } from "@/Components/v2/Monitors/HistogramResponseTime";
+import type { Header } from "@/Components/v2/DesignElements/Table";
+import { ActionsMenu } from "@/Components/v2/ActionsMenu";
+import { StatusLabel } from "@/Components/v2/DesignElements";
+
+import { useTranslation } from "react-i18next";
+import { useMediaQuery } from "@mui/material";
+import { useTheme } from "@mui/material/styles";
+import { useNavigate } from "react-router-dom";
+import { usePatch } from "@/Hooks/v2/UseApi";
+import type { ApiResponse } from "@/Hooks/v2/UseApi";
+
+import type { IMonitor } from "@/Types/Monitor";
+import type { ActionMenuItem } from "@/Components/v2/ActionsMenu";
+
+export const MonitorTable = ({
+ monitors,
+ refetch,
+}: {
+ monitors: IMonitor[];
+ refetch: Function;
+}) => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+ const isSmall = useMediaQuery(theme.breakpoints.down("md"));
+ const navigate = useNavigate();
+ const {
+ patch,
+ // loading: isPatching,
+ // error: postError,
+ } = usePatch();
+
+ const getActions = (monitor: IMonitor): ActionMenuItem[] => {
+ return [
+ {
+ id: 1,
+ label: "Open site",
+ action: () => {
+ window.open(monitor.url, "_blank", "noreferrer");
+ },
+ closeMenu: true,
+ },
+ {
+ id: 2,
+ label: "Details",
+ action: () => {
+ navigate(`${monitor._id}`);
+ },
+ },
+ {
+ id: 3,
+ label: "Incidents",
+ action: () => {
+ navigate(`/v2/incidents/${monitor._id}`);
+ },
+ },
+ {
+ id: 4,
+ label: "Configure",
+ action: () => {
+ console.log("Open configure");
+ },
+ },
+ {
+ id: 5,
+ label: "Clone",
+ action: () => {
+ console.log("Open clone");
+ },
+ },
+ {
+ id: 6,
+ label: monitor.isActive ? "Pause" : "Resume",
+ action: async () => {
+ await patch(`/monitors/${monitor._id}/active`);
+ refetch();
+ },
+ closeMenu: true,
+ },
+ {
+ id: 7,
+ label: Remove,
+ action: () => {
+ console.log("Open delete");
+ },
+ closeMenu: true,
+ },
+ ];
+ };
+
+ const getHeaders = () => {
+ const headers: Header[] = [
+ {
+ id: "name",
+ content: t("host"),
+ render: (row) => {
+ return row.name;
+ },
+ },
+ {
+ id: "status",
+ content: t("status"),
+ render: (row) => {
+ return (
+
+ );
+ },
+ },
+ {
+ id: "histogram",
+ content: t("responseTime"),
+ render: (row) => {
+ return (
+
+
+
+ );
+ },
+ },
+ {
+ id: "type",
+ content: t("type"),
+ render: (row) => {
+ return row.type;
+ },
+ },
+ {
+ id: "actions",
+ content: t("actions"),
+ render: (row) => {
+ return ;
+ },
+ },
+ ];
+ return headers;
+ };
+
+ let headers = getHeaders();
+
+ if (isSmall) {
+ headers = headers.filter((h) => h.id !== "histogram");
+ }
+ return (
+ {
+ navigate(row._id);
+ }}
+ />
+ );
+};
diff --git a/client/src/Pages/v2/Uptime/UptimeForm.tsx b/client/src/Pages/v2/Uptime/UptimeForm.tsx
new file mode 100644
index 000000000..e9636e5dd
--- /dev/null
+++ b/client/src/Pages/v2/Uptime/UptimeForm.tsx
@@ -0,0 +1,247 @@
+import Stack from "@mui/material/Stack";
+import { TextInput } from "@/Components/v2/Inputs/TextInput";
+import { AutoCompleteInput } from "@/Components/v2/Inputs/AutoComplete";
+import { ConfigBox, BasePage } from "@/Components/v2/DesignElements";
+import RadioGroup from "@mui/material/RadioGroup";
+import FormControl from "@mui/material/FormControl";
+import { RadioWithDescription } from "@/Components/v2/Inputs/RadioInput";
+import { Button } from "@/Components/v2/Inputs";
+import DeleteOutlineRoundedIcon from "@mui/icons-material/DeleteOutlineRounded";
+import { Typography } from "@mui/material";
+
+import { useTranslation } from "react-i18next";
+import { monitorSchema } from "@/Validation/v2/zod";
+import { z } from "zod";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { useForm, Controller, useWatch, type SubmitHandler } from "react-hook-form";
+import { useTheme } from "@mui/material/styles";
+import { useInitForm } from "@/Hooks/v2/useInitMonitorForm";
+
+type FormValues = z.infer;
+
+export const UptimeForm = ({
+ initialData,
+ onSubmit,
+ notificationOptions,
+ loading,
+}: {
+ initialData?: Partial;
+ onSubmit: SubmitHandler;
+ notificationOptions: any[];
+ loading: boolean;
+}) => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+ const { defaults } = useInitForm({ initialData: initialData });
+ const {
+ handleSubmit,
+ control,
+ setValue,
+ formState: { errors },
+ } = useForm({
+ resolver: zodResolver(monitorSchema) as any,
+ defaultValues: defaults,
+ mode: "onChange",
+ });
+
+ const selectedType = useWatch({
+ control,
+ name: "type",
+ });
+ const notificationChannels = useWatch({
+ control,
+ name: "notificationChannels",
+ });
+
+ return (
+
+ (
+
+
+
+
+
+
+
+ )}
+ />
+ }
+ />
+
+ (
+
+ )}
+ />
+ (
+
+ )}
+ />
+
+ }
+ />
+ (
+ {
+ const target = e.target as HTMLInputElement;
+ field.onChange(target.valueAsNumber);
+ }}
+ />
+ )}
+ />
+ }
+ />
+
+ (
+ option.name}
+ value={notificationOptions.filter((o: any) =>
+ (field.value || []).includes(o._id)
+ )}
+ onChange={(_, newValue) => {
+ field.onChange(newValue.map((o: any) => o._id));
+ }}
+ />
+ )}
+ />
+
+ {notificationChannels.map((notificationId) => {
+ const option = notificationOptions.find(
+ (o: any) => o._id === notificationId
+ );
+ if (!option) return null;
+ return (
+
+ {option.name}
+ {
+ const updated = notificationChannels.filter(
+ (id) => id !== notificationId
+ );
+ setValue("notificationChannels", updated);
+ }}
+ sx={{ cursor: "pointer" }}
+ />
+
+ );
+ })}
+
+
+ }
+ />
+ (
+
+ )}
+ />
+ }
+ />
+
+
+
+
+ );
+};
diff --git a/client/src/Pages/v2/Uptime/UptimeMonitors.tsx b/client/src/Pages/v2/Uptime/UptimeMonitors.tsx
new file mode 100644
index 000000000..e5a151eb1
--- /dev/null
+++ b/client/src/Pages/v2/Uptime/UptimeMonitors.tsx
@@ -0,0 +1,74 @@
+import {
+ BasePageWithStates,
+ UpStatusBox,
+ DownStatusBox,
+ PausedStatusBox,
+} from "@/Components/v2/DesignElements";
+import { HeaderCreate } from "@/Components/v2/Monitors";
+import Stack from "@mui/material/Stack";
+import { MonitorTable } from "@/Pages/v2/Uptime/MonitorTable";
+
+import { useTheme } from "@mui/material/styles";
+import { useGet } from "@/Hooks/v2/UseApi";
+import type { ApiResponse } from "@/Hooks/v2/UseApi";
+import type { IMonitor } from "@/Types/Monitor";
+import { useMediaQuery } from "@mui/material";
+
+const UptimeMonitors = () => {
+ const theme = useTheme();
+ const isSmall = useMediaQuery(theme.breakpoints.down("md"));
+
+ const { response, isValidating, error, refetch } = useGet(
+ "/monitors?embedChecks=true",
+ {},
+ { refreshInterval: 30000, keepPreviousData: true }
+ );
+ const monitors: IMonitor[] = response?.data ?? ([] as IMonitor[]);
+
+ const monitorStatuses = monitors.reduce(
+ (acc, monitor) => {
+ if (monitor.status === "up") {
+ acc.up += 1;
+ } else if (monitor.status === "down") {
+ acc.down += 1;
+ } else if (monitor.isActive === false) {
+ acc.paused += 1;
+ }
+ return acc;
+ },
+ {
+ up: 0,
+ down: 0,
+ paused: 0,
+ }
+ );
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default UptimeMonitors;
diff --git a/client/src/Routes/index.jsx b/client/src/Routes/index.jsx
index 7ddcd7e3c..90dcaa3f5 100644
--- a/client/src/Routes/index.jsx
+++ b/client/src/Routes/index.jsx
@@ -1,62 +1,71 @@
+import { useSelector } from "react-redux";
+import { lightTheme, darkTheme } from "@/Utils/Theme/v2/theme";
import { Navigate, Route, Routes as LibRoutes } from "react-router";
-import HomeLayout from "../Components/Layouts/HomeLayout";
-import NotFound from "../Pages/NotFound";
-
+import HomeLayout from "@/Components/v1/Layouts/HomeLayout";
+import NotFound from "../Pages/v1/NotFound";
// Auth
-import AuthLogin from "../Pages/Auth/Login";
-import AuthRegister from "../Pages/Auth/Register/";
-import AuthForgotPassword from "../Pages/Auth/ForgotPassword";
-import AuthCheckEmail from "../Pages/Auth/CheckEmail";
-import AuthSetNewPassword from "../Pages/Auth/SetNewPassword";
-import AuthNewPasswordConfirmed from "../Pages/Auth/NewPasswordConfirmed";
+import AuthLogin from "../Pages/v1/Auth/Login";
+import AuthRegister from "../Pages/v1/Auth/Register/";
+import AuthForgotPassword from "../Pages/v1/Auth/ForgotPassword";
+import AuthCheckEmail from "../Pages/v1/Auth/CheckEmail";
+import AuthSetNewPassword from "../Pages/v1/Auth/SetNewPassword";
+import AuthNewPasswordConfirmed from "../Pages/v1/Auth/NewPasswordConfirmed";
// Uptime
-import Uptime from "../Pages/Uptime/Monitors";
-import UptimeDetails from "../Pages/Uptime/Details";
-import UptimeCreate from "../Pages/Uptime/Create";
+import Uptime from "../Pages/v1/Uptime/Monitors";
+import UptimeDetails from "../Pages/v1/Uptime/Details";
+import UptimeCreate from "../Pages/v1/Uptime/Create";
// PageSpeed
-import PageSpeed from "../Pages/PageSpeed/Monitors";
-import PageSpeedDetails from "../Pages/PageSpeed/Details";
-import PageSpeedCreate from "../Pages/PageSpeed/Create";
+import PageSpeed from "../Pages/v1/PageSpeed/Monitors";
+import PageSpeedDetails from "../Pages/v1/PageSpeed/Details";
+import PageSpeedCreate from "../Pages/v1/PageSpeed/Create";
// Infrastructure
-import Infrastructure from "../Pages/Infrastructure/Monitors";
-import InfrastructureCreate from "../Pages/Infrastructure/Create";
-import InfrastructureDetails from "../Pages/Infrastructure/Details";
+import Infrastructure from "../Pages/v1/Infrastructure/Monitors";
+import InfrastructureCreate from "../Pages/v1/Infrastructure/Create";
+import InfrastructureDetails from "../Pages/v1/Infrastructure/Details";
// Server Status
-import ServerUnreachable from "../Pages/ServerUnreachable";
+import ServerUnreachable from "../Pages/v1/ServerUnreachable.jsx";
// Incidents
-import Incidents from "../Pages/Incidents";
+import Incidents from "../Pages/v1/Incidents";
// Status pages
-import CreateStatus from "../Pages/StatusPage/Create";
-import StatusPages from "../Pages/StatusPage/StatusPages";
-import Status from "../Pages/StatusPage/Status";
+import CreateStatus from "../Pages/v1/StatusPage/Create";
+import StatusPages from "../Pages/v1/StatusPage/StatusPages";
+import Status from "../Pages/v1/StatusPage/Status";
-import Notifications from "../Pages/Notifications";
-import CreateNotifications from "../Pages/Notifications/create";
+import Notifications from "../Pages/v1/Notifications";
+import CreateNotifications from "../Pages/v1/Notifications/create";
// Settings
-import Account from "../Pages/Account";
-import EditUser from "../Pages/Account/EditUser";
-import Settings from "../Pages/Settings";
+import Account from "../Pages/v1/Account";
+import EditUser from "../Pages/v1/Account/EditUser";
+import Settings from "../Pages/v1/Settings";
-import Maintenance from "../Pages/Maintenance";
+import Maintenance from "../Pages/v1/Maintenance";
-import ProtectedRoute from "../Components/ProtectedRoute";
-import RoleProtectedRoute from "../Components/RoleProtectedRoute";
-import CreateNewMaintenanceWindow from "../Pages/Maintenance/CreateMaintenance";
-import withAdminCheck from "../Components/HOC/withAdminCheck";
-import BulkImport from "../Pages/Uptime/BulkImport";
-import Logs from "../Pages/Logs";
+import ProtectedRoute from "../Components/v1/ProtectedRoute";
+import RoleProtectedRoute from "../Components/v1/RoleProtectedRoute";
+import CreateNewMaintenanceWindow from "../Pages/v1/Maintenance/CreateMaintenance";
+import withAdminCheck from "@/Components/v1/HOC/withAdminCheck";
+import BulkImport from "../Pages/v1/Uptime/BulkImport";
+import Logs from "../Pages/v1/Logs";
+
+import V2Routes from "@/Routes/v2router";
const Routes = () => {
+ const mode = useSelector((state) => state.ui.mode);
const AdminCheckedRegister = withAdminCheck(AuthRegister);
return (
+ }
+ />
+
{
+ const v2Theme = mode === "light" ? lightTheme : darkTheme;
+
+ return (
+
+
+ }
+ />
+ }
+ />
+ }
+ >
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+
+
+
+ );
+};
+
+export default V2Routes;
diff --git a/client/src/Types/Check.ts b/client/src/Types/Check.ts
new file mode 100644
index 000000000..cccc56f3d
--- /dev/null
+++ b/client/src/Types/Check.ts
@@ -0,0 +1,50 @@
+export interface CheckTimingPhases {
+ wait: number;
+ dns: number;
+ tcp: number;
+ tls: number;
+ request: number;
+ firstByte: number;
+ download: number;
+ total: number;
+}
+
+export interface CheckTimings {
+ start: string;
+ socket: string;
+ lookup: string;
+ connect: string;
+ secureConnect: string;
+ response: string;
+ end: string;
+ phases: CheckTimingPhases;
+}
+
+export interface Check {
+ _id: string;
+ monitorId: string;
+ type: string;
+ status: string;
+ message: string;
+ responseTime: number;
+ normalResponseTime?: number;
+ httpStatusCode: number;
+ ack: boolean;
+ expiry: string;
+ createdAt: string;
+ updatedAt: string;
+ timings: CheckTimings;
+}
+
+export interface GroupedCheck {
+ _id: string;
+ avgResponseTime: number;
+ count: number;
+}
+
+export interface LatestCheck {
+ status: string;
+ responseTime: number;
+ checkedAt: string;
+ _id: string;
+}
diff --git a/client/src/Types/Monitor.ts b/client/src/Types/Monitor.ts
new file mode 100644
index 000000000..05f7d7d6f
--- /dev/null
+++ b/client/src/Types/Monitor.ts
@@ -0,0 +1,20 @@
+import type { Check } from "@/Types/Check";
+export type MonitorStatus = "up" | "down" | "initializing";
+
+export interface IMonitor {
+ 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;
+ __v: number;
+ _id: string;
+}
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..84bcd799f
--- /dev/null
+++ b/client/src/Types/mui.d.ts
@@ -0,0 +1,24 @@
+import "@mui/material/Button";
+
+declare module "@mui/material/styles" {
+ interface Palette {
+ accent: Palette["primary"];
+ tertiary: Palette["primary"];
+ }
+ interface PaletteOptions {
+ accent?: PaletteOptions["primary"];
+ tertiary?: PaletteOptions["primary"];
+ }
+
+ interface PaletteColor {
+ contrastTextSecondary?: string;
+ contrastTextTertiary?: string;
+ lowContrast?: string;
+ }
+}
+
+declare module "@mui/material/Button" {
+ interface ButtonPropsColorOverrides {
+ accent: true;
+ }
+}
diff --git a/client/src/Utils/NetworkService.js b/client/src/Utils/NetworkService.js
index dce0faaf5..6372be646 100644
--- a/client/src/Utils/NetworkService.js
+++ b/client/src/Utils/NetworkService.js
@@ -1136,6 +1136,15 @@ class NetworkService {
},
});
}
+
+ async changePasswordByAdmin(config) {
+ const { userId, passwordForm } = config;
+ return this.axiosInstance.put(`auth/users/${userId}/password`, passwordForm, {
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+ }
}
export default NetworkService;
diff --git a/client/src/Utils/Theme/constants.js b/client/src/Utils/Theme/constants.js
index 9df6665d0..45217fdeb 100644
--- a/client/src/Utils/Theme/constants.js
+++ b/client/src/Utils/Theme/constants.js
@@ -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",
diff --git a/client/src/Utils/Theme/globalTheme.js b/client/src/Utils/Theme/globalTheme.js
index bda415c90..e65baf4c0 100644
--- a/client/src/Utils/Theme/globalTheme.js
+++ b/client/src/Utils/Theme/globalTheme.js
@@ -58,8 +58,8 @@ 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)",
diff --git a/client/src/Utils/Theme/v2/palette.ts b/client/src/Utils/Theme/v2/palette.ts
new file mode 100644
index 000000000..43370fe8b
--- /dev/null
+++ b/client/src/Utils/Theme/v2/palette.ts
@@ -0,0 +1,124 @@
+import { lighten, darken } from "@mui/material/styles";
+
+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`,
+};
+
+const colors = {
+ offWhite: "#FEFEFE",
+ offBlack: "#131315",
+ gray0: "#FDFDFD",
+ gray10: "#F4F4FF",
+ gray50: "#F9F9F9",
+ gray100: "#F3F3F3",
+ gray200: "#EFEFEF",
+ gray250: "#DADADA",
+ gray500: "#A2A3A3",
+ gray900: "#1c1c1c",
+ blueGray50: "#E8F0FE",
+ blueGray500: "#475467",
+ blueGray600: "#344054",
+ blueGray800: "#1C2130",
+ blueGray900: "#515151",
+ blueBlueWave: "#1570EF",
+ lightBlueWave: "#CDE2FF",
+ green100: "#67cd78",
+ green200: "#4B9B77",
+ green400: "#079455",
+ green700: "#026513",
+ orange100: "#FD8F22",
+ orange200: "#D69A5D",
+ orange600: "#9B734B",
+ orange700: "#884605",
+ red100: "#F27C7C",
+ red400: "#D92020",
+ red600: "#9B4B4B",
+ red700: "#980303",
+};
+
+export const lightPalette = {
+ accent: {
+ main: colors.blueBlueWave,
+ light: lighten(colors.blueBlueWave, 0.2),
+ dark: darken(colors.blueBlueWave, 0.2),
+ contrastText: colors.offWhite,
+ },
+ primary: {
+ main: colors.offWhite,
+ contrastText: colors.blueGray800,
+ contrastTextSecondary: colors.blueGray600,
+ contrastTextTertiary: colors.blueGray500,
+ lowContrast: colors.gray250,
+ },
+ secondary: {
+ main: colors.gray200,
+ light: colors.lightBlueWave,
+ contrastText: colors.blueGray600,
+ },
+ tertiary: {
+ main: colors.gray100,
+ contrastText: colors.blueGray800,
+ },
+ success: {
+ main: colors.green700,
+ contrastText: colors.offWhite,
+ lowContrast: colors.green400,
+ },
+ warning: {
+ main: colors.orange700,
+ contrastText: colors.offWhite,
+ lowContrast: colors.orange100,
+ },
+ error: {
+ main: colors.red700,
+ contrastText: colors.offWhite,
+ lowContrast: colors.red400,
+ },
+};
+
+export const darkPalette = {
+ accent: {
+ main: colors.blueBlueWave,
+ light: lighten(colors.blueBlueWave, 0.2),
+ dark: darken(colors.blueBlueWave, 0.2),
+ contrastText: colors.offWhite,
+ },
+ primary: {
+ main: colors.offBlack,
+ contrastText: colors.blueGray50,
+ contrastTextSecondary: colors.gray200,
+ contrastTextTertiary: colors.gray500,
+ lowContrast: colors.blueGray600,
+ },
+ secondary: {
+ main: "#313131",
+ light: colors.lightBlueWave,
+ contrastText: colors.gray200,
+ },
+ tertiary: {
+ main: colors.blueGray800,
+ contrastText: colors.gray100,
+ },
+ success: {
+ main: colors.green100,
+ contrastText: colors.offBlack,
+ lowContrast: colors.green200,
+ },
+ warning: {
+ main: colors.orange200,
+ contrastText: colors.offBlack,
+ lowContrast: colors.orange600,
+ },
+ error: {
+ main: colors.red100,
+ contrastText: colors.offBlack,
+ lowContrast: colors.red600,
+ },
+};
diff --git a/client/src/Utils/Theme/v2/theme.ts b/client/src/Utils/Theme/v2/theme.ts
new file mode 100644
index 000000000..d2581e236
--- /dev/null
+++ b/client/src/Utils/Theme/v2/theme.ts
@@ -0,0 +1,103 @@
+import { createTheme } from "@mui/material";
+import { lightPalette, darkPalette, typographyLevels } from "./palette";
+
+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 = '"Inter" , 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({
+ spacing: 2,
+ palette: {
+ mode: mode,
+ ...palette,
+ },
+ typography: {
+ fontFamily: fontFamilyPrimary,
+ fontSize: typographyLevels.base,
+ h1: {
+ fontSize: typographyLevels.xl,
+ color: palette.primary.contrastText,
+ fontWeight: 500,
+ },
+ h2: {
+ fontSize: typographyLevels.l,
+ color: palette.primary.contrastTextSecondary,
+ fontWeight: 400,
+ },
+
+ body1: {
+ fontSize: typographyLevels.m,
+ color: palette.primary.contrastTextTertiary,
+ fontWeight: 400,
+ },
+ body2: {
+ fontSize: typographyLevels.s,
+ color: palette.primary.contrastTextTertiary,
+ fontWeight: 400,
+ },
+ },
+
+ components: {
+ 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.accent.main,
+ },
+ },
+ },
+ },
+
+ MuiPaper: {
+ styleOverrides: {
+ root: ({ theme }) => {
+ return {
+ marginTop: 4,
+ padding: 0,
+ border: 1,
+ borderStyle: "solid",
+ borderColor: theme.palette.primary.lowContrast,
+ borderRadius: 4,
+ boxShadow: shadow,
+ backgroundColor: theme.palette.primary.main,
+ 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/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/Utils/v2/ApiClient.ts b/client/src/Utils/v2/ApiClient.ts
new file mode 100644
index 000000000..f41407b8f
--- /dev/null
+++ b/client/src/Utils/v2/ApiClient.ts
@@ -0,0 +1,26 @@
+import axios from "axios";
+import type { AxiosRequestConfig, AxiosResponse } from "axios";
+const BASE_URL = import.meta.env.VITE_APP_API_V2_BASE_URL;
+const api = axios.create({
+ baseURL: BASE_URL || "http://localhost:55555/api/v2",
+ withCredentials: true,
+});
+
+export const get = (
+ url: string,
+ config: AxiosRequestConfig = {}
+): Promise> => api.get(url, config);
+
+export const post = (
+ url: string,
+ data: any,
+ config: AxiosRequestConfig = {}
+): Promise> => api.post(url, data, config);
+
+export const patch = (
+ url: string,
+ data: any,
+ config: AxiosRequestConfig = {}
+): Promise> => api.patch(url, data, config);
+
+export default api;
diff --git a/client/src/Utils/v2/DataUtils.ts b/client/src/Utils/v2/DataUtils.ts
new file mode 100644
index 000000000..3878d7d6a
--- /dev/null
+++ b/client/src/Utils/v2/DataUtils.ts
@@ -0,0 +1,37 @@
+const MIN_OUT = 10;
+const MAX_OUT = 100;
+
+export const normalizeResponseTimes = , K extends keyof T>(
+ 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 { min, max } = checks.reduce(
+ (acc, check) => {
+ if (check[key] > acc.max) acc.max = check[key];
+ if (check[key] < acc.min) acc.min = check[key];
+ return acc;
+ },
+ { max: -Infinity, min: Infinity }
+ );
+
+ const range = max - min || 1;
+
+ return checks.map((check) => ({
+ ...check,
+ normalResponseTime: MIN_OUT + ((check[key] - min) * (MAX_OUT - MIN_OUT)) / range,
+ }));
+};
diff --git a/client/src/Utils/v2/MonitorUtils.ts b/client/src/Utils/v2/MonitorUtils.ts
new file mode 100644
index 000000000..c47b7a82e
--- /dev/null
+++ b/client/src/Utils/v2/MonitorUtils.ts
@@ -0,0 +1,38 @@
+import type { MonitorStatus } from "@/Types/Monitor";
+import type { PaletteKey } from "@/Utils/Theme/v2/theme";
+export const getStatusPalette = (status: MonitorStatus): PaletteKey => {
+ const paletteMap: Record = {
+ up: "success",
+ down: "error",
+ initializing: "warning",
+ };
+ return paletteMap[status];
+};
+
+export const getStatusColor = (status: MonitorStatus, theme: any): string => {
+ const statusColors: Record = {
+ up: theme.palette.success.lowContrast,
+ down: theme.palette.error.lowContrast,
+ initializing: theme.palette.warning.lowContrast,
+ };
+ return statusColors[status];
+};
+
+export const getResponseTimeColor = (responseTime: number): PaletteKey => {
+ if (responseTime < 200) {
+ return "success";
+ } else if (responseTime < 300) {
+ 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;
+};
diff --git a/client/src/Utils/v2/TimeUtils.ts b/client/src/Utils/v2/TimeUtils.ts
new file mode 100644
index 000000000..618d9d1bc
--- /dev/null
+++ b/client/src/Utils/v2/TimeUtils.ts
@@ -0,0 +1,53 @@
+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 = {
+ "2h": "h:mm A",
+ "24h": "h:mm A",
+ "7d": "MM/D, h:mm A",
+ "30d": "ddd. M/D",
+ };
+ const format = tickFormatLookup[range];
+ if (format === undefined) {
+ return "";
+ }
+ return format;
+};
+
+export const tooltipDateFormatLookup = (range: string) => {
+ const dateFormatLookup: Record = {
+ "2h": "ddd. MMMM D, YYYY, hh:mm A",
+ "24h": "ddd. MMMM D, YYYY, hh:mm A",
+ "7d": "ddd. MMMM D, YYYY, hh:mm A",
+ "30d": "ddd. MMMM D, YYYY",
+ };
+ const format = dateFormatLookup[range];
+ if (format === undefined) {
+ return "";
+ }
+ return format;
+};
diff --git a/client/src/Validation/v2/zod.ts b/client/src/Validation/v2/zod.ts
new file mode 100644
index 000000000..6f0dbb951
--- /dev/null
+++ b/client/src/Validation/v2/zod.ts
@@ -0,0 +1,36 @@
+import { z } from "zod";
+import humanInterval from "human-interval";
+const urlRegex =
+ /^(?:https?:\/\/)?([a-zA-Z0-9.-]+|\d{1,3}(\.\d{1,3}){3}|\[[0-9a-fA-F:]+\])(:\d{1,5})?$/;
+
+const durationSchema = z
+ .string()
+ .optional()
+ .superRefine((val, ctx) => {
+ if (!val || val.trim() === "") return;
+ const ms = humanInterval(val);
+
+ if (!ms || isNaN(ms)) {
+ ctx.addIssue({
+ code: "custom",
+ message: "Invalid duration format",
+ });
+ } else if (ms < 10000) {
+ ctx.addIssue({
+ code: "custom",
+ message: "Minimum duration is 10 seconds",
+ });
+ }
+ });
+
+export const monitorSchema = z.object({
+ type: z.string().min(1, "You must select an option"),
+ url: z.string().min(1, "URL is required").regex(urlRegex, "Invalid URL"),
+ n: z.coerce
+ .number({ message: "Number required" })
+ .min(1, "Minimum value is 1")
+ .max(25, "Maximum value is 25"),
+ notificationChannels: z.array(z.string()).optional().default([]),
+ name: z.string().min(1, "Display name is required"),
+ interval: durationSchema,
+});
diff --git a/client/src/Validation/validation.js b/client/src/Validation/validation.js
index 67c267ffd..ce50d96c1 100644
--- a/client/src/Validation/validation.js
+++ b/client/src/Validation/validation.js
@@ -458,7 +458,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 +495,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/index.css b/client/src/index.css
index a5d305d46..d58a7e872 100644
--- a/client/src/index.css
+++ b/client/src/index.css
@@ -15,15 +15,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 +31,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,16 +39,6 @@ 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 {
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..da5a3ee61 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,77 @@
"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"
}
diff --git a/client/src/locales/en.json b/client/src/locales/en.json
index d89ebc44e..13d4a7731 100644
--- a/client/src/locales/en.json
+++ b/client/src/locales/en.json
@@ -425,7 +425,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",
@@ -487,7 +493,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."
+ "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."
},
"common": {
"appName": "Checkmate",
@@ -1096,7 +1103,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",
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..1ebc8bad4 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": "",
diff --git a/client/src/locales/ja.json b/client/src/locales/ja.json
index 6107c19d0..f458b8ed0 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": "",
diff --git a/client/src/locales/pt-BR.json b/client/src/locales/pt-BR.json
index 9e1fa6bc0..1a6d9a62f 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": "",
diff --git a/client/src/locales/ru.json b/client/src/locales/ru.json
index aa54de393..cdb4d235d 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,471 @@
"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": "Выберите интерфейс"
}
diff --git a/client/src/locales/th.json b/client/src/locales/th.json
index c3f571b6b..c32849afe 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": "ข้อมูลที่รับ",
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..305cefe87 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": "已接收数据",
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..dd42587af 100644
--- a/client/src/main.jsx
+++ b/client/src/main.jsx
@@ -8,7 +8,7 @@ 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";
ReactDOM.createRoot(document.getElementById("root")).render(
diff --git a/client/src/store.js b/client/src/store.js
index 69aa3d50b..565d68519 100644
--- a/client/src/store.js
+++ b/client/src/store.js
@@ -1,6 +1,7 @@
import { configureStore, combineReducers } from "@reduxjs/toolkit";
import authReducer from "./Features/Auth/authSlice";
+import v2AuthReducer from "./Features/Auth/v2AuthSlice";
import uiReducer from "./Features/UI/uiSlice";
import storage from "redux-persist/lib/storage";
import { persistReducer, persistStore, createTransform } from "redux-persist";
@@ -19,12 +20,13 @@ const authTransform = createTransform(
const persistConfig = {
key: "root",
storage,
- whitelist: ["auth", "ui"],
+ whitelist: ["auth", "v2Auth", "ui"],
transforms: [authTransform],
};
const rootReducer = combineReducers({
auth: authReducer,
+ v2Auth: v2AuthReducer,
ui: uiReducer,
});
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..5a6b428c7
--- /dev/null
+++ b/client/vite.config.ts
@@ -0,0 +1,27 @@
+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()],
+ 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/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/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/nodemon.json b/server/nodemon.json
index 88d6ac686..389209fa7 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"
+ "watch": ["src/**/*.ts", "src/**/*.js", "*.json"],
+ "ext": "ts,js,json"
}
diff --git a/server/package-lock.json b/server/package-lock.json
index 3e1130dc4..8de5cfc0c 100644
--- a/server/package-lock.json
+++ b/server/package-lock.json
@@ -39,12 +39,16 @@
"ping": "0.4.4",
"sharp": "0.33.5",
"ssl-checker": "2.0.10",
- "super-simple-scheduler": "1.4.1",
+ "super-simple-scheduler": "1.4.4",
"swagger-ui-express": "5.0.1",
"winston": "^3.13.0"
},
"devDependencies": {
"@eslint/js": "^9.17.0",
+ "@types/express": "5.0.3",
+ "@types/jsonwebtoken": "9.0.10",
+ "@types/nodemailer": "7.0.1",
+ "@types/ping": "0.4.4",
"c8": "10.1.3",
"chai": "5.2.0",
"eslint": "^9.17.0",
@@ -52,9 +56,13 @@
"esm": "3.2.25",
"globals": "^15.14.0",
"mocha": "11.1.0",
- "nodemon": "3.1.9",
"prettier": "^3.3.3",
- "sinon": "19.0.2"
+ "sinon": "19.0.2",
+ "tsx": "4.20.5",
+ "typescript": "5.9.2"
+ },
+ "engines": {
+ "node": ">=20"
}
},
"node_modules/@asamuzakjp/css-color": {
@@ -70,6 +78,757 @@
"lru-cache": "^10.4.3"
}
},
+ "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": {
+ "@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.27.1",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
@@ -85,18 +844,18 @@
}
},
"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==",
+ "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/runtime": {
- "version": "7.28.3",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz",
- "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==",
+ "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"
@@ -218,6 +977,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",
@@ -238,30 +1019,472 @@
}
},
"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/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/@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 +1514,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 +1524,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 +1539,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 +1602,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 +1615,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 +1625,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 +1639,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 +2116,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": {
@@ -922,9 +2166,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 +2187,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"
@@ -1152,9 +2396,9 @@
"license": "BSD-3-Clause"
},
"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"
@@ -1211,6 +2455,682 @@
"dev": true,
"license": "(Unlicense OR Apache-2.0)"
},
+ "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": "Apache-2.0",
+ "dependencies": {
+ "@smithy/types": "^4.8.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "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": ">=12.20"
+ }
+ },
+ "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",
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz",
@@ -1223,15 +3143,25 @@
"node": ">=14.16"
}
},
- "node_modules/@trysound/sax": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
- "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==",
- "license": "ISC",
- "optional": true,
- "peer": true,
- "engines": {
- "node": ">=10.13.0"
+ "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/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/estree": {
@@ -1241,12 +3171,44 @@
"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",
+ "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/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",
@@ -1261,21 +3223,111 @@
"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/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/node": {
+ "version": "24.9.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz",
+ "integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==",
+ "license": "MIT",
+ "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/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/triple-beam": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz",
@@ -1385,9 +3437,9 @@
}
},
"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"
@@ -1480,9 +3532,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",
@@ -1543,6 +3595,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 +3622,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",
@@ -1629,6 +3699,13 @@
"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",
@@ -1660,9 +3737,9 @@
"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",
@@ -1679,10 +3756,11 @@
],
"license": "MIT",
"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"
@@ -1905,9 +3983,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",
@@ -2176,41 +4254,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",
@@ -2477,9 +4520,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 +4548,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": {
@@ -2838,9 +4879,9 @@
}
},
"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"
@@ -2950,9 +4991,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 +5061,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": {
@@ -3110,9 +5151,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 +5204,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 +5287,24 @@
}
},
"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",
"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",
@@ -3576,9 +5658,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",
@@ -3855,19 +5937,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 +6036,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": {
@@ -4079,6 +6165,19 @@
"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",
@@ -4295,9 +6394,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",
@@ -4472,13 +6571,6 @@
"node": ">= 4"
}
},
- "node_modules/ignore-by-default": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
- "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
- "dev": true,
- "license": "ISC"
- },
"node_modules/import-fresh": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
@@ -4512,12 +6604,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",
@@ -4670,16 +6762,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": {
@@ -4892,14 +7160,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 +7180,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 +7231,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"
@@ -5218,9 +7498,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"
@@ -5263,12 +7543,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",
@@ -5938,14 +8216,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 +8272,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 +8293,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",
@@ -6300,9 +8532,9 @@
}
},
"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": {
@@ -6314,58 +8546,6 @@
"node": ">=6.0.0"
}
},
- "node_modules/nodemon": {
- "version": "3.1.9",
- "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz",
- "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "chokidar": "^3.5.2",
- "debug": "^4",
- "ignore-by-default": "^1.0.1",
- "minimatch": "^3.1.2",
- "pstree.remy": "^1.1.8",
- "semver": "^7.5.3",
- "simple-update-notifier": "^2.0.0",
- "supports-color": "^5.5.0",
- "touch": "^3.1.0",
- "undefsafe": "^2.0.5"
- },
- "bin": {
- "nodemon": "bin/nodemon.js"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/nodemon"
- }
- },
- "node_modules/nodemon/node_modules/has-flag": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
- "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/nodemon/node_modules/supports-color": {
- "version": "5.5.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
- "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "has-flag": "^3.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@@ -6376,9 +8556,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"
@@ -6406,9 +8586,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": {
@@ -7145,59 +9325,6 @@
"postcss": "^8.4.32"
}
},
- "node_modules/postcss-svgo/node_modules/commander": {
- "version": "11.1.0",
- "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
- "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
- "license": "MIT",
- "engines": {
- "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",
- "integrity": "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==",
- "license": "MIT",
- "dependencies": {
- "commander": "^11.1.0",
- "css-select": "^5.1.0",
- "css-tree": "^3.0.1",
- "css-what": "^6.1.0",
- "csso": "^5.0.5",
- "picocolors": "^1.1.1",
- "sax": "^1.4.1"
- },
- "bin": {
- "svgo": "bin/svgo.js"
- },
- "engines": {
- "node": ">=16"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/svgo"
- }
- },
"node_modules/postcss-unique-selectors": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.4.tgz",
@@ -7220,9 +9347,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",
@@ -7413,13 +9540,6 @@
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
- "node_modules/pstree.remy": {
- "version": "1.1.8",
- "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
- "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/pump": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
@@ -7563,6 +9683,15 @@
"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",
@@ -7578,6 +9707,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",
@@ -7675,9 +9814,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"
@@ -7916,33 +10055,20 @@
}
},
"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": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
- "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "semver": "^7.5.3"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/sinon": {
"version": "19.0.2",
"resolved": "https://registry.npmjs.org/sinon/-/sinon-19.0.2.tgz",
@@ -8193,9 +10319,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"
@@ -8271,15 +10397,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.4",
+ "resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.4.4.tgz",
+ "integrity": "sha512-o99zHFrrFECwj1FFE+ns4su5hKSYif+SUNB3fUJbaRWGkwLLoj19TRuRJYp72TYt9eDiCii68UBuKiLF4tlkMg==",
"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 +10431,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",
@@ -8307,26 +10467,24 @@
}
},
"node_modules/svgo": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz",
- "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz",
+ "integrity": "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==",
"license": "MIT",
- "optional": true,
- "peer": true,
"dependencies": {
- "@trysound/sax": "0.2.0",
- "commander": "^7.2.0",
+ "commander": "^11.1.0",
"css-select": "^5.1.0",
- "css-tree": "^2.3.1",
+ "css-tree": "^3.0.1",
"css-what": "^6.1.0",
"csso": "^5.0.5",
- "picocolors": "^1.0.0"
+ "picocolors": "^1.1.1",
+ "sax": "^1.4.1"
},
"bin": {
- "svgo": "bin/svgo"
+ "svgo": "bin/svgo.js"
},
"engines": {
- "node": ">=14.0.0"
+ "node": ">=16"
},
"funding": {
"type": "opencollective",
@@ -8334,20 +10492,18 @@
}
},
"node_modules/svgo/node_modules/commander": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
- "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
+ "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
"license": "MIT",
- "optional": true,
- "peer": true,
"engines": {
- "node": ">= 10"
+ "node": ">=16"
}
},
"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"
@@ -8375,9 +10531,9 @@
"license": "MIT"
},
"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 +10559,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",
@@ -8502,16 +10658,6 @@
"node": ">=0.6"
}
},
- "node_modules/touch": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
- "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "nodetouch": "bin/nodetouch.js"
- }
- },
"node_modules/tough-cookie": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz",
@@ -8551,6 +10697,26 @@
"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 +10777,20 @@
"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",
+ "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",
@@ -8624,26 +10804,19 @@
"node": ">=0.8.0"
}
},
- "node_modules/undefsafe": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
- "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
- "dev": true,
- "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": {
@@ -8665,9 +10838,9 @@
}
},
"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",
@@ -8988,13 +11161,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",
@@ -9135,9 +11308,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"
diff --git a/server/package.json b/server/package.json
index d879776a8..380755b5b 100755
--- a/server/package.json
+++ b/server/package.json
@@ -6,7 +6,9 @@
"type": "module",
"scripts": {
"test": "c8 mocha",
- "dev": "nodemon src/index.js",
+ "dev": "tsx watch src/index.js",
+ "start": "node --watch ./dist/index.js",
+ "build": "tsc",
"lint": "eslint .",
"lint-fix": "eslint --fix .",
"format": "prettier --write .",
@@ -15,6 +17,9 @@
"keywords": [],
"author": "",
"license": "ISC",
+ "engines": {
+ "node": ">=20"
+ },
"dependencies": {
"@pulsecron/pulse": "1.6.8",
"axios": "^1.7.2",
@@ -46,12 +51,16 @@
"ping": "0.4.4",
"sharp": "0.33.5",
"ssl-checker": "2.0.10",
- "super-simple-scheduler": "1.4.1",
+ "super-simple-scheduler": "1.4.4",
"swagger-ui-express": "5.0.1",
"winston": "^3.13.0"
},
"devDependencies": {
"@eslint/js": "^9.17.0",
+ "@types/express": "5.0.3",
+ "@types/jsonwebtoken": "9.0.10",
+ "@types/nodemailer": "7.0.1",
+ "@types/ping": "0.4.4",
"c8": "10.1.3",
"chai": "5.2.0",
"eslint": "^9.17.0",
@@ -59,8 +68,9 @@
"esm": "3.2.25",
"globals": "^15.14.0",
"mocha": "11.1.0",
- "nodemon": "3.1.9",
"prettier": "^3.3.3",
- "sinon": "19.0.2"
+ "sinon": "19.0.2",
+ "tsx": "4.20.5",
+ "typescript": "5.9.2"
}
}
diff --git a/server/src/app.js b/server/src/app.js
index 9cabda6b6..4578de896 100644
--- a/server/src/app.js
+++ b/server/src/app.js
@@ -1,16 +1,16 @@
import express from "express";
import path from "path";
-import { responseHandler } from "./middleware/responseHandler.js";
+import { responseHandler } from "./middleware/v1/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 languageMiddleware from "./middleware/v1/languageMiddleware.js";
import swaggerUi from "swagger-ui-express";
-import { handleErrors } from "./middleware/handleErrors.js";
+import { handleErrors } from "./middleware/v1/handleErrors.js";
import { setupRoutes } from "./config/routes.js";
-import { generalApiLimiter } from "./middleware/rateLimiter.js";
-import { sanitizeBody, sanitizeQuery } from "./middleware/sanitization.js";
+import { generalApiLimiter } from "./middleware/v1/rateLimiter.js";
+import { sanitizeBody, sanitizeQuery } from "./middleware/v1/sanitization.js";
export const createApp = ({ services, controllers, envSettings, frontendPath, openApiSpec }) => {
const allowedOrigin = envSettings.clientHost;
@@ -27,7 +27,7 @@ export const createApp = ({ services, controllers, envSettings, frontendPath, op
cors({
origin: allowedOrigin,
methods: "GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS",
- allowedHeaders: "*",
+ allowedHeaders: ["Content-Type", "Authorization"],
credentials: true,
})
);
diff --git a/server/src/config/controllers.js b/server/src/config/controllers.js
index 693e35ab8..927acd1a2 100644
--- a/server/src/config/controllers.js
+++ b/server/src/config/controllers.js
@@ -1,24 +1,32 @@
-import { createCommonDependencies } from "../controllers/baseController.js";
+import { createCommonDependencies } from "../controllers/v1/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";
+// V1 Controllers
+import MonitorController from "../controllers/v1/monitorController.js";
+import AuthController from "../controllers/v1/authController.js";
+import SettingsController from "../controllers/v1/settingsController.js";
+import CheckController from "../controllers/v1/checkController.js";
+import InviteController from "../controllers/v1/inviteController.js";
+import MaintenanceWindowController from "../controllers/v1/maintenanceWindowController.js";
+import QueueController from "../controllers/v1/queueController.js";
+import LogController from "../controllers/v1/logController.js";
+import StatusPageController from "../controllers/v1/statusPageController.js";
+import NotificationController from "../controllers/v1/notificationController.js";
+import DiagnosticController from "../controllers/v1/diagnosticController.js";
+// V2 Controllers
+import AuthControllerV2 from "../controllers/v2/AuthController.js";
+import InviteControllerV2 from "../controllers/v2/InviteController.js";
+import MaintenanceControllerV2 from "../controllers/v2/MaintenanceController.js";
+import MonitorControllerV2 from "../controllers/v2/MonitorController.js";
+import NotificationChannelControllerV2 from "../controllers/v2/NotificationChannelController.js";
+import QueueControllerV2 from "../controllers/v2/QueueController.js";
export const initializeControllers = (services) => {
const controllers = {};
const commonDependencies = createCommonDependencies(services.db, services.errorService, services.logger, services.stringService);
+ // V1
controllers.authController = new AuthController(commonDependencies, {
settingsService: services.settingsService,
emailService: services.emailService,
@@ -62,5 +70,13 @@ export const initializeControllers = (services) => {
diagnosticService: services.diagnosticService,
});
+ //V2
+ controllers.authControllerV2 = new AuthControllerV2(services.authServiceV2, services.inviteServiceV2);
+ controllers.inviteControllerV2 = new InviteControllerV2(services.inviteServiceV2);
+ controllers.maintenanceControllerV2 = new MaintenanceControllerV2(services.maintenanceServiceV2);
+ controllers.monitorControllerV2 = new MonitorControllerV2(services.monitorServiceV2, services.checkServiceV2);
+ controllers.notificationChannelControllerV2 = new NotificationChannelControllerV2(services.notificationChannelServiceV2);
+ controllers.queueControllerV2 = new QueueControllerV2(services.jobQueueV2);
+
return controllers;
};
diff --git a/server/src/config/index.ts b/server/src/config/index.ts
new file mode 100644
index 000000000..7ace6f99c
--- /dev/null
+++ b/server/src/config/index.ts
@@ -0,0 +1,25 @@
+import dotenv from "dotenv";
+
+export interface IEevConfig {
+ NODE_ENV: string;
+ JWT_SECRET: string;
+ PORT: number;
+ PAGESPEED_API_KEY: string;
+ SMTP_HOST: string;
+ SMTP_PORT: number;
+ SMTP_USER: string;
+ SMTP_PASS: string;
+}
+
+dotenv.config();
+
+export const config: IEevConfig = {
+ NODE_ENV: process.env.NODE_ENV || "development",
+ JWT_SECRET: process.env.JWT_SECRET || "your_jwt_secret",
+ PORT: process.env.PORT ? parseInt(process.env.PORT, 10) : 3000,
+ PAGESPEED_API_KEY: process.env.PAGESPEED_API_KEY || "",
+ SMTP_HOST: process.env.SMTP_HOST || "smtp.example.com",
+ SMTP_PORT: process.env.SMTP_PORT ? parseInt(process.env.SMTP_PORT, 10) : 587,
+ SMTP_USER: process.env.SMTP_USER || "user@example.com",
+ SMTP_PASS: process.env.SMTP_PASS || "your_smtp_password",
+};
diff --git a/server/src/config/routes.js b/server/src/config/routes.js
index 7056393f9..13b369262 100644
--- a/server/src/config/routes.js
+++ b/server/src/config/routes.js
@@ -1,19 +1,28 @@
-import { verifyJWT } from "../middleware/verifyJWT.js";
-import { authApiLimiter } from "../middleware/rateLimiter.js";
+import { verifyJWT } from "../middleware/v1/verifyJWT.js";
+import { authApiLimiter } from "../middleware/v1/rateLimiter.js";
-import AuthRoutes from "../routes/authRoute.js";
-import InviteRoutes from "../routes/inviteRoute.js";
-import MonitorRoutes from "../routes/monitorRoute.js";
-import CheckRoutes from "../routes/checkRoute.js";
-import SettingsRoutes from "../routes/settingsRoute.js";
-import MaintenanceWindowRoutes from "../routes/maintenanceWindowRoute.js";
-import StatusPageRoutes from "../routes/statusPageRoute.js";
-import QueueRoutes from "../routes/queueRoute.js";
-import LogRoutes from "../routes/logRoutes.js";
-import DiagnosticRoutes from "../routes/diagnosticRoute.js";
-import NotificationRoutes from "../routes/notificationRoute.js";
+import AuthRoutes from "../routes/v1/authRoute.js";
+import InviteRoutes from "../routes/v1//inviteRoute.js";
+import MonitorRoutes from "../routes/v1/monitorRoute.js";
+import CheckRoutes from "../routes/v1/checkRoute.js";
+import SettingsRoutes from "../routes/v1/settingsRoute.js";
+import MaintenanceWindowRoutes from "../routes/v1/maintenanceWindowRoute.js";
+import StatusPageRoutes from "../routes/v1/statusPageRoute.js";
+import QueueRoutes from "../routes/v1/queueRoute.js";
+import LogRoutes from "../routes/v1/logRoutes.js";
+import DiagnosticRoutes from "../routes/v1//diagnosticRoute.js";
+import NotificationRoutes from "../routes/v1/notificationRoute.js";
+
+//V2
+import AuthRoutesV2 from "../routes/v2/auth.js";
+import InviteRoutesV2 from "../routes/v2/invite.js";
+import MaintenanceRoutesV2 from "../routes/v2/maintenance.js";
+import MonitorRoutesV2 from "../routes/v2/monitors.js";
+import NotificationChannelRoutesV2 from "../routes/v2/notificationChannels.js";
+import QueueRoutesV2 from "../routes/v2/queue.js";
export const setupRoutes = (app, controllers) => {
+ // V1
const authRoutes = new AuthRoutes(controllers.authController);
const monitorRoutes = new MonitorRoutes(controllers.monitorController);
const settingsRoutes = new SettingsRoutes(controllers.settingsController);
@@ -37,4 +46,19 @@ 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());
+
+ // V2
+ const authRoutesV2 = new AuthRoutesV2(controllers.authControllerV2);
+ const inviteRoutesV2 = new InviteRoutesV2(controllers.inviteControllerV2);
+ const maintenanceRoutesV2 = new MaintenanceRoutesV2(controllers.maintenanceControllerV2);
+ const monitorRoutesV2 = new MonitorRoutesV2(controllers.monitorControllerV2);
+ const notificationChannelRoutesV2 = new NotificationChannelRoutesV2(controllers.notificationChannelControllerV2);
+ const queueRoutesV2 = new QueueRoutesV2(controllers.queueControllerV2);
+
+ app.use("/api/v2/auth", authApiLimiter, authRoutesV2.getRouter());
+ app.use("/api/v2/invite", inviteRoutesV2.getRouter());
+ app.use("/api/v2/maintenance", maintenanceRoutesV2.getRouter());
+ app.use("/api/v2/monitors", monitorRoutesV2.getRouter());
+ app.use("/api/v2/notification-channels", notificationChannelRoutesV2.getRouter());
+ app.use("/api/v2/queue", queueRoutesV2.getRouter());
};
diff --git a/server/src/config/services.js b/server/src/config/services.js
index 08c2525f3..c1679868d 100644
--- a/server/src/config/services.js
+++ b/server/src/config/services.js
@@ -1,22 +1,22 @@
-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 ServiceRegistry from "../service/v1/system/serviceRegistry.js";
+import TranslationService from "../service/v1/system/translationService.js";
+import StringService from "../service/v1/system/stringService.js";
+import MongoDB from "../db/v1/MongoDB.js";
+import NetworkService from "../service/v1/infrastructure/networkService.js";
+import EmailService from "../service/v1/infrastructure/emailService.js";
+import BufferService from "../service/v1/infrastructure/bufferService.js";
+import StatusService from "../service/v1/infrastructure/statusService.js";
+import NotificationUtils from "../service/v1/infrastructure/notificationUtils.js";
+import NotificationService from "../service/v1/infrastructure/notificationService.js";
+import ErrorService from "../service/v1/infrastructure/errorService.js";
+import SuperSimpleQueueHelper from "../service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js";
+import SuperSimpleQueue from "../service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js";
+import UserService from "../service/v1/business/userService.js";
+import CheckService from "../service/v1/business/checkService.js";
+import DiagnosticService from "../service/v1/business/diagnosticService.js";
+import InviteService from "../service/v1/business/inviteService.js";
+import MaintenanceWindowService from "../service/v1/business/maintenanceWindowService.js";
+import MonitorService from "../service/v1/business/monitorService.js";
import papaparse from "papaparse";
import axios from "axios";
import got from "got";
@@ -46,27 +46,49 @@ 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 Check from "../db/v1/models/Check.js";
+import Monitor from "../db/v1/models/Monitor.js";
+import User from "../db/v1/models/User.js";
+import InviteToken from "../db/v1/models/InviteToken.js";
+import StatusPage from "../db/v1/models/StatusPage.js";
+import Team from "../db/v1/models/Team.js";
+import MaintenanceWindow from "../db/v1/models/MaintenanceWindow.js";
+import MonitorStats from "../db/v1/models/MonitorStats.js";
+import Notification from "../db/v1/models/Notification.js";
+import RecoveryToken from "../db/v1/models/RecoveryToken.js";
+import AppSettings from "../db/v1/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";
+import InviteModule from "../db/v1/modules/inviteModule.js";
+import CheckModule from "../db/v1/modules/checkModule.js";
+import StatusPageModule from "../db/v1/modules/statusPageModule.js";
+import UserModule from "../db/v1/modules/userModule.js";
+import MaintenanceWindowModule from "../db/v1/modules/maintenanceWindowModule.js";
+import MonitorModule from "../db/v1/modules/monitorModule.js";
+import NotificationModule from "../db/v1/modules/notificationModule.js";
+import RecoveryModule from "../db/v1/modules/recoveryModule.js";
+import SettingsModule from "../db/v1/modules/settingsModule.js";
+
+// V2 Business
+import AuthServiceV2 from "../service/v2/business/AuthService.js";
+import CheckServiceV2 from "../service/v2/business/CheckService.js";
+import InviteServiceV2 from "../service/v2/business/InviteService.js";
+import MaintenanceServiceV2 from "../service/v2/business/MaintenanceService.js";
+import MonitorServiceV2 from "../service/v2/business/MonitorService.js";
+import MonitorStatsServiceV2 from "../service/v2/business/MonitorStatsService.js";
+import NotificationChannelServiceV2 from "../service/v2/business/NotificationChannelService.js";
+import QueueServiceV2 from "../service/v2/business/QueueService.js";
+import UserServiceV2 from "../service/v2/business/UserService.js";
+
+// V2 Infra
+import DiscordServiceV2 from "../service/v2/infrastructure/NotificationServices/Discord.js";
+import EmailServiceV2 from "../service/v2/infrastructure/NotificationServices/Email.js";
+import SlackServiceV2 from "../service/v2/infrastructure/NotificationServices/Slack.js";
+import WebhookServiceV2 from "../service/v2/infrastructure/NotificationServices/Webhook.js";
+import JobGeneratorV2 from "../service/v2/infrastructure/JobGenerator.js";
+import JobQueueV2 from "../service/v2/infrastructure/JobQueue.js";
+import NetworkServiceV2 from "../service/v2/infrastructure/NetworkService.js";
+import NotificationServiceV2 from "../service/v2/infrastructure/NotificationService.js";
+import StatusServiceV2 from "../service/v2/infrastructure/StatusService.js";
export const initializeServices = async ({ logger, envSettings, settingsService }) => {
const serviceRegistry = new ServiceRegistry({ logger });
@@ -208,7 +230,35 @@ export const initializeServices = async ({ logger, envSettings, settingsService
games,
});
+ // V2 Services
+ const checkServiceV2 = new CheckServiceV2();
+ const inviteServiceV2 = new InviteServiceV2();
+ const maintenanceServiceV2 = new MaintenanceServiceV2();
+ const monitorStatsServiceV2 = new MonitorStatsServiceV2();
+ const notificationChannelServiceV2 = new NotificationChannelServiceV2();
+ const userServiceV2 = new UserServiceV2();
+ const discordServiceV2 = new DiscordServiceV2();
+ const emailServiceV2 = new EmailServiceV2(userServiceV2);
+ const slackServiceV2 = new SlackServiceV2();
+ const webhookServiceV2 = new WebhookServiceV2();
+ const networkServiceV2 = new NetworkServiceV2();
+ const statusServiceV2 = new StatusServiceV2();
+ const notificationServiceV2 = new NotificationServiceV2(userServiceV2);
+ const jobGeneratorV2 = new JobGeneratorV2(
+ networkServiceV2,
+ checkServiceV2,
+ monitorStatsServiceV2,
+ statusServiceV2,
+ notificationServiceV2,
+ maintenanceServiceV2
+ );
+ const jobQueueV2 = await JobQueueV2.create(jobGeneratorV2);
+ const authServiceV2 = new AuthServiceV2(jobQueueV2);
+ const monitorServiceV2 = new MonitorServiceV2(jobQueueV2);
+ const queueServiceV2 = new QueueServiceV2(jobQueueV2);
+
const services = {
+ //v1
settingsService,
translationService,
stringService,
@@ -227,6 +277,25 @@ export const initializeServices = async ({ logger, envSettings, settingsService
monitorService,
errorService,
logger,
+ //v2
+ jobQueueV2,
+ authServiceV2,
+ checkServiceV2,
+ inviteServiceV2,
+ maintenanceServiceV2,
+ monitorServiceV2,
+ monitorStatsServiceV2,
+ notificationChannelServiceV2,
+ queueServiceV2,
+ userServiceV2,
+ discordServiceV2,
+ emailServiceV2,
+ slackServiceV2,
+ webhookServiceV2,
+ networkServiceV2,
+ statusServiceV2,
+ notificationServiceV2,
+ jobGeneratorV2,
};
Object.values(services).forEach((service) => {
diff --git a/server/src/controllers/announcementsController.js b/server/src/controllers/v1/announcementsController.js
similarity index 96%
rename from server/src/controllers/announcementsController.js
rename to server/src/controllers/v1/announcementsController.js
index 430859791..0425b44aa 100755
--- a/server/src/controllers/announcementsController.js
+++ b/server/src/controllers/v1/announcementsController.js
@@ -1,4 +1,4 @@
-import { createAnnouncementValidation } from "../validation/joi.js";
+import { createAnnouncementValidation } from "../../validation/joi.js";
import BaseController from "./baseController.js";
const SERVICE_NAME = "announcementController";
diff --git a/server/src/controllers/authController.js b/server/src/controllers/v1/authController.js
similarity index 95%
rename from server/src/controllers/authController.js
rename to server/src/controllers/v1/authController.js
index c186e8423..e52de3849 100755
--- a/server/src/controllers/authController.js
+++ b/server/src/controllers/v1/authController.js
@@ -10,10 +10,10 @@ import {
editUserByIdParamValidation,
editUserByIdBodyValidation,
editSuperadminUserByIdBodyValidation,
-} from "../validation/joi.js";
+ editUserPasswordByIdBodyValidation,
+} from "../../validation/joi.js";
const SERVICE_NAME = "authController";
-
/**
* Authentication Controller
*
@@ -436,7 +436,7 @@ class AuthController extends BaseController {
async (req, res) => {
const roles = req?.user?.role;
if (!roles.includes("superadmin")) {
- throw createError("Unauthorized", 403);
+ throw this.errorService.createError("Unauthorized", 403);
}
const userId = req.params.userId;
@@ -456,6 +456,23 @@ class AuthController extends BaseController {
SERVICE_NAME,
"editUserById"
);
+ editUserPasswordById = this.asyncHandler(
+ async (req, res) => {
+ const roles = req?.user?.role;
+ if (!roles.includes("superadmin")) {
+ throw this.errorService.createError("Unauthorized", 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.success({ msg: "Password reset successfully" });
+ },
+ SERVICE_NAME,
+ "editUserPasswordById"
+ );
}
export default AuthController;
diff --git a/server/src/controllers/baseController.js b/server/src/controllers/v1/baseController.js
similarity index 97%
rename from server/src/controllers/baseController.js
rename to server/src/controllers/v1/baseController.js
index 910cf57b4..e8612cec5 100644
--- a/server/src/controllers/baseController.js
+++ b/server/src/controllers/v1/baseController.js
@@ -1,4 +1,4 @@
-import { AppError } from "../service/infrastructure/errorService.js";
+import { AppError } from "../../service/v1/infrastructure/errorService.js";
export const createCommonDependencies = (db, errorService, logger, stringService) => {
return {
diff --git a/server/src/controllers/checkController.js b/server/src/controllers/v1/checkController.js
similarity index 99%
rename from server/src/controllers/checkController.js
rename to server/src/controllers/v1/checkController.js
index 4069df3ff..ea293d74d 100755
--- a/server/src/controllers/checkController.js
+++ b/server/src/controllers/v1/checkController.js
@@ -9,7 +9,7 @@ import {
ackCheckBodyValidation,
ackAllChecksParamValidation,
ackAllChecksBodyValidation,
-} from "../validation/joi.js";
+} from "../../validation/joi.js";
const SERVICE_NAME = "checkController";
diff --git a/server/src/controllers/controllerUtils.js b/server/src/controllers/v1/controllerUtils.js
similarity index 100%
rename from server/src/controllers/controllerUtils.js
rename to server/src/controllers/v1/controllerUtils.js
diff --git a/server/src/controllers/diagnosticController.js b/server/src/controllers/v1/diagnosticController.js
similarity index 100%
rename from server/src/controllers/diagnosticController.js
rename to server/src/controllers/v1/diagnosticController.js
diff --git a/server/src/controllers/inviteController.js b/server/src/controllers/v1/inviteController.js
similarity index 99%
rename from server/src/controllers/inviteController.js
rename to server/src/controllers/v1/inviteController.js
index a377234c0..1ec586f31 100755
--- a/server/src/controllers/inviteController.js
+++ b/server/src/controllers/v1/inviteController.js
@@ -1,4 +1,4 @@
-import { inviteBodyValidation, inviteVerificationBodyValidation } from "../validation/joi.js";
+import { inviteBodyValidation, inviteVerificationBodyValidation } from "../../validation/joi.js";
import BaseController from "./baseController.js";
const SERVICE_NAME = "inviteController";
diff --git a/server/src/controllers/logController.js b/server/src/controllers/v1/logController.js
similarity index 100%
rename from server/src/controllers/logController.js
rename to server/src/controllers/v1/logController.js
diff --git a/server/src/controllers/maintenanceWindowController.js b/server/src/controllers/v1/maintenanceWindowController.js
old mode 100755
new mode 100644
similarity index 99%
rename from server/src/controllers/maintenanceWindowController.js
rename to server/src/controllers/v1/maintenanceWindowController.js
index 2fdb1ee15..be373ace5
--- a/server/src/controllers/maintenanceWindowController.js
+++ b/server/src/controllers/v1/maintenanceWindowController.js
@@ -6,7 +6,7 @@ import {
getMaintenanceWindowsByMonitorIdParamValidation,
getMaintenanceWindowsByTeamIdQueryValidation,
deleteMaintenanceWindowByIdParamValidation,
-} from "../validation/joi.js";
+} from "../../validation/joi.js";
import BaseController from "./baseController.js";
const SERVICE_NAME = "maintenanceWindowController";
diff --git a/server/src/controllers/monitorController.js b/server/src/controllers/v1/monitorController.js
old mode 100755
new mode 100644
similarity index 96%
rename from server/src/controllers/monitorController.js
rename to server/src/controllers/v1/monitorController.js
index 0f40a4a01..5ac44fa94
--- a/server/src/controllers/monitorController.js
+++ b/server/src/controllers/v1/monitorController.js
@@ -11,7 +11,7 @@ import {
getCertificateParamValidation,
getHardwareDetailsByIdParamValidation,
getHardwareDetailsByIdQueryValidation,
-} from "../validation/joi.js";
+} from "../../validation/joi.js";
import sslChecker from "ssl-checker";
import { fetchMonitorCertificate } from "./controllerUtils.js";
import BaseController from "./baseController.js";
@@ -452,6 +452,24 @@ class MonitorController extends BaseController {
SERVICE_NAME,
"getAllGames"
);
+
+ getGroupsByTeamId = this.asyncHandler(
+ async (req, res) => {
+ const teamId = req?.user?.teamId;
+ if (!teamId) {
+ throw this.errorService.createBadRequestError("Team ID is required");
+ }
+
+ const groups = await this.monitorService.getGroupsByTeamId({ teamId });
+
+ return res.success({
+ msg: "OK",
+ data: groups,
+ });
+ },
+ SERVICE_NAME,
+ "getGroupsByTeamId"
+ );
}
export default MonitorController;
diff --git a/server/src/controllers/notificationController.js b/server/src/controllers/v1/notificationController.js
old mode 100755
new mode 100644
similarity index 98%
rename from server/src/controllers/notificationController.js
rename to server/src/controllers/v1/notificationController.js
index e00dd5b6f..37c2b4e60
--- a/server/src/controllers/notificationController.js
+++ b/server/src/controllers/v1/notificationController.js
@@ -1,4 +1,4 @@
-import { createNotificationBodyValidation } from "../validation/joi.js";
+import { createNotificationBodyValidation } from "../../validation/joi.js";
import BaseController from "./baseController.js";
const SERVICE_NAME = "NotificationController";
diff --git a/server/src/controllers/queueController.js b/server/src/controllers/v1/queueController.js
similarity index 100%
rename from server/src/controllers/queueController.js
rename to server/src/controllers/v1/queueController.js
diff --git a/server/src/controllers/settingsController.js b/server/src/controllers/v1/settingsController.js
old mode 100755
new mode 100644
similarity index 95%
rename from server/src/controllers/settingsController.js
rename to server/src/controllers/v1/settingsController.js
index 7da724d23..3db73a05a
--- a/server/src/controllers/settingsController.js
+++ b/server/src/controllers/v1/settingsController.js
@@ -1,5 +1,5 @@
-import { updateAppSettingsBodyValidation } from "../validation/joi.js";
-import { sendTestEmailBodyValidation } from "../validation/joi.js";
+import { updateAppSettingsBodyValidation } from "../../validation/joi.js";
+import { sendTestEmailBodyValidation } from "../../validation/joi.js";
import BaseController from "./baseController.js";
const SERVICE_NAME = "SettingsController";
diff --git a/server/src/controllers/statusPageController.js b/server/src/controllers/v1/statusPageController.js
old mode 100755
new mode 100644
similarity index 99%
rename from server/src/controllers/statusPageController.js
rename to server/src/controllers/v1/statusPageController.js
index a46688590..de6a1ca7c
--- a/server/src/controllers/statusPageController.js
+++ b/server/src/controllers/v1/statusPageController.js
@@ -1,4 +1,4 @@
-import { createStatusPageBodyValidation, getStatusPageParamValidation, getStatusPageQueryValidation, imageValidation } from "../validation/joi.js";
+import { createStatusPageBodyValidation, getStatusPageParamValidation, getStatusPageQueryValidation, imageValidation } from "../../validation/joi.js";
import BaseController from "./baseController.js";
const SERVICE_NAME = "statusPageController";
diff --git a/server/src/controllers/v2/AuthController.ts b/server/src/controllers/v2/AuthController.ts
new file mode 100644
index 000000000..3afed5312
--- /dev/null
+++ b/server/src/controllers/v2/AuthController.ts
@@ -0,0 +1,150 @@
+import { Request, Response, NextFunction } from "express";
+import { encode, decode } from "../../utils/JWTUtils.js";
+import AuthService from "../../service/v2/business/AuthService.js";
+import ApiError from "../../utils/ApiError.js";
+import InviteService from "../../service/v2/business/InviteService.js";
+import { IInvite } from "../../db/v2/models/index.js";
+
+class AuthController {
+ private authService: AuthService;
+ private inviteService: InviteService;
+ constructor(authService: AuthService, inviteService: InviteService) {
+ this.authService = authService;
+ this.inviteService = inviteService;
+ }
+
+ register = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const { email, firstName, lastName, password } = req.body;
+
+ if (!email || !firstName || !lastName || !password) {
+ throw new Error("Email, firstName, lastName, and password are required");
+ }
+
+ const result = await this.authService.register({
+ email,
+ firstName,
+ lastName,
+ password,
+ });
+
+ const token = encode(result);
+
+ res.cookie("token", token, {
+ httpOnly: true,
+ secure: process.env.NODE_ENV === "production",
+ sameSite: "strict",
+ maxAge: 7 * 24 * 60 * 60 * 1000, // 1 week
+ });
+
+ res.status(201).json({
+ message: "User created successfully",
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ registerWithInvite = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const token = req.params.token;
+ if (!token) {
+ throw new ApiError("Invite token is required", 400);
+ }
+
+ const invite: IInvite = await this.inviteService.get(token);
+
+ const { firstName, lastName, password } = req.body;
+ const email = invite?.email;
+ const roles = invite?.roles;
+
+ if (!email || !firstName || !lastName || !password || !roles || roles.length === 0) {
+ throw new Error("Email, firstName, lastName, password, and roles are required");
+ }
+
+ const result = await this.authService.registerWithInvite({
+ email,
+ firstName,
+ lastName,
+ password,
+ roles,
+ });
+
+ if (!result) {
+ throw new Error("Registration failed");
+ }
+
+ await this.inviteService.delete(invite._id.toString());
+
+ const jwt = encode(result);
+
+ res.cookie("token", jwt, {
+ httpOnly: true,
+ secure: process.env.NODE_ENV === "production",
+ sameSite: "strict",
+ maxAge: 7 * 24 * 60 * 60 * 1000, // 1 week
+ });
+
+ res.status(201).json({ message: "User created successfully" });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ login = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const { email, password } = req.body;
+ // Validation
+ if (!email || !password) {
+ return res.status(400).json({ message: "Email and password are required" });
+ }
+ const result = await this.authService.login({ email, password });
+
+ const token = encode(result);
+
+ res.cookie("token", token, {
+ httpOnly: true,
+ secure: process.env.NODE_ENV === "production",
+ sameSite: "strict",
+ maxAge: 7 * 24 * 60 * 60 * 1000, // 1 week
+ });
+
+ res.status(200).json({
+ message: "Login successful",
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ logout = (req: Request, res: Response) => {
+ res.clearCookie("token", {
+ httpOnly: true,
+ secure: process.env.NODE_ENV === "production",
+ sameSite: "strict",
+ });
+ res.status(200).json({ message: "Logout successful" });
+ };
+
+ me = (req: Request, res: Response, next: NextFunction) => {
+ return res.status(200).json({ message: "OK" });
+ };
+
+ cleanup = async (req: Request, res: Response) => {
+ try {
+ await this.authService.cleanup();
+ res.status(200).json({ message: "Cleanup successful" });
+ } catch (error) {}
+ };
+
+ cleanMonitors = async (req: Request, res: Response) => {
+ try {
+ await this.authService.cleanMonitors();
+ res.status(200).json({ message: "Monitors cleanup successful" });
+ } catch (error) {
+ res.status(500).json({ message: "Internal server error" });
+ }
+ };
+}
+
+export default AuthController;
diff --git a/server/src/controllers/v2/InviteController.ts b/server/src/controllers/v2/InviteController.ts
new file mode 100644
index 000000000..4d72bf8d4
--- /dev/null
+++ b/server/src/controllers/v2/InviteController.ts
@@ -0,0 +1,62 @@
+import { Request, Response, NextFunction } from "express";
+import InviteService from "../../service/v2/business/InviteService.js";
+
+class InviteController {
+ private inviteService: InviteService;
+ constructor(inviteService: InviteService) {
+ this.inviteService = inviteService;
+ }
+
+ create = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const tokenizedUser = req.user;
+ if (!tokenizedUser) {
+ return res.status(401).json({ message: "Unauthorized" });
+ }
+ const invite = await this.inviteService.create(tokenizedUser, req.body);
+ res.status(201).json({ message: "OK", data: invite });
+ } catch (error: any) {
+ next(error);
+ }
+ };
+
+ getAll = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const invites = await this.inviteService.getAll();
+ res.status(200).json({
+ message: "OK",
+ data: invites,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ get = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const token = req.params.token;
+ if (!token) {
+ return res.status(400).json({ message: "Token parameter is required" });
+ }
+ const invite = await this.inviteService.get(token);
+ res.status(200).json({ message: "OK", data: invite });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ delete = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const id = req.params.id;
+ if (!id) {
+ return res.status(400).json({ message: "ID parameter is required" });
+ }
+ await this.inviteService.delete(id);
+ res.status(204).json({ message: "OK" });
+ } catch (error: any) {
+ next(error);
+ }
+ };
+}
+
+export default InviteController;
diff --git a/server/src/controllers/v2/MaintenanceController.ts b/server/src/controllers/v2/MaintenanceController.ts
new file mode 100644
index 000000000..7c6be11d5
--- /dev/null
+++ b/server/src/controllers/v2/MaintenanceController.ts
@@ -0,0 +1,96 @@
+import { Request, Response, NextFunction } from "express";
+import MaintenanceService from "../../service/v2/business/MaintenanceService.js";
+
+class MaintenanceController {
+ private maintenanceService: MaintenanceService;
+ constructor(maintenanceService: MaintenanceService) {
+ this.maintenanceService = maintenanceService;
+ }
+
+ create = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const tokenizedUser = req.user;
+ if (!tokenizedUser) {
+ return res.status(401).json({ message: "Unauthorized" });
+ }
+ const maintenance = await this.maintenanceService.create(tokenizedUser, req.body);
+ res.status(201).json({ message: "OK", data: maintenance });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getAll = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const maintenances = await this.maintenanceService.getAll();
+ res.status(200).json({
+ message: "OK",
+ data: maintenances,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ toggleActive = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const tokenizedUser = req.user;
+ if (!tokenizedUser) {
+ return res.status(401).json({ message: "Unauthorized" });
+ }
+ const id = req.params.id;
+ if (!id) {
+ return res.status(400).json({ message: "ID parameter is required" });
+ }
+ const maintenance = await this.maintenanceService.toggleActive(tokenizedUser, id);
+ res.status(200).json({ message: "OK", data: maintenance });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ update = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const tokenizedUser = req.user;
+ if (!tokenizedUser) {
+ return res.status(401).json({ message: "Unauthorized" });
+ }
+ const id = req.params.id;
+ if (!id) {
+ return res.status(400).json({ message: "ID parameter is required" });
+ }
+ const updatedMaintenance = await this.maintenanceService.update(tokenizedUser, id, req.body);
+ res.status(200).json({ message: "OK", data: updatedMaintenance });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ get = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const id = req.params.id;
+ if (!id) {
+ return res.status(400).json({ message: "ID parameter is required" });
+ }
+ const maintenance = await this.maintenanceService.get(id);
+ res.status(200).json({ message: "OK", data: maintenance });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ delete = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const id = req.params.id;
+ if (!id) {
+ return res.status(400).json({ message: "ID parameter is required" });
+ }
+ await this.maintenanceService.delete(id);
+ res.status(204).json({ message: "OK" });
+ } catch (error) {
+ next(error);
+ }
+ };
+}
+
+export default MaintenanceController;
diff --git a/server/src/controllers/v2/MonitorController.ts b/server/src/controllers/v2/MonitorController.ts
new file mode 100644
index 000000000..7c0c63d2c
--- /dev/null
+++ b/server/src/controllers/v2/MonitorController.ts
@@ -0,0 +1,191 @@
+import { Request, Response, NextFunction } from "express";
+import ApiError from "../../utils/ApiError.js";
+import MonitorService from "../../service/v2/business/MonitorService.js";
+import { MonitorType } from "../../db/v2/models/monitors/Monitor.js";
+import CheckService from "../../service/v2/business/CheckService.js";
+class MonitorController {
+ private monitorService: MonitorService;
+ private checkService: CheckService;
+ constructor(monitorService: MonitorService, checkService: CheckService) {
+ this.monitorService = monitorService;
+ this.checkService = checkService;
+ }
+
+ create = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const tokenizedUser = req.user;
+ if (!tokenizedUser) {
+ return res.status(401).json({ message: "Unauthorized" });
+ }
+
+ const monitor = await this.monitorService.create(tokenizedUser, req.body);
+ res.status(201).json({
+ message: "Monitor created successfully",
+ data: monitor,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getAll = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const tokenizedUser = req.user;
+ if (!tokenizedUser) {
+ return res.status(401).json({ message: "Unauthorized" });
+ }
+
+ let monitors;
+ if (req.query.embedChecks === "true") {
+ const page = Math.max(1, Number(req.query.page) || 1);
+ const limit = Math.max(1, Number(req.query.limit) || 10);
+ const type: MonitorType[] = req.query.type as MonitorType[];
+
+ monitors = await this.monitorService.getAllEmbedChecks(page, limit, type);
+ } else {
+ monitors = await this.monitorService.getAll();
+ }
+
+ res.status(200).json({
+ message: "Monitors retrieved successfully",
+ data: monitors,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getChecks = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const tokenizedUser = req.user;
+ if (!tokenizedUser) {
+ return res.status(401).json({ message: "Unauthorized" });
+ }
+
+ const id = req.params.id;
+ if (!id) {
+ throw new ApiError("Monitor ID is required", 400);
+ }
+
+ const page = Number(req.query.page);
+ const rowsPerPage = Number(req.query.rowsPerPage);
+
+ if (isNaN(page)) throw new ApiError("Page query parameter must be a number", 400);
+ if (isNaN(rowsPerPage)) throw new ApiError("rowsPerPage query parameter must be a number", 400);
+
+ if (page < 0) throw new ApiError("Page must be greater than 0", 400);
+ if (rowsPerPage < 0) throw new ApiError("rowsPerPage must be greater than 0", 400);
+
+ const { count, checks } = await this.checkService.getChecks(id, page, rowsPerPage);
+ res.status(200).json({
+ message: "Checks retrieved successfully",
+ data: { count, checks },
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ toggleActive = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const tokenizedUser = req.user;
+ if (!tokenizedUser) {
+ return res.status(401).json({ message: "Unauthorized" });
+ }
+
+ const id = req.params.id;
+ if (!id) {
+ throw new ApiError("Monitor ID is required", 400);
+ }
+
+ const monitor = await this.monitorService.toggleActive(id, tokenizedUser);
+ res.status(200).json({
+ message: "Monitor paused/unpaused successfully",
+ data: monitor,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ get = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const tokenizedUser = req.user;
+ if (!tokenizedUser) {
+ return res.status(401).json({ message: "Unauthorized" });
+ }
+
+ const id = req.params.id;
+ if (!id) {
+ throw new ApiError("Monitor ID is required", 400);
+ }
+
+ const range = req.query.range;
+ if (!range || typeof range !== "string") throw new ApiError("Range query parameter is required", 400);
+
+ let monitor;
+
+ const status = req.query.status;
+ if (status && typeof status !== "string") {
+ throw new ApiError("Status query parameter must be a string", 400);
+ }
+
+ if (req.query.embedChecks === "true") {
+ monitor = await this.monitorService.getEmbedChecks(id, range, status);
+ } else {
+ monitor = await this.monitorService.get(id);
+ }
+
+ res.status(200).json({
+ message: "Monitor retrieved successfully",
+ data: monitor,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ update = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const tokenizedUser = req.user;
+ if (!tokenizedUser) {
+ return res.status(401).json({ message: "Unauthorized" });
+ }
+
+ const id = req.params.id;
+ if (!id) {
+ throw new ApiError("Monitor ID is required", 400);
+ }
+
+ const monitor = await this.monitorService.update(tokenizedUser, id, req.body);
+ res.status(200).json({
+ message: "Monitor updated successfully",
+ data: monitor,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ delete = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const tokenizedUser = req.user;
+ if (!tokenizedUser) {
+ return res.status(401).json({ message: "Unauthorized" });
+ }
+ const id = req.params.id;
+ if (!id) {
+ throw new ApiError("Monitor ID is required", 400);
+ }
+ await this.monitorService.delete(id);
+
+ res.status(200).json({
+ message: "Monitor deleted successfully",
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+}
+
+export default MonitorController;
diff --git a/server/src/controllers/v2/NotificationChannelController.ts b/server/src/controllers/v2/NotificationChannelController.ts
new file mode 100644
index 000000000..e13f572f4
--- /dev/null
+++ b/server/src/controllers/v2/NotificationChannelController.ts
@@ -0,0 +1,96 @@
+import { Request, Response, NextFunction } from "express";
+import NotificationService from "../../service/v2/business/NotificationChannelService.js";
+
+class NotificationChannelController {
+ private notificationService: NotificationService;
+ constructor(notificationService: NotificationService) {
+ this.notificationService = notificationService;
+ }
+
+ create = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const tokenizedUser = req.user;
+ if (!tokenizedUser) {
+ return res.status(401).json({ message: "Unauthorized" });
+ }
+ const channel = await this.notificationService.create(tokenizedUser, req.body);
+ res.status(201).json({ message: "OK", data: channel });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getAll = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const notificationChannels = await this.notificationService.getAll();
+ res.status(200).json({
+ message: "OK",
+ data: notificationChannels,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ toggleActive = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const tokenizedUser = req.user;
+ if (!tokenizedUser) {
+ return res.status(401).json({ message: "Unauthorized" });
+ }
+ const id = req.params.id;
+ if (!id) {
+ return res.status(400).json({ message: "ID parameter is required" });
+ }
+ const notificationChannel = await this.notificationService.toggleActive(tokenizedUser, id);
+ res.status(200).json({ message: "OK", data: notificationChannel });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ update = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const tokenizedUser = req.user;
+ if (!tokenizedUser) {
+ return res.status(401).json({ message: "Unauthorized" });
+ }
+ const id = req.params.id;
+ if (!id) {
+ return res.status(400).json({ message: "ID parameter is required" });
+ }
+ const updatedChannel = await this.notificationService.update(tokenizedUser, id, req.body);
+ res.status(200).json({ message: "OK", data: updatedChannel });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ get = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const id = req.params.id;
+ if (!id) {
+ return res.status(400).json({ message: "ID parameter is required" });
+ }
+ const notificationChannel = await this.notificationService.get(id);
+ res.status(200).json({ message: "OK", data: notificationChannel });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ delete = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const id = req.params.id;
+ if (!id) {
+ return res.status(400).json({ message: "ID parameter is required" });
+ }
+ await this.notificationService.delete(id);
+ res.status(204).json({ message: "OK" });
+ } catch (error) {
+ next(error);
+ }
+ };
+}
+
+export default NotificationChannelController;
diff --git a/server/src/controllers/v2/QueueController.ts b/server/src/controllers/v2/QueueController.ts
new file mode 100644
index 000000000..510703894
--- /dev/null
+++ b/server/src/controllers/v2/QueueController.ts
@@ -0,0 +1,29 @@
+import { Request, Response, NextFunction } from "express";
+import QueueService from "../../service/v2/business/QueueService.js";
+class QueueController {
+ private queueService: QueueService;
+ constructor(queueService: QueueService) {
+ this.queueService = queueService;
+ }
+
+ getJobs = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const jobs = await this.queueService.getJobs();
+ res.status(200).json({ message: "ok", data: jobs });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getMetrics = async (req: Request, res: Response, next: NextFunction) => {
+ const metrics = await this.queueService.getMetrics();
+ res.status(200).json({ message: "ok", data: metrics });
+ };
+
+ flush = async (req: Request, res: Response, next: NextFunction) => {
+ const result = await this.queueService.flush();
+ res.status(200).json({ message: "ok", flushed: result });
+ };
+}
+
+export default QueueController;
diff --git a/server/src/db/mongo/MongoDB.js b/server/src/db/v1/MongoDB.js
similarity index 97%
rename from server/src/db/mongo/MongoDB.js
rename to server/src/db/v1/MongoDB.js
index 74fcbd89f..3bdae3525 100755
--- a/server/src/db/mongo/MongoDB.js
+++ b/server/src/db/v1/MongoDB.js
@@ -1,5 +1,5 @@
import mongoose from "mongoose";
-import AppSettings from "../models/AppSettings.js";
+import AppSettings from "./models/AppSettings.js";
import { runMigrations } from "./migration/index.js";
class MongoDB {
static SERVICE_NAME = "MongoDB";
diff --git a/server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.js b/server/src/db/v1/migration/0001_migrateStatusWindowThreshold.ts
similarity index 92%
rename from server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.js
rename to server/src/db/v1/migration/0001_migrateStatusWindowThreshold.ts
index 8f6988de4..857d36381 100644
--- a/server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.js
+++ b/server/src/db/v1/migration/0001_migrateStatusWindowThreshold.ts
@@ -1,4 +1,4 @@
-import Monitor from "../../models/Monitor.js";
+import Monitor from "../models/Monitor.js";
async function migrateStatusWindowThreshold() {
try {
const monitors = await Monitor.find({ statusWindowThreshold: { $lt: 1 } });
diff --git a/server/src/db/mongo/migration/index.js b/server/src/db/v1/migration/index.ts
similarity index 100%
rename from server/src/db/mongo/migration/index.js
rename to server/src/db/v1/migration/index.ts
diff --git a/server/src/db/models/AppSettings.js b/server/src/db/v1/models/AppSettings.js
similarity index 100%
rename from server/src/db/models/AppSettings.js
rename to server/src/db/v1/models/AppSettings.js
diff --git a/server/src/db/models/Check.js b/server/src/db/v1/models/Check.js
similarity index 100%
rename from server/src/db/models/Check.js
rename to server/src/db/v1/models/Check.js
diff --git a/server/src/db/models/InviteToken.js b/server/src/db/v1/models/InviteToken.js
similarity index 100%
rename from server/src/db/models/InviteToken.js
rename to server/src/db/v1/models/InviteToken.js
diff --git a/server/src/db/models/MaintenanceWindow.js b/server/src/db/v1/models/MaintenanceWindow.js
similarity index 100%
rename from server/src/db/models/MaintenanceWindow.js
rename to server/src/db/v1/models/MaintenanceWindow.js
diff --git a/server/src/db/models/Monitor.js b/server/src/db/v1/models/Monitor.js
similarity index 96%
rename from server/src/db/models/Monitor.js
rename to server/src/db/v1/models/Monitor.js
index 0e12d3c26..e6e1a0024 100755
--- a/server/src/db/models/Monitor.js
+++ b/server/src/db/v1/models/Monitor.js
@@ -128,6 +128,15 @@ const MonitorSchema = mongoose.Schema(
gameId: {
type: String,
},
+ group: {
+ type: String,
+ trim: true,
+ maxLength: 50,
+ default: null,
+ set: function (value) {
+ return value && value.trim() ? value.trim() : null;
+ },
+ },
},
{
timestamps: true,
diff --git a/server/src/db/models/MonitorStats.js b/server/src/db/v1/models/MonitorStats.js
similarity index 100%
rename from server/src/db/models/MonitorStats.js
rename to server/src/db/v1/models/MonitorStats.js
diff --git a/server/src/db/models/Notification.js b/server/src/db/v1/models/Notification.js
similarity index 87%
rename from server/src/db/models/Notification.js
rename to server/src/db/v1/models/Notification.js
index a6a3c7c09..72c716a26 100755
--- a/server/src/db/models/Notification.js
+++ b/server/src/db/v1/models/Notification.js
@@ -16,7 +16,7 @@ const NotificationSchema = mongoose.Schema(
},
type: {
type: String,
- enum: ["email", "slack", "discord", "webhook", "pager_duty"],
+ enum: ["email", "slack", "discord", "webhook", "pager_duty", "matrix"],
},
notificationName: {
type: String,
@@ -28,6 +28,16 @@ const NotificationSchema = mongoose.Schema(
phone: {
type: String,
},
+ // Matrix-specific fields
+ homeserverUrl: {
+ type: String,
+ },
+ roomId: {
+ type: String,
+ },
+ accessToken: {
+ type: String,
+ },
},
{
timestamps: true,
diff --git a/server/src/db/models/RecoveryToken.js b/server/src/db/v1/models/RecoveryToken.js
similarity index 100%
rename from server/src/db/models/RecoveryToken.js
rename to server/src/db/v1/models/RecoveryToken.js
diff --git a/server/src/db/models/StatusPage.js b/server/src/db/v1/models/StatusPage.js
similarity index 100%
rename from server/src/db/models/StatusPage.js
rename to server/src/db/v1/models/StatusPage.js
diff --git a/server/src/db/models/Team.js b/server/src/db/v1/models/Team.js
similarity index 100%
rename from server/src/db/models/Team.js
rename to server/src/db/v1/models/Team.js
diff --git a/server/src/db/models/User.js b/server/src/db/v1/models/User.js
similarity index 98%
rename from server/src/db/models/User.js
rename to server/src/db/v1/models/User.js
index 9368d98b1..bf3012b26 100755
--- a/server/src/db/models/User.js
+++ b/server/src/db/v1/models/User.js
@@ -1,6 +1,5 @@
import mongoose from "mongoose";
import bcrypt from "bcryptjs";
-import logger from "../../utils/logger.js";
import Monitor from "./Monitor.js";
import Team from "./Team.js";
import Notification from "./Notification.js";
diff --git a/server/src/db/mongo/modules/checkModule.js b/server/src/db/v1/modules/checkModule.js
similarity index 100%
rename from server/src/db/mongo/modules/checkModule.js
rename to server/src/db/v1/modules/checkModule.js
diff --git a/server/src/db/mongo/modules/checkModuleQueries.js b/server/src/db/v1/modules/checkModuleQueries.js
similarity index 100%
rename from server/src/db/mongo/modules/checkModuleQueries.js
rename to server/src/db/v1/modules/checkModuleQueries.js
diff --git a/server/src/db/mongo/modules/inviteModule.js b/server/src/db/v1/modules/inviteModule.js
similarity index 100%
rename from server/src/db/mongo/modules/inviteModule.js
rename to server/src/db/v1/modules/inviteModule.js
diff --git a/server/src/db/mongo/modules/maintenanceWindowModule.js b/server/src/db/v1/modules/maintenanceWindowModule.js
similarity index 100%
rename from server/src/db/mongo/modules/maintenanceWindowModule.js
rename to server/src/db/v1/modules/maintenanceWindowModule.js
diff --git a/server/src/db/mongo/modules/monitorModule.js b/server/src/db/v1/modules/monitorModule.js
similarity index 97%
rename from server/src/db/mongo/modules/monitorModule.js
rename to server/src/db/v1/modules/monitorModule.js
index 8beb9316c..39168f0b2 100755
--- a/server/src/db/mongo/modules/monitorModule.js
+++ b/server/src/db/v1/modules/monitorModule.js
@@ -232,7 +232,7 @@ class MonitorModule {
const formatLookup = {
recent: "%Y-%m-%dT%H:%M:00Z",
day: "%Y-%m-%dT%H:00:00Z",
- week: "%Y-%m-%dT%H:00:00Z",
+ week: "%Y-%m-%dT00:00:00Z",
month: "%Y-%m-%dT00:00:00Z",
};
@@ -308,7 +308,7 @@ class MonitorModule {
const formatLookup = {
recent: "%Y-%m-%dT%H:%M:00Z",
day: "%Y-%m-%dT%H:00:00Z",
- week: "%Y-%m-%dT%H:00:00Z",
+ week: "%Y-%m-%dT00:00:00Z",
month: "%Y-%m-%dT00:00:00Z",
};
const dateString = formatLookup[dateRange];
@@ -573,6 +573,21 @@ class MonitorModule {
throw error;
}
};
+
+ getGroupsByTeamId = async ({ teamId }) => {
+ try {
+ const groups = await this.Monitor.distinct("group", {
+ teamId: new this.ObjectId(teamId),
+ group: { $ne: null, $ne: "" },
+ });
+
+ return groups.filter(Boolean).sort();
+ } catch (error) {
+ error.service = SERVICE_NAME;
+ error.method = "getGroupsByTeamId";
+ throw error;
+ }
+ };
}
export default MonitorModule;
diff --git a/server/src/db/mongo/modules/monitorModuleQueries.js b/server/src/db/v1/modules/monitorModuleQueries.js
similarity index 100%
rename from server/src/db/mongo/modules/monitorModuleQueries.js
rename to server/src/db/v1/modules/monitorModuleQueries.js
diff --git a/server/src/db/mongo/modules/notificationModule.js b/server/src/db/v1/modules/notificationModule.js
similarity index 100%
rename from server/src/db/mongo/modules/notificationModule.js
rename to server/src/db/v1/modules/notificationModule.js
diff --git a/server/src/db/mongo/modules/recoveryModule.js b/server/src/db/v1/modules/recoveryModule.js
similarity index 100%
rename from server/src/db/mongo/modules/recoveryModule.js
rename to server/src/db/v1/modules/recoveryModule.js
diff --git a/server/src/db/mongo/modules/settingsModule.js b/server/src/db/v1/modules/settingsModule.js
similarity index 100%
rename from server/src/db/mongo/modules/settingsModule.js
rename to server/src/db/v1/modules/settingsModule.js
diff --git a/server/src/db/mongo/modules/statusPageModule.js b/server/src/db/v1/modules/statusPageModule.js
similarity index 100%
rename from server/src/db/mongo/modules/statusPageModule.js
rename to server/src/db/v1/modules/statusPageModule.js
diff --git a/server/src/db/mongo/modules/userModule.js b/server/src/db/v1/modules/userModule.js
similarity index 100%
rename from server/src/db/mongo/modules/userModule.js
rename to server/src/db/v1/modules/userModule.js
diff --git a/server/src/db/mongo/utils/seedDb.js b/server/src/db/v1/utils/seedDb.js
similarity index 95%
rename from server/src/db/mongo/utils/seedDb.js
rename to server/src/db/v1/utils/seedDb.js
index 224744a51..cdfe3675c 100755
--- a/server/src/db/mongo/utils/seedDb.js
+++ b/server/src/db/v1/utils/seedDb.js
@@ -1,5 +1,5 @@
-import Monitor from "../../models/Monitor.js";
-import Check from "../../models/Check.js";
+import Monitor from "../models/Monitor.js";
+import Check from "../models/Check.js";
import { logger } from "../../../utils/logger.js";
const generateRandomUrl = () => {
diff --git a/server/src/db/v2/index.ts b/server/src/db/v2/index.ts
new file mode 100644
index 000000000..2cd194283
--- /dev/null
+++ b/server/src/db/v2/index.ts
@@ -0,0 +1,25 @@
+import mongoose from "mongoose";
+
+const MONGODB_URI = process.env.MONGODB_URI || "mongodb://localhost:27017/checkmate";
+
+export const connectDatabase = async (): Promise => {
+ try {
+ await mongoose.connect(MONGODB_URI);
+ console.log("Connected to MongoDB");
+ return true;
+ } catch (error) {
+ console.error("MongoDB connection error:", error);
+ process.exit(1);
+ }
+};
+
+export const disconnectDatabase = async (): Promise => {
+ try {
+ await mongoose.disconnect();
+ console.log("Disconnected from MongoDB");
+ return true;
+ } catch (error) {
+ console.error("MongoDB disconnection error:", error);
+ return false;
+ }
+};
diff --git a/server/src/db/v2/models/auth/Role.ts b/server/src/db/v2/models/auth/Role.ts
new file mode 100644
index 000000000..1031de8b2
--- /dev/null
+++ b/server/src/db/v2/models/auth/Role.ts
@@ -0,0 +1,50 @@
+import mongoose, { Schema, Document, Types } from "mongoose";
+
+export interface IRole extends Document {
+ _id: Types.ObjectId;
+ name: string;
+ description?: string;
+ permissions: string[];
+ isSystem: boolean;
+ isActive: boolean;
+ createdAt: Date;
+ updatedAt: Date;
+}
+
+const roleSchema = new Schema(
+ {
+ name: {
+ type: String,
+ required: true,
+ trim: true,
+ maxlength: 50,
+ },
+ description: {
+ type: String,
+ trim: true,
+ maxlength: 200,
+ },
+
+ permissions: [
+ {
+ type: String,
+ required: true,
+ },
+ ],
+ isSystem: {
+ type: Boolean,
+ default: false,
+ },
+ isActive: {
+ type: Boolean,
+ default: true,
+ },
+ },
+ {
+ timestamps: true,
+ }
+);
+
+roleSchema.index({ name: 1 }, { unique: true });
+
+export const Role = mongoose.model("Role_v2", roleSchema);
diff --git a/server/src/db/v2/models/auth/User.ts b/server/src/db/v2/models/auth/User.ts
new file mode 100644
index 000000000..25b9794c7
--- /dev/null
+++ b/server/src/db/v2/models/auth/User.ts
@@ -0,0 +1,102 @@
+import mongoose, { Schema, Document, Types } from "mongoose";
+
+export interface ITokenizedUser {
+ sub: string;
+ roles: string[];
+}
+export interface IUser extends Document {
+ _id: Types.ObjectId;
+ email: string;
+ firstName: string;
+ lastName: string;
+ passwordHash: string;
+ roles: Types.ObjectId[];
+ profile: {
+ avatar?: string;
+ bio?: string;
+ timezone: string;
+ locale: string;
+ };
+ preferences: {
+ theme: "light" | "dark" | "system";
+ };
+ lastLoginAt?: Date;
+ isActive: boolean;
+ isVerified: boolean;
+ createdAt: Date;
+ updatedAt: Date;
+}
+
+const userSchema = new Schema(
+ {
+ email: {
+ type: String,
+ required: true,
+ unique: true,
+ trim: true,
+ lowercase: true,
+ },
+ firstName: {
+ type: String,
+ required: true,
+ trim: true,
+ maxlength: 50,
+ },
+ lastName: {
+ type: String,
+ required: true,
+ trim: true,
+ maxlength: 50,
+ },
+ passwordHash: {
+ type: String,
+ required: true,
+ },
+ roles: [
+ {
+ type: Schema.Types.ObjectId,
+ ref: "Role_v2",
+ },
+ ],
+ profile: {
+ avatar: {
+ type: String,
+ },
+ bio: {
+ type: String,
+ maxlength: 200,
+ },
+ timezone: {
+ type: String,
+ default: "UTC",
+ },
+ locale: {
+ type: String,
+ default: "en",
+ },
+ },
+ preferences: {
+ theme: {
+ type: String,
+ enum: ["light", "dark", "system"],
+ default: "system",
+ },
+ },
+ lastLoginAt: {
+ type: Date,
+ },
+ isActive: {
+ type: Boolean,
+ default: true,
+ },
+ isVerified: {
+ type: Boolean,
+ default: false,
+ },
+ },
+ {
+ timestamps: true,
+ }
+);
+
+export const User = mongoose.model("User_v2", userSchema);
diff --git a/server/src/db/v2/models/checks/Check.ts b/server/src/db/v2/models/checks/Check.ts
new file mode 100644
index 000000000..83bbc9f91
--- /dev/null
+++ b/server/src/db/v2/models/checks/Check.ts
@@ -0,0 +1,297 @@
+import mongoose, { Schema, Document, Types } from "mongoose";
+import { MonitorType, MonitorTypes, MonitorStatus, MonitorStatuses } from "../monitors/Monitor.js";
+import type { Response } from "got";
+export type GotTimings = Response["timings"];
+export interface ITimingPhases {
+ wait: number;
+ dns: number;
+ tcp: number;
+ tls: number;
+ request: number;
+ firstByte: number;
+ download: number;
+ total: number;
+}
+
+export interface ICpuInfo {
+ physical_core: number;
+ logical_core: number;
+ frequency: number;
+ current_frequency: number;
+ temperature: number[]; // per-core temps
+ free_percent: number;
+ usage_percent: number;
+}
+
+export interface IMemoryInfo {
+ total_bytes: number;
+ available_bytes: number;
+ used_bytes: number;
+ usage_percent: number;
+}
+
+export interface IHostInfo {
+ os?: string;
+ platform?: string;
+ kernel_version?: string;
+ pretty_name?: string;
+}
+
+export interface IDiskInfo {
+ device: string;
+ total_bytes: number;
+ free_bytes: number;
+ used_bytes: number;
+ usage_percent: number;
+ total_inodes?: number;
+ free_inodes?: number;
+ used_inodes?: number;
+ inodes_usage_percent?: number;
+ read_bytes?: number;
+ write_bytes?: number;
+ read_time?: number;
+ write_time?: number;
+}
+
+export interface INetInfo {
+ 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 ICaptureInfo {
+ version?: string;
+ mode?: string;
+}
+
+export interface ISystemInfo {
+ cpu: ICpuInfo;
+ memory: IMemoryInfo;
+ disk: IDiskInfo[];
+ host: IHostInfo;
+ net: INetInfo[];
+}
+
+export interface ILighthouseAudit {
+ id?: string;
+ title?: string;
+ score?: number | null;
+ displayValue?: string;
+ numericValue?: number;
+ numericUnit?: string;
+}
+export interface ILighthouseCategories {
+ accessibility?: { score?: number | null };
+ "best-practices"?: { score?: number | null };
+ seo?: { score?: number | null };
+ performance?: { score?: number | null };
+}
+
+export interface ILighthouseResult {
+ categories?: ILighthouseCategories;
+ audits?: Record;
+}
+
+export interface ICheckLighthouseFields {
+ accessibility: number;
+ bestPractices: number;
+ seo: number;
+ performance: number;
+ audits: {
+ cls: ILighthouseAudit;
+ si: ILighthouseAudit;
+ fcp: ILighthouseAudit;
+ lcp: ILighthouseAudit;
+ tbt: ILighthouseAudit;
+ };
+}
+
+export interface ICheck extends Document {
+ _id: Types.ObjectId;
+ monitorId: Types.ObjectId;
+ type: MonitorType;
+ status: MonitorStatus;
+ message: string;
+ responseTime?: number; // in ms
+ timings?: GotTimings;
+ httpStatusCode?: number;
+ errorMessage?: string;
+ ack: boolean;
+ ackAt?: Date;
+ ackBy?: Types.ObjectId;
+ expiry: Date;
+ createdAt: Date;
+ updatedAt: Date;
+ system?: ISystemInfo;
+ capture?: ICaptureInfo;
+ lighthouse?: ICheckLighthouseFields;
+}
+
+const CheckSchema = new Schema(
+ {
+ monitorId: { type: Schema.Types.ObjectId, ref: "Monitor_v2", required: true },
+
+ type: {
+ type: String,
+ required: true,
+ enum: MonitorTypes,
+ },
+ status: {
+ type: String,
+ required: true,
+ enum: MonitorStatuses,
+ },
+ message: { type: String, trim: true },
+ responseTime: { type: Number },
+ timings: {
+ start: { type: Date },
+ socket: { type: Date },
+ lookup: { type: Date },
+ connect: { type: Date },
+ secureConnect: { type: Date },
+ response: { type: Date },
+ end: { type: Date },
+ phases: {
+ wait: { type: Number },
+ dns: { type: Number },
+ tcp: { type: Number },
+ tls: { type: Number },
+ request: { type: Number },
+ firstByte: { type: Number },
+ download: { type: Number },
+ total: { type: Number },
+ },
+ },
+ system: {
+ type: {
+ cpu: {
+ physical_core: { type: Number },
+ logical_core: { type: Number },
+ frequency: { type: Number },
+ current_frequency: { type: Number },
+ temperature: [{ type: Number }],
+ free_percent: { type: Number },
+ usage_percent: { type: Number },
+ },
+ memory: {
+ total_bytes: { type: Number },
+ available_bytes: { type: Number },
+ used_bytes: { type: Number },
+ usage_percent: { type: Number },
+ },
+ disk: [
+ {
+ device: { type: String },
+ total_bytes: { type: Number },
+ free_bytes: { type: Number },
+ used_bytes: { type: Number },
+ usage_percent: { type: Number },
+ total_inodes: { type: Number },
+ free_inodes: { type: Number },
+ used_inodes: { type: Number },
+ inodes_usage_percent: { type: Number },
+ read_bytes: { type: Number },
+ write_bytes: { type: Number },
+ read_time: { type: Number },
+ write_time: { type: Number },
+ },
+ ],
+ host: {
+ os: { type: String },
+ platform: { type: String },
+ kernel_version: { type: String },
+ pretty_name: { type: String },
+ },
+ net: [
+ {
+ 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 },
+ },
+ ],
+ },
+ required: false,
+ },
+
+ capture: {
+ type: {
+ version: { type: String },
+ mode: { type: String },
+ },
+ required: false,
+ },
+ lighthouse: {
+ accessibility: { type: Number, required: false },
+ bestPractices: { type: Number, required: false },
+ seo: { type: Number, required: false },
+ performance: { type: Number, required: false },
+ audits: {
+ cls: {
+ type: Object,
+ },
+ si: {
+ type: Object,
+ },
+ fcp: {
+ type: Object,
+ },
+ lcp: {
+ type: Object,
+ },
+ tbt: {
+ type: Object,
+ },
+ },
+ type: {
+ accessibility: { type: Number },
+ bestPractices: { type: Number },
+ seo: { type: Number },
+ performance: { type: Number },
+ audits: {
+ cls: { type: Object },
+ si: { type: Object },
+ fcp: { type: Object },
+ lcp: { type: Object },
+ tbt: { type: Object },
+ },
+ },
+ required: false,
+ },
+
+ httpStatusCode: { type: Number },
+ errorMessage: { type: String, trim: true },
+ ack: { type: Boolean, required: true, default: false },
+ ackAt: { type: Date },
+ ackBy: { type: Schema.Types.ObjectId, ref: "User_v2" },
+ expiry: {
+ type: Date,
+ default: Date.now,
+ expires: 60 * 60 * 24 * 30,
+ },
+ },
+ { timestamps: true }
+);
+
+CheckSchema.index({ monitorId: 1, createdAt: -1 });
+CheckSchema.index({ status: 1 });
+CheckSchema.index({ status: 1, ack: 1 });
+CheckSchema.index({ ack: 1, ackAt: 1 });
+CheckSchema.index({ createdAt: -1 });
+
+export const Check = mongoose.model("Check_v2", CheckSchema);
diff --git a/server/src/db/v2/models/index.ts b/server/src/db/v2/models/index.ts
new file mode 100644
index 000000000..fbc05c10f
--- /dev/null
+++ b/server/src/db/v2/models/index.ts
@@ -0,0 +1,33 @@
+export { User } from "./auth/User.js";
+export type { IUser } from "./auth/User.js";
+export type { ITokenizedUser } from "./auth/User.js";
+export { Role } from "./auth/Role.js";
+export type { IRole } from "./auth/Role.js";
+export { connectDatabase, disconnectDatabase } from "../index.js";
+export { Monitor } from "./monitors/Monitor.js";
+export { MonitorStatuses } from "./monitors/Monitor.js";
+export type { IMonitor } from "./monitors/Monitor.js";
+export { Check } from "./checks/Check.js";
+export type {
+ ICheck,
+ ISystemInfo,
+ ICaptureInfo,
+ INetInfo,
+ IDiskInfo,
+ IHostInfo,
+ IMemoryInfo,
+ ICpuInfo,
+ ILighthouseAudit,
+ ITimingPhases,
+ ILighthouseCategories,
+ ILighthouseResult,
+ ICheckLighthouseFields,
+} from "./checks/Check.js";
+export type { IMonitorStats } from "./monitors/MonitorStats.js";
+export { MonitorStats } from "./monitors/MonitorStats.js";
+export type { INotificationChannel } from "./notification-channel/NotificationChannel.js";
+export { NotificationChannel } from "./notification-channel/NotificationChannel.js";
+export type { IMaintenance } from "./maintenance/Maintenance.js";
+export { Maintenance } from "./maintenance/Maintenance.js";
+export type { IInvite } from "./invite/Invite.js";
+export { Invite } from "./invite/Invite.js";
diff --git a/server/src/db/v2/models/invite/Invite.ts b/server/src/db/v2/models/invite/Invite.ts
new file mode 100644
index 000000000..2bc569900
--- /dev/null
+++ b/server/src/db/v2/models/invite/Invite.ts
@@ -0,0 +1,43 @@
+import mongoose, { Schema, Document, Types } from "mongoose";
+
+export interface IInvite extends Document {
+ _id: Types.ObjectId;
+ email: string;
+ tokenHash: string;
+ roles: Types.ObjectId[];
+ createdBy: Types.ObjectId;
+ updatedBy: Types.ObjectId;
+ expiry: Date;
+ createdAt: Date;
+ updatedAt: Date;
+}
+
+const InviteSchema = new Schema(
+ {
+ email: {
+ type: String,
+ required: true,
+ unique: true,
+ trim: true,
+ lowercase: true,
+ },
+ roles: [
+ {
+ type: Schema.Types.ObjectId,
+ ref: "Role_v2",
+ required: true,
+ },
+ ],
+ tokenHash: { type: String, required: true, unique: true },
+ expiry: {
+ type: Date,
+ default: Date.now,
+ expires: 60 * 60 * 24,
+ },
+ createdBy: { type: Schema.Types.ObjectId, ref: "User_v2", required: true },
+ updatedBy: { type: Schema.Types.ObjectId, ref: "User_v2", required: true },
+ },
+ { timestamps: true }
+);
+
+export const Invite = mongoose.model("Invite_v2", InviteSchema);
diff --git a/server/src/db/v2/models/maintenance/Maintenance.ts b/server/src/db/v2/models/maintenance/Maintenance.ts
new file mode 100644
index 000000000..c2ce33a2b
--- /dev/null
+++ b/server/src/db/v2/models/maintenance/Maintenance.ts
@@ -0,0 +1,39 @@
+import mongoose, { Schema, Document, Types } from "mongoose";
+
+export interface IMaintenance extends Document {
+ _id: Types.ObjectId;
+ name: string;
+ isActive: boolean;
+ monitors: Types.ObjectId[];
+ startTime: Date;
+ endTime: Date;
+ createdBy: Types.ObjectId;
+ updatedBy: Types.ObjectId;
+ createdAt: Date;
+ updatedAt: Date;
+}
+
+const MaintenanceSchema = new Schema(
+ {
+ name: { type: String, required: true, trim: true },
+ isActive: { type: Boolean, required: true, default: true },
+ monitors: [
+ {
+ type: Schema.Types.ObjectId,
+ ref: "Monitor_v2",
+ required: true,
+ },
+ ],
+ startTime: { type: Date, required: true },
+ endTime: { type: Date, required: true },
+ createdBy: { type: Schema.Types.ObjectId, ref: "User_v2", required: true },
+ updatedBy: { type: Schema.Types.ObjectId, ref: "User_v2", required: true },
+ },
+ { timestamps: true }
+);
+
+MaintenanceSchema.index({ isActive: 1 });
+MaintenanceSchema.index({ startTime: 1 });
+MaintenanceSchema.index({ endTime: 1 });
+
+export const Maintenance = mongoose.model("Maintenance_v2", MaintenanceSchema);
diff --git a/server/src/db/v2/models/monitors/Monitor.ts b/server/src/db/v2/models/monitors/Monitor.ts
new file mode 100644
index 000000000..52cbae9d8
--- /dev/null
+++ b/server/src/db/v2/models/monitors/Monitor.ts
@@ -0,0 +1,96 @@
+import mongoose, { Schema, Document, Types } from "mongoose";
+import { Check, MonitorStats } from "../index.js";
+
+export const MonitorTypes = ["http", "https", "ping", "infrastructure", "pagespeed"] as const;
+export type MonitorType = (typeof MonitorTypes)[number];
+
+export const MonitorStatuses = ["up", "down", "paused", "initializing"] as const;
+export type MonitorStatus = (typeof MonitorStatuses)[number];
+export interface IMonitor extends Document {
+ _id: Types.ObjectId;
+ name: string;
+ url: string;
+ secret?: string;
+ type: MonitorType;
+ interval: number; // in ms
+ isActive: boolean;
+ status: MonitorStatus;
+ n: number; // Number of consecutive successes required to change status
+ lastCheckedAt?: Date;
+ latestChecks: {
+ status: MonitorStatus;
+ responseTime: number;
+ checkedAt: Date;
+ }[];
+ notificationChannels?: Types.ObjectId[];
+ createdBy: Types.ObjectId;
+ updatedBy: Types.ObjectId;
+ createdAt: Date;
+ updatedAt: Date;
+}
+
+const MonitorSchema = new Schema(
+ {
+ name: { type: String, required: true, trim: true, maxlength: 100 },
+ url: { type: String, required: true, trim: true },
+ secret: { type: String, required: false },
+ type: {
+ type: String,
+ required: true,
+ enum: MonitorTypes,
+ },
+ interval: { type: Number, required: true, default: 60000 },
+ isActive: { type: Boolean, required: true, default: true },
+ status: {
+ type: String,
+ required: true,
+ default: "initializing",
+ enum: MonitorStatuses,
+ },
+ n: { type: Number, required: true, default: 1 },
+ lastCheckedAt: { type: Date },
+ latestChecks: {
+ type: [
+ {
+ status: {
+ type: String,
+ required: true,
+ enum: MonitorStatuses,
+ },
+ responseTime: { type: Number, required: true },
+ checkedAt: { type: Date, required: true },
+ },
+ ],
+ default: [],
+ },
+ notificationChannels: {
+ type: [{ type: Schema.Types.ObjectId, ref: "NotificationChannel_v2" }],
+ default: [],
+ },
+
+ createdBy: { type: Schema.Types.ObjectId, ref: "User_v2", required: true },
+ updatedBy: { type: Schema.Types.ObjectId, ref: "User_v2", required: true },
+ },
+ { timestamps: true }
+);
+
+MonitorSchema.pre("deleteOne", { document: true, query: false }, async function (next) {
+ try {
+ const monitorId = this._id;
+ await Check.deleteMany({ monitorId });
+ await MonitorStats.deleteMany({ monitorId });
+ next();
+ } catch (error: any) {
+ next(error);
+ }
+});
+
+MonitorSchema.index({ isActive: 1 });
+MonitorSchema.index({ status: 1 });
+MonitorSchema.index({ type: 1 });
+MonitorSchema.index({ lastCheckedAt: 1 });
+MonitorSchema.index({ isActive: 1, status: 1 });
+MonitorSchema.index({ createdBy: 1 });
+MonitorSchema.index({ updatedBy: 1 });
+
+export const Monitor = mongoose.model("Monitor_v2", MonitorSchema);
diff --git a/server/src/db/v2/models/monitors/MonitorStats.ts b/server/src/db/v2/models/monitors/MonitorStats.ts
new file mode 100644
index 000000000..56e76f9ab
--- /dev/null
+++ b/server/src/db/v2/models/monitors/MonitorStats.ts
@@ -0,0 +1,77 @@
+import mongoose, { Schema, Document, Types } from "mongoose";
+import { MonitorStatus, MonitorStatuses } from "./Monitor.js";
+
+export interface IMonitorStats extends mongoose.Document {
+ monitorId: mongoose.Types.ObjectId;
+ avgResponseTime: number;
+ maxResponseTime: number;
+ totalChecks: number;
+ totalUpChecks: number;
+ totalDownChecks: number;
+ uptimePercentage: number;
+ lastCheckTimestamp: number;
+ lastResponseTime: number;
+ timeOfLastFailure: number;
+ currentStreak: number;
+ currentStreakStatus: MonitorStatus;
+ currentStreakStartedAt: number;
+ createdAt: Date;
+ updatedAt: Date;
+}
+
+const MonitorStatsSchema = new Schema(
+ {
+ monitorId: {
+ type: mongoose.Schema.Types.ObjectId,
+ ref: "Monitor_v2",
+ immutable: true,
+ index: true,
+ },
+ avgResponseTime: {
+ type: Number,
+ default: 0,
+ },
+ maxResponseTime: {
+ type: Number,
+ default: 0,
+ },
+ lastResponseTime: {
+ 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,
+ },
+ timeOfLastFailure: {
+ type: Number,
+ default: 0,
+ },
+ currentStreak: { type: Number, required: false, default: 0 },
+ currentStreakStatus: {
+ type: String,
+ required: false,
+ enum: MonitorStatuses,
+ },
+ currentStreakStartedAt: { type: Number, required: false },
+ },
+ { timestamps: true }
+);
+
+export const MonitorStats = mongoose.model("MonitorStats_v2", MonitorStatsSchema);
diff --git a/server/src/db/v2/models/notification-channel/NotificationChannel.ts b/server/src/db/v2/models/notification-channel/NotificationChannel.ts
new file mode 100644
index 000000000..e77639608
--- /dev/null
+++ b/server/src/db/v2/models/notification-channel/NotificationChannel.ts
@@ -0,0 +1,50 @@
+import mongoose, { Schema, Document, Types } from "mongoose";
+export const ChannelTypes = ["email", "slack", "discord", "webhook"] as const;
+export type ChannelType = (typeof ChannelTypes)[number];
+
+export interface INotificationChannelConfig {
+ url?: string; // For webhook, slack, discord
+ emailAddress?: string; // For email
+}
+
+export interface INotificationChannel {
+ _id: Types.ObjectId;
+ name: string;
+ type: ChannelType;
+ config: INotificationChannelConfig;
+ isActive: boolean;
+ createdBy: Types.ObjectId;
+ updatedBy: Types.ObjectId;
+ createdAt: Date;
+ updatedAt: Date;
+}
+
+const NotificationChannelConfigSchema = new Schema(
+ {
+ url: { type: String, required: false },
+ emailAddress: { type: String, required: false },
+ },
+ { _id: false, strict: "throw" }
+);
+
+const NotificationChannelSchema = new Schema(
+ {
+ name: { type: String, required: true, trim: true },
+ type: {
+ type: String,
+ required: true,
+ enum: ChannelTypes,
+ },
+ config: { type: NotificationChannelConfigSchema, required: true },
+ isActive: { type: Boolean, required: true, default: true },
+ createdBy: { type: Schema.Types.ObjectId, ref: "User_v2", required: true },
+ updatedBy: { type: Schema.Types.ObjectId, ref: "User_v2", required: true },
+ },
+ { timestamps: true }
+);
+
+NotificationChannelSchema.index({ isActive: 1 });
+NotificationChannelSchema.index({ type: 1 });
+NotificationChannelSchema.index({ type: 1, isActive: 1 });
+
+export const NotificationChannel = mongoose.model("NotificationChannel_v2", NotificationChannelSchema);
diff --git a/server/src/index.js b/server/src/index.js
index d1ff649eb..e8b756d0e 100755
--- a/server/src/index.js
+++ b/server/src/index.js
@@ -7,8 +7,8 @@ import path from "path";
import fs from "fs";
import Logger from "./utils/logger.js";
-import SettingsService from "./service/system/settingsService.js";
-import AppSettings from "./db/models/AppSettings.js";
+import SettingsService from "./service/v1/system/settingsService.js";
+import AppSettings from "./db/v1/models/AppSettings.js";
const SERVICE_NAME = "Server";
let logger;
diff --git a/server/src/locales/en.json b/server/src/locales/en.json
index 1345f84a8..373911a55 100755
--- a/server/src/locales/en.json
+++ b/server/src/locales/en.json
@@ -175,7 +175,7 @@
"checkmate": "Checkmate",
"url": "URL",
"unknown": "Unknown",
- "uptimeAlert": "Uptime Alert: One of your monitors is back online",
- "downtimeAlert": "Downtime Alert: One of your monitors went offline"
+ "uptimeAlert": "Uptime Alert: Your monitor {monitorName} is back online",
+ "downtimeAlert": "Downtime Alert: Your monitor {monitorName} went offline"
}
}
diff --git a/server/src/middleware/handleErrors.js b/server/src/middleware/v1/handleErrors.js
similarity index 71%
rename from server/src/middleware/handleErrors.js
rename to server/src/middleware/v1/handleErrors.js
index b0ded5ab7..43671fbe2 100755
--- a/server/src/middleware/handleErrors.js
+++ b/server/src/middleware/v1/handleErrors.js
@@ -1,6 +1,6 @@
-import { logger } from "../utils/logger.js";
-import ServiceRegistry from "../service/system/serviceRegistry.js";
-import StringService from "../service/system/stringService.js";
+import { logger } from "../../utils/logger.js";
+import ServiceRegistry from "../../service/v1/system/serviceRegistry.js";
+import StringService from "../../service/v1/system/stringService.js";
const handleErrors = (error, req, res, next) => {
const status = error.status || 500;
diff --git a/server/src/middleware/isAllowed.js b/server/src/middleware/v1/isAllowed.js
similarity index 87%
rename from server/src/middleware/isAllowed.js
rename to server/src/middleware/v1/isAllowed.js
index 5d61974be..cc240f531 100755
--- a/server/src/middleware/isAllowed.js
+++ b/server/src/middleware/v1/isAllowed.js
@@ -1,9 +1,9 @@
import jwt from "jsonwebtoken";
const TOKEN_PREFIX = "Bearer ";
const SERVICE_NAME = "allowedRoles";
-import ServiceRegistry from "../service/system/serviceRegistry.js";
-import StringService from "../service/system/stringService.js";
-import SettingsService from "../service/system/settingsService.js";
+import ServiceRegistry from "../../service/v1/system/serviceRegistry.js";
+import StringService from "../../service/v1/system/stringService.js";
+import SettingsService from "../../service/v1/system/settingsService.js";
const isAllowed = (allowedRoles) => {
return (req, res, next) => {
diff --git a/server/src/middleware/languageMiddleware.js b/server/src/middleware/v1/languageMiddleware.js
similarity index 93%
rename from server/src/middleware/languageMiddleware.js
rename to server/src/middleware/v1/languageMiddleware.js
index 6293dc837..819858a21 100755
--- a/server/src/middleware/languageMiddleware.js
+++ b/server/src/middleware/v1/languageMiddleware.js
@@ -1,4 +1,4 @@
-import { logger } from "../utils/logger.js";
+import { logger } from "../../utils/logger.js";
const languageMiddleware = (stringService, translationService) => async (req, res, next) => {
try {
diff --git a/server/src/middleware/rateLimiter.js b/server/src/middleware/v1/rateLimiter.js
similarity index 100%
rename from server/src/middleware/rateLimiter.js
rename to server/src/middleware/v1/rateLimiter.js
diff --git a/server/src/middleware/responseHandler.js b/server/src/middleware/v1/responseHandler.js
similarity index 100%
rename from server/src/middleware/responseHandler.js
rename to server/src/middleware/v1/responseHandler.js
diff --git a/server/src/middleware/sanitization.js b/server/src/middleware/v1/sanitization.js
similarity index 100%
rename from server/src/middleware/sanitization.js
rename to server/src/middleware/v1/sanitization.js
diff --git a/server/src/middleware/verifyJWT.js b/server/src/middleware/v1/verifyJWT.js
similarity index 87%
rename from server/src/middleware/verifyJWT.js
rename to server/src/middleware/v1/verifyJWT.js
index d01db0a97..b57391dfd 100755
--- a/server/src/middleware/verifyJWT.js
+++ b/server/src/middleware/v1/verifyJWT.js
@@ -1,7 +1,7 @@
import jwt from "jsonwebtoken";
-import ServiceRegistry from "../service/system/serviceRegistry.js";
-import SettingsService from "../service/system/settingsService.js";
-import StringService from "../service/system/stringService.js";
+import ServiceRegistry from "../../service/v1/system/serviceRegistry.js";
+import SettingsService from "../../service/v1/system/settingsService.js";
+import StringService from "../../service/v1/system/stringService.js";
const SERVICE_NAME = "verifyJWT";
const TOKEN_PREFIX = "Bearer ";
diff --git a/server/src/middleware/verifyOwnership.js b/server/src/middleware/v1/verifyOwnership.js
similarity index 88%
rename from server/src/middleware/verifyOwnership.js
rename to server/src/middleware/v1/verifyOwnership.js
index 4431b0c26..741fb6436 100755
--- a/server/src/middleware/verifyOwnership.js
+++ b/server/src/middleware/v1/verifyOwnership.js
@@ -1,6 +1,6 @@
-import { logger } from "../utils/logger.js";
-import ServiceRegistry from "../service/system/serviceRegistry.js";
-import StringService from "../service/system/stringService.js";
+import { logger } from "../../utils/logger.js";
+import ServiceRegistry from "../../service/v1/system/serviceRegistry.js";
+import StringService from "../../service/v1/system/stringService.js";
import { ObjectId } from "mongodb";
const SERVICE_NAME = "verifyOwnership";
diff --git a/server/src/middleware/verifySuperAdmin.js b/server/src/middleware/v1/verifySuperAdmin.js
similarity index 97%
rename from server/src/middleware/verifySuperAdmin.js
rename to server/src/middleware/v1/verifySuperAdmin.js
index a5a93eede..41ac0a3e0 100755
--- a/server/src/middleware/verifySuperAdmin.js
+++ b/server/src/middleware/v1/verifySuperAdmin.js
@@ -1,5 +1,5 @@
const jwt = require("jsonwebtoken");
-const logger = require("../utils/logger");
+const logger = require("../../utils/logger.js");
const SERVICE_NAME = "verifyAdmin";
const TOKEN_PREFIX = "Bearer ";
import ServiceRegistry from "../service/serviceRegistry.js";
diff --git a/server/src/middleware/verifyTeamAccess.js b/server/src/middleware/v1/verifyTeamAccess.js
similarity index 100%
rename from server/src/middleware/verifyTeamAccess.js
rename to server/src/middleware/v1/verifyTeamAccess.js
diff --git a/server/src/middleware/v2/VerifyPermissions.ts b/server/src/middleware/v2/VerifyPermissions.ts
new file mode 100644
index 000000000..64d573e00
--- /dev/null
+++ b/server/src/middleware/v2/VerifyPermissions.ts
@@ -0,0 +1,77 @@
+import { Request, Response, NextFunction } from "express";
+import ApiError from "../../utils/ApiError.js";
+import { User, IUser, Role, IRole } from "../../db/v2/models/index.js";
+
+const rolesCache = new Map();
+// const CACHE_TTL = 30 * 60 * 1000; // 30 minutes
+const CACHE_TTL = 1; // 30 minutes
+const MAX_CACHE_SIZE = 1000;
+
+const getCachedRoles = async (userId: string) => {
+ if (rolesCache.size >= MAX_CACHE_SIZE) {
+ const oldestKey = rolesCache.keys().next().value;
+ if (!oldestKey) return null;
+ rolesCache.delete(oldestKey);
+ }
+
+ const cached = rolesCache.get(userId);
+
+ if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
+ return cached.roles;
+ }
+
+ const user: IUser | null = await User.findById(userId);
+ if (!user) {
+ return null;
+ }
+
+ const roles = await Role.find({ _id: { $in: user.roles } });
+ rolesCache.set(userId, { roles, timestamp: Date.now() });
+ return roles;
+};
+
+const hasPermission = (roles: IRole[], requiredPermissions: string[]) => {
+ const userPermissions = [...new Set(roles.flatMap((role) => role.permissions))];
+
+ if (userPermissions.includes("*")) return true;
+
+ const matches = (requiredPermission: string, userPermission: string) => {
+ if (userPermission === requiredPermission) return true;
+ if (userPermission.endsWith(".*")) {
+ const prefix = userPermission.slice(0, -2);
+ return requiredPermission.startsWith(prefix + ".");
+ }
+ return false;
+ };
+
+ return requiredPermissions.every((requiredPermission) => {
+ return userPermissions.some((userPermission) => matches(requiredPermission, userPermission));
+ });
+};
+
+const verifyPermission = (resourceActions: string[]) => {
+ return async (req: Request, res: Response, next: NextFunction) => {
+ const tokenizedUser = req.user;
+
+ if (!tokenizedUser) {
+ throw new ApiError("No user", 400);
+ }
+
+ const userId = tokenizedUser.sub;
+ if (!userId) {
+ throw new ApiError("No user ID", 400);
+ }
+
+ const userRoles = await getCachedRoles(userId);
+ if (!userRoles) {
+ throw new ApiError("User roles not found", 400);
+ }
+ const allowed = hasPermission(userRoles, resourceActions);
+ if (!allowed) {
+ throw new ApiError("Insufficient permissions", 403);
+ }
+ next();
+ };
+};
+
+export { verifyPermission };
diff --git a/server/src/middleware/v2/VerifyToken.ts b/server/src/middleware/v2/VerifyToken.ts
new file mode 100644
index 000000000..7dabd51cc
--- /dev/null
+++ b/server/src/middleware/v2/VerifyToken.ts
@@ -0,0 +1,21 @@
+import { Request, Response, NextFunction } from "express";
+import { decode } from "../../utils/JWTUtils.js";
+import ApiError from "../../utils/ApiError.js";
+const verifyToken = (req: Request, res: Response, next: NextFunction) => {
+ const token = req.cookies.token;
+
+ if (!token) {
+ const error = new ApiError("No token provided", 401);
+ return next(error);
+ }
+
+ try {
+ const decoded = decode(token);
+ req.user = decoded;
+ next();
+ } catch (error) {
+ next(error);
+ }
+};
+
+export { verifyToken };
diff --git a/server/src/routes/announcementsRoute.js b/server/src/routes/v1/announcementsRoute.js
similarity index 100%
rename from server/src/routes/announcementsRoute.js
rename to server/src/routes/v1/announcementsRoute.js
diff --git a/server/src/routes/authRoute.js b/server/src/routes/v1/authRoute.js
similarity index 83%
rename from server/src/routes/authRoute.js
rename to server/src/routes/v1/authRoute.js
index e790c60b0..63b4c4201 100755
--- a/server/src/routes/authRoute.js
+++ b/server/src/routes/v1/authRoute.js
@@ -1,9 +1,7 @@
import { Router } from "express";
-import { verifyJWT } from "../middleware/verifyJWT.js";
-import { verifyOwnership } from "../middleware/verifyOwnership.js";
-import { isAllowed } from "../middleware/isAllowed.js";
+import { verifyJWT } from "../../middleware/v1/verifyJWT.js";
+import { isAllowed } from "../../middleware/v1/isAllowed.js";
import multer from "multer";
-import User from "../db/models/User.js";
const upload = multer();
@@ -27,6 +25,7 @@ class AuthRoutes {
this.router.get("/users", verifyJWT, isAllowed(["admin", "superadmin"]), this.authController.getAllUsers);
this.router.get("/users/:userId", verifyJWT, isAllowed(["superadmin"]), this.authController.getUserById);
this.router.put("/users/:userId", verifyJWT, isAllowed(["superadmin"]), this.authController.editUserById);
+ this.router.put("/users/:userId/password", verifyJWT, isAllowed(["superadmin"]), this.authController.editUserPasswordById);
this.router.put("/user", verifyJWT, upload.single("profileImage"), this.authController.editUser);
this.router.delete("/user", verifyJWT, this.authController.deleteUser);
diff --git a/server/src/routes/checkRoute.js b/server/src/routes/v1/checkRoute.js
similarity index 76%
rename from server/src/routes/checkRoute.js
rename to server/src/routes/v1/checkRoute.js
index 8bdb2d4fb..78f6b8a42 100755
--- a/server/src/routes/checkRoute.js
+++ b/server/src/routes/v1/checkRoute.js
@@ -1,9 +1,6 @@
import { Router } from "express";
-import { verifyOwnership } from "../middleware/verifyOwnership.js";
-import { verifyTeamAccess } from "../middleware/verifyTeamAccess.js";
-import { isAllowed } from "../middleware/isAllowed.js";
-import Monitor from "../db/models/Monitor.js";
-import Check from "../db/models/Check.js";
+
+import { isAllowed } from "../../middleware/v1/isAllowed.js";
class CheckRoutes {
constructor(checkController) {
diff --git a/server/src/routes/diagnosticRoute.js b/server/src/routes/v1/diagnosticRoute.js
similarity index 76%
rename from server/src/routes/diagnosticRoute.js
rename to server/src/routes/v1/diagnosticRoute.js
index a15f5a038..2cf481e08 100755
--- a/server/src/routes/diagnosticRoute.js
+++ b/server/src/routes/v1/diagnosticRoute.js
@@ -1,6 +1,6 @@
import { Router } from "express";
-import { verifyJWT } from "../middleware/verifyJWT.js";
-import { isAllowed } from "../middleware/isAllowed.js";
+import { verifyJWT } from "../../middleware/v1/verifyJWT.js";
+import { isAllowed } from "../../middleware/v1/isAllowed.js";
class DiagnosticRoutes {
constructor(diagnosticController) {
diff --git a/server/src/routes/inviteRoute.js b/server/src/routes/v1/inviteRoute.js
similarity index 82%
rename from server/src/routes/inviteRoute.js
rename to server/src/routes/v1/inviteRoute.js
index d495c5476..a47550536 100755
--- a/server/src/routes/inviteRoute.js
+++ b/server/src/routes/v1/inviteRoute.js
@@ -1,6 +1,6 @@
import { Router } from "express";
-import { verifyJWT } from "../middleware/verifyJWT.js";
-import { isAllowed } from "../middleware/isAllowed.js";
+import { verifyJWT } from "../../middleware/v1/verifyJWT.js";
+import { isAllowed } from "../../middleware/v1/isAllowed.js";
class InviteRoutes {
constructor(inviteController) {
diff --git a/server/src/routes/logRoutes.js b/server/src/routes/v1/logRoutes.js
similarity index 84%
rename from server/src/routes/logRoutes.js
rename to server/src/routes/v1/logRoutes.js
index 867826dde..43da8b75a 100755
--- a/server/src/routes/logRoutes.js
+++ b/server/src/routes/v1/logRoutes.js
@@ -1,5 +1,5 @@
import { Router } from "express";
-import { isAllowed } from "../middleware/isAllowed.js";
+import { isAllowed } from "../../middleware/v1/isAllowed.js";
class LogRoutes {
constructor(logController) {
this.router = Router();
diff --git a/server/src/routes/maintenanceWindowRoute.js b/server/src/routes/v1/maintenanceWindowRoute.js
similarity index 91%
rename from server/src/routes/maintenanceWindowRoute.js
rename to server/src/routes/v1/maintenanceWindowRoute.js
index 774434ed6..345f2c13c 100755
--- a/server/src/routes/maintenanceWindowRoute.js
+++ b/server/src/routes/v1/maintenanceWindowRoute.js
@@ -1,5 +1,5 @@
import { Router } from "express";
-import MaintenanceWindow from "../db/models/MaintenanceWindow.js";
+
class MaintenanceWindowRoutes {
constructor(maintenanceWindowController) {
this.router = Router();
diff --git a/server/src/routes/monitorRoute.js b/server/src/routes/v1/monitorRoute.js
similarity index 91%
rename from server/src/routes/monitorRoute.js
rename to server/src/routes/v1/monitorRoute.js
index 043d361d1..dc0dd694b 100755
--- a/server/src/routes/monitorRoute.js
+++ b/server/src/routes/v1/monitorRoute.js
@@ -1,7 +1,7 @@
import { Router } from "express";
-import { isAllowed } from "../middleware/isAllowed.js";
+import { isAllowed } from "../../middleware/v1/isAllowed.js";
import multer from "multer";
-import { fetchMonitorCertificate } from "../controllers/controllerUtils.js";
+import { fetchMonitorCertificate } from "../../controllers/v1/controllerUtils.js";
const upload = multer({
storage: multer.memoryStorage(), // Store file in memory as Buffer
});
@@ -18,6 +18,7 @@ class MonitorRoutes {
this.router.get("/team", this.monitorController.getMonitorsByTeamId);
this.router.get("/team/with-checks", this.monitorController.getMonitorsWithChecksByTeamId);
this.router.get("/team/summary", this.monitorController.getMonitorsAndSummaryByTeamId);
+ this.router.get("/team/groups", this.monitorController.getGroupsByTeamId);
// Uptime routes
this.router.get("/uptime/details/:monitorId", this.monitorController.getUptimeDetailsById);
diff --git a/server/src/routes/notificationRoute.js b/server/src/routes/v1/notificationRoute.js
similarity index 100%
rename from server/src/routes/notificationRoute.js
rename to server/src/routes/v1/notificationRoute.js
diff --git a/server/src/routes/queueRoute.js b/server/src/routes/v1/queueRoute.js
similarity index 93%
rename from server/src/routes/queueRoute.js
rename to server/src/routes/v1/queueRoute.js
index 55e829b91..c6cfa05da 100755
--- a/server/src/routes/queueRoute.js
+++ b/server/src/routes/v1/queueRoute.js
@@ -1,5 +1,5 @@
import { Router } from "express";
-import { isAllowed } from "../middleware/isAllowed.js";
+import { isAllowed } from "../../middleware/v1/isAllowed.js";
class QueueRoutes {
constructor(queueController) {
this.router = Router();
diff --git a/server/src/routes/settingsRoute.js b/server/src/routes/v1/settingsRoute.js
similarity index 90%
rename from server/src/routes/settingsRoute.js
rename to server/src/routes/v1/settingsRoute.js
index 88eb67e65..e88186af2 100755
--- a/server/src/routes/settingsRoute.js
+++ b/server/src/routes/v1/settingsRoute.js
@@ -1,5 +1,5 @@
import { Router } from "express";
-import { isAllowed } from "../middleware/isAllowed.js";
+import { isAllowed } from "../../middleware/v1/isAllowed.js";
class SettingsRoutes {
constructor(settingsController) {
diff --git a/server/src/routes/statusPageRoute.js b/server/src/routes/v1/statusPageRoute.js
similarity index 93%
rename from server/src/routes/statusPageRoute.js
rename to server/src/routes/v1/statusPageRoute.js
index 53eb21fba..2c3b0b265 100755
--- a/server/src/routes/statusPageRoute.js
+++ b/server/src/routes/v1/statusPageRoute.js
@@ -1,5 +1,5 @@
import { Router } from "express";
-import { verifyJWT } from "../middleware/verifyJWT.js";
+import { verifyJWT } from "../../middleware/v1/verifyJWT.js";
import multer from "multer";
const upload = multer();
diff --git a/server/src/routes/v2/auth.ts b/server/src/routes/v2/auth.ts
new file mode 100644
index 000000000..919d0fd76
--- /dev/null
+++ b/server/src/routes/v2/auth.ts
@@ -0,0 +1,33 @@
+import { Router } from "express";
+
+import express from "express";
+import AuthController from "../../controllers/v2/AuthController.js";
+import { verifyToken } from "../../middleware/v2/VerifyToken.js";
+
+const router = express.Router();
+
+class AuthRoutes {
+ private controller: AuthController;
+ private router: Router;
+ constructor(authController: AuthController) {
+ this.controller = authController;
+ this.router = Router();
+ this.initRoutes();
+ }
+
+ initRoutes = () => {
+ this.router.post("/register", this.controller.register);
+ this.router.post("/register/invite/:token", this.controller.registerWithInvite);
+ this.router.post("/login", this.controller.login);
+ this.router.post("/logout", this.controller.logout);
+ this.router.get("/me", verifyToken, this.controller.me);
+ this.router.post("/cleanup", this.controller.cleanup);
+ this.router.post("/cleanup-monitors", this.controller.cleanMonitors);
+ };
+
+ getRouter() {
+ return this.router;
+ }
+}
+
+export default AuthRoutes;
diff --git a/server/src/routes/v2/invite.ts b/server/src/routes/v2/invite.ts
new file mode 100644
index 000000000..8066d0c77
--- /dev/null
+++ b/server/src/routes/v2/invite.ts
@@ -0,0 +1,30 @@
+import { Router } from "express";
+import InviteController from "../../controllers/v2/InviteController.js";
+import { verifyToken } from "../../middleware/v2/VerifyToken.js";
+import { verifyPermission } from "../../middleware/v2/VerifyPermissions.js";
+
+class InviteRoutes {
+ private router;
+ private controller;
+ constructor(inviteController: InviteController) {
+ this.router = Router();
+ this.controller = inviteController;
+ this.initRoutes();
+ }
+
+ initRoutes = () => {
+ this.router.post("/", verifyToken, verifyPermission(["invite.create"]), this.controller.create);
+
+ this.router.get("/", verifyToken, verifyPermission(["invite.view"]), this.controller.getAll);
+
+ this.router.get("/:token", verifyToken, verifyPermission(["invite.view"]), this.controller.get);
+
+ this.router.delete("/:id", verifyToken, verifyPermission(["invite.delete"]), this.controller.delete);
+ };
+
+ getRouter() {
+ return this.router;
+ }
+}
+
+export default InviteRoutes;
diff --git a/server/src/routes/v2/maintenance.ts b/server/src/routes/v2/maintenance.ts
new file mode 100644
index 000000000..62bc5f1cf
--- /dev/null
+++ b/server/src/routes/v2/maintenance.ts
@@ -0,0 +1,34 @@
+import { Router } from "express";
+import MaintenanceController from "../../controllers/v2/MaintenanceController.js";
+import { verifyToken } from "../../middleware/v2/VerifyToken.js";
+import { verifyPermission } from "../../middleware/v2/VerifyPermissions.js";
+
+class MaintenanceRoutes {
+ private router;
+ private controller;
+ constructor(maintenanceController: MaintenanceController) {
+ this.router = Router();
+ this.controller = maintenanceController;
+ this.initRoutes();
+ }
+
+ initRoutes = () => {
+ this.router.post("/", verifyToken, verifyPermission(["maintenance.create"]), this.controller.create);
+
+ this.router.get("/", verifyToken, verifyPermission(["maintenance.view"]), this.controller.getAll);
+
+ this.router.patch("/:id/active", verifyToken, verifyPermission(["maintenance.update"]), this.controller.toggleActive);
+
+ this.router.patch("/:id", verifyToken, verifyPermission(["maintenance.update"]), this.controller.update);
+
+ this.router.get("/:id", verifyToken, verifyPermission(["maintenance.view"]), this.controller.get);
+
+ this.router.delete("/:id", verifyToken, verifyPermission(["maintenance.delete"]), this.controller.delete);
+ };
+
+ getRouter() {
+ return this.router;
+ }
+}
+
+export default MaintenanceRoutes;
diff --git a/server/src/routes/v2/monitors.ts b/server/src/routes/v2/monitors.ts
new file mode 100644
index 000000000..61e5b61d6
--- /dev/null
+++ b/server/src/routes/v2/monitors.ts
@@ -0,0 +1,36 @@
+import { Router } from "express";
+import MonitorController from "../../controllers/v2/MonitorController.js";
+import { verifyToken } from "../../middleware/v2/VerifyToken.js";
+import { verifyPermission } from "../../middleware/v2/VerifyPermissions.js";
+
+class MonitorRoutes {
+ private router;
+ private controller;
+ constructor(monitorController: MonitorController) {
+ this.router = Router();
+ this.controller = monitorController;
+ this.initRoutes();
+ }
+
+ initRoutes = () => {
+ this.router.post("/", verifyToken, verifyPermission(["monitors.create"]), this.controller.create);
+
+ this.router.get("/", verifyToken, verifyPermission(["monitors.view"]), this.controller.getAll);
+
+ this.router.get("/:id/checks", verifyToken, verifyPermission(["monitors.view"]), this.controller.getChecks);
+
+ this.router.patch("/:id/active", verifyToken, verifyPermission(["monitors.update"]), this.controller.toggleActive);
+
+ this.router.get("/:id", verifyToken, verifyPermission(["monitors.view"]), this.controller.get);
+
+ this.router.patch("/:id", verifyToken, verifyPermission(["monitors.update"]), this.controller.update);
+
+ this.router.delete("/:id", verifyToken, verifyPermission(["monitors.delete"]), this.controller.delete);
+ };
+
+ getRouter() {
+ return this.router;
+ }
+}
+
+export default MonitorRoutes;
diff --git a/server/src/routes/v2/notificationChannels.ts b/server/src/routes/v2/notificationChannels.ts
new file mode 100644
index 000000000..1db32902a
--- /dev/null
+++ b/server/src/routes/v2/notificationChannels.ts
@@ -0,0 +1,34 @@
+import { Router } from "express";
+import NotificationController from "../../controllers/v2/NotificationChannelController.js";
+import { verifyToken } from "../../middleware/v2/VerifyToken.js";
+import { verifyPermission } from "../../middleware/v2/VerifyPermissions.js";
+
+class NotificationChannelRoutes {
+ private router;
+ private controller;
+ constructor(notificationController: NotificationController) {
+ this.router = Router();
+ this.controller = notificationController;
+ this.initRoutes();
+ }
+
+ initRoutes = () => {
+ this.router.post("/", verifyToken, verifyPermission(["notifications.create"]), this.controller.create);
+
+ this.router.get("/", verifyToken, verifyPermission(["notifications.view"]), this.controller.getAll);
+
+ this.router.patch("/:id/active", verifyToken, verifyPermission(["notifications.update"]), this.controller.toggleActive);
+
+ this.router.patch("/:id", verifyToken, verifyPermission(["notifications.update"]), this.controller.update);
+
+ this.router.get("/:id", verifyToken, verifyPermission(["notifications.view"]), this.controller.get);
+
+ this.router.delete("/:id", verifyToken, verifyPermission(["notifications.delete"]), this.controller.delete);
+ };
+
+ getRouter() {
+ return this.router;
+ }
+}
+
+export default NotificationChannelRoutes;
diff --git a/server/src/routes/v2/queue.ts b/server/src/routes/v2/queue.ts
new file mode 100644
index 000000000..428776e61
--- /dev/null
+++ b/server/src/routes/v2/queue.ts
@@ -0,0 +1,24 @@
+import QueueController from "../../controllers/v2/QueueController.js";
+import { Router } from "express";
+
+class QueueRoutes {
+ private router;
+ private controller;
+ constructor(queueController: QueueController) {
+ this.router = Router();
+ this.controller = queueController;
+ this.initRoutes();
+ }
+
+ initRoutes() {
+ this.router.get("/jobs", this.controller.getJobs);
+ this.router.get("/metrics", this.controller.getMetrics);
+ this.router.post("/flush", this.controller.flush);
+ }
+
+ getRouter() {
+ return this.router;
+ }
+}
+
+export default QueueRoutes;
diff --git a/server/src/service/business/checkService.js b/server/src/service/v1/business/checkService.js
similarity index 100%
rename from server/src/service/business/checkService.js
rename to server/src/service/v1/business/checkService.js
diff --git a/server/src/service/business/diagnosticService.js b/server/src/service/v1/business/diagnosticService.js
similarity index 100%
rename from server/src/service/business/diagnosticService.js
rename to server/src/service/v1/business/diagnosticService.js
diff --git a/server/src/service/business/inviteService.js b/server/src/service/v1/business/inviteService.js
similarity index 100%
rename from server/src/service/business/inviteService.js
rename to server/src/service/v1/business/inviteService.js
diff --git a/server/src/service/business/maintenanceWindowService.js b/server/src/service/v1/business/maintenanceWindowService.js
similarity index 100%
rename from server/src/service/business/maintenanceWindowService.js
rename to server/src/service/v1/business/maintenanceWindowService.js
diff --git a/server/src/service/business/monitorService.js b/server/src/service/v1/business/monitorService.js
similarity index 97%
rename from server/src/service/business/monitorService.js
rename to server/src/service/v1/business/monitorService.js
index 637870b71..2b643ee64 100644
--- a/server/src/service/business/monitorService.js
+++ b/server/src/service/v1/business/monitorService.js
@@ -1,4 +1,4 @@
-import { createMonitorsBodyValidation } from "../../validation/joi.js";
+import { createMonitorsBodyValidation } from "../../../validation/joi.js";
const SERVICE_NAME = "MonitorService";
class MonitorService {
@@ -267,6 +267,11 @@ class MonitorService {
getAllGames = () => {
return this.games;
};
+
+ getGroupsByTeamId = async ({ teamId }) => {
+ const groups = await this.db.monitorModule.getGroupsByTeamId({ teamId });
+ return groups;
+ };
}
export default MonitorService;
diff --git a/server/src/service/business/userService.js b/server/src/service/v1/business/userService.js
similarity index 97%
rename from server/src/service/business/userService.js
rename to server/src/service/v1/business/userService.js
index 0a965bced..7d7f7b014 100644
--- a/server/src/service/business/userService.js
+++ b/server/src/service/v1/business/userService.js
@@ -211,5 +211,9 @@ class UserService {
editUserById = async (userId, user) => {
await this.db.userModule.editUserById(userId, user);
};
+ setPasswordByUserId = async (userId, password) => {
+ const updatedUser = await this.db.userModule.updateUser({ userId: userId, user: { password: password }, file: null });
+ return updatedUser;
+ };
}
export default UserService;
diff --git a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js b/server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js
similarity index 100%
rename from server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js
rename to server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js
diff --git a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js b/server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js
similarity index 90%
rename from server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js
rename to server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js
index b348e2534..2833a59f5 100644
--- a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js
+++ b/server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js
@@ -5,11 +5,11 @@ class SuperSimpleQueueHelper {
/**
* @param {{
- * db: import("../database").Database,
- * logger: import("../logger").Logger,
- * networkService: import("../networkService").NetworkService,
- * statusService: import("../statusService").StatusService,
- * notificationService: import("../notificationService").NotificationService
+ * db: import("../database.js").Database,
+ * logger: import("../logger.js").Logger,
+ * networkService: import("../networkService.js").NetworkService,
+ * statusService: import("../statusService.js").StatusService,
+ * notificationService: import("../notificationService.js").NotificationService
* }}
*/
constructor({ db, logger, networkService, statusService, notificationService }) {
diff --git a/server/src/service/infrastructure/bufferService.js b/server/src/service/v1/infrastructure/bufferService.js
similarity index 92%
rename from server/src/service/infrastructure/bufferService.js
rename to server/src/service/v1/infrastructure/bufferService.js
index ed48cc654..459d25af3 100755
--- a/server/src/service/infrastructure/bufferService.js
+++ b/server/src/service/v1/infrastructure/bufferService.js
@@ -64,11 +64,11 @@ class BufferService {
});
}
this.buffer = [];
- this.logger.debug({
- message: `Flushed ${items} items`,
- service: this.SERVICE_NAME,
- method: "flushBuffer",
- });
+ // this.logger.debug({
+ // message: `Flushed ${items} items`,
+ // service: this.SERVICE_NAME,
+ // method: "flushBuffer",
+ // });
items = 0;
}
}
diff --git a/server/src/service/infrastructure/emailService.js b/server/src/service/v1/infrastructure/emailService.js
similarity index 100%
rename from server/src/service/infrastructure/emailService.js
rename to server/src/service/v1/infrastructure/emailService.js
diff --git a/server/src/service/infrastructure/errorService.js b/server/src/service/v1/infrastructure/errorService.js
similarity index 100%
rename from server/src/service/infrastructure/errorService.js
rename to server/src/service/v1/infrastructure/errorService.js
diff --git a/server/src/service/infrastructure/networkService.js b/server/src/service/v1/infrastructure/networkService.js
similarity index 92%
rename from server/src/service/infrastructure/networkService.js
rename to server/src/service/v1/infrastructure/networkService.js
index 47ef6878f..ade86b1dc 100644
--- a/server/src/service/infrastructure/networkService.js
+++ b/server/src/service/v1/infrastructure/networkService.js
@@ -70,7 +70,12 @@ class NetworkService {
throw new Error("Monitor URL is required");
}
- const { response, responseTime, error } = await this.timeRequest(() => this.ping.promise.probe(monitor.url));
+ const rawUrl = monitor.url;
+ const sanitizedHost = rawUrl
+ .replace(/^https?:\/\//, "")
+ .replace(/\/.*$/, "")
+ .replace(/:.*/, "");
+ const { response, error } = await this.timeRequest(() => this.ping.promise.probe(sanitizedHost));
if (!response) {
if (error) {
@@ -84,7 +89,7 @@ class NetworkService {
type: "ping",
status: response.alive,
code: 200,
- responseTime,
+ responseTime: response.time,
message: "Success",
payload: response,
};
@@ -403,7 +408,7 @@ class NetworkService {
message: this.stringService.portSuccess,
monitorId: monitor._id,
type: monitor.type,
- responseTime,
+ responseTime: responseTime,
};
if (error) {
@@ -524,6 +529,42 @@ class NetworkService {
throw error;
}
}
+
+ async requestMatrix({ homeserverUrl, accessToken, roomId, message }) {
+ try {
+ const url = `${homeserverUrl}/_matrix/client/v3/rooms/${roomId}/send/m.room.message?access_token=${accessToken}`;
+ const body = {
+ msgtype: "m.text",
+ body: message,
+ format: "org.matrix.custom.html",
+ formatted_body: message,
+ };
+ const response = await this.axios.post(url, body, {
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+
+ return {
+ status: true,
+ code: response.status,
+ message: "Successfully sent Matrix notification",
+ };
+ } catch (error) {
+ this.logger.warn({
+ message: error.message,
+ service: this.SERVICE_NAME,
+ method: "requestMatrix",
+ });
+
+ return {
+ status: false,
+ code: error.response?.status || this.NETWORK_ERROR,
+ message: "Failed to send Matrix notification",
+ payload: error.response?.data,
+ };
+ }
+ }
}
export default NetworkService;
diff --git a/server/src/service/v1/infrastructure/notificationProviders/matrix.js b/server/src/service/v1/infrastructure/notificationProviders/matrix.js
new file mode 100644
index 000000000..a8b33d4b8
--- /dev/null
+++ b/server/src/service/v1/infrastructure/notificationProviders/matrix.js
@@ -0,0 +1,34 @@
+const SERVICE_NAME = "Matrix";
+
+class Matrix {
+ static SERVICE_NAME = SERVICE_NAME;
+
+ constructor({ networkService, logger }) {
+ this.networkService = networkService;
+ this.logger = logger;
+ }
+
+ get serviceName() {
+ return Matrix.SERVICE_NAME;
+ }
+
+ async send({ friendlyName, homeserverUrl, accessToken, roomId, message, monitorName }) {
+ const title = `Checkmate status for ${monitorName}`;
+ const formattedMessage = `## ${title}\n${message}`;
+ try {
+ await this.networkService.requestMatrix({
+ homeserverUrl,
+ accessToken,
+ roomId,
+ message: formattedMessage,
+ });
+ this.logger.info(`Successfully sent Matrix notification for ${friendlyName}`);
+ return true;
+ } catch (error) {
+ this.logger.error(`Failed to send Matrix notification for ${friendlyName}: ${error.message}`);
+ return false;
+ }
+ }
+}
+
+export default Matrix;
diff --git a/server/src/service/infrastructure/notificationService.js b/server/src/service/v1/infrastructure/notificationService.js
similarity index 90%
rename from server/src/service/infrastructure/notificationService.js
rename to server/src/service/v1/infrastructure/notificationService.js
index 6c5b8b25e..8c6fb063f 100644
--- a/server/src/service/infrastructure/notificationService.js
+++ b/server/src/service/v1/infrastructure/notificationService.js
@@ -1,4 +1,5 @@
const SERVICE_NAME = "NotificationService";
+import Matrix from "./notificationProviders/matrix.js";
class NotificationService {
static SERVICE_NAME = SERVICE_NAME;
@@ -46,6 +47,14 @@ class NotificationService {
return response;
}
+ if (type === "matrix") {
+ const { friendlyName, homeserverUrl, accessToken, roomId } = notification;
+ const monitorName = subject;
+ const message = content;
+ const matrix = new Matrix({ networkService: this.networkService, logger: this.logger });
+ const success = await matrix.send({ friendlyName, homeserverUrl, accessToken, roomId, message, monitorName });
+ return success;
+ }
};
async handleNotifications(networkResponse) {
diff --git a/server/src/service/infrastructure/notificationUtils.js b/server/src/service/v1/infrastructure/notificationUtils.js
similarity index 97%
rename from server/src/service/infrastructure/notificationUtils.js
rename to server/src/service/v1/infrastructure/notificationUtils.js
index d6b92cc0e..879ad927c 100644
--- a/server/src/service/infrastructure/notificationUtils.js
+++ b/server/src/service/v1/infrastructure/notificationUtils.js
@@ -74,7 +74,9 @@ class NotificationUtils {
let discordMessageText = {
embeds: [
{
- title: status ? dn.uptimeAlert : dn.downtimeAlert,
+ title: status
+ ? dn.uptimeAlert.replace("{monitorName}", monitor?.name ?? dn.unknown)
+ : dn.downtimeAlert.replace("{monitorName}", monitor?.name ?? dn.unknown),
color: status ? 5763719 : 15548997,
fields: [
diff --git a/server/src/service/infrastructure/statusService.js b/server/src/service/v1/infrastructure/statusService.js
similarity index 99%
rename from server/src/service/infrastructure/statusService.js
rename to server/src/service/v1/infrastructure/statusService.js
index 72fa359d7..248d1c910 100755
--- a/server/src/service/infrastructure/statusService.js
+++ b/server/src/service/v1/infrastructure/statusService.js
@@ -1,4 +1,4 @@
-import MonitorStats from "../../db/models/MonitorStats.js";
+import MonitorStats from "../../../db/v1/models/MonitorStats.js";
const SERVICE_NAME = "StatusService";
class StatusService {
diff --git a/server/src/service/system/serviceRegistry.js b/server/src/service/v1/system/serviceRegistry.js
similarity index 100%
rename from server/src/service/system/serviceRegistry.js
rename to server/src/service/v1/system/serviceRegistry.js
diff --git a/server/src/service/system/settingsService.js b/server/src/service/v1/system/settingsService.js
similarity index 100%
rename from server/src/service/system/settingsService.js
rename to server/src/service/v1/system/settingsService.js
diff --git a/server/src/service/system/stringService.js b/server/src/service/v1/system/stringService.js
similarity index 100%
rename from server/src/service/system/stringService.js
rename to server/src/service/v1/system/stringService.js
diff --git a/server/src/service/system/translationService.js b/server/src/service/v1/system/translationService.js
similarity index 100%
rename from server/src/service/system/translationService.js
rename to server/src/service/v1/system/translationService.js
diff --git a/server/src/service/v2/business/AuthService.ts b/server/src/service/v2/business/AuthService.ts
new file mode 100644
index 000000000..67ece226e
--- /dev/null
+++ b/server/src/service/v2/business/AuthService.ts
@@ -0,0 +1,210 @@
+import bcrypt from "bcryptjs";
+import { User, Role, ITokenizedUser, Monitor, Check, NotificationChannel } from "../../../db/v2/models/index.js";
+import ApiError from "../../../utils/ApiError.js";
+import { Types } from "mongoose";
+import { IJobQueue } from "../infrastructure/JobQueue.js";
+
+const SERVICE_NAME = "AuthServiceV2";
+
+export const PERMISSIONS = {
+ users: {
+ all: "users.*",
+ create: "users.create",
+ view: "users.view",
+ update: "users.update",
+ delete: "users.delete",
+ },
+
+ monitors: {
+ all: "monitors.*",
+ create: "monitors.create",
+ view: "monitors.view",
+ update: "monitors.update",
+ delete: "monitors.delete",
+ },
+ notifications: {
+ all: "notifications.*",
+ create: "notifications.create",
+ view: "notifications.view",
+ update: "notifications.update",
+ delete: "notifications.delete",
+ },
+ checks: {
+ all: "checks.*",
+ create: "checks.create",
+ view: "checks.view",
+ update: "checks.update",
+ delete: "checks.delete",
+ },
+ statusPages: {
+ all: "statusPages.*",
+ create: "statusPages.create",
+ view: "statusPages.view",
+ update: "statusPages.update",
+ delete: "statusPages.delete",
+ },
+};
+
+const DEFAULT_ROLES = [
+ {
+ name: "SuperAdmin",
+ description: "Super admin with all permissions",
+ permissions: ["*"],
+ isSystem: true,
+ },
+ {
+ name: "Admin",
+ description: "Admin with full permissions",
+ permissions: [PERMISSIONS.monitors.all, PERMISSIONS.users.all],
+ isSystem: true,
+ },
+ {
+ name: "Manager",
+ description: "Can manage users",
+ permissions: [PERMISSIONS.users.create, PERMISSIONS.users.update, PERMISSIONS.monitors.all],
+ isSystem: true,
+ },
+ {
+ name: "Member",
+ description: "Basic team member",
+ permissions: [PERMISSIONS.users.update, PERMISSIONS.monitors.create, PERMISSIONS.monitors.view, PERMISSIONS.monitors.update],
+ isSystem: true,
+ },
+];
+
+export type RegisterData = {
+ email: string;
+ firstName: string;
+ lastName: string;
+ password: string;
+ roles?: Types.ObjectId[]; // Optional roles for invite-based registration
+};
+
+export type LoginData = {
+ email: string;
+ password: string;
+};
+
+export type AuthResult = ITokenizedUser;
+
+export interface IAuthService {
+ register(signupData: RegisterData): Promise;
+ registerWithInvite(signupData: RegisterData): Promise;
+ login(loginData: LoginData): Promise;
+ cleanup(): Promise;
+ cleanMonitors(): Promise;
+}
+
+class AuthService implements IAuthService {
+ static SERVICE_NAME = SERVICE_NAME;
+
+ private jobQueue: IJobQueue;
+ constructor(jobQueue: IJobQueue) {
+ this.jobQueue = jobQueue;
+ }
+
+ async register(signupData: RegisterData): Promise {
+ const userCount = await User.countDocuments();
+
+ if (userCount > 0) {
+ throw new Error("Registration is closed. Please request an invite.");
+ }
+
+ const { email, firstName, lastName, password } = signupData;
+
+ // Create all default roles
+ const rolePromises = DEFAULT_ROLES.map((roleData) =>
+ new Role({
+ ...roleData,
+ }).save()
+ );
+ const roles = await Promise.all(rolePromises);
+
+ // Hash password and create user
+ const saltRounds = 12;
+ const passwordHash = await bcrypt.hash(password, saltRounds);
+
+ // Find admin role and assign to first user
+ const superAdminRole = roles.find((role) => role.name === "SuperAdmin");
+
+ const user = new User({
+ email,
+ firstName,
+ lastName,
+ passwordHash,
+ roles: [superAdminRole!._id],
+ });
+
+ const savedUser = await user.save();
+ return {
+ sub: savedUser._id.toString(),
+ roles: savedUser.roles.map((role) => role.toString()),
+ };
+ }
+
+ async registerWithInvite(signupData: RegisterData): Promise {
+ const { email, firstName, lastName, password, roles } = signupData;
+
+ const saltRounds = 12;
+ const passwordHash = await bcrypt.hash(password, saltRounds);
+ const user = new User({
+ email,
+ firstName,
+ lastName,
+ passwordHash,
+ roles: roles || [],
+ });
+ try {
+ const savedUser = await user.save();
+ return {
+ sub: savedUser._id.toString(),
+ roles: savedUser.roles.map((role) => role.toString()),
+ };
+ } catch (error: any) {
+ if (error?.code === 11000) {
+ const dupError = new ApiError("Email already in use", 409);
+ dupError.stack = error?.stack;
+ throw dupError;
+ }
+ throw error;
+ }
+ }
+
+ async login(loginData: LoginData): Promise {
+ const { email, password } = loginData;
+
+ // Find user by email
+ const user = await User.findOne({ email });
+
+ if (!user) {
+ throw new Error("Invalid email or password");
+ }
+
+ // Check password
+ const isPasswordValid = await bcrypt.compare(password, user.passwordHash);
+ if (!isPasswordValid) {
+ throw new Error("Invalid email or password");
+ }
+
+ return {
+ sub: user._id.toString(),
+ roles: user.roles.map((role) => role.toString()),
+ };
+ }
+
+ async cleanup() {
+ await User.deleteMany({});
+ await Role.deleteMany({});
+ await Monitor.deleteMany({});
+ await Check.deleteMany({});
+ await NotificationChannel.deleteMany({});
+ await this.jobQueue.flush();
+ }
+
+ async cleanMonitors() {
+ await Monitor.deleteMany({});
+ await Check.deleteMany({});
+ }
+}
+
+export default AuthService;
diff --git a/server/src/service/v2/business/CheckService.ts b/server/src/service/v2/business/CheckService.ts
new file mode 100644
index 000000000..54593ed2b
--- /dev/null
+++ b/server/src/service/v2/business/CheckService.ts
@@ -0,0 +1,147 @@
+import { json } from "stream/consumers";
+import { ICheck, Check, Monitor } from "../../../db/v2/models/index.js";
+import type { ISystemInfo, ICaptureInfo } from "../../../db/v2/models/index.js";
+import { MonitorType } from "../../../db/v2/models/monitors/Monitor.js";
+import { StatusResponse } from "../infrastructure/NetworkService.js";
+import type { ICapturePayload, ILighthousePayload } from "../infrastructure/NetworkService.js";
+import mongoose from "mongoose";
+import { stat } from "fs";
+
+const SERVICE_NAME = "CheckServiceV2";
+export interface ICheckService {
+ buildCheck: (statusResponse: StatusResponse, type: MonitorType) => Promise;
+ cleanupOrphanedChecks: () => Promise;
+}
+
+class CheckService implements ICheckService {
+ static SERVICE_NAME = SERVICE_NAME;
+
+ private isCapturePayload = (payload: any): payload is ICapturePayload => {
+ if (!payload || typeof payload !== "object") return false;
+
+ if (!("data" in payload) || typeof payload.data !== "object") {
+ return false;
+ }
+
+ const data = payload.data as Partial;
+ if (!data.cpu || typeof data.cpu !== "object" || typeof data.cpu.usage_percent !== "number") {
+ return false;
+ }
+
+ if (!data.memory || typeof data.memory !== "object" || typeof data.memory.usage_percent !== "number") {
+ return false;
+ }
+
+ if (data.disk && !Array.isArray(data.disk)) {
+ return false;
+ }
+ if (data.net && !Array.isArray(data.net)) {
+ return false;
+ }
+
+ if (!("capture" in payload) || typeof payload.capture !== "object") return false;
+ const capture = payload.capture as Record;
+ if (typeof capture.version !== "string" || typeof capture.mode !== "string") return false;
+
+ return true;
+ };
+
+ private isPagespeedPayload = (payload: any): payload is ILighthousePayload => {
+ if (!payload || typeof payload !== "object") return false;
+
+ if (!("lighthouseResult" in payload) || typeof payload.lighthouseResult !== "object") {
+ return false;
+ }
+ return true;
+ };
+
+ private buildBaseCheck = (statusResponse: StatusResponse) => {
+ const monitorId = new mongoose.Types.ObjectId(statusResponse.monitorId);
+ const check = new Check({
+ monitorId: monitorId,
+ type: statusResponse?.type,
+ status: statusResponse?.status,
+ httpStatusCode: statusResponse?.code,
+ message: statusResponse?.message,
+ responseTime: statusResponse?.responseTime,
+ timings: statusResponse?.timings,
+ });
+ return check;
+ };
+
+ private buildInfrastructureCheck = (statusResponse: StatusResponse) => {
+ if (!this.isCapturePayload(statusResponse.payload)) {
+ throw new Error("Invalid payload for infrastructure monitor");
+ }
+ const check = this.buildBaseCheck(statusResponse);
+ check.system = statusResponse.payload.data;
+ check.capture = statusResponse.payload.capture;
+ return check;
+ };
+
+ private buildPagespeedCheck = (statusResponse: StatusResponse) => {
+ if (!this.isPagespeedPayload(statusResponse.payload)) {
+ throw new Error("Invalid payload for pagespeed monitor");
+ }
+ const check = this.buildBaseCheck(statusResponse);
+ const lighthouseResult = statusResponse?.payload?.lighthouseResult;
+ check.lighthouse = {
+ accessibility: lighthouseResult?.categories?.accessibility?.score || 0,
+ bestPractices: lighthouseResult?.categories?.["best-practices"]?.score || 0,
+ seo: lighthouseResult?.categories?.seo?.score || 0,
+ performance: lighthouseResult?.categories?.performance?.score || 0,
+ audits: {
+ cls: lighthouseResult?.audits?.["cumulative-layout-shift"] || {},
+ si: lighthouseResult?.audits?.["speed-index"] || {},
+ fcp: lighthouseResult?.audits?.["first-contentful-paint"] || {},
+ lcp: lighthouseResult?.audits?.["largest-contentful-paint"] || {},
+ tbt: lighthouseResult?.audits?.["total-blocking-time"] || {},
+ },
+ };
+ return check;
+ };
+
+ buildCheck = async (statusResponse: StatusResponse, type: MonitorType): Promise => {
+ switch (type) {
+ case "infrastructure":
+ return this.buildInfrastructureCheck(statusResponse as StatusResponse);
+
+ case "pagespeed":
+ return this.buildPagespeedCheck(statusResponse as StatusResponse);
+ case "http":
+ case "https":
+ return this.buildBaseCheck(statusResponse);
+
+ case "ping":
+ return this.buildBaseCheck(statusResponse);
+ default:
+ throw new Error(`Unsupported monitor type: ${type}`);
+ }
+ };
+
+ cleanupOrphanedChecks = async () => {
+ try {
+ const monitorIds = await Monitor.find().distinct("_id");
+ const result = await Check.deleteMany({
+ monitorId: { $nin: monitorIds },
+ });
+ console.log(`Deleted ${result.deletedCount} orphaned Checks.`);
+ return true;
+ } catch (error) {
+ console.error("Error cleaning up orphaned Checks:", error);
+ return false;
+ }
+ };
+
+ getChecks = async (monitorId: string, page: number, rowsPerPage: number) => {
+ const count = await Check.countDocuments({ monitorId: new mongoose.Types.ObjectId(monitorId) });
+ const checks = await Check.find({ monitorId: new mongoose.Types.ObjectId(monitorId) })
+ .sort({ createdAt: -1 })
+ .skip(page * rowsPerPage)
+ .limit(rowsPerPage)
+ .exec();
+ return { checks, count };
+ };
+}
+
+export default CheckService;
diff --git a/server/src/service/v2/business/InviteService.ts b/server/src/service/v2/business/InviteService.ts
new file mode 100644
index 000000000..b06b2a2c2
--- /dev/null
+++ b/server/src/service/v2/business/InviteService.ts
@@ -0,0 +1,63 @@
+import crypto from "node:crypto";
+import { ITokenizedUser, IInvite, Invite } from "../../../db/v2/models/index.js";
+import ApiError from "../../../utils/ApiError.js";
+
+const SERVICE_NAME = "InviteServiceV2";
+export interface IInviteService {
+ create: (tokenizedUser: ITokenizedUser, invite: IInvite) => Promise<{ token: string }>;
+ getAll: () => Promise;
+ get: (tokenHash: string) => Promise;
+ delete: (id: string) => Promise;
+}
+
+class InviteService implements IInviteService {
+ static SERVICE_NAME = SERVICE_NAME;
+ constructor() {}
+
+ create = async (tokenizedUser: ITokenizedUser, inviteData: IInvite) => {
+ const token = crypto.randomBytes(32).toString("hex");
+ const tokenHash = crypto.createHash("sha256").update(token).digest("hex");
+ try {
+ const invite = await Invite.create({
+ ...inviteData,
+ tokenHash,
+ createdBy: tokenizedUser.sub,
+ updatedBy: tokenizedUser.sub,
+ });
+ if (!invite) {
+ throw new ApiError("Failed to create invite", 500);
+ }
+ return { token };
+ } catch (error: any) {
+ if (error?.code === 11000) {
+ const dupError = new ApiError("Invite with this email already exists", 409);
+ dupError.stack = error?.stack;
+ throw dupError;
+ }
+ throw error;
+ }
+ };
+
+ get = async (token: string) => {
+ const tokenHash = crypto.createHash("sha256").update(token).digest("hex");
+ const invite = await Invite.findOne({ tokenHash });
+ if (!invite) {
+ throw new ApiError("Invite not found", 404);
+ }
+ return invite;
+ };
+
+ getAll = async () => {
+ return Invite.find();
+ };
+
+ delete = async (id: string) => {
+ const result = await Invite.deleteOne({ _id: id });
+ if (!result.deletedCount) {
+ throw new ApiError("Invite not found", 404);
+ }
+ return result.deletedCount === 1;
+ };
+}
+
+export default InviteService;
diff --git a/server/src/service/v2/business/MaintenanceService.ts b/server/src/service/v2/business/MaintenanceService.ts
new file mode 100644
index 000000000..55b2d787b
--- /dev/null
+++ b/server/src/service/v2/business/MaintenanceService.ts
@@ -0,0 +1,144 @@
+import { ITokenizedUser, IMaintenance, Maintenance } from "../../../db/v2/models/index.js";
+import ApiError from "../../../utils/ApiError.js";
+
+const SERVICE_NAME = "MaintenanceServiceV2";
+
+export interface IMaintenanceService {
+ create: (
+ tokenizedUser: ITokenizedUser,
+
+ maintenance: IMaintenance
+ ) => Promise;
+ getAll: () => Promise;
+ get: (id: string) => Promise;
+ toggleActive: (tokenizedUser: ITokenizedUser, id: string) => Promise;
+ update: (tokenizedUser: ITokenizedUser, id: string, updateData: Partial) => Promise;
+ delete: (id: string) => Promise;
+ isInMaintenance: (monitorId: string) => Promise;
+}
+
+type MaintenanceCache = Map;
+
+class MaintenanceService implements IMaintenanceService {
+ static SERVICE_NAME = SERVICE_NAME;
+ private maintenanceCache: MaintenanceCache;
+ private lastRefresh: number;
+ private CACHE_TTL_MS = 60 * 1000;
+
+ constructor() {
+ this.maintenanceCache = new Map();
+ this.lastRefresh = 0;
+ }
+
+ create = async (tokenizedUser: ITokenizedUser, maintenanceData: IMaintenance) => {
+ const maintenance = await Maintenance.create({
+ ...maintenanceData,
+ createdBy: tokenizedUser.sub,
+ updatedBy: tokenizedUser.sub,
+ });
+ return maintenance;
+ };
+
+ get = async (id: string) => {
+ const maintenance = await Maintenance.findById(id);
+ if (!maintenance) {
+ throw new ApiError("Maintenance not found", 404);
+ }
+ return maintenance;
+ };
+
+ getAll = async () => {
+ return Maintenance.find();
+ };
+
+ toggleActive = async (tokenizedUser: ITokenizedUser, id: string) => {
+ const updatedMaintenance = await Maintenance.findOneAndUpdate(
+ { _id: id },
+ [
+ {
+ $set: {
+ isActive: { $not: "$isActive" },
+ updatedBy: tokenizedUser.sub,
+ updatedAt: new Date(),
+ },
+ },
+ ],
+ { new: true }
+ );
+ if (!updatedMaintenance) {
+ throw new ApiError("Maintenance not found", 404);
+ }
+ return updatedMaintenance;
+ };
+
+ update = async (tokenizedUser: ITokenizedUser, id: string, updateData: Partial) => {
+ const allowedFields: (keyof IMaintenance)[] = ["name", "monitors", "startTime", "endTime", "isActive"];
+ const safeUpdate: Partial = {};
+ for (const field of allowedFields) {
+ if (updateData[field] !== undefined) {
+ (safeUpdate as any)[field] = updateData[field];
+ }
+ }
+
+ const updatedMaintenance = await Maintenance.findByIdAndUpdate(
+ id,
+ {
+ $set: {
+ ...safeUpdate,
+ updatedAt: new Date(),
+ updatedBy: tokenizedUser.sub,
+ },
+ },
+ { new: true, runValidators: true }
+ );
+
+ if (!updatedMaintenance) {
+ throw new ApiError("Failed to update maintenance", 500);
+ }
+
+ return updatedMaintenance;
+ };
+
+ delete = async (id: string) => {
+ const result = await Maintenance.deleteOne({ _id: id });
+ if (!result.deletedCount) {
+ throw new ApiError("Maintenance not found", 404);
+ }
+ return result.deletedCount === 1;
+ };
+
+ private refreshCache = async () => {
+ const now = new Date();
+
+ const activeMaintenances = await Maintenance.find({
+ isActive: true,
+ startTime: { $lte: now },
+ endTime: { $gte: now },
+ }).lean();
+
+ // Reset cache
+ const newCache = new Map();
+
+ for (const m of activeMaintenances) {
+ for (const monitorId of m.monitors) {
+ const key = monitorId.toString();
+ if (!newCache.has(key)) newCache.set(key, []);
+ newCache.get(key)!.push(m);
+ }
+ }
+
+ this.maintenanceCache = newCache;
+ this.lastRefresh = Date.now();
+ };
+
+ isInMaintenance = async (monitorId: string) => {
+ const now = Date.now();
+ if (now - this.lastRefresh > this.CACHE_TTL_MS) {
+ await this.refreshCache();
+ }
+ const maintenances = this.maintenanceCache.get(monitorId) || [];
+ return maintenances.length > 0;
+ };
+}
+
+export default MaintenanceService;
diff --git a/server/src/service/v2/business/MonitorService.ts b/server/src/service/v2/business/MonitorService.ts
new file mode 100644
index 000000000..fb338c348
--- /dev/null
+++ b/server/src/service/v2/business/MonitorService.ts
@@ -0,0 +1,468 @@
+import mongoose from "mongoose";
+
+import { IMonitor, Monitor, ITokenizedUser, MonitorStats, Check } from "../../../db/v2/models/index.js";
+import ApiError from "../../../utils/ApiError.js";
+import { IJobQueue } from "../infrastructure/JobQueue.js";
+import { MonitorWithChecksResponse } from "../../../types/index.js";
+import { MonitorStatus, MonitorType } from "../../../db/v2/models/monitors/Monitor.js";
+
+const SERVICE_NAME = "MonitorServiceV2";
+
+export interface IMonitorService {
+ create: (tokenizedUser: ITokenizedUser, monitorData: IMonitor) => Promise;
+ getAll: () => Promise;
+ getAllEmbedChecks: (page: number, limit: number, type: MonitorType[]) => Promise;
+ get: (monitorId: string) => Promise;
+ getEmbedChecks: (monitorId: string, range: string, status?: string) => Promise;
+ toggleActive: (monitorId: string, tokenizedUser: ITokenizedUser) => Promise;
+ update: (tokenizedUser: ITokenizedUser, monitorId: string, updateData: Partial) => Promise;
+ delete: (monitorId: string) => Promise;
+}
+
+class MonitorService implements IMonitorService {
+ static SERVICE_NAME = SERVICE_NAME;
+ private jobQueue: IJobQueue;
+ constructor(jobQueue: IJobQueue) {
+ this.jobQueue = jobQueue;
+ }
+
+ create = async (tokenizedUser: ITokenizedUser, monitorData: IMonitor) => {
+ const monitor = await Monitor.create({
+ ...monitorData,
+ createdBy: tokenizedUser.sub,
+ updatedBy: tokenizedUser.sub,
+ });
+ await MonitorStats.create({
+ monitorId: monitor._id,
+ currentStreakStartedAt: Date.now(),
+ });
+ await this.jobQueue.addJob(monitor);
+ return monitor;
+ };
+
+ getAll = async () => {
+ return Monitor.find();
+ };
+
+ getAllEmbedChecks = async (page: number, limit: number, type: MonitorType[] = []) => {
+ const skip = (page - 1) * limit;
+ let find = {};
+ if (type.length > 0) find = { type: { $in: type } };
+ const monitors = await Monitor.find(find).skip(skip).limit(limit);
+ return monitors;
+ };
+
+ get = async (monitorId: string) => {
+ const monitor = await Monitor.findById(monitorId);
+ if (!monitor) {
+ throw new ApiError("Monitor not found", 404);
+ }
+ return monitor;
+ };
+
+ private getStartDate(range: string): Date {
+ const now = new Date();
+ switch (range) {
+ case "2h":
+ return new Date(now.getTime() - 2 * 60 * 60 * 1000);
+ case "24h":
+ return new Date(now.getTime() - 24 * 60 * 60 * 1000);
+ case "7d":
+ return new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
+ case "30d":
+ return new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
+ default:
+ throw new ApiError("Invalid range parameter", 400);
+ }
+ }
+
+ private getDateFormat(range: string): string {
+ switch (range) {
+ case "2h":
+ return "%Y-%m-%dT%H:%M:00Z";
+ case "24h":
+ case "7d":
+ return "%Y-%m-%dT%H:00:00Z";
+ case "30d":
+ return "%Y-%m-%d";
+ default:
+ throw new ApiError("Invalid range parameter", 400);
+ }
+ }
+
+ private getBaseGroup = (dateFormat: string): Record => {
+ return {
+ _id: { $dateToString: { format: dateFormat, date: "$createdAt" } },
+ count: { $sum: 1 },
+ avgResponseTime: { $avg: "$responseTime" },
+ };
+ };
+
+ private getBaseProjection = (): object => {
+ return { status: 1, responseTime: 1, createdAt: 1 };
+ };
+
+ private getPageSpeedGroup = (dateFormat: string): Record => {
+ return {
+ _id: { $dateToString: { format: dateFormat, date: "$createdAt" } },
+ count: { $sum: 1 },
+ avgResponseTime: { $avg: "$responseTime" },
+ accessibility: { $avg: "$lighthouse.accessibility" },
+ bestPractices: { $avg: "$lighthouse.bestPractices" },
+ seo: { $avg: "$lighthouse.seo" },
+ performance: { $avg: "$lighthouse.performance" },
+ cls: { $avg: "$lighthouse.audits.cls.score" },
+ si: { $avg: "$lighthouse.audits.si.score" },
+ fcp: { $avg: "$lighthouse.audits.fcp.score" },
+ lcp: { $avg: "$lighthouse.audits.lcp.score" },
+ tbt: { $avg: "$lighthouse.audits.tbt.score" },
+ };
+ };
+
+ private getPageSpeedProjection = (): object => {
+ const projectStage: any = { status: 1, responseTime: 1, createdAt: 1 };
+ projectStage["lighthouse.accessibility"] = 1;
+ projectStage["lighthouse.seo"] = 1;
+ projectStage["lighthouse.bestPractices"] = 1;
+ projectStage["lighthouse.performance"] = 1;
+ projectStage["lighthouse.audits.cls.score"] = 1;
+ projectStage["lighthouse.audits.si.score"] = 1;
+ projectStage["lighthouse.audits.fcp.score"] = 1;
+ projectStage["lighthouse.audits.lcp.score"] = 1;
+ projectStage["lighthouse.audits.tbt.score"] = 1;
+ return projectStage;
+ };
+
+ private getInfraGroup = (dateFormat: string): Record => {
+ return {
+ _id: { $dateToString: { format: dateFormat, date: "$createdAt" } },
+ count: { $sum: 1 },
+ avgResponseTime: { $avg: "$responseTime" },
+ physicalCores: { $last: "$system.cpu.physical_core" },
+ logicalCores: { $last: "$system.cpu.logical_core" },
+ frequency: { $avg: "$system.cpu.frequency" },
+ currentFrequency: { $last: "$system.cpu.current_frequency" },
+ tempsArrays: { $push: "$system.cpu.temperature" },
+ freePercent: { $avg: "$system.cpu.free_percent" },
+ usedPercent: { $avg: "$system.cpu.usage_percent" },
+ total_bytes: { $last: "$system.memory.total_bytes" },
+ available_bytes: { $last: "$system.memory.available_bytes" },
+ used_bytes: { $last: "$system.memory.used_bytes" },
+ memory_usage_percent: { $avg: "$system.memory.usage_percent" },
+ disksArray: { $push: "$system.disk" },
+ os: { $last: "$system.host.os" },
+ platform: { $last: "$system.host.platform" },
+ kernel_version: { $last: "$system.host.kernel_version" },
+ pretty_name: { $last: "$system.host.pretty_name" },
+ netsArray: { $push: "$system.net" },
+ };
+ };
+
+ private getInfraProjection = (): object => {
+ const projectStage: any = { status: 1, responseTime: 1, createdAt: 1 };
+ projectStage["system.cpu.physical_core"] = 1;
+ projectStage["system.cpu.logical_core"] = 1;
+ projectStage["system.cpu.frequency"] = 1;
+ projectStage["system.cpu.current_frequency"] = 1;
+ projectStage["system.cpu.temperature"] = 1;
+ projectStage["system.cpu.free_percent"] = 1;
+ projectStage["system.cpu.usage_percent"] = 1;
+ projectStage["system.memory.total_bytes"] = 1;
+ projectStage["system.memory.available_bytes"] = 1;
+ projectStage["system.memory.used_bytes"] = 1;
+ projectStage["system.memory.usage_percent"] = 1;
+ projectStage["system.disk"] = 1;
+ projectStage["system.host.os"] = 1;
+ projectStage["system.host.platform"] = 1;
+ projectStage["system.host.kernel_version"] = 1;
+ projectStage["system.host.pretty_name"] = 1;
+ projectStage["system.net"] = 1;
+ return projectStage;
+ };
+
+ private getFinalProjection = (type: string): object => {
+ if (type === "pagespeed") {
+ return {
+ _id: 1,
+ count: 1,
+ avgResponseTime: 1,
+ accessibility: "$accessibility",
+ seo: "$seo",
+ bestPractices: "$bestPractices",
+ performance: "$performance",
+ cls: "$cls",
+ si: "$si",
+ fcp: "$fcp",
+ lcp: "$lcp",
+ tbt: "$tbt",
+ };
+ }
+
+ if (type === "infrastructure") {
+ return {
+ _id: 1,
+ count: 1,
+ avgResponseTime: 1,
+ cpu: {
+ physicalCores: "$physicalCores",
+ logicalCores: "$logicalCores",
+ frequency: "$frequency",
+ currentFrequency: "$currentFrequency",
+ temperatures: {
+ $map: {
+ input: {
+ $range: [0, { $size: { $arrayElemAt: ["$tempsArrays", 0] } }],
+ },
+ as: "idx",
+ in: {
+ $avg: {
+ $map: {
+ input: "$tempsArrays",
+ as: "arr",
+ in: { $arrayElemAt: ["$$arr", "$$idx"] },
+ },
+ },
+ },
+ },
+ },
+ freePercent: "$freePercent",
+ usedPercent: "$usedPercent",
+ },
+ memory: {
+ total_bytes: "$total_bytes",
+ available_bytes: "$available_bytes",
+ used_bytes: "$used_bytes",
+ usage_percent: "$memory_usage_percent",
+ },
+ disks: {
+ $map: {
+ input: {
+ $range: [0, { $size: { $arrayElemAt: ["$disksArray", 0] } }],
+ },
+ as: "idx",
+ in: {
+ $let: {
+ vars: {
+ diskGroup: {
+ $map: {
+ input: "$disksArray",
+ as: "diskArr",
+ in: { $arrayElemAt: ["$$diskArr", "$$idx"] },
+ },
+ },
+ },
+ in: {
+ device: { $arrayElemAt: ["$$diskGroup.device", 0] },
+ total_bytes: { $avg: "$$diskGroup.total_bytes" },
+ free_bytes: { $avg: "$$diskGroup.free_bytes" },
+ used_bytes: { $avg: "$$diskGroup.used_bytes" },
+ usage_percent: { $avg: "$$diskGroup.usage_percent" },
+ total_inodes: { $avg: "$$diskGroup.total_inodes" },
+ free_inodes: { $avg: "$$diskGroup.free_inodes" },
+ used_inodes: { $avg: "$$diskGroup.used_inodes" },
+ inodes_usage_percent: {
+ $avg: "$$diskGroup.inodes_usage_percent",
+ },
+ read_bytes: { $avg: "$$diskGroup.read_bytes" },
+ write_bytes: { $avg: "$$diskGroup.write_bytes" },
+ read_time: { $avg: "$$diskGroup.read_time" },
+ write_time: { $avg: "$$diskGroup.write_time" },
+ },
+ },
+ },
+ },
+ },
+ host: {
+ os: "$os",
+ platform: "$platform",
+ kernel_version: "$kernel_version",
+ pretty_name: "$pretty_name",
+ },
+ net: {
+ $map: {
+ input: {
+ $range: [0, { $size: { $arrayElemAt: ["$netsArray", 0] } }],
+ },
+ as: "idx",
+ in: {
+ $let: {
+ vars: {
+ netGroup: {
+ $map: {
+ input: "$netsArray",
+ as: "netArr",
+ in: { $arrayElemAt: ["$$netArr", "$$idx"] },
+ },
+ },
+ },
+ in: {
+ name: { $arrayElemAt: ["$$netGroup.name", 0] },
+ bytes_sent: { $avg: "$$netGroup.bytes_sent" },
+ bytes_recv: { $avg: "$$netGroup.bytes_recv" },
+ packets_sent: { $avg: "$$netGroup.packets_sent" },
+ packets_recv: { $avg: "$$netGroup.packets_recv" },
+ err_in: { $avg: "$$netGroup.err_in" },
+ err_out: { $avg: "$$netGroup.err_out" },
+ drop_in: { $avg: "$$netGroup.drop_in" },
+ drop_out: { $avg: "$$netGroup.drop_out" },
+ fifo_in: { $avg: "$$netGroup.fifo_in" },
+ fifo_out: { $avg: "$$netGroup.fifo_out" },
+ },
+ },
+ },
+ },
+ },
+ };
+ }
+ return {};
+ };
+
+ getEmbedChecks = async (monitorId: string, range: string, status: string | undefined): Promise => {
+ const monitor = await Monitor.findById(monitorId);
+ if (!monitor) {
+ throw new ApiError("Monitor not found", 404);
+ }
+ const startDate = this.getStartDate(range);
+ const dateFormat = this.getDateFormat(range);
+
+ // Build match stage
+ const matchStage: {
+ monitorId: mongoose.Types.ObjectId;
+ createdAt: { $gte: Date };
+ status?: string;
+ } = {
+ monitorId: monitor._id,
+ createdAt: { $gte: startDate },
+ };
+
+ if (status) {
+ matchStage.status = status;
+ }
+
+ let groupClause;
+
+ if (monitor.type === "pagespeed") {
+ groupClause = this.getPageSpeedGroup(dateFormat);
+ } else if (monitor.type === "infrastructure") {
+ groupClause = this.getInfraGroup(dateFormat);
+ } else {
+ groupClause = this.getBaseGroup(dateFormat);
+ }
+
+ let projectStage;
+ if (monitor.type === "pagespeed") {
+ projectStage = this.getPageSpeedProjection();
+ } else if (monitor.type === "infrastructure") {
+ projectStage = this.getInfraProjection();
+ } else {
+ projectStage = this.getBaseProjection();
+ }
+
+ let finalProjection = {};
+ if (monitor.type === "pagespeed" || monitor.type === "infrastructure") {
+ finalProjection = this.getFinalProjection(monitor.type);
+ } else {
+ finalProjection = { _id: 1, count: 1, avgResponseTime: 1 };
+ }
+
+ const checks = await Check.aggregate([
+ {
+ $match: matchStage,
+ },
+ { $sort: { createdAt: 1 } },
+ { $project: projectStage },
+ { $group: groupClause },
+ { $sort: { _id: -1 } },
+ {
+ $project: finalProjection,
+ },
+ ]);
+
+ // Get monitor stats
+ const monitorStats = await MonitorStats.findOne({
+ monitorId: monitor._id,
+ });
+
+ if (!monitorStats) {
+ throw new ApiError("Monitor stats not found", 404);
+ }
+
+ return {
+ monitor: monitor.toObject(),
+ checks,
+ stats: monitorStats,
+ };
+ };
+
+ async toggleActive(id: string, tokenizedUser: ITokenizedUser) {
+ const pendingStatus: MonitorStatus = "initializing";
+ const updatedMonitor = await Monitor.findOneAndUpdate(
+ { _id: id },
+ [
+ {
+ $set: {
+ isActive: { $not: "$isActive" },
+ status: pendingStatus,
+ updatedBy: tokenizedUser.sub,
+ updatedAt: new Date(),
+ },
+ },
+ ],
+ { new: true }
+ );
+
+ if (!updatedMonitor) {
+ throw new ApiError("Monitor not found", 404);
+ }
+
+ await this.jobQueue.updateJob(updatedMonitor);
+
+ if (updatedMonitor?.isActive) {
+ await this.jobQueue.resumeJob(updatedMonitor);
+ } else {
+ await this.jobQueue.pauseJob(updatedMonitor);
+ }
+ return updatedMonitor;
+ }
+
+ async update(tokenizedUser: ITokenizedUser, monitorId: string, updateData: Partial) {
+ const allowedFields: (keyof IMonitor)[] = ["name", "interval", "isActive", "n", "notificationChannels"];
+ const safeUpdate: Partial = {};
+
+ for (const field of allowedFields) {
+ if (updateData[field] !== undefined) {
+ (safeUpdate as any)[field] = updateData[field];
+ }
+ }
+
+ const updatedMonitor = await Monitor.findByIdAndUpdate(
+ monitorId,
+ {
+ $set: {
+ ...safeUpdate,
+ updatedAt: new Date(),
+ updatedBy: tokenizedUser.sub,
+ },
+ },
+ { new: true, runValidators: true }
+ );
+
+ if (!updatedMonitor) {
+ throw new ApiError("Monitor not found", 404);
+ }
+ await this.jobQueue.updateJob(updatedMonitor);
+ return updatedMonitor;
+ }
+
+ async delete(monitorId: string) {
+ const monitor = await Monitor.findById(monitorId);
+ if (!monitor) {
+ throw new ApiError("Monitor not found", 404);
+ }
+ await monitor.deleteOne();
+ await this.jobQueue.deleteJob(monitor);
+ return true;
+ }
+}
+
+export default MonitorService;
diff --git a/server/src/service/v2/business/MonitorStatsService.ts b/server/src/service/v2/business/MonitorStatsService.ts
new file mode 100644
index 000000000..089b43ec4
--- /dev/null
+++ b/server/src/service/v2/business/MonitorStatsService.ts
@@ -0,0 +1,28 @@
+import { MonitorStats } from "../../../db/v2/models/index.js";
+import { Monitor } from "../../../db/v2/models/index.js";
+
+const SERVICE_NAME = "MonitorStatsServiceV2";
+export interface IMonitorStatsService {
+ cleanupOrphanedMonitorStats: () => Promise;
+}
+
+class MonitorStatsService implements IMonitorStatsService {
+ static SERVICE_NAME = SERVICE_NAME;
+ constructor() {}
+
+ async cleanupOrphanedMonitorStats() {
+ try {
+ const monitorIds = await Monitor.find().distinct("_id");
+ const result = await MonitorStats.deleteMany({
+ monitorId: { $nin: monitorIds },
+ });
+ console.log(`Deleted ${result.deletedCount} orphaned MonitorStats.`);
+ return true;
+ } catch (error) {
+ console.error("Error cleaning up orphaned MonitorStats:", error);
+ return false;
+ }
+ }
+}
+
+export default MonitorStatsService;
diff --git a/server/src/service/v2/business/NotificationChannelService.ts b/server/src/service/v2/business/NotificationChannelService.ts
new file mode 100644
index 000000000..112efa691
--- /dev/null
+++ b/server/src/service/v2/business/NotificationChannelService.ts
@@ -0,0 +1,104 @@
+import { ITokenizedUser, INotificationChannel, NotificationChannel, Monitor } from "../../../db/v2/models/index.js";
+import ApiError from "../../../utils/ApiError.js";
+
+const SERVICE_NAME = "NotificationChannelServiceV2";
+
+export interface INotificationChannelService {
+ create: (
+ tokenizedUser: ITokenizedUser,
+
+ notificationChannel: INotificationChannel
+ ) => Promise;
+ getAll: () => Promise;
+ get: (id: string) => Promise;
+ toggleActive: (tokenizedUser: ITokenizedUser, id: string) => Promise;
+ update: (tokenizedUser: ITokenizedUser, id: string, updateData: Partial) => Promise;
+ delete: (id: string) => Promise;
+}
+
+class NotificationChannelService implements INotificationChannelService {
+ static SERVICE_NAME = SERVICE_NAME;
+
+ constructor() {}
+
+ create = async (tokenizedUser: ITokenizedUser, notificationChannelData: INotificationChannel) => {
+ const notificationChannel = await NotificationChannel.create({
+ ...notificationChannelData,
+ createdBy: tokenizedUser.sub,
+ updatedBy: tokenizedUser.sub,
+ });
+ return notificationChannel;
+ };
+
+ get = async (id: string) => {
+ const channel = await NotificationChannel.findById(id);
+ if (!channel) {
+ throw new ApiError("Notification channel not found", 404);
+ }
+ return channel;
+ };
+
+ getAll = async () => {
+ return NotificationChannel.find();
+ };
+
+ toggleActive = async (tokenizedUser: ITokenizedUser, id: string) => {
+ const updatedChannel = await NotificationChannel.findOneAndUpdate(
+ { _id: id },
+ [
+ {
+ $set: {
+ isActive: { $not: "$isActive" },
+ updatedBy: tokenizedUser.sub,
+ updatedAt: new Date(),
+ },
+ },
+ ],
+ { new: true }
+ );
+ if (!updatedChannel) {
+ throw new ApiError("Notification channel not found", 404);
+ }
+ return updatedChannel;
+ };
+
+ update = async (tokenizedUser: ITokenizedUser, id: string, updateData: Partial) => {
+ const allowedFields: (keyof INotificationChannel)[] = ["name", "config", "isActive"];
+ const safeUpdate: Partial = {};
+ for (const field of allowedFields) {
+ if (updateData[field] !== undefined) {
+ (safeUpdate as any)[field] = updateData[field];
+ }
+ }
+
+ const updatedChannel = await NotificationChannel.findByIdAndUpdate(
+ id,
+ {
+ $set: {
+ ...safeUpdate,
+ updatedAt: new Date(),
+ updatedBy: tokenizedUser.sub,
+ },
+ },
+ { new: true, runValidators: true }
+ );
+
+ if (!updatedChannel) {
+ throw new ApiError("Failed to update notification channel", 500);
+ }
+
+ return updatedChannel;
+ };
+
+ delete = async (id: string) => {
+ const result = await NotificationChannel.deleteOne({ _id: id });
+ if (!result.deletedCount) {
+ throw new ApiError("Notification channel not found", 404);
+ }
+
+ await Monitor.updateMany({ notificationChannels: id }, { $pull: { notificationChannels: id } });
+ return result.deletedCount === 1;
+ };
+}
+
+export default NotificationChannelService;
diff --git a/server/src/service/v2/business/QueueService.ts b/server/src/service/v2/business/QueueService.ts
new file mode 100644
index 000000000..620544ba7
--- /dev/null
+++ b/server/src/service/v2/business/QueueService.ts
@@ -0,0 +1,26 @@
+import { IJobQueue } from "../infrastructure/JobQueue.js";
+
+const SERVICE_NAME = "QueueServiceV2";
+
+class QueueService {
+ static SERVICE_NAME = SERVICE_NAME;
+ private jobQueue: IJobQueue;
+
+ constructor(jobQueue: IJobQueue) {
+ this.jobQueue = jobQueue;
+ }
+
+ async getMetrics() {
+ return await this.jobQueue.getMetrics();
+ }
+
+ async getJobs() {
+ return await this.jobQueue.getJobs();
+ }
+
+ async flush() {
+ return await this.jobQueue.flush();
+ }
+}
+
+export default QueueService;
diff --git a/server/src/service/v2/business/UserService.ts b/server/src/service/v2/business/UserService.ts
new file mode 100644
index 000000000..09c50dcbc
--- /dev/null
+++ b/server/src/service/v2/business/UserService.ts
@@ -0,0 +1,15 @@
+import { IUser, User } from "../../../db/v2/models/index.js";
+
+const SERVICE_NAME = "UserServiceV2";
+export interface IUserService {
+ getAllUsers(): Promise;
+}
+
+class UserService implements IUserService {
+ static SERVICE_NAME = SERVICE_NAME;
+ async getAllUsers(): Promise {
+ return await User.find();
+ }
+}
+
+export default UserService;
diff --git a/server/src/service/v2/infrastructure/JobGenerator.ts b/server/src/service/v2/infrastructure/JobGenerator.ts
new file mode 100644
index 000000000..69b716920
--- /dev/null
+++ b/server/src/service/v2/infrastructure/JobGenerator.ts
@@ -0,0 +1,82 @@
+import { IMonitor } from "../../../db/v2/models/index.js";
+import { INetworkService } from "./NetworkService.js";
+import { ICheckService } from "../business/CheckService.js";
+import { IMonitorStatsService } from "../business/MonitorStatsService.js";
+import { IStatusService } from "./StatusService.js";
+import { INotificationService } from "./NotificationService.js";
+import { IMaintenanceService } from "../business/MaintenanceService.js";
+import ApiError from "../../../utils/ApiError.js";
+
+const SERVICE_NAME = "JobGeneratorV2";
+export interface IJobGenerator {
+ generateJob: () => (Monitor: IMonitor) => Promise;
+ generateCleanupJob: () => () => Promise;
+}
+
+class JobGenerator implements IJobGenerator {
+ static SERVICE_NAME = SERVICE_NAME;
+ private networkService: INetworkService;
+ private checkService: ICheckService;
+ private monitorStatsService: IMonitorStatsService;
+ private statusService: IStatusService;
+ private notificationService: INotificationService;
+ private maintenanceService: IMaintenanceService;
+
+ constructor(
+ networkService: INetworkService,
+ checkService: ICheckService,
+ monitorStatsService: IMonitorStatsService,
+ statusService: IStatusService,
+ notificationService: INotificationService,
+ maintenanceService: IMaintenanceService
+ ) {
+ this.networkService = networkService;
+ this.checkService = checkService;
+ this.monitorStatsService = monitorStatsService;
+ this.statusService = statusService;
+ this.notificationService = notificationService;
+ this.maintenanceService = maintenanceService;
+ }
+
+ generateJob = () => {
+ return async (monitor: IMonitor) => {
+ try {
+ const monitorId = monitor._id.toString();
+ if (!monitorId) {
+ throw new ApiError("No monitorID for creating job", 400);
+ }
+
+ // Check for active maintenance window, if found, skip the check
+ const isInMaintenance = await this.maintenanceService.isInMaintenance(monitorId);
+ if (isInMaintenance) {
+ return;
+ }
+
+ const status = await this.networkService.requestStatus(monitor);
+ const check = await this.checkService.buildCheck(status, monitor.type);
+ await check.save();
+ const [updatedMonitor, statusChanged] = await this.statusService.updateMonitorStatus(monitor, status);
+
+ if (statusChanged) {
+ await this.notificationService.handleNotifications(updatedMonitor);
+ }
+ await this.statusService.updateMonitorStats(updatedMonitor, status, statusChanged);
+ } catch (error) {
+ throw error;
+ }
+ };
+ };
+
+ generateCleanupJob = () => {
+ return async () => {
+ try {
+ await this.checkService.cleanupOrphanedChecks();
+ await this.monitorStatsService.cleanupOrphanedMonitorStats();
+ } catch (error) {
+ throw error;
+ }
+ };
+ };
+}
+
+export default JobGenerator;
diff --git a/server/src/service/v2/infrastructure/JobQueue.ts b/server/src/service/v2/infrastructure/JobQueue.ts
new file mode 100644
index 000000000..85479d8e5
--- /dev/null
+++ b/server/src/service/v2/infrastructure/JobQueue.ts
@@ -0,0 +1,231 @@
+import { IJob } from "super-simple-scheduler/dist/job/job.js";
+import { Monitor, IMonitor } from "../../../db/v2/models/index.js";
+import Scheduler from "super-simple-scheduler";
+import { IJobGenerator } from "./JobGenerator.js";
+
+const SERVICE_NAME = "JobQueueV2";
+export interface IJobMetrics {
+ jobs: number;
+ activeJobs: number;
+ failingJobs: number;
+ jobsWithFailures: Array<{
+ monitorId: string | number;
+ monitorUrl: string | null;
+ monitorType: string | null;
+ failedAt: number | null;
+ failCount: number | null;
+ failReason: string | null;
+ }>;
+ totalRuns: number;
+ totalFailures: number;
+}
+
+export interface IJobData extends IJob {
+ lastRunTook: number | null;
+}
+
+export interface IJobQueue {
+ init: () => Promise;
+ addJob: (monitor: IMonitor) => Promise;
+ pauseJob: (monitor: IMonitor) => Promise;
+ resumeJob: (monitor: IMonitor) => Promise;
+ updateJob: (monitor: IMonitor) => Promise;
+ deleteJob: (monitor: IMonitor) => Promise;
+ getMetrics: () => Promise;
+ getJobs: () => Promise;
+ flush: () => Promise;
+ shutdown: () => Promise;
+}
+
+export default class JobQueue implements IJobQueue {
+ static SERVICE_NAME = SERVICE_NAME;
+
+ private scheduler: Scheduler;
+ private static instance: JobQueue | null = null;
+ private jobGenerator: any;
+ constructor() {
+ this.scheduler = new Scheduler({
+ logLevel: "debug",
+ });
+ }
+
+ static async create(jobGenerator: IJobGenerator) {
+ if (!JobQueue.instance) {
+ const instance = new JobQueue();
+ instance.jobGenerator = jobGenerator;
+ await instance.init();
+ JobQueue.instance = instance;
+ }
+ return JobQueue.instance;
+ }
+
+ static getInstance(): JobQueue | null {
+ return JobQueue.instance;
+ }
+
+ init = async () => {
+ try {
+ this.scheduler.start();
+ // Add template and jobs
+ this.scheduler.addTemplate("monitor-job", this.jobGenerator.generateJob());
+
+ // Add a cleanup job
+ this.scheduler.addTemplate("cleanup-job", this.jobGenerator.generateCleanupJob());
+ await this.scheduler.addJob({
+ id: "cleanup-orphaned-checks",
+ template: "cleanup-job",
+ repeat: 24 * 60 * 60 * 1000, // 24 hours
+ active: true,
+ });
+
+ const monitors = await Monitor.find();
+ for (const monitor of monitors) {
+ this.addJob(monitor);
+ }
+
+ return true;
+ } catch (error) {
+ console.error(error);
+ return false;
+ }
+ };
+
+ addJob = async (monitor: IMonitor) => {
+ try {
+ return await this.scheduler?.addJob({
+ id: monitor._id.toString(),
+ template: "monitor-job",
+ repeat: monitor.interval,
+ active: monitor.isActive,
+ data: monitor,
+ });
+ } catch (error) {
+ console.error(error);
+ return false;
+ }
+ };
+
+ pauseJob = async (monitor: IMonitor) => {
+ try {
+ return await this.scheduler?.pauseJob(monitor._id.toString());
+ } catch (error) {
+ console.error(error);
+ return false;
+ }
+ };
+
+ resumeJob = async (monitor: IMonitor) => {
+ try {
+ return await this.scheduler.resumeJob(monitor._id.toString());
+ } catch (error) {
+ console.error(error);
+ return false;
+ }
+ };
+
+ updateJob = async (monitor: IMonitor) => {
+ try {
+ return await this.scheduler.updateJob(monitor._id.toString(), {
+ repeat: monitor.interval,
+ data: monitor,
+ });
+ } catch (error) {
+ console.error(error);
+ return false;
+ }
+ };
+
+ deleteJob = async (monitor: IMonitor) => {
+ try {
+ this.scheduler?.removeJob(monitor._id.toString());
+ return true;
+ } catch (error) {
+ console.error(error);
+ return false;
+ }
+ };
+
+ getMetrics = async (): Promise => {
+ try {
+ const jobs = await this.scheduler.getJobs();
+ const metrics: IJobMetrics = jobs.reduce(
+ (acc, job) => {
+ if (!job.data) return acc;
+
+ acc.totalRuns += job.runCount || 0;
+ acc.totalFailures += job.failCount || 0;
+ acc.jobs++;
+
+ // Check if job is currently failing (has recent failures)
+ const hasFailures = job.failCount && job.failCount > 0;
+ const isCurrentlyFailing = hasFailures && job.lastFailedAt && (!job.lastRunAt || job.lastFailedAt > job.lastRunAt);
+
+ if (isCurrentlyFailing) {
+ acc.failingJobs++;
+ }
+
+ if (job.lockedAt) {
+ acc.activeJobs++;
+ }
+
+ if (hasFailures) {
+ acc.jobsWithFailures.push({
+ monitorId: job.id,
+ monitorUrl: job.data?.url || null,
+ monitorType: job.data?.type || null,
+ failedAt: job.lastFailedAt || null,
+ failCount: job.failCount || null,
+ failReason: job.lastFailReason || null,
+ });
+ }
+ return acc;
+ },
+ {
+ jobs: 0,
+ activeJobs: 0,
+ failingJobs: 0,
+ jobsWithFailures: [],
+ totalRuns: 0,
+ totalFailures: 0,
+ }
+ );
+ return metrics;
+ } catch (error) {
+ console.error(error);
+ return null;
+ }
+ };
+
+ getJobs = async (): Promise => {
+ try {
+ const jobs = await this.scheduler.getJobs();
+ return jobs.map((job) => {
+ return {
+ ...job,
+ lastRunTook: job.lockedAt || !job.lastFinishedAt || !job.lastRunAt ? null : job.lastFinishedAt - job.lastRunAt,
+ };
+ });
+ } catch (error) {
+ console.error(error);
+ return null;
+ }
+ };
+
+ flush = async () => {
+ try {
+ return await this.scheduler.flushJobs();
+ } catch (error) {
+ console.error(error);
+ return false;
+ }
+ };
+
+ shutdown = async () => {
+ try {
+ return await this.scheduler.stop();
+ } catch (error) {
+ console.error(error);
+ return false;
+ }
+ };
+}
diff --git a/server/src/service/v2/infrastructure/NetworkService.ts b/server/src/service/v2/infrastructure/NetworkService.ts
new file mode 100644
index 000000000..ab8427d84
--- /dev/null
+++ b/server/src/service/v2/infrastructure/NetworkService.ts
@@ -0,0 +1,197 @@
+import { Got, HTTPError } from "got";
+import got from "got";
+import ping from "ping";
+import { IMonitor } from "../../../db/v2/models/index.js";
+import { GotTimings } from "../../../db/v2/models/checks/Check.js";
+import type { Response } from "got";
+import type { ISystemInfo, ICaptureInfo, ILighthouseResult } from "../../../db/v2/models/index.js";
+import { MonitorType, MonitorStatus } from "../../../db/v2/models/monitors/Monitor.js";
+import ApiError from "../../../utils/ApiError.js";
+import { config } from "../../../config/index.js";
+
+const SERVICE_NAME = "NetworkServiceV2";
+export interface INetworkService {
+ requestHttp: (monitor: IMonitor) => Promise;
+ requestInfrastructure: (monitor: IMonitor) => Promise;
+ requestStatus: (monitor: IMonitor) => Promise;
+ requestPagespeed: (monitor: IMonitor) => Promise;
+ requestPing: (monitor: IMonitor) => Promise;
+}
+
+export interface ICapturePayload {
+ data: ISystemInfo;
+ capture: ICaptureInfo;
+}
+
+export interface ILighthousePayload {
+ lighthouseResult: ILighthouseResult;
+}
+
+export interface StatusResponse {
+ monitorId: string;
+ type: MonitorType;
+ code?: number;
+ status: MonitorStatus;
+ message: string;
+ responseTime: number;
+ timings?: GotTimings;
+ payload?: TPayload;
+}
+
+class NetworkService implements INetworkService {
+ static SERVICE_NAME = SERVICE_NAME;
+ private got: Got;
+ private NETWORK_ERROR: number;
+ constructor() {
+ this.got = got;
+ this.NETWORK_ERROR = 5000;
+ }
+
+ private buildStatusResponse = (monitor: IMonitor, response: Response | null, error: any | null): StatusResponse => {
+ if (error) {
+ const statusResponse: StatusResponse = {
+ monitorId: monitor._id.toString(),
+ type: monitor.type,
+ status: "down" as MonitorStatus,
+ code: this.NETWORK_ERROR,
+ message: error.message || "Network error",
+ responseTime: 0,
+ timings: { phases: {} } as GotTimings,
+ };
+ if (error instanceof HTTPError) {
+ statusResponse.code = error?.response?.statusCode || this.NETWORK_ERROR;
+ statusResponse.message = error.message || "HTTP error";
+ statusResponse.responseTime = error.timings?.phases?.total || 0;
+ statusResponse.timings = error.timings;
+ }
+ return statusResponse;
+ }
+
+ const statusResponse: StatusResponse = {
+ monitorId: monitor._id.toString(),
+ type: monitor.type,
+ code: response?.statusCode || this.NETWORK_ERROR,
+ status: response?.ok === true ? "up" : "down",
+ message: response?.statusMessage || "",
+ responseTime: response?.timings?.phases?.total || 0,
+ timings: response?.timings || ({ phases: {} } as GotTimings),
+ };
+
+ return statusResponse;
+ };
+
+ requestHttp = async (monitor: IMonitor) => {
+ try {
+ const url = monitor.url;
+ if (!url) {
+ throw new Error("No URL provided");
+ }
+
+ try {
+ const response: Response = await this.got(url);
+ return this.buildStatusResponse(monitor, response, null);
+ } catch (error) {
+ return this.buildStatusResponse(monitor, null, error);
+ }
+ } catch (error) {
+ throw error;
+ }
+ };
+
+ requestInfrastructure = async (monitor: IMonitor) => {
+ const url = monitor.url;
+ if (!url) {
+ throw new Error("No URL provided");
+ }
+ const secret = monitor.secret;
+ if (!secret) {
+ throw new Error("No secret provided for infrastructure monitor");
+ }
+
+ let statusResponse: StatusResponse;
+ try {
+ const response: Response | null = await this.got(url, {
+ headers: { Authorization: `Bearer ${secret}` },
+ responseType: "json",
+ });
+
+ statusResponse = this.buildStatusResponse(monitor, response, null);
+ if (!response?.body) {
+ throw new ApiError("No payload received from infrastructure monitor", 500);
+ }
+ statusResponse.payload = response?.body;
+ return statusResponse;
+ } catch (error) {
+ statusResponse = this.buildStatusResponse(monitor, null, error);
+ }
+ return statusResponse;
+ };
+
+ requestPagespeed = async (monitor: IMonitor) => {
+ const apiKey = config.PAGESPEED_API_KEY;
+ if (!apiKey) {
+ throw new Error("No API key provided for pagespeed monitor");
+ }
+ const url = monitor.url;
+ if (!url) {
+ throw new Error("No URL provided");
+ }
+
+ let statusResponse: StatusResponse;
+
+ try {
+ const response: Response = await this.got(url);
+ statusResponse = this.buildStatusResponse(monitor, response, null) as StatusResponse;
+ } catch (error) {
+ statusResponse = this.buildStatusResponse(monitor, null, error);
+ }
+
+ const pagespeedUrl = `https://pagespeedonline.googleapis.com/pagespeedonline/v5/runPagespeed?url=${url}&category=seo&category=accessibility&category=best-practices&category=performance&key=${apiKey}`;
+ const pagespeedResponse = await this.got(pagespeedUrl, {
+ responseType: "json",
+ });
+ const payload = pagespeedResponse.body;
+ if (payload) {
+ statusResponse.payload = payload;
+ return statusResponse;
+ } else {
+ throw new ApiError("No payload received from pagespeed monitor", 500);
+ }
+ };
+
+ requestPing = async (monitor: IMonitor) => {
+ const response = await ping.promise.probe(monitor.url);
+ const status = response?.alive === true ? "up" : "down";
+
+ const rawTime = typeof response?.time === "string" ? parseFloat(response.time) : Number(response?.time);
+ const responseTime = Number.isFinite(rawTime) ? rawTime : 0;
+
+ return {
+ monitorId: monitor._id.toString(),
+ type: monitor.type,
+ status: status as MonitorStatus,
+ message: "Ping successful",
+ responseTime,
+ timings: { phases: {} } as GotTimings,
+ };
+ };
+
+ requestStatus = async (monitor: IMonitor) => {
+ switch (monitor?.type) {
+ case "http":
+ return await this.requestHttp(monitor); // uses GOT
+ case "https":
+ return await this.requestHttp(monitor); // uses GOT
+ case "infrastructure":
+ return await this.requestInfrastructure(monitor); // uses GOT
+ case "pagespeed":
+ return await this.requestPagespeed(monitor); // uses GOT
+ case "ping":
+ return await this.requestPing(monitor); // uses PING
+ default:
+ throw new Error("Not implemented");
+ }
+ };
+}
+
+export default NetworkService;
diff --git a/server/src/service/v2/infrastructure/NotificationService.ts b/server/src/service/v2/infrastructure/NotificationService.ts
new file mode 100644
index 000000000..2cc5e75d8
--- /dev/null
+++ b/server/src/service/v2/infrastructure/NotificationService.ts
@@ -0,0 +1,61 @@
+import UserService from "../business/UserService.js";
+import { IMonitor, NotificationChannel } from "../../../db/v2/models/index.js";
+import { EmailService, SlackService, DiscordService, WebhookService } from "./NotificationServices/index.js";
+
+const SERVICE_NAME = "NotificationServiceV2";
+export interface INotificationService {
+ handleNotifications: (monitor: IMonitor) => Promise;
+}
+
+class NotificationService implements INotificationService {
+ static SERVICE_NAME = SERVICE_NAME;
+ private emailService: EmailService;
+ private slackService: SlackService;
+ private discordService: DiscordService;
+ private webhookService: WebhookService;
+ private userService: UserService;
+
+ constructor(userService: UserService) {
+ this.userService = userService;
+ this.emailService = new EmailService(userService);
+ this.slackService = new SlackService();
+ this.discordService = new DiscordService();
+ this.webhookService = new WebhookService();
+ }
+
+ handleNotifications = async (monitor: IMonitor) => {
+ const notificationIds = monitor.notificationChannels || [];
+
+ if (notificationIds.length === 0) {
+ return;
+ }
+
+ const notificationChannels = await NotificationChannel.find({
+ _id: { $in: notificationIds },
+ });
+
+ for (const channel of notificationChannels) {
+ // Implement sending logic based on channel.type and channel.config
+ let service;
+ switch (channel.type) {
+ case "email":
+ await this.emailService.sendMessage(this.emailService.buildAlert(monitor), channel);
+ break;
+ case "slack":
+ await this.slackService.sendMessage(this.slackService.buildAlert(monitor), channel);
+ break;
+ case "discord":
+ await this.discordService.sendMessage(this.discordService.buildAlert(monitor), channel);
+ break;
+ case "webhook":
+ await this.webhookService.sendMessage(this.webhookService.buildAlert(monitor), channel);
+ break;
+ default:
+ console.warn(`Unknown notification channel type: ${channel.type}`);
+ }
+ }
+ return;
+ };
+}
+
+export default NotificationService;
diff --git a/server/src/service/v2/infrastructure/NotificationServices/Discord.ts b/server/src/service/v2/infrastructure/NotificationServices/Discord.ts
new file mode 100644
index 000000000..c3a5d375a
--- /dev/null
+++ b/server/src/service/v2/infrastructure/NotificationServices/Discord.ts
@@ -0,0 +1,76 @@
+import { IMonitor, INotificationChannel } from "../../../../db/v2/models/index.js";
+import { IAlert, IMessageService } from "./IMessageService.js";
+import got from "got";
+import ApiError from "../../../../utils/ApiError.js";
+
+const SERVICE_NAME = "DiscordServiceV2";
+class DiscordService implements IMessageService {
+ static SERVICE_NAME = SERVICE_NAME;
+ constructor() {}
+
+ private toDiscordEmbeds = (alert: IAlert) => {
+ return {
+ color: alert.status === "up" ? 65280 : 16711680,
+ title: `Monitor name: ${alert.name}`,
+ description: `Status: **${alert.status}**`,
+ fields: [
+ {
+ name: "Url",
+ value: alert.url,
+ },
+ {
+ name: "Checked at",
+ value: alert.checkTime ? alert.checkTime.toISOString() : "N/A",
+ },
+ { name: "Alert time", value: alert.alertTime.toISOString() },
+ ...(alert.details
+ ? Object.entries(alert.details).map(([key, value]) => ({
+ name: key,
+ value,
+ }))
+ : []),
+ ],
+ };
+ };
+
+ buildAlert = (monitor: IMonitor) => {
+ const name = monitor?.name || "Unnamed monitor";
+ const monitorStatus = monitor?.status || "unknown status";
+ const url = monitor?.url || "no URL";
+ const checkTime = monitor?.lastCheckedAt || null;
+ const alertTime = new Date();
+ return {
+ name,
+ url,
+ status: monitorStatus,
+ checkTime,
+ alertTime,
+ };
+ };
+
+ sendMessage = async (alert: IAlert, channel: INotificationChannel) => {
+ const notificationUrl = channel?.config?.url;
+ if (!notificationUrl) {
+ throw new ApiError("Webhook URL not configured", 400);
+ }
+
+ try {
+ const payload = {
+ content: "Status Alert",
+ embeds: [this.toDiscordEmbeds(alert)],
+ };
+ await got.post(notificationUrl, { json: payload });
+ } catch (error) {
+ console.warn("Failed to send Discord message", error);
+ return false;
+ }
+
+ return true;
+ };
+
+ testMessage = async () => {
+ return true;
+ };
+}
+
+export default DiscordService;
diff --git a/server/src/service/v2/infrastructure/NotificationServices/Email.ts b/server/src/service/v2/infrastructure/NotificationServices/Email.ts
new file mode 100644
index 000000000..4bed9a156
--- /dev/null
+++ b/server/src/service/v2/infrastructure/NotificationServices/Email.ts
@@ -0,0 +1,68 @@
+import { IMonitor, INotificationChannel } from "../../../../db/v2/models/index.js";
+import { IMessageService, IAlert } from "./IMessageService.js";
+import nodemailer, { Transporter } from "nodemailer";
+import { config } from "../../../../config/index.js";
+import UserService from "../../business/UserService.js";
+import ApiError from "../../../../utils/ApiError.js";
+
+const SERVICE_NAME = "EmailServiceV2";
+class EmailService implements IMessageService {
+ static SERVICE_NAME = SERVICE_NAME;
+ private transporter: Transporter;
+ private userService: UserService;
+
+ constructor(userService: UserService) {
+ this.userService = userService;
+ this.transporter = nodemailer.createTransport({
+ host: config.SMTP_HOST,
+ port: config.SMTP_PORT,
+ secure: config.SMTP_PORT === 465,
+ auth: {
+ user: config.SMTP_USER,
+ pass: config.SMTP_PASS,
+ },
+ });
+ }
+
+ buildAlert = (monitor: IMonitor) => {
+ const name = monitor?.name || "Unnamed monitor";
+ const monitorStatus = monitor?.status || "unknown status";
+ const url = monitor?.url || "no URL";
+ const checkTime = monitor?.lastCheckedAt || null;
+ const alertTime = new Date();
+ return {
+ name,
+ url,
+ status: monitorStatus,
+ checkTime,
+ alertTime,
+ };
+ };
+
+ sendMessage = async (alert: string | IAlert, channel: INotificationChannel) => {
+ try {
+ const users = await this.userService.getAllUsers();
+ const emails = users.map((u) => u.email).join(",");
+
+ if (!emails || emails.length === 0) {
+ throw new ApiError("No user emails found", 500);
+ }
+
+ await this.transporter.sendMail({
+ from: `"Checkmate" <${config.SMTP_USER}>`,
+ to: emails,
+ subject: "Monitor Alert",
+ text: JSON.stringify(alert, null, 2),
+ });
+ return true;
+ } catch (error) {
+ return false;
+ }
+ };
+
+ testMessage = async () => {
+ return true;
+ };
+}
+
+export default EmailService;
diff --git a/server/src/service/v2/infrastructure/NotificationServices/IMessageService.ts b/server/src/service/v2/infrastructure/NotificationServices/IMessageService.ts
new file mode 100644
index 000000000..f00a20549
--- /dev/null
+++ b/server/src/service/v2/infrastructure/NotificationServices/IMessageService.ts
@@ -0,0 +1,15 @@
+import { IMonitor, INotificationChannel } from "../../../../db/v2/models/index.js";
+
+export interface IAlert {
+ name: string;
+ url: string;
+ status: string;
+ details?: Record;
+ checkTime: Date | null;
+ alertTime: Date;
+}
+export interface IMessageService {
+ buildAlert: (monitor: IMonitor) => IAlert;
+ sendMessage: (alert: IAlert, channel: INotificationChannel) => Promise;
+ testMessage: (message: string, channel: INotificationChannel) => Promise;
+}
diff --git a/server/src/service/v2/infrastructure/NotificationServices/Slack.ts b/server/src/service/v2/infrastructure/NotificationServices/Slack.ts
new file mode 100644
index 000000000..0be56ca56
--- /dev/null
+++ b/server/src/service/v2/infrastructure/NotificationServices/Slack.ts
@@ -0,0 +1,118 @@
+import { IMonitor, INotificationChannel } from "../../../../db/v2/models/index.js";
+import { IAlert, IMessageService } from "./IMessageService.js";
+import got from "got";
+
+const SERVICE_NAME = "SlackServiceV2";
+class SlackService implements IMessageService {
+ static SERVICE_NAME = SERVICE_NAME;
+ constructor() {}
+
+ private toSlackBlocks = (alert: IAlert) => {
+ return [
+ {
+ type: "header",
+ text: {
+ type: "plain_text",
+ text: "Status Alert",
+ },
+ },
+ {
+ type: "section",
+ text: {
+ type: "mrkdwn",
+ text: `*Monitor name:* ${alert.name}`,
+ },
+ },
+
+ {
+ type: "section",
+ text: {
+ type: "mrkdwn",
+ text: `*Status:* ${alert.status}`,
+ },
+ },
+
+ {
+ type: "section",
+ text: {
+ type: "mrkdwn",
+ text: `*URL:* ${alert.url}`,
+ },
+ },
+ {
+ type: "section",
+ text: {
+ type: "mrkdwn",
+ text: `*Checked at:* ${alert?.checkTime?.toISOString() || "N/A"}`,
+ },
+ },
+
+ {
+ type: "divider",
+ },
+
+ ...(alert.details
+ ? Object.entries(alert.details).map(([key, value]) => ({
+ type: "section",
+ fields: [
+ {
+ type: "mrkdwn",
+ text: `*${key}:* ${value}`,
+ },
+ ],
+ }))
+ : []),
+
+ {
+ type: "context",
+ elements: [
+ {
+ type: "mrkdwn",
+ text: `*Alert generated at:* ${alert?.alertTime?.toISOString() || "N/A"}`,
+ },
+ ],
+ },
+ ];
+ };
+
+ buildAlert = (monitor: IMonitor) => {
+ const name = monitor?.name || "Unnamed monitor";
+ const monitorStatus = monitor?.status || "unknown status";
+ const url = monitor?.url || "no URL";
+ const checkTime = monitor?.lastCheckedAt || null;
+ const alertTime = new Date();
+ return {
+ name,
+ url,
+ status: monitorStatus,
+ checkTime,
+ alertTime,
+ };
+ };
+
+ sendMessage = async (alert: IAlert, channel: INotificationChannel) => {
+ const notificationUrl = channel?.config?.url;
+ if (!notificationUrl) {
+ throw new Error("Webhook URL not configured");
+ }
+
+ try {
+ const payload = {
+ text: "Status Alert",
+ blocks: this.toSlackBlocks(alert),
+ };
+ await got.post(notificationUrl, { json: payload });
+ } catch (error) {
+ console.warn("Error sending Slack message:", error);
+ return false;
+ }
+
+ return true;
+ };
+
+ testMessage = async () => {
+ return true;
+ };
+}
+
+export default SlackService;
diff --git a/server/src/service/v2/infrastructure/NotificationServices/Webhook.ts b/server/src/service/v2/infrastructure/NotificationServices/Webhook.ts
new file mode 100644
index 000000000..5dd89b16d
--- /dev/null
+++ b/server/src/service/v2/infrastructure/NotificationServices/Webhook.ts
@@ -0,0 +1,47 @@
+import { IMonitor, INotificationChannel } from "../../../../db/v2/models/index.js";
+import { IAlert, IMessageService } from "./IMessageService.js";
+import ApiError from "../../../../utils/ApiError.js";
+import got from "got";
+
+const SERVICE_NAME = "WebhookServiceV2";
+class WebhookService implements IMessageService {
+ static SERVICE_NAME = SERVICE_NAME;
+
+ constructor() {}
+
+ buildAlert = (monitor: IMonitor) => {
+ const name = monitor?.name || "Unnamed monitor";
+ const monitorStatus = monitor?.status || "unknown status";
+ const url = monitor?.url || "no URL";
+ const checkTime = monitor?.lastCheckedAt || null;
+ const alertTime = new Date();
+ return {
+ name,
+ url,
+ status: monitorStatus,
+ checkTime,
+ alertTime,
+ };
+ };
+
+ sendMessage = async (alert: IAlert, channel: INotificationChannel) => {
+ const notificationUrl = channel?.config?.url;
+ if (!notificationUrl) {
+ throw new ApiError("Webhook URL not configured", 400);
+ }
+ try {
+ await got.post(notificationUrl, { json: { ...alert } });
+ } catch (error) {
+ console.warn("Failed to send webhook notification:", error);
+ return false;
+ }
+
+ return true;
+ };
+
+ testMessage = async () => {
+ return true;
+ };
+}
+
+export default WebhookService;
diff --git a/server/src/service/v2/infrastructure/NotificationServices/index.ts b/server/src/service/v2/infrastructure/NotificationServices/index.ts
new file mode 100644
index 000000000..2b949f716
--- /dev/null
+++ b/server/src/service/v2/infrastructure/NotificationServices/index.ts
@@ -0,0 +1,5 @@
+export { default as DiscordService } from "./Discord.js";
+export { default as EmailService } from "./Email.js";
+export { default as SlackService } from "./Slack.js";
+export { default as WebhookService } from "./Webhook.js";
+export * from "./IMessageService.js";
diff --git a/server/src/service/v2/infrastructure/StatusService.ts b/server/src/service/v2/infrastructure/StatusService.ts
new file mode 100644
index 000000000..f2881b160
--- /dev/null
+++ b/server/src/service/v2/infrastructure/StatusService.ts
@@ -0,0 +1,102 @@
+import { IMonitor, IMonitorStats, MonitorStats } from "../../../db/v2/models/index.js";
+import { StatusResponse } from "./NetworkService.js";
+import ApiError from "../../../utils/ApiError.js";
+
+const SERVICE_NAME = "StatusServiceV2";
+const MAX_LATEST_CHECKS = 25;
+export interface IStatusService {
+ updateMonitorStatus: (monitor: IMonitor, status: StatusResponse) => Promise;
+
+ calculateAvgResponseTime: (stats: IMonitorStats, statusResponse: StatusResponse) => number;
+
+ updateMonitorStats: (monitor: IMonitor, status: StatusResponse, statusChanged: boolean) => Promise;
+}
+
+export type StatusChangeResult = [updatedMonitor: IMonitor, statusChanged: boolean];
+
+class StatusService implements IStatusService {
+ static SERVICE_NAME = SERVICE_NAME;
+ updateMonitorStatus = async (monitor: IMonitor, statusResponse: StatusResponse): Promise => {
+ const newStatus = statusResponse.status;
+ monitor.lastCheckedAt = new Date();
+
+ // Store latest checks for display
+ monitor.latestChecks = monitor.latestChecks || [];
+ monitor.latestChecks.push({
+ status: newStatus,
+ responseTime: statusResponse.responseTime,
+ checkedAt: monitor.lastCheckedAt,
+ });
+ while (monitor.latestChecks.length > MAX_LATEST_CHECKS) {
+ monitor.latestChecks.shift();
+ }
+
+ // Update monitor status
+ if (monitor.status === "initializing") {
+ monitor.status = newStatus;
+ return [await monitor.save(), true];
+ } else {
+ const { n } = monitor;
+ const latestChecks = monitor.latestChecks.slice(-n);
+ // Return early if not enough statuses to evaluate
+ if (latestChecks.length < n) {
+ return [await monitor.save(), false];
+ }
+
+ // If all different than current status, update status
+ const allDifferent = latestChecks.every((check) => check.status !== monitor.status);
+ if (allDifferent && monitor.status !== newStatus) {
+ monitor.status = newStatus;
+ }
+ return [await monitor.save(), allDifferent];
+ }
+ };
+
+ calculateAvgResponseTime = (stats: IMonitorStats, statusResponse: StatusResponse): number => {
+ let avgResponseTime = stats.avgResponseTime;
+ // Set initial
+ if (avgResponseTime === 0) {
+ avgResponseTime = statusResponse.responseTime;
+ } else {
+ avgResponseTime = (avgResponseTime * (stats.totalChecks - 1) + statusResponse.responseTime) / stats.totalChecks;
+ }
+ return avgResponseTime;
+ };
+
+ updateMonitorStats = async (monitor: IMonitor, statusResponse: StatusResponse, statusChanged: boolean) => {
+ const stats = await MonitorStats.findOne({ monitorId: monitor._id });
+ if (!stats) {
+ throw new ApiError("MonitorStats not found", 500);
+ }
+
+ // Update check counts
+ stats.totalChecks += 1;
+ stats.totalUpChecks += statusResponse.status === "up" ? 1 : 0;
+ stats.totalDownChecks += statusResponse.status === "down" ? 1 : 0;
+
+ // Update streak
+ if (!statusChanged) {
+ stats.currentStreak += 1;
+ } else {
+ stats.currentStreak = 1;
+ stats.currentStreakStatus = statusResponse.status;
+ stats.currentStreakStartedAt = Date.now();
+ }
+
+ // Update time stamps
+ stats.lastCheckTimestamp = Date.now();
+ stats.timeOfLastFailure = statusResponse.status === "down" ? Date.now() : stats.timeOfLastFailure;
+
+ // Update stats that need updated check counts
+ stats.avgResponseTime = this.calculateAvgResponseTime(stats, statusResponse);
+ stats.uptimePercentage = stats.totalUpChecks / stats.totalChecks;
+
+ // Other
+ stats.lastResponseTime = statusResponse.responseTime;
+ stats.maxResponseTime = Math.max(stats.maxResponseTime, statusResponse.responseTime);
+
+ return await stats.save();
+ };
+}
+
+export default StatusService;
diff --git a/server/src/types/express.d.ts b/server/src/types/express.d.ts
new file mode 100644
index 000000000..7c78a053c
--- /dev/null
+++ b/server/src/types/express.d.ts
@@ -0,0 +1,10 @@
+import { ITokenizedUser } from "../db/models/index.ts";
+
+declare global {
+ namespace Express {
+ interface Request {
+ user?: ITokenizedUser;
+ resource?: any;
+ }
+ }
+}
diff --git a/server/src/types/index.ts b/server/src/types/index.ts
new file mode 100644
index 000000000..6e708b934
--- /dev/null
+++ b/server/src/types/index.ts
@@ -0,0 +1 @@
+export type { MonitorWithChecksResponse } from "./monitor-response-with-checks.js";
diff --git a/server/src/types/monitor-response-with-checks.ts b/server/src/types/monitor-response-with-checks.ts
new file mode 100644
index 000000000..660ce0035
--- /dev/null
+++ b/server/src/types/monitor-response-with-checks.ts
@@ -0,0 +1,11 @@
+import { IMonitor, IMonitorStats } from "../db/v2/models/index.js";
+
+export interface MonitorWithChecksResponse {
+ monitor: IMonitor;
+ checks: Array<{
+ _id: string;
+ count: number;
+ avgResponseTime: number;
+ }>;
+ stats: IMonitorStats;
+}
diff --git a/server/src/utils/ApiError.ts b/server/src/utils/ApiError.ts
new file mode 100644
index 000000000..313d01c2a
--- /dev/null
+++ b/server/src/utils/ApiError.ts
@@ -0,0 +1,15 @@
+class ApiError extends Error {
+ public status: number;
+
+ constructor(message: string, status: number = 500) {
+ super(message);
+ this.status = status;
+ this.name = this.constructor.name;
+
+ if (Error.captureStackTrace) {
+ Error.captureStackTrace(this, this.constructor);
+ }
+ }
+}
+
+export default ApiError;
diff --git a/server/src/utils/JWTUtils.ts b/server/src/utils/JWTUtils.ts
new file mode 100644
index 000000000..4eebb78a0
--- /dev/null
+++ b/server/src/utils/JWTUtils.ts
@@ -0,0 +1,22 @@
+import jwt from "jsonwebtoken";
+import { AuthResult } from "../service/v2/business/AuthService.js";
+
+const encode = (data: AuthResult): string => {
+ const secret = process.env.JWT_SECRET;
+ if (!secret) {
+ throw new Error("JWT_SECRET is not defined");
+ }
+ const token = jwt.sign(data, secret, { expiresIn: "99d" });
+ return token;
+};
+
+const decode = (token: string): AuthResult => {
+ const secret = process.env.JWT_SECRET;
+ if (!secret) {
+ throw new Error("JWT_SECRET is not defined");
+ }
+ const decoded = jwt.verify(token, secret) as AuthResult;
+ return decoded;
+};
+
+export { encode, decode };
diff --git a/server/src/validation/joi.js b/server/src/validation/joi.js
index fa8dccde2..bc88d4567 100755
--- a/server/src/validation/joi.js
+++ b/server/src/validation/joi.js
@@ -174,6 +174,7 @@ const createMonitorBodyValidation = joi.object({
expectedValue: joi.string().allow(""),
matchMethod: joi.string(),
gameId: joi.string().allow(""),
+ group: joi.string().max(50).trim().allow(null, "").optional(),
});
const createMonitorsBodyValidation = joi.array().items(
@@ -203,6 +204,7 @@ const editMonitorBodyValidation = joi.object({
usage_temperature: joi.number(),
}),
gameId: joi.string(),
+ group: joi.string().max(50).trim().allow(null, "").optional(),
});
const pauseMonitorParamValidation = joi.object({
@@ -573,10 +575,10 @@ const createNotificationBodyValidation = joi.object({
"any.required": "Notification name is required",
}),
- type: joi.string().valid("email", "webhook", "slack", "discord", "pager_duty").required().messages({
+ type: joi.string().valid("email", "webhook", "slack", "discord", "pager_duty", "matrix").required().messages({
"string.empty": "Notification type is required",
"any.required": "Notification type is required",
- "any.only": "Notification type must be email, webhook, or pager_duty",
+ "any.only": "Notification type must be email, webhook, slack, discord, pager_duty, or matrix",
}),
address: joi.when("type", {
@@ -604,8 +606,40 @@ const createNotificationBodyValidation = 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(),
+ }),
});
//****************************************
@@ -670,6 +704,10 @@ const editSuperadminUserByIdBodyValidation = joi.object({
.required(),
});
+const editUserPasswordByIdBodyValidation = joi.object({
+ password: joi.string().min(8).required().pattern(passwordPattern),
+});
+
export {
roleValidatior,
loginValidation,
@@ -739,4 +777,5 @@ export {
editUserByIdParamValidation,
editUserByIdBodyValidation,
editSuperadminUserByIdBodyValidation,
+ editUserPasswordByIdBodyValidation,
};
diff --git a/server/tests/controllers/authController.test.js b/server/tests/controllers/authController.test.js
deleted file mode 100755
index 341c8eda6..000000000
--- a/server/tests/controllers/authController.test.js
+++ /dev/null
@@ -1,962 +0,0 @@
-import {
- issueToken,
- registerUser,
- loginUser,
- editUser,
- checkSuperadminExists,
- requestRecovery,
- validateRecovery,
- resetPassword,
- deleteUser,
- getAllUsers,
-} from "../../controllers/authController.js";
-import jwt from "jsonwebtoken";
-import { errorMessages, successMessages } from "../../utils/messages.js";
-import sinon from "sinon";
-import { tokenType } from "../../utils/utils.js";
-import logger from "../../utils/logger.js";
-
-describe("Auth Controller - issueToken", function () {
- let stub;
-
- afterEach(function () {
- sinon.restore(); // Restore stubs after each test
- });
-
- it("should reject with an error if jwt.sign fails", function () {
- const error = new Error("jwt.sign error");
- stub = sinon.stub(jwt, "sign").throws(error);
- const payload = { id: "123" };
- const appSettings = { jwtSecret: "my_secret" };
- expect(() => issueToken(payload, tokenType.ACCESS_TOKEN, appSettings)).to.throw(error);
- });
-
- it("should return a token if jwt.sign is successful and appSettings.jwtTTL is not defined", function () {
- const payload = { id: "123" };
- const appSettings = { jwtSecret: "my_secret" };
- const expectedToken = "mockToken";
-
- stub = sinon.stub(jwt, "sign").returns(expectedToken);
- const token = issueToken(payload, tokenType.ACCESS_TOKEN, appSettings);
- expect(token).to.equal(expectedToken);
- });
-
- it("should return a token if jwt.sign is successful and appSettings.jwtTTL is defined", function () {
- const payload = { id: "123" };
- const appSettings = { jwtSecret: "my_secret", jwtTTL: "1s" };
- const expectedToken = "mockToken";
-
- stub = sinon.stub(jwt, "sign").returns(expectedToken);
- const token = issueToken(payload, tokenType.ACCESS_TOKEN, appSettings);
- expect(token).to.equal(expectedToken);
- });
-
- it("should return a refresh token if jwt.sign is successful and appSettings.refreshTokenTTL is not defined", function () {
- const payload = {};
- const appSettings = { refreshTokenSecret: "my_refresh_secret" };
- const expectedToken = "mockRefreshToken";
-
- stub = sinon.stub(jwt, "sign").returns(expectedToken);
- const token = issueToken(payload, tokenType.REFRESH_TOKEN, appSettings);
- expect(token).to.equal(expectedToken);
- });
-
- it("should return a refresh token if jwt.sign is successful and appSettings.refreshTokenTTL is defined", function () {
- const payload = {};
- const appSettings = {
- refreshTokenSecret: "my_refresh_secret",
- refreshTokenTTL: "7d",
- };
- const expectedToken = "mockRefreshToken";
-
- stub = sinon.stub(jwt, "sign").returns(expectedToken);
- const token = issueToken(payload, tokenType.REFRESH_TOKEN, appSettings);
- expect(token).to.equal(expectedToken);
- });
-});
-
-describe("Auth Controller - registerUser", function () {
- let req, res, next;
-
- beforeEach(function () {
- req = {
- body: {
- firstName: "firstname",
- lastName: "lastname",
- email: "test@test.com",
- password: "Uptime1!",
- role: ["admin"],
- teamId: "123",
- inviteToken: "invite",
- },
- db: {
- checkSuperadmin: sinon.stub(),
- getInviteTokenAndDelete: sinon.stub(),
- updateAppSettings: sinon.stub(),
- insertUser: sinon.stub(),
- },
- settingsService: {
- getSettings: sinon.stub().resolves({
- jwtSecret: "my_secret",
- refreshTokenSecret: "my_refresh_secret",
- }),
- },
- emailService: {
- buildAndSendEmail: sinon.stub(),
- },
- file: {},
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- sinon.stub(logger, "error");
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should reject with an error if body validation fails", async function () {
- req.body = {};
- await registerUser(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should reject with an error if checkSuperadmin fails", async function () {
- req.db.checkSuperadmin.throws(new Error("checkSuperadmin error"));
- await registerUser(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("checkSuperadmin error");
- });
-
- it("should reject with an error if getInviteTokenAndDelete fails", async function () {
- req.db.checkSuperadmin.returns(true);
- req.db.getInviteTokenAndDelete.throws(new Error("getInviteTokenAndDelete error"));
- await registerUser(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("getInviteTokenAndDelete error");
- });
-
- it("should reject with an error if updateAppSettings fails", async function () {
- req.db.checkSuperadmin.returns(false);
- req.db.updateAppSettings.throws(new Error("updateAppSettings error"));
- await registerUser(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("updateAppSettings error");
- });
-
- it("should reject with an error if insertUser fails", async function () {
- req.db.checkSuperadmin.resolves(false);
- req.db.updateAppSettings.resolves();
- req.db.insertUser.rejects(new Error("insertUser error"));
- await registerUser(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("insertUser error");
- });
-
- it("should reject with an error if settingsService.getSettings fails", async function () {
- req.db.checkSuperadmin.resolves(false);
- req.db.updateAppSettings.resolves();
- req.db.insertUser.resolves({ _id: "123" });
- req.settingsService.getSettings.rejects(new Error("settingsService.getSettings error"));
- await registerUser(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("settingsService.getSettings error");
- });
-
- it("should log an error if emailService.buildAndSendEmail fails", async function () {
- req.db.checkSuperadmin.resolves(false);
- req.db.updateAppSettings.resolves();
- req.db.insertUser.returns({ _id: "123" });
- req.settingsService.getSettings.returns({
- jwtSecret: "my_secret",
- refreshTokenSecret: "my_secret",
- });
- req.emailService.buildAndSendEmail.rejects(new Error("emailService error"));
- await registerUser(req, res, next);
- expect(logger.error.calledOnce).to.be.true;
- expect(logger.error.firstCall.args[0].message).to.equal("emailService error");
- });
-
- it("should return a success message and data if all operations are successful", async function () {
- const user = { _id: "123" };
- req.db.checkSuperadmin.resolves(false);
- req.db.updateAppSettings.resolves();
- req.db.insertUser.returns(user);
- req.settingsService.getSettings.returns({
- jwtSecret: "my_secret",
- refreshTokenSecret: "my_secret",
- });
- req.emailService.buildAndSendEmail.resolves("message-id");
- await registerUser(req, res, next);
- expect(res.status.calledWith(200)).to.be.true;
- expect(
- res.json.calledWith({
- success: true,
- msg: successMessages.AUTH_CREATE_USER,
- data: { user, token: sinon.match.string, refreshToken: sinon.match.string },
- })
- ).to.be.true;
- expect(next.notCalled).to.be.true;
- });
-
- it("should return a success message and data if all operations are successful and superAdmin true", async function () {
- const user = { _id: "123" };
- req.db.checkSuperadmin.resolves(true);
- req.db.updateAppSettings.resolves();
- req.db.insertUser.returns(user);
- req.settingsService.getSettings.returns({
- jwtSecret: "my_secret",
- refreshTokenSecret: "my_secret",
- });
- req.emailService.buildAndSendEmail.resolves("message-id");
- await registerUser(req, res, next);
- expect(res.status.calledWith(200)).to.be.true;
- expect(
- res.json.calledWith({
- success: true,
- msg: successMessages.AUTH_CREATE_USER,
- data: { user, token: sinon.match.string, refreshToken: sinon.match.string },
- })
- ).to.be.true;
- expect(next.notCalled).to.be.true;
- });
-});
-
-describe("Auth Controller - loginUser", function () {
- let req, res, next, user;
-
- beforeEach(function () {
- req = {
- body: { email: "test@example.com", password: "Password123!" },
- db: {
- getUserByEmail: sinon.stub(),
- },
- settingsService: {
- getSettings: sinon.stub().resolves({
- jwtSecret: "my_secret",
- refreshTokenSecret: "my_refresh_token",
- }),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- user = {
- _doc: {
- email: "test@example.com",
- },
- comparePassword: sinon.stub(),
- };
- });
-
- it("should reject with an error if validation fails", async function () {
- req.body = {};
- await loginUser(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should reject with an error if getUserByEmail fails", async function () {
- req.db.getUserByEmail.rejects(new Error("getUserByEmail error"));
- await loginUser(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("getUserByEmail error");
- });
-
- it("should login user successfully", async function () {
- req.db.getUserByEmail.resolves(user);
- user.comparePassword.resolves(true);
- await loginUser(req, res, next);
- expect(res.status.calledWith(200)).to.be.true;
- expect(
- res.json.calledWith({
- success: true,
- msg: successMessages.AUTH_LOGIN_USER,
- data: {
- user: {
- email: "test@example.com",
- avatarImage: undefined,
- },
- token: sinon.match.string,
- refreshToken: sinon.match.string,
- },
- })
- ).to.be.true;
- expect(next.notCalled).to.be.true;
- });
-
- it("should reject a user with an incorrect password", async function () {
- req.body = {
- email: "test@test.com",
- password: "Password123!",
- };
- req.db.getUserByEmail.resolves(user);
- user.comparePassword.resolves(false);
- await loginUser(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal(errorMessages.AUTH_INCORRECT_PASSWORD);
- });
-});
-
-describe("Auth Controller - refreshAuthToken", function () {
- let req, res, next, issueTokenStub;
-
- beforeEach(function () {
- req = {
- headers: {
- "x-refresh-token": "valid_refresh_token",
- authorization: "Bearer old_auth_token",
- },
- settingsService: {
- getSettings: sinon.stub().resolves({
- jwtSecret: "my_secret",
- refreshTokenSecret: "my_refresh_secret",
- }),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- sinon.stub(jwt, "verify");
-
- issueTokenStub = sinon.stub().returns("new_auth_token");
- sinon.replace({ issueToken }, "issueToken", issueTokenStub);
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should reject if no refresh token is provided", async function () {
- delete req.headers["x-refresh-token"];
- await refreshAuthToken(req, res, next);
-
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal(errorMessages.NO_REFRESH_TOKEN);
- expect(next.firstCall.args[0].status).to.equal(401);
- });
-
- it("should reject if the refresh token is invalid", async function () {
- jwt.verify.yields(new Error("invalid token"));
- await refreshAuthToken(req, res, next);
-
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal(errorMessages.INVALID_REFRESH_TOKEN);
- expect(next.firstCall.args[0].status).to.equal(401);
- });
-
- it("should reject if the refresh token is expired", async function () {
- const error = new Error("Token expired");
- error.name = "TokenExpiredError";
- jwt.verify.yields(error);
- await refreshAuthToken(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal(errorMessages.EXPIRED_REFRESH_TOKEN);
- expect(next.firstCall.args[0].status).to.equal(401);
- });
-
- it("should reject if settingsService.getSettings fails", async function () {
- req.settingsService.getSettings.rejects(new Error("settingsService.getSettings error"));
- await refreshAuthToken(req, res, next);
-
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("settingsService.getSettings error");
- });
-
- it("should generate a new auth token if the refresh token is valid", async function () {
- const decodedPayload = { expiresIn: "60" };
- jwt.verify.callsFake(() => {
- return decodedPayload;
- });
- await refreshAuthToken(req, res, next);
-
- expect(res.status.calledWith(200)).to.be.true;
- expect(
- res.json.calledWith({
- success: true,
- msg: successMessages.AUTH_TOKEN_REFRESHED,
- data: {
- user: decodedPayload,
- token: sinon.match.string,
- refreshToken: "valid_refresh_token",
- },
- })
- ).to.be.true;
- });
-});
-
-describe("Auth Controller - editUser", function () {
- let req, res, next, stub, user;
-
- beforeEach(function () {
- req = {
- params: { userId: "123" },
- body: { password: "Password1!", newPassword: "Password2!" },
- headers: { authorization: "Bearer token" },
- user: { _id: "123" },
- settingsService: {
- getSettings: sinon.stub().returns({ jwtSecret: "my_secret" }),
- },
- db: {
- getUserByEmail: sinon.stub(),
- updateUser: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- stub = sinon.stub(jwt, "verify").returns({ email: "test@example.com" });
- });
-
- afterEach(function () {
- sinon.restore();
- stub.restore();
- });
-
- it("should reject with an error if param validation fails", async function () {
- req.params = {};
- await editUser(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should reject with an error if body validation fails", async function () {
- req.body = { invalid: 1 };
- await editUser(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should reject with an error if param.userId !== req.user._id", async function () {
- req.params = { userId: "456" };
- await editUser(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(401);
- });
-
- it("should reject with an error if !req.body.password and getUserByEmail fails", async function () {
- req.db.getUserByEmail.rejects(new Error("getUserByEmail error"));
- await editUser(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("getUserByEmail error");
- });
-
- it("should reject with an error if user.comparePassword fails", async function () {
- req.db.getUserByEmail.returns({
- comparePassword: sinon.stub().rejects(new Error("Bad Password Match")),
- });
- await editUser(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("Bad Password Match");
- });
-
- it("should reject with an error if user.comparePassword returns false", async function () {
- req.db.getUserByEmail.returns({
- comparePassword: sinon.stub().returns(false),
- });
- await editUser(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(401);
- expect(next.firstCall.args[0].message).to.equal(errorMessages.AUTH_INCORRECT_PASSWORD);
- });
-
- it("should edit a user if it receives a proper request", async function () {
- const user = {
- comparePassword: sinon.stub().resolves(true),
- };
- req.db.getUserByEmail.resolves(user);
- req.db.updateUser.resolves({ email: "test@example.com" });
-
- await editUser(req, res, next);
-
- expect(req.db.getUserByEmail.calledOnce).to.be.true;
- expect(req.db.updateUser.calledOnce).to.be.true;
- expect(res.status.calledWith(200)).to.be.true;
- expect(
- res.json.calledWith({
- success: true,
- msg: successMessages.AUTH_UPDATE_USER,
- data: { email: "test@example.com" },
- })
- ).to.be.true;
- expect(next.notCalled).to.be.true;
- });
-
- it("should edit a user if it receives a proper request and both password fields are undefined", async function () {
- req.body.password = undefined;
- req.body.newPassword = undefined;
- req.db.getUserByEmail.resolves(user);
- req.db.updateUser.resolves({ email: "test@example.com" });
-
- await editUser(req, res, next);
- expect(res.status.calledWith(200)).to.be.true;
- expect(
- res.json.calledWith({
- success: true,
- msg: successMessages.AUTH_UPDATE_USER,
- data: { email: "test@example.com" },
- })
- ).to.be.true;
- expect(next.notCalled).to.be.true;
- });
-
- it("should reject an edit request if password format is incorrect", async function () {
- req.body = { password: "bad_password", newPassword: "bad_password" };
- const user = {
- comparePassword: sinon.stub().resolves(true),
- };
- req.db.getUserByEmail.resolves(user);
-
- await editUser(req, res, next);
-
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-});
-
-describe("Auth Controller - checkSuperadminExists", function () {
- let req, res, next;
-
- beforeEach(function () {
- req = {
- db: {
- checkSuperadmin: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- });
-
- it("should reject with an error if checkSuperadmin fails", async function () {
- req.db.checkSuperadmin.rejects(new Error("checkSuperadmin error"));
- await checkSuperadminExists(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("checkSuperadmin error");
- });
-
- it("should return true if a superadmin exists", async function () {
- req.db.checkSuperadmin.resolves(true);
- await checkSuperadminExists(req, res, next);
- expect(res.status.calledWith(200)).to.be.true;
- expect(
- res.json.calledWith({
- success: true,
- msg: successMessages.AUTH_SUPERADMIN_EXISTS,
- data: true,
- })
- ).to.be.true;
- expect(next.notCalled).to.be.true;
- });
-
- it("should return false if a superadmin does not exist", async function () {
- req.db.checkSuperadmin.resolves(false);
- await checkSuperadminExists(req, res, next);
- expect(res.status.calledWith(200)).to.be.true;
- expect(
- res.json.calledWith({
- success: true,
- msg: successMessages.AUTH_SUPERADMIN_EXISTS,
- data: false,
- })
- ).to.be.true;
- expect(next.notCalled).to.be.true;
- });
-});
-
-describe("Auth Controller - requestRecovery", function () {
- let req, res, next;
-
- beforeEach(function () {
- req = {
- body: { email: "test@test.com" },
- db: {
- getUserByEmail: sinon.stub(),
- requestRecoveryToken: sinon.stub(),
- },
- settingsService: {
- getSettings: sinon.stub().returns({ clientHost: "http://localhost" }),
- },
- emailService: {
- buildAndSendEmail: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- });
-
- it("should reject with an error if validation fails", async function () {
- req.body = {};
- await requestRecovery(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should reject with an error if getUserByEmail fails", async function () {
- req.db.getUserByEmail.rejects(new Error("getUserByEmail error"));
- await requestRecovery(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("getUserByEmail error");
- });
-
- it("should throw an error if the user is not found", async function () {
- req.db.getUserByEmail.resolves(null);
- await requestRecovery(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- // expect(next.firstCall.args[0].message).to.equal(
- // errorMessages.FRIENDLY_ERROR
- // );
- });
-
- it("should throw an error if the email is not provided", async function () {
- req.body = {};
- await requestRecovery(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should return a success message if the email is provided", async function () {
- const user = { firstName: "John" };
- const recoveryToken = { token: "recovery-token" };
- const msgId = "message-id";
- req.db.getUserByEmail.resolves(user);
- req.db.requestRecoveryToken.resolves(recoveryToken);
- req.emailService.buildAndSendEmail.resolves(msgId);
- await requestRecovery(req, res, next);
- expect(req.db.getUserByEmail.calledOnceWith("test@test.com")).to.be.true;
- expect(req.db.requestRecoveryToken.calledOnceWith(req, res)).to.be.true;
- expect(
- req.emailService.buildAndSendEmail.calledOnceWith(
- "passwordResetTemplate",
- {
- name: "John",
- email: "test@test.com",
- url: "http://localhost/set-new-password/recovery-token",
- },
- "test@test.com",
- "Checkmate Password Reset"
- )
- ).to.be.true;
- expect(res.status.calledOnceWith(200)).to.be.true;
- expect(
- res.json.calledOnceWith({
- success: true,
- msg: successMessages.AUTH_CREATE_RECOVERY_TOKEN,
- data: msgId,
- })
- ).to.be.true;
- expect(next.notCalled).to.be.true;
- });
-});
-
-describe("Auth Controller - validateRecovery", function () {
- let req, res, next;
-
- beforeEach(function () {
- req = {
- body: { recoveryToken: "recovery-token" },
- db: {
- validateRecoveryToken: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- });
-
- it("should reject with an error if validation fails", async function () {
- req.body = {};
- await validateRecovery(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should reject with an error if validateRecoveryToken fails", async function () {
- req.db.validateRecoveryToken.rejects(new Error("validateRecoveryToken error"));
- await validateRecovery(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("validateRecoveryToken error");
- });
-
- it("should return a success message if the token is valid", async function () {
- req.db.validateRecoveryToken.resolves();
- await validateRecovery(req, res, next);
- expect(res.status.calledOnceWith(200)).to.be.true;
- expect(
- res.json.calledOnceWith({
- success: true,
- msg: successMessages.AUTH_VERIFY_RECOVERY_TOKEN,
- })
- ).to.be.true;
- expect(next.notCalled).to.be.true;
- });
-});
-
-describe("Auth Controller - resetPassword", function () {
- let req, res, next, newPasswordValidation, handleValidationError, handleError;
-
- beforeEach(function () {
- req = {
- body: {
- recoveryToken: "recovery-token",
- password: "Password1!",
- },
- db: {
- resetPassword: sinon.stub(),
- },
- settingsService: {
- getSettings: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- newPasswordValidation = {
- validateAsync: sinon.stub(),
- };
- handleValidationError = sinon.stub();
- handleError = sinon.stub();
- });
-
- it("should reject with an error if validation fails", async function () {
- req.body = { password: "bad_password" };
- await resetPassword(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should reject with an error if resetPassword fails", async function () {
- const error = new Error("resetPassword error");
- newPasswordValidation.validateAsync.resolves();
- req.db.resetPassword.rejects(error);
- await resetPassword(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("resetPassword error");
- });
-
- it("should reset password successfully", async function () {
- const user = { _doc: {} };
- const appSettings = { jwtSecret: "my_secret" };
- const token = "token";
-
- newPasswordValidation.validateAsync.resolves();
- req.db.resetPassword.resolves(user);
- req.settingsService.getSettings.resolves(appSettings);
-
- await resetPassword(req, res, next);
-
- expect(req.db.resetPassword.calledOnceWith(req, res)).to.be.true;
- expect(req.settingsService.getSettings.calledOnce).to.be.true;
- expect(res.status.calledOnceWith(200)).to.be.true;
- expect(
- res.json.calledOnceWith({
- success: true,
- msg: successMessages.AUTH_RESET_PASSWORD,
- data: { user: sinon.match.object, token: sinon.match.string },
- })
- ).to.be.true;
- expect(next.notCalled).to.be.true;
- });
-});
-
-describe("Auth Controller - deleteUser", function () {
- let req, res, next, handleError;
-
- beforeEach(function () {
- req = {
- headers: {
- authorization: "Bearer token",
- },
- db: {
- getUserByEmail: sinon.stub(),
- getMonitorsByTeamId: sinon.stub(),
- deleteJob: sinon.stub(),
- deleteChecks: sinon.stub(),
- deletePageSpeedChecksByMonitorId: sinon.stub(),
- deleteNotificationsByMonitorId: sinon.stub(),
- deleteTeam: sinon.stub(),
- deleteAllOtherUsers: sinon.stub(),
- deleteMonitorsByUserId: sinon.stub(),
- deleteUser: sinon.stub(),
- },
- jobQueue: {
- deleteJob: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
-
- sinon.stub(jwt, "decode");
-
- handleError = sinon.stub();
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should throw an error if user is not found", async function () {
- jwt.decode.returns({ email: "test@example.com" });
- req.db.getUserByEmail.throws(new Error(errorMessages.DB_USER_NOT_FOUND));
-
- await deleteUser(req, res, next);
-
- expect(req.db.getUserByEmail.calledOnceWith("test@example.com")).to.be.true;
- expect(next.calledOnce).to.be.true;
- expect(next.firstCall.args[0].message).to.equal(errorMessages.DB_USER_NOT_FOUND);
- expect(res.status.notCalled).to.be.true;
- expect(res.json.notCalled).to.be.true;
- });
-
- it("should delete user and associated data if user is superadmin", async function () {
- const user = {
- _id: "user_id",
- email: "test@example.com",
- role: ["superadmin"],
- teamId: "team_id",
- };
- const monitors = [{ _id: "monitor_id" }];
-
- jwt.decode.returns({ email: "test@example.com" });
- req.db.getUserByEmail.resolves(user);
- req.db.getMonitorsByTeamId.resolves({ monitors });
-
- await deleteUser(req, res, next);
-
- expect(req.db.getUserByEmail.calledOnceWith("test@example.com")).to.be.true;
- expect(
- req.db.getMonitorsByTeamId.calledOnceWith({
- params: { teamId: "team_id" },
- })
- ).to.be.true;
- expect(req.jobQueue.deleteJob.calledOnceWith(monitors[0])).to.be.true;
- expect(req.db.deleteChecks.calledOnceWith("monitor_id")).to.be.true;
- expect(req.db.deletePageSpeedChecksByMonitorId.calledOnceWith("monitor_id")).to.be.true;
- expect(req.db.deleteNotificationsByMonitorId.calledOnceWith("monitor_id")).to.be.true;
- expect(req.db.deleteTeam.calledOnceWith("team_id")).to.be.true;
- expect(req.db.deleteAllOtherUsers.calledOnce).to.be.true;
- expect(req.db.deleteMonitorsByUserId.calledOnceWith("user_id")).to.be.true;
- expect(req.db.deleteUser.calledOnceWith("user_id")).to.be.true;
- expect(res.status.calledOnceWith(200)).to.be.true;
- expect(
- res.json.calledOnceWith({
- success: true,
- msg: successMessages.AUTH_DELETE_USER,
- })
- ).to.be.true;
- expect(next.notCalled).to.be.true;
- });
-
- it("should delete user if user is not superadmin", async function () {
- const user = {
- _id: "user_id",
- email: "test@example.com",
- role: ["user"],
- teamId: "team_id",
- };
-
- jwt.decode.returns({ email: "test@example.com" });
- req.db.getUserByEmail.resolves(user);
-
- await deleteUser(req, res, next);
-
- expect(req.db.getUserByEmail.calledOnceWith("test@example.com")).to.be.true;
- expect(
- req.db.getMonitorsByTeamId.calledOnceWith({
- params: { teamId: "team_id" },
- })
- ).to.be.true;
- expect(req.db.deleteUser.calledOnceWith("user_id")).to.be.true;
- expect(res.status.calledOnceWith(200)).to.be.true;
- expect(
- res.json.calledOnceWith({
- success: true,
- msg: successMessages.AUTH_DELETE_USER,
- })
- ).to.be.true;
- expect(next.notCalled).to.be.true;
- });
-
- it("should handle errors", async function () {
- const error = new Error("Something went wrong");
- const SERVICE_NAME = "AuthController";
- jwt.decode.returns({ email: "test@example.com" });
- req.db.getUserByEmail.rejects(error);
- await deleteUser(req, res, next);
- expect(next.calledOnce).to.be.true;
- expect(next.firstCall.args[0].message).to.equal("Something went wrong");
- expect(res.status.notCalled).to.be.true;
- expect(res.json.notCalled).to.be.true;
- });
-});
-
-describe("Auth Controller - getAllUsers", function () {
- let req, res, next;
-
- beforeEach(function () {
- req = {
- db: {
- getAllUsers: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- });
-
- afterEach(function () {
- sinon.restore(); // Restore the original methods after each test
- });
-
- it("should return 200 and all users", async function () {
- const allUsers = [{ id: 1, name: "John Doe" }];
- req.db.getAllUsers.resolves(allUsers);
-
- await getAllUsers(req, res, next);
-
- expect(req.db.getAllUsers.calledOnce).to.be.true;
- expect(res.status.calledOnceWith(200)).to.be.true;
- expect(
- res.json.calledOnceWith({
- success: true,
- msg: "Got all users",
- data: allUsers,
- })
- ).to.be.true;
- expect(next.notCalled).to.be.true;
- });
-
- it("should call next with error when an exception occurs", async function () {
- const error = new Error("Something went wrong");
- req.db.getAllUsers.rejects(error);
- await getAllUsers(req, res, next);
- expect(req.db.getAllUsers.calledOnce).to.be.true;
- expect(next.calledOnce).to.be.true;
- expect(res.status.notCalled).to.be.true;
- expect(res.json.notCalled).to.be.true;
- });
-});
diff --git a/server/tests/controllers/checkController.test.js b/server/tests/controllers/checkController.test.js
deleted file mode 100755
index c5fb57d50..000000000
--- a/server/tests/controllers/checkController.test.js
+++ /dev/null
@@ -1,372 +0,0 @@
-import { createCheck, getChecks, getTeamChecks, deleteChecks, deleteChecksByTeamId, updateChecksTTL } from "../../controllers/checkController.js";
-import jwt from "jsonwebtoken";
-import { errorMessages, successMessages } from "../../utils/messages.js";
-import sinon from "sinon";
-describe("Check Controller - createCheck", function () {
- let req, res, next, handleError;
-
- beforeEach(function () {
- req = {
- params: {},
- body: {},
- db: {
- createCheck: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- handleError = sinon.stub();
- });
-
- afterEach(function () {
- sinon.restore(); // Restore the original methods after each test
- });
-
- it("should reject with a validation if params are invalid", async function () {
- await createCheck(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should reject with a validation error if body is invalid", async function () {
- req.params = {
- monitorId: "monitorId",
- };
- await createCheck(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should call next with error if data retrieval fails", async function () {
- req.params = {
- monitorId: "monitorId",
- };
- req.body = {
- monitorId: "monitorId",
- status: true,
- responseTime: 100,
- statusCode: 200,
- message: "message",
- };
- req.db.createCheck.rejects(new Error("error"));
- await createCheck(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- });
-
- it("should return a success message if check is created", async function () {
- req.params = {
- monitorId: "monitorId",
- };
- req.db.createCheck.resolves({ id: "123" });
- req.body = {
- monitorId: "monitorId",
- status: true,
- responseTime: 100,
- statusCode: 200,
- message: "message",
- };
- await createCheck(req, res, next);
- expect(res.status.calledWith(200)).to.be.true;
- expect(
- res.json.calledWith({
- success: true,
- msg: successMessages.CHECK_CREATE,
- data: { id: "123" },
- })
- ).to.be.true;
- expect(next.notCalled).to.be.true;
- });
-});
-
-describe("Check Controller - getChecks", function () {
- let req, res, next;
-
- beforeEach(function () {
- req = {
- params: {},
- query: {},
- db: {
- getChecks: sinon.stub(),
- getChecksCount: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should reject with a validation error if params are invalid", async function () {
- await getChecks(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should return a success message if checks are found", async function () {
- req.params = {
- monitorId: "monitorId",
- };
- req.db.getChecks.resolves([{ id: "123" }]);
- req.db.getChecksCount.resolves(1);
- await getChecks(req, res, next);
- expect(res.status.calledWith(200)).to.be.true;
- expect(
- res.json.calledWith({
- success: true,
- msg: successMessages.CHECK_GET,
- data: { checksCount: 1, checks: [{ id: "123" }] },
- })
- ).to.be.true;
- expect(next.notCalled).to.be.true;
- });
-
- it("should call next with error if data retrieval fails", async function () {
- req.params = {
- monitorId: "monitorId",
- };
- req.db.getChecks.rejects(new Error("error"));
- await getChecks(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- });
-});
-
-describe("Check Controller - getTeamChecks", function () {
- let req, res, next;
-
- beforeEach(function () {
- req = {
- params: {},
- query: {},
- db: {
- getTeamChecks: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should reject with a validation error if params are invalid", async function () {
- await getTeamChecks(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should return 200 and check data on successful validation and data retrieval", async function () {
- req.params = { teamId: "1" };
- const checkData = [{ id: 1, name: "Check 1" }];
- req.db.getTeamChecks.resolves(checkData);
-
- await getTeamChecks(req, res, next);
- expect(req.db.getTeamChecks.calledOnceWith(req)).to.be.true;
- expect(res.status.calledOnceWith(200)).to.be.true;
- expect(
- res.json.calledOnceWith({
- success: true,
- msg: successMessages.CHECK_GET,
- data: checkData,
- })
- ).to.be.true;
- });
-
- it("should call next with error if data retrieval fails", async function () {
- req.params = { teamId: "1" };
- req.db.getTeamChecks.rejects(new Error("Retrieval Error"));
- await getTeamChecks(req, res, next);
- expect(req.db.getTeamChecks.calledOnceWith(req)).to.be.true;
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(res.status.notCalled).to.be.true;
- expect(res.json.notCalled).to.be.true;
- });
-});
-
-describe("Check Controller - deleteChecks", function () {
- let req, res, next;
-
- beforeEach(function () {
- req = {
- params: {},
- db: {
- deleteChecks: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should reject with an error if param validation fails", async function () {
- await deleteChecks(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should call next with error if data retrieval fails", async function () {
- req.params = { monitorId: "1" };
- req.db.deleteChecks.rejects(new Error("Deletion Error"));
- await deleteChecks(req, res, next);
- expect(req.db.deleteChecks.calledOnceWith(req.params.monitorId)).to.be.true;
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(res.status.notCalled).to.be.true;
- expect(res.json.notCalled).to.be.true;
- });
-
- it("should delete checks successfully", async function () {
- req.params = { monitorId: "123" };
- req.db.deleteChecks.resolves(1);
- await deleteChecks(req, res, next);
- expect(req.db.deleteChecks.calledOnceWith(req.params.monitorId)).to.be.true;
- expect(res.status.calledOnceWith(200)).to.be.true;
- expect(
- res.json.calledOnceWith({
- success: true,
- msg: successMessages.CHECK_DELETE,
- data: { deletedCount: 1 },
- })
- ).to.be.true;
- });
-});
-
-describe("Check Controller - deleteChecksByTeamId", function () {
- let req, res, next;
-
- beforeEach(function () {
- req = {
- params: {},
- db: {
- deleteChecksByTeamId: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should reject with an error if param validation fails", async function () {
- await deleteChecksByTeamId(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should call next with error if data retrieval fails", async function () {
- req.params = { teamId: "1" };
- req.db.deleteChecksByTeamId.rejects(new Error("Deletion Error"));
- await deleteChecksByTeamId(req, res, next);
- expect(req.db.deleteChecksByTeamId.calledOnceWith(req.params.teamId)).to.be.true;
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(res.status.notCalled).to.be.true;
- expect(res.json.notCalled).to.be.true;
- });
-
- it("should delete checks successfully", async function () {
- req.params = { teamId: "123" };
- req.db.deleteChecksByTeamId.resolves(1);
- await deleteChecksByTeamId(req, res, next);
- expect(req.db.deleteChecksByTeamId.calledOnceWith(req.params.teamId)).to.be.true;
- expect(res.status.calledOnceWith(200)).to.be.true;
- expect(
- res.json.calledOnceWith({
- success: true,
- msg: successMessages.CHECK_DELETE,
- data: { deletedCount: 1 },
- })
- ).to.be.true;
- });
-});
-
-describe("Check Controller - updateCheckTTL", function () {
- let stub, req, res, next;
-
- beforeEach(function () {
- stub = sinon.stub(jwt, "verify").callsFake(() => {
- return { teamId: "123" };
- });
-
- req = {
- body: {},
- headers: { authorization: "Bearer token" },
- settingsService: {
- getSettings: sinon.stub().returns({ jwtSecret: "my_secret" }),
- },
- db: {
- updateChecksTTL: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- });
-
- afterEach(function () {
- sinon.restore();
- stub.restore();
- });
-
- it("should reject if body validation fails", async function () {
- await updateChecksTTL(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should throw a JwtError if verification fails", async function () {
- stub.restore();
- req.body = {
- ttl: 1,
- };
- await updateChecksTTL(req, res, next);
- expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError);
- });
-
- it("should call next with error if data retrieval fails", async function () {
- req.body = {
- ttl: 1,
- };
- req.db.updateChecksTTL.rejects(new Error("Update Error"));
- await updateChecksTTL(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- });
-
- it("should update TTL successfully", async function () {
- req.body = {
- ttl: 1,
- };
- req.db.updateChecksTTL.resolves();
- await updateChecksTTL(req, res, next);
- expect(req.db.updateChecksTTL.calledOnceWith("123", 1 * 86400)).to.be.true;
- expect(res.status.calledOnceWith(200)).to.be.true;
- expect(
- res.json.calledOnceWith({
- success: true,
- msg: successMessages.CHECK_UPDATE_TTL,
- })
- ).to.be.true;
- });
-});
diff --git a/server/tests/controllers/controllerUtils.test.js b/server/tests/controllers/controllerUtils.test.js
deleted file mode 100755
index a55318857..000000000
--- a/server/tests/controllers/controllerUtils.test.js
+++ /dev/null
@@ -1,157 +0,0 @@
-import sinon from "sinon";
-
-import { handleValidationError, handleError, fetchMonitorCertificate } from "../../controllers/controllerUtils.js";
-import { expect } from "chai";
-import sslChecker from "ssl-checker";
-import { afterEach } from "node:test";
-import exp from "constants";
-
-describe("controllerUtils - handleValidationError", function () {
- it("should set status to 422", function () {
- const error = {};
- const serviceName = "TestService";
- const result = handleValidationError(error, serviceName);
- expect(result.status).to.equal(422);
- });
-
- it("should set service to the provided serviceName", function () {
- const error = {};
- const serviceName = "TestService";
- const result = handleValidationError(error, serviceName);
- expect(result.service).to.equal(serviceName);
- });
-
- it("should set message to error.details[0].message if present", function () {
- const error = {
- details: [{ message: "Detail message" }],
- };
- const serviceName = "TestService";
- const result = handleValidationError(error, serviceName);
- expect(result.message).to.equal("Detail message");
- });
-
- it("should set message to error.message if error.details is not present", function () {
- const error = {
- message: "Error message",
- };
- const serviceName = "TestService";
- const result = handleValidationError(error, serviceName);
- expect(result.message).to.equal("Error message");
- });
-
- it('should set message to "Validation Error" if neither error.details nor error.message is present', function () {
- const error = {};
- const serviceName = "TestService";
- const result = handleValidationError(error, serviceName);
- expect(result.message).to.equal("Validation Error");
- });
-});
-
-describe("controllerUtils - handleError", function () {
- it("should set stats to the provided status if error.code is undefined", function () {
- const error = {};
- const serviceName = "TestService";
- const method = "testMethod";
- const status = 400;
- const result = handleError(error, serviceName, method, status);
- expect(result.status).to.equal(status);
- });
-
- it("should not overwrite error.code if it is already defined", function () {
- const error = { status: 404 };
- const serviceName = "TestService";
- const method = "testMethod";
- const status = 400;
- const result = handleError(error, serviceName, method, status);
- expect(result.status).to.equal(404);
- });
-
- it("should set service to the provided serviceName if error.service is undefined", function () {
- const error = {};
- const serviceName = "TestService";
- const method = "testMethod";
- const result = handleError(error, serviceName, method);
- expect(result.service).to.equal(serviceName);
- });
-
- it("should not overwrite error.service if it is already defined", function () {
- const error = { service: "ExistingService" };
- const serviceName = "TestService";
- const method = "testMethod";
- const result = handleError(error, serviceName, method);
- expect(result.service).to.equal("ExistingService");
- });
-
- it("should set method to the provided method if error.method is undefined", function () {
- const error = {};
- const serviceName = "TestService";
- const method = "testMethod";
- const result = handleError(error, serviceName, method);
- expect(result.method).to.equal(method);
- });
-
- it("should not overwrite error.method if it is already defined", function () {
- const error = { method: "existingMethod" };
- const serviceName = "TestService";
- const method = "testMethod";
- const result = handleError(error, serviceName, method);
- expect(result.method).to.equal("existingMethod");
- });
-
- it("should set code to 500 if error.code is undefined and no code is provided", function () {
- const error = {};
- const serviceName = "TestService";
- const method = "testMethod";
- const result = handleError(error, serviceName, method);
- expect(result.status).to.equal(500);
- });
-});
-
-describe("controllerUtils - fetchMonitorCertificate", function () {
- let sslChecker, monitor;
-
- beforeEach(function () {
- monitor = {
- url: "https://www.google.com",
- };
- sslChecker = sinon.stub();
- });
-
- afterEach(() => {
- sinon.restore();
- });
-
- it("should reject with an error if a URL does not parse", async function () {
- monitor.url = "invalidurl";
- try {
- await fetchMonitorCertificate(sslChecker, monitor);
- } catch (error) {
- expect(error).to.be.an("error");
- expect(error.message).to.equal("Invalid URL");
- }
- });
-
- it("should reject with an error if sslChecker throws an error", async function () {
- sslChecker.rejects(new Error("Test error"));
- try {
- await fetchMonitorCertificate(sslChecker, monitor);
- } catch (error) {
- expect(error).to.be.an("error");
- expect(error.message).to.equal("Test error");
- }
- });
-
- it("should return a certificate if sslChecker resolves", async function () {
- sslChecker.resolves({ validTo: "2022-01-01" });
- const result = await fetchMonitorCertificate(sslChecker, monitor);
- expect(result).to.deep.equal({ validTo: "2022-01-01" });
- });
-
- it("should throw an error if a ssl-checker returns null", async function () {
- sslChecker.returns(null);
- await fetchMonitorCertificate(sslChecker, monitor).catch((error) => {
- expect(error).to.be.an("error");
- expect(error.message).to.equal("Certificate not found");
- });
- });
-});
diff --git a/server/tests/controllers/inviteController.test.js b/server/tests/controllers/inviteController.test.js
deleted file mode 100755
index 635bdf653..000000000
--- a/server/tests/controllers/inviteController.test.js
+++ /dev/null
@@ -1,202 +0,0 @@
-import { issueInvitation, inviteVerifyController } from "../../controllers/inviteController.js";
-import jwt from "jsonwebtoken";
-import sinon from "sinon";
-import joi from "joi";
-describe("inviteController - issueInvitation", function () {
- let req, res, next, stub;
-
- beforeEach(function () {
- req = {
- headers: { authorization: "Bearer token" },
- body: {
- email: "test@test.com",
- role: ["admin"],
- teamId: "123",
- },
- db: { requestInviteToken: sinon.stub() },
- settingsService: { getSettings: sinon.stub() },
- emailService: { buildAndSendEmail: sinon.stub() },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should reject with an error if role validation fails", async function () {
- stub = sinon.stub(jwt, "decode").callsFake(() => {
- return { role: ["bad_role"], firstname: "first_name", teamId: "1" };
- });
- await issueInvitation(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0]).to.be.instanceOf(joi.ValidationError);
- expect(next.firstCall.args[0].status).to.equal(422);
- stub.restore();
- });
-
- it("should reject with an error if body validation fails", async function () {
- stub = sinon.stub(jwt, "decode").callsFake(() => {
- return { role: ["admin"], firstname: "first_name", teamId: "1" };
- });
- req.body = {};
- await issueInvitation(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- stub.restore();
- });
-
- it("should reject with an error if DB operations fail", async function () {
- stub = sinon.stub(jwt, "decode").callsFake(() => {
- return { role: ["admin"], firstname: "first_name", teamId: "1" };
- });
- req.db.requestInviteToken.throws(new Error("DB error"));
- await issueInvitation(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("DB error");
- stub.restore();
- });
-
- it("should send an invite successfully", async function () {
- const token = "token";
- const decodedToken = {
- role: "admin",
- firstname: "John",
- teamId: "team123",
- };
- const inviteToken = { token: "inviteToken" };
- const clientHost = "http://localhost";
-
- stub = sinon.stub(jwt, "decode").callsFake(() => {
- return decodedToken;
- });
- req.db.requestInviteToken.resolves(inviteToken);
- req.settingsService.getSettings.returns({ clientHost });
- req.emailService.buildAndSendEmail.resolves();
- await issueInvitation(req, res, next);
- expect(res.status.calledWith(200)).to.be.true;
- expect(
- res.json.calledWith({
- success: true,
- msg: "Invite sent",
- data: inviteToken,
- })
- ).to.be.true;
- stub.restore();
- });
-
- it("should send an email successfully", async function () {
- const token = "token";
- const decodedToken = {
- role: "admin",
- firstname: "John",
- teamId: "team123",
- };
- const inviteToken = { token: "inviteToken" };
- const clientHost = "http://localhost";
-
- stub = sinon.stub(jwt, "decode").callsFake(() => {
- return decodedToken;
- });
- req.db.requestInviteToken.resolves(inviteToken);
- req.settingsService.getSettings.returns({ clientHost });
- req.emailService.buildAndSendEmail.resolves();
-
- await issueInvitation(req, res, next);
- expect(req.emailService.buildAndSendEmail.calledOnce).to.be.true;
- expect(
- req.emailService.buildAndSendEmail.calledWith(
- "employeeActivationTemplate",
- {
- name: "John",
- link: "http://localhost/register/inviteToken",
- },
- "test@test.com",
- "Welcome to Uptime Monitor"
- )
- ).to.be.true;
- stub.restore();
- });
-
- it("should continue executing if sending an email fails", async function () {
- const token = "token";
- req.emailService.buildAndSendEmail.rejects(new Error("Email error"));
- const decodedToken = {
- role: "admin",
- firstname: "John",
- teamId: "team123",
- };
- const inviteToken = { token: "inviteToken" };
- const clientHost = "http://localhost";
-
- stub = sinon.stub(jwt, "decode").callsFake(() => {
- return decodedToken;
- });
- req.db.requestInviteToken.resolves(inviteToken);
- req.settingsService.getSettings.returns({ clientHost });
- await issueInvitation(req, res, next);
- expect(res.status.calledWith(200)).to.be.true;
- expect(
- res.json.calledWith({
- success: true,
- msg: "Invite sent",
- data: inviteToken,
- })
- ).to.be.true;
- stub.restore();
- });
-});
-
-describe("inviteController - inviteVerifyController", function () {
- let req, res, next;
-
- beforeEach(function () {
- req = {
- body: { token: "token" },
- db: {
- getInviteToken: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should reject with an error if body validation fails", async function () {
- req.body = {};
- await inviteVerifyController(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should reject with an error if DB operations fail", async function () {
- req.db.getInviteToken.throws(new Error("DB error"));
- await inviteVerifyController(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("DB error");
- });
-
- it("should return 200 and invite data when validation and invite retrieval are successful", async function () {
- req.db.getInviteToken.resolves({ invite: "data" });
- await inviteVerifyController(req, res, next);
- expect(res.status.calledWith(200)).to.be.true;
- expect(
- res.json.calledWith({
- status: "success",
- msg: "Invite verified",
- data: { invite: "data" },
- })
- ).to.be.true;
- expect(next.called).to.be.false;
- });
-});
diff --git a/server/tests/controllers/maintenanceWindowController.test.js b/server/tests/controllers/maintenanceWindowController.test.js
deleted file mode 100755
index 0ea07b389..000000000
--- a/server/tests/controllers/maintenanceWindowController.test.js
+++ /dev/null
@@ -1,422 +0,0 @@
-import {
- createMaintenanceWindows,
- getMaintenanceWindowById,
- getMaintenanceWindowsByTeamId,
- getMaintenanceWindowsByMonitorId,
- deleteMaintenanceWindow,
- editMaintenanceWindow,
-} from "../../controllers/maintenanceWindowController.js";
-
-import jwt from "jsonwebtoken";
-import { successMessages } from "../../utils/messages.js";
-import sinon from "sinon";
-
-describe("maintenanceWindowController - createMaintenanceWindows", function () {
- let req, res, next, stub;
-
- beforeEach(function () {
- req = {
- body: {
- monitors: ["66ff52e7c5911c61698ac724"],
- name: "window",
- active: true,
- start: "2024-10-11T05:27:13.747Z",
- end: "2024-10-11T05:27:14.747Z",
- repeat: "123",
- },
- headers: {
- authorization: "Bearer token",
- },
- settingsService: {
- getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }),
- },
- db: {
- createMaintenanceWindow: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should reject with an error if body validation fails", async function () {
- stub = sinon.stub(jwt, "verify").callsFake(() => {
- return { teamId: "123" };
- });
- req.body = {};
- await createMaintenanceWindows(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- stub.restore();
- });
-
- it("should reject with an error if jwt.verify fails", async function () {
- stub = sinon.stub(jwt, "verify").throws(new jwt.JsonWebTokenError());
- await createMaintenanceWindows(req, res, next);
- expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError);
- stub.restore();
- });
-
- it("should reject with an error DB operations fail", async function () {
- stub = sinon.stub(jwt, "verify").callsFake(() => {
- return { teamId: "123" };
- });
- req.db.createMaintenanceWindow.throws(new Error("DB error"));
- await createMaintenanceWindows(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("DB error");
- stub.restore();
- });
-
- it("should return success message if all operations are successful", async function () {
- stub = sinon.stub(jwt, "verify").callsFake(() => {
- return { teamId: "123" };
- });
- await createMaintenanceWindows(req, res, next);
- expect(res.status.firstCall.args[0]).to.equal(201);
- expect(
- res.json.calledOnceWith({
- success: true,
- msg: successMessages.MAINTENANCE_WINDOW_CREATE,
- })
- ).to.be.true;
- stub.restore();
- });
-
- it("should return success message if all operations are successful with active set to undefined", async function () {
- req.body.active = undefined;
- stub = sinon.stub(jwt, "verify").callsFake(() => {
- return { teamId: "123" };
- });
- await createMaintenanceWindows(req, res, next);
- expect(res.status.firstCall.args[0]).to.equal(201);
- expect(
- res.json.calledOnceWith({
- success: true,
- msg: successMessages.MAINTENANCE_WINDOW_CREATE,
- })
- ).to.be.true;
- stub.restore();
- });
-});
-
-describe("maintenanceWindowController - getMaintenanceWindowById", function () {
- let req, res, next;
-
- beforeEach(function () {
- req = {
- body: {},
- params: {
- id: "123",
- },
- headers: {
- authorization: "Bearer token",
- },
- settingsService: {
- getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }),
- },
- db: {
- getMaintenanceWindowById: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- });
-
- it("should reject if param validation fails", async function () {
- req.params = {};
- await getMaintenanceWindowById(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should reject if DB operations fail", async function () {
- req.db.getMaintenanceWindowById.throws(new Error("DB error"));
- await getMaintenanceWindowById(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("DB error");
- });
-
- it("should return success message with data if all operations are successful", async function () {
- req.db.getMaintenanceWindowById.returns({ id: "123" });
- await getMaintenanceWindowById(req, res, next);
- expect(res.status.firstCall.args[0]).to.equal(200);
- expect(
- res.json.calledOnceWith({
- success: true,
- msg: successMessages.MAINTENANCE_WINDOW_GET_BY_ID,
- data: { id: "123" },
- })
- ).to.be.true;
- });
-});
-
-describe("maintenanceWindowController - getMaintenanceWindowsByTeamId", function () {
- let req, res, next, stub;
-
- beforeEach(function () {
- req = {
- body: {},
- params: {},
- query: {},
- headers: {
- authorization: "Bearer token",
- },
- settingsService: {
- getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }),
- },
- db: {
- getMaintenanceWindowsByTeamId: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- });
-
- it("should reject if query validation fails", async function () {
- req.query = {
- invalid: 1,
- };
- await getMaintenanceWindowsByTeamId(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should reject if jwt.verify fails", async function () {
- stub = sinon.stub(jwt, "verify").throws(new jwt.JsonWebTokenError());
- await getMaintenanceWindowsByTeamId(req, res, next);
- expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError);
- stub.restore();
- });
-
- it("should reject with an error if DB operations fail", async function () {
- stub = sinon.stub(jwt, "verify").callsFake(() => {
- return { teamId: "123" };
- });
- req.db.getMaintenanceWindowsByTeamId.throws(new Error("DB error"));
- await getMaintenanceWindowsByTeamId(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("DB error");
- stub.restore();
- });
-
- it("should return success message with data if all operations are successful", async function () {
- stub = sinon.stub(jwt, "verify").callsFake(() => {
- return { teamId: "123" };
- });
- req.db.getMaintenanceWindowsByTeamId.returns([{ id: "123" }]);
- await getMaintenanceWindowsByTeamId(req, res, next);
- expect(res.status.firstCall.args[0]).to.equal(200);
- expect(
- res.json.calledOnceWith({
- success: true,
- msg: successMessages.MAINTENANCE_WINDOW_GET_BY_TEAM,
- data: [{ id: jwt.verify().teamId }],
- })
- ).to.be.true;
- stub.restore();
- });
-});
-
-describe("maintenanceWindowController - getMaintenanceWindowsByMonitorId", function () {
- let req, res, next;
-
- beforeEach(function () {
- req = {
- body: {},
- params: {
- monitorId: "123",
- },
- query: {},
- headers: {
- authorization: "Bearer token",
- },
- settingsService: {
- getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }),
- },
- db: {
- getMaintenanceWindowsByMonitorId: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should reject if param validation fails", async function () {
- req.params = {};
- await getMaintenanceWindowsByMonitorId(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should reject with an error if DB operations fail", async function () {
- req.db.getMaintenanceWindowsByMonitorId.throws(new Error("DB error"));
- await getMaintenanceWindowsByMonitorId(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("DB error");
- });
-
- it("should return success message with data if all operations are successful", async function () {
- const data = [{ monitorId: "123" }];
- req.db.getMaintenanceWindowsByMonitorId.returns(data);
- await getMaintenanceWindowsByMonitorId(req, res, next);
- expect(req.db.getMaintenanceWindowsByMonitorId.calledOnceWith(req.params.monitorId));
- expect(res.status.firstCall.args[0]).to.equal(200);
- expect(
- res.json.calledOnceWith({
- success: true,
- msg: successMessages.MAINTENANCE_WINDOW_GET_BY_MONITOR,
- data: data,
- })
- ).to.be.true;
- });
-});
-
-describe("maintenanceWindowController - deleteMaintenanceWindow", function () {
- let req, res, next;
-
- beforeEach(function () {
- req = {
- body: {},
- params: {
- id: "123",
- },
- query: {},
- headers: {
- authorization: "Bearer token",
- },
- settingsService: {
- getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }),
- },
- db: {
- deleteMaintenanceWindowById: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should reject if param validation fails", async function () {
- req.params = {};
- await deleteMaintenanceWindow(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should reject with an error if DB operations fail", async function () {
- req.db.deleteMaintenanceWindowById.throws(new Error("DB error"));
- await deleteMaintenanceWindow(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("DB error");
- });
-
- it("should return success message if all operations are successful", async function () {
- await deleteMaintenanceWindow(req, res, next);
- expect(req.db.deleteMaintenanceWindowById.calledOnceWith(req.params.id));
- expect(res.status.firstCall.args[0]).to.equal(200);
- expect(
- res.json.calledOnceWith({
- success: true,
- msg: successMessages.MAINTENANCE_WINDOW_DELETE,
- })
- ).to.be.true;
- });
-});
-
-describe("maintenanceWindowController - editMaintenanceWindow", function () {
- let req, res, next;
-
- beforeEach(function () {
- req = {
- body: {
- active: true,
- name: "test",
- },
- params: {
- id: "123",
- },
- query: {},
- headers: {
- authorization: "Bearer token",
- },
- settingsService: {
- getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }),
- },
- db: {
- editMaintenanceWindowById: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should reject if param validation fails", async function () {
- req.params = {};
- await editMaintenanceWindow(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should reject if body validation fails", async function () {
- req.body = { invalid: 1 };
- await editMaintenanceWindow(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should reject with an error if DB operations fail", async function () {
- req.db.editMaintenanceWindowById.throws(new Error("DB error"));
- await editMaintenanceWindow(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("DB error");
- });
-
- it("should return success message with data if all operations are successful", async function () {
- const data = { id: "123" };
- req.db.editMaintenanceWindowById.returns(data);
-
- await editMaintenanceWindow(req, res, next);
- expect(req.db.editMaintenanceWindowById.calledOnceWith(req.params.id, req.body));
- expect(res.status.firstCall.args[0]).to.equal(200);
- expect(
- res.json.calledOnceWith({
- success: true,
- msg: successMessages.MAINTENANCE_WINDOW_EDIT,
- data: data,
- })
- ).to.be.true;
- });
-});
diff --git a/server/tests/controllers/monitorController.test.js b/server/tests/controllers/monitorController.test.js
deleted file mode 100755
index 963862579..000000000
--- a/server/tests/controllers/monitorController.test.js
+++ /dev/null
@@ -1,1116 +0,0 @@
-import {
- getAllMonitors,
- getAllMonitorsWithUptimeStats,
- getMonitorStatsById,
- getMonitorCertificate,
- getMonitorById,
- getMonitorsAndSummaryByTeamId,
- getMonitorsByTeamId,
- createMonitor,
- checkEndpointResolution,
- deleteMonitor,
- deleteAllMonitors,
- editMonitor,
- pauseMonitor,
- addDemoMonitors,
-} from "../../controllers/monitorController.js";
-import jwt from "jsonwebtoken";
-import sinon from "sinon";
-import { successMessages } from "../../utils/messages.js";
-import logger from "../../utils/logger.js";
-import axios from "axios";
-const SERVICE_NAME = "monitorController";
-
-describe("Monitor Controller - getAllMonitors", function () {
- let req, res, next;
-
- beforeEach(function () {
- req = {
- params: {},
- query: {},
- body: {},
- db: {
- getAllMonitors: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should reject with an error if DB operations fail", async function () {
- req.db.getAllMonitors.throws(new Error("DB error"));
- await getAllMonitors(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("DB error");
- });
-
- it("should return success message and data if all operations succeed", async function () {
- const data = [{ monitor: "data" }];
- req.db.getAllMonitors.returns(data);
- await getAllMonitors(req, res, next);
- expect(res.status.firstCall.args[0]).to.equal(200);
- expect(
- res.json.calledOnceWith({
- success: true,
- msg: successMessages.MONITOR_GET_ALL,
- data: data,
- })
- ).to.be.true;
- });
-});
-describe("Monitor Controller - getAllMonitorsWithUptimeStats", function () {
- let req, res, next;
-
- beforeEach(function () {
- req = {
- params: {},
- query: {},
- body: {},
- db: {
- getAllMonitorsWithUptimeStats: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should reject with an error if DB operations fail", async function () {
- req.db.getAllMonitorsWithUptimeStats.throws(new Error("DB error"));
- await getAllMonitorsWithUptimeStats(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("DB error");
- });
-
- it("should return success message and data if all operations succeed", async function () {
- const data = [{ monitor: "data" }];
- req.db.getAllMonitorsWithUptimeStats.returns(data);
- await getAllMonitorsWithUptimeStats(req, res, next);
- expect(res.status.firstCall.args[0]).to.equal(200);
- expect(
- res.json.calledOnceWith({
- success: true,
- msg: successMessages.MONITOR_GET_ALL,
- data: data,
- })
- ).to.be.true;
- });
-});
-
-describe("Monitor Controller - getMonitorStatsById", function () {
- let req, res, next;
-
- beforeEach(function () {
- req = {
- params: {
- monitorId: "123",
- },
- query: {},
- body: {},
- db: {
- getMonitorStatsById: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should reject with an error if param validation fails", async function () {
- req.params = {};
- await getMonitorStatsById(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should reject with an error if query validation fails", async function () {
- req.query = { invalid: 1 };
- await getMonitorStatsById(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should reject with an error if DB operations fail", async function () {
- req.db.getMonitorStatsById.throws(new Error("DB error"));
- await getMonitorStatsById(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("DB error");
- });
-
- it("should return success message and data if all operations succeed", async function () {
- const data = [{ monitorStats: "data" }];
- req.db.getMonitorStatsById.returns(data);
- await getMonitorStatsById(req, res, next);
- expect(res.status.firstCall.args[0]).to.equal(200);
- expect(
- res.json.calledOnceWith({
- success: true,
- msg: successMessages.MONITOR_STATS_BY_ID,
- data: data,
- })
- ).to.be.true;
- });
-});
-
-describe("Monitor Controller - getMonitorCertificate", function () {
- let req, res, next, fetchMonitorCertificate;
-
- beforeEach(function () {
- req = {
- params: {
- monitorId: "123",
- },
- query: {},
- body: {},
- db: {
- getMonitorById: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- fetchMonitorCertificate = sinon.stub();
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should reject with an error if param validation fails", async function () {
- req.params = {};
- await getMonitorCertificate(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should reject with an error if getMonitorById operation fails", async function () {
- req.db.getMonitorById.throws(new Error("DB error"));
- await getMonitorCertificate(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("DB error");
- });
-
- it("should return success message and data if all operations succeed with a valid cert", async function () {
- req.db.getMonitorById.returns({ url: "https://www.google.com" });
- const data = { certificate: "cert", validTo: "2024/08/08" };
- fetchMonitorCertificate.returns(data);
- await getMonitorCertificate(req, res, next, fetchMonitorCertificate);
- expect(res.status.firstCall.args[0]).to.equal(200);
- expect(
- res.json.calledOnceWith({
- success: true,
- msg: successMessages.MONITOR_CERTIFICATE,
- data: { certificateDate: new Date(data.validTo) },
- })
- ).to.be.true;
- });
-
- it("should return an error if fetchMonitorCertificate fails", async function () {
- req.db.getMonitorById.returns({ url: "https://www.google.com" });
- fetchMonitorCertificate.throws(new Error("Certificate error"));
- await getMonitorCertificate(req, res, next, fetchMonitorCertificate);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("Certificate error");
- });
-});
-
-describe("Monitor Controller - getMonitorById", function () {
- let req, res, next;
-
- beforeEach(function () {
- req = {
- params: {
- monitorId: "123",
- },
- query: {},
- body: {},
- db: {
- getMonitorById: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should reject with an error if param validation fails", async function () {
- req.params = {};
- await getMonitorById(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should reject with an error if query param validation fails", async function () {
- req.query = { invalid: 1 };
- await getMonitorById(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should reject with an error if DB operations fail", async function () {
- req.db.getMonitorById.throws(new Error("DB error"));
- await getMonitorById(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("DB error");
- });
-
- it("should return 404 if a monitor is not found", async function () {
- const error = new Error("Monitor not found");
- error.status = 404;
- req.db.getMonitorById.throws(error);
- await getMonitorById(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(404);
- });
-
- it("should return success message and data if all operations succeed", async function () {
- const data = { monitor: "data" };
- req.db.getMonitorById.returns(data);
- await getMonitorById(req, res, next);
- expect(res.status.firstCall.args[0]).to.equal(200);
- expect(
- res.json.calledOnceWith({
- success: true,
- msg: successMessages.MONITOR_GET_BY_ID,
- data: data,
- })
- ).to.be.true;
- });
-});
-
-describe("Monitor Controller - getMonitorsAndSummaryByTeamId", function () {
- let req, res, next;
-
- beforeEach(function () {
- req = {
- params: {
- teamId: "123",
- },
- query: {},
- body: {},
- db: {
- getMonitorsAndSummaryByTeamId: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should reject with an error if param validation fails", async function () {
- req.params = {};
- await getMonitorsAndSummaryByTeamId(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should reject with an error if query validation fails", async function () {
- req.query = { invalid: 1 };
- await getMonitorsAndSummaryByTeamId(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should reject with an error if DB operations fail", async function () {
- req.db.getMonitorsAndSummaryByTeamId.throws(new Error("DB error"));
- await getMonitorsAndSummaryByTeamId(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("DB error");
- });
-
- it("should return success message and data if all operations succeed", async function () {
- const data = { monitors: "data", summary: "data" };
- req.db.getMonitorsAndSummaryByTeamId.returns(data);
- await getMonitorsAndSummaryByTeamId(req, res, next);
- expect(res.status.firstCall.args[0]).to.equal(200);
- expect(
- res.json.calledOnceWith({
- success: true,
- msg: successMessages.MONITOR_GET_BY_USER_ID(req.params.teamId),
- data: data,
- })
- ).to.be.true;
- });
-});
-
-describe("Monitor Controller - getMonitorsByTeamId", function () {
- let req, res, next;
-
- beforeEach(function () {
- req = {
- params: {
- teamId: "123",
- },
- query: {},
- body: {},
- db: {
- getMonitorsByTeamId: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should reject with an error if param validation fails", async function () {
- req.params = {};
- await getMonitorsByTeamId(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should reject with an error if query validation fails", async function () {
- req.query = { invalid: 1 };
- await getMonitorsByTeamId(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should reject with an error if DB operations fail", async function () {
- req.db.getMonitorsByTeamId.throws(new Error("DB error"));
- await getMonitorsByTeamId(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("DB error");
- });
-
- it("should return success message and data if all operations succeed", async function () {
- const data = { monitors: "data" };
- req.db.getMonitorsByTeamId.returns(data);
- await getMonitorsByTeamId(req, res, next);
- expect(res.status.firstCall.args[0]).to.equal(200);
- expect(
- res.json.calledOnceWith({
- success: true,
- msg: successMessages.MONITOR_GET_BY_USER_ID(req.params.teamId),
- data: data,
- })
- ).to.be.true;
- });
-});
-
-describe("Monitor Controller - createMonitor", function () {
- let req, res, next;
-
- beforeEach(function () {
- req = {
- params: {},
- query: {},
- body: {
- userId: "123",
- teamId: "123",
- name: "test_monitor",
- description: "test_monitor_desc",
- type: "http",
- url: "https://example.com",
- notifications: [{ email: "example@example.com" }],
- },
- db: {
- createMonitor: sinon.stub(),
- createNotification: sinon.stub(),
- },
- jobQueue: {
- addJob: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should reject with an error if body validation fails", async function () {
- req.body = {};
- await createMonitor(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should reject with an error if DB createMonitor operation fail", async function () {
- req.db.createMonitor.throws(new Error("DB error"));
- await createMonitor(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("DB error");
- });
-
- it("should reject with an error if DB createNotification operation fail", async function () {
- req.db.createNotification.throws(new Error("DB error"));
- req.db.createMonitor.returns({ _id: "123" });
- await createMonitor(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("DB error");
- });
-
- it("should reject with an error if monitor.save operation fail", async function () {
- req.db.createMonitor.returns({
- _id: "123",
- save: sinon.stub().throws(new Error("Monitor save error")),
- });
- await createMonitor(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("Monitor save error");
- });
-
- it("should throw an error if addJob operation fails", async function () {
- req.db.createMonitor.returns({ _id: "123", save: sinon.stub() });
- req.jobQueue.addJob.throws(new Error("Job error"));
- await createMonitor(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("Job error");
- });
-
- it("should return success message and data if all operations succeed", async function () {
- const monitor = { _id: "123", save: sinon.stub() };
- req.db.createMonitor.returns(monitor);
- await createMonitor(req, res, next);
- expect(res.status.firstCall.args[0]).to.equal(201);
- expect(
- res.json.calledOnceWith({
- success: true,
- msg: successMessages.MONITOR_CREATE,
- data: monitor,
- })
- ).to.be.true;
- });
-});
-
-describe("Monitor Controller - checkEndpointResolution", function () {
- let req, res, next, axiosGetStub;
-
- beforeEach(function () {
- req = { query: { monitorURL: "https://example.com" } };
- res = { status: sinon.stub().returnsThis(), json: sinon.stub() };
- next = sinon.stub();
- axiosGetStub = sinon.stub(axios, "get");
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should resolve the URL successfully", async function () {
- axiosGetStub.resolves({ status: 200, statusText: "OK" });
- await checkEndpointResolution(req, res, next);
- expect(res.status.calledWith(200)).to.be.true;
- expect(
- res.json.calledWith({
- success: true,
- code: 200,
- statusText: "OK",
- msg: "URL resolved successfully",
- })
- ).to.be.true;
- expect(next.called).to.be.false;
- });
-
- it("should return an error if endpoint resolution fails", async function () {
- const axiosError = new Error("resolution failed");
- axiosError.code = "ENOTFOUND";
- axiosGetStub.rejects(axiosError);
- await checkEndpointResolution(req, res, next);
- expect(next.calledOnce).to.be.true;
- const errorPassedToNext = next.getCall(0).args[0];
- expect(errorPassedToNext).to.be.an.instanceOf(Error);
- expect(errorPassedToNext.message).to.include("resolution failed");
- expect(errorPassedToNext.code).to.equal("ENOTFOUND");
- expect(errorPassedToNext.status).to.equal(500);
- });
-
- it("should reject with an error if query validation fails", async function () {
- req.query.monitorURL = "invalid-url";
- await checkEndpointResolution(req, res, next);
- expect(next.calledOnce).to.be.true;
- const error = next.getCall(0).args[0];
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- expect(error.message).to.equal('"monitorURL" must be a valid uri');
- });
-});
-
-describe("Monitor Controller - deleteMonitor", function () {
- let req, res, next;
-
- beforeEach(function () {
- req = {
- params: {
- monitorId: "123",
- },
- query: {},
- body: {},
- db: {
- deleteMonitor: sinon.stub(),
- deleteChecks: sinon.stub(),
- deletePageSpeedChecksByMonitorId: sinon.stub(),
- deleteNotificationsByMonitorId: sinon.stub(),
- },
- jobQueue: {
- deleteJob: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- sinon.stub(logger, "error");
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should reject with an error if param validation fails", async function () {
- req.params = {};
- await deleteMonitor(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should reject with an error if DB deleteMonitor operation fail", async function () {
- req.db.deleteMonitor.throws(new Error("DB error"));
- await deleteMonitor(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("DB error");
- });
-
- it("should log an error if deleteJob throws an error", async function () {
- const error = new Error("Job error");
- const monitor = { name: "test_monitor", _id: "123" };
- req.db.deleteMonitor.returns(monitor);
- req.jobQueue.deleteJob.rejects(error);
- await deleteMonitor(req, res, next);
- expect(logger.error.calledOnce).to.be.true;
- expect(logger.error.firstCall.args[0].message).to.equal(`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`);
- });
-
- it("should log an error if deleteChecks throws an error", async function () {
- const error = new Error("Checks error");
- const monitor = { name: "test_monitor", _id: "123" };
- req.db.deleteMonitor.returns(monitor);
- req.db.deleteChecks.rejects(error);
- await deleteMonitor(req, res, next);
- expect(logger.error.calledOnce).to.be.true;
- expect(logger.error.firstCall.args[0].message).to.equal(`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`);
- });
-
- it("should log an error if deletePageSpeedChecksByMonitorId throws an error", async function () {
- const error = new Error("PageSpeed error");
- const monitor = { name: "test_monitor", _id: "123" };
- req.db.deleteMonitor.returns(monitor);
- req.db.deletePageSpeedChecksByMonitorId.rejects(error);
- await deleteMonitor(req, res, next);
- expect(logger.error.calledOnce).to.be.true;
- expect(logger.error.firstCall.args[0].message).to.equal(`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`);
- });
-
- it("should log an error if deleteNotificationsByMonitorId throws an error", async function () {
- const error = new Error("Notifications error");
- const monitor = { name: "test_monitor", _id: "123" };
- req.db.deleteMonitor.returns(monitor);
- req.db.deleteNotificationsByMonitorId.rejects(error);
- await deleteMonitor(req, res, next);
- expect(logger.error.calledOnce).to.be.true;
- expect(logger.error.firstCall.args[0].message).to.equal(`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`);
- });
-
- it("should return success message if all operations succeed", async function () {
- const monitor = { name: "test_monitor", _id: "123" };
- req.db.deleteMonitor.returns(monitor);
- await deleteMonitor(req, res, next);
- expect(res.status.firstCall.args[0]).to.equal(200);
- expect(
- res.json.calledOnceWith({
- success: true,
- msg: successMessages.MONITOR_DELETE,
- })
- ).to.be.true;
- });
-});
-
-describe("Monitor Controller - deleteAllMonitors", function () {
- let req, res, next, stub;
-
- beforeEach(function () {
- stub = sinon.stub(jwt, "verify").callsFake(() => {
- return { teamId: "123" };
- });
- req = {
- headers: {
- authorization: "Bearer token",
- },
- params: {
- monitorId: "123",
- },
- query: {},
- body: {},
- db: {
- deleteAllMonitors: sinon.stub(),
- deleteChecks: sinon.stub(),
- deletePageSpeedChecksByMonitorId: sinon.stub(),
- deleteNotificationsByMonitorId: sinon.stub(),
- },
- jobQueue: {
- deleteJob: sinon.stub(),
- },
- settingsService: {
- getSettings: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- sinon.stub(logger, "error");
- });
-
- afterEach(function () {
- sinon.restore();
- stub.restore();
- });
-
- it("should reject with an error if getTokenFromHeaders throws an error", async function () {
- req.headers = {};
- await deleteAllMonitors(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("No auth headers");
- expect(next.firstCall.args[0].status).to.equal(500);
- });
-
- it("should reject with an error if token validation fails", async function () {
- stub.restore();
- req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
- await deleteAllMonitors(req, res, next);
- expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError);
- });
-
- it("should reject with an error if DB deleteAllMonitors operation fail", async function () {
- req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
- req.db.deleteAllMonitors.throws(new Error("DB error"));
- await deleteAllMonitors(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("DB error");
- });
-
- it("should log an error if deleteChecks throws an error", async function () {
- const monitors = [{ name: "test_monitor", _id: "123" }];
- req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
- req.db.deleteAllMonitors.returns({ monitors, deletedCount: 1 });
- const error = new Error("Check error");
- req.db.deleteChecks.rejects(error);
- await deleteAllMonitors(req, res, next);
- expect(logger.error.calledOnce).to.be.true;
- expect(logger.error.firstCall.args[0].message).to.equal(
- `Error deleting associated records for monitor ${monitors[0]._id} with name ${monitors[0].name}`
- );
- });
-
- it("should log an error if deletePageSpeedChecksByMonitorId throws an error", async function () {
- const monitors = [{ name: "test_monitor", _id: "123" }];
- req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
- req.db.deleteAllMonitors.returns({ monitors, deletedCount: 1 });
- const error = new Error("Pagespeed Check error");
- req.db.deletePageSpeedChecksByMonitorId.rejects(error);
- await deleteAllMonitors(req, res, next);
- expect(logger.error.calledOnce).to.be.true;
- expect(logger.error.firstCall.args[0].message).to.equal(
- `Error deleting associated records for monitor ${monitors[0]._id} with name ${monitors[0].name}`
- );
- });
-
- it("should log an error if deleteNotificationsByMonitorId throws an error", async function () {
- const monitors = [{ name: "test_monitor", _id: "123" }];
- req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
- req.db.deleteAllMonitors.returns({ monitors, deletedCount: 1 });
- const error = new Error("Notifications Check error");
- req.db.deleteNotificationsByMonitorId.rejects(error);
- await deleteAllMonitors(req, res, next);
- expect(logger.error.calledOnce).to.be.true;
- expect(logger.error.firstCall.args[0].message).to.equal(
- `Error deleting associated records for monitor ${monitors[0]._id} with name ${monitors[0].name}`
- );
- });
-
- it("should return success message if all operations succeed", async function () {
- req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
- req.db.deleteAllMonitors.returns({
- monitors: [{ name: "test_monitor", _id: "123" }],
- deletedCount: 1,
- });
- await deleteAllMonitors(req, res, next);
- expect(res.status.firstCall.args[0]).to.equal(200);
- expect(
- res.json.calledOnceWith({
- success: true,
- msg: "Deleted 1 monitors",
- })
- ).to.be.true;
- });
-});
-
-describe("Monitor Controller - editMonitor", function () {
- let req, res, next;
-
- beforeEach(function () {
- req = {
- headers: {},
- params: {
- monitorId: "123",
- },
- query: {},
- body: {
- notifications: [{ email: "example@example.com" }],
- },
- db: {
- getMonitorById: sinon.stub(),
- editMonitor: sinon.stub(),
- deleteNotificationsByMonitorId: sinon.stub(),
- createNotification: sinon.stub(),
- },
- jobQueue: {
- deleteJob: sinon.stub(),
- addJob: sinon.stub(),
- },
- settingsService: {
- getSettings: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should reject with an error if param validation fails", async function () {
- req.params = {};
- await editMonitor(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should reject with an error if body validation fails", async function () {
- req.body = { invalid: 1 };
- await editMonitor(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should reject with an error if getMonitorById operation fails", async function () {
- req.db.getMonitorById.throws(new Error("DB error"));
- await editMonitor(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("DB error");
- });
-
- it("should reject with an error if editMonitor operation fails", async function () {
- req.db.getMonitorById.returns({ teamId: "123" });
- req.db.editMonitor.throws(new Error("DB error"));
- await editMonitor(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("DB error");
- });
-
- it("should reject with an error if deleteNotificationsByMonitorId operation fails", async function () {
- req.db.getMonitorById.returns({ teamId: "123" });
- req.db.editMonitor.returns({ _id: "123" });
- req.db.deleteNotificationsByMonitorId.throws(new Error("DB error"));
- await editMonitor(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("DB error");
- });
-
- it("should reject with an error if createNotification operation fails", async function () {
- req.db.getMonitorById.returns({ teamId: "123" });
- req.db.editMonitor.returns({ _id: "123" });
- req.db.createNotification.throws(new Error("DB error"));
- await editMonitor(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("DB error");
- });
-
- it("should reject with an error if deleteJob operation fails", async function () {
- req.db.getMonitorById.returns({ teamId: "123" });
- req.db.editMonitor.returns({ _id: "123" });
- req.jobQueue.deleteJob.throws(new Error("Job error"));
- await editMonitor(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("Job error");
- });
-
- it("should reject with an error if addJob operation fails", async function () {
- req.db.getMonitorById.returns({ teamId: "123" });
- req.db.editMonitor.returns({ _id: "123" });
- req.jobQueue.addJob.throws(new Error("Add Job error"));
- await editMonitor(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("Add Job error");
- });
-
- it("should return success message with data if all operations succeed", async function () {
- const monitor = { _id: "123" };
- req.db.getMonitorById.returns({ teamId: "123" });
- req.db.editMonitor.returns(monitor);
- await editMonitor(req, res, next);
- expect(res.status.firstCall.args[0]).to.equal(200);
- expect(
- res.json.calledOnceWith({
- success: true,
- msg: successMessages.MONITOR_EDIT,
- data: monitor,
- })
- ).to.be.true;
- });
-});
-
-describe("Monitor Controller - pauseMonitor", function () {
- let req, res, next;
-
- beforeEach(function () {
- req = {
- headers: {},
- params: {
- monitorId: "123",
- },
- query: {},
- body: {},
- db: {
- getMonitorById: sinon.stub(),
- },
- jobQueue: {
- deleteJob: sinon.stub(),
- addJob: sinon.stub(),
- },
- settingsService: {
- getSettings: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should reject with an error if param validation fails", async function () {
- req.params = {};
- await pauseMonitor(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should reject with an error if getMonitorById operation fails", async function () {
- req.db.getMonitorById.throws(new Error("DB error"));
- await pauseMonitor(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("DB error");
- });
-
- it("should reject with an error if deleteJob operation fails", async function () {
- const monitor = { _id: req.params.monitorId, isActive: true };
- req.db.getMonitorById.returns(monitor);
- req.jobQueue.deleteJob.throws(new Error("Delete Job error"));
- await pauseMonitor(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("Delete Job error");
- });
-
- it("should reject with an error if addJob operation fails", async function () {
- const monitor = { _id: req.params.monitorId, isActive: false };
- req.db.getMonitorById.returns(monitor);
- req.jobQueue.addJob.throws(new Error("Add Job error"));
- await pauseMonitor(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("Add Job error");
- });
-
- it("should reject with an error if monitor.save operation fails", async function () {
- const monitor = {
- _id: req.params.monitorId,
- active: false,
- save: sinon.stub().throws(new Error("Save error")),
- };
- req.db.getMonitorById.returns(monitor);
- await pauseMonitor(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("Save error");
- });
-
- it("should return success pause message with data if all operations succeed with inactive monitor", async function () {
- const monitor = {
- _id: req.params.monitorId,
- isActive: false,
- save: sinon.stub().resolves(),
- };
- req.db.getMonitorById.returns(monitor);
- await pauseMonitor(req, res, next);
- expect(res.status.firstCall.args[0]).to.equal(200);
- expect(
- res.json.calledOnceWith({
- success: true,
- msg: successMessages.MONITOR_PAUSE,
- data: monitor,
- })
- ).to.be.true;
- });
-
- it("should return success resume message with data if all operations succeed with active monitor", async function () {
- const monitor = {
- _id: req.params.monitorId,
- isActive: true,
- save: sinon.stub().resolves(),
- };
- req.db.getMonitorById.returns(monitor);
- await pauseMonitor(req, res, next);
- expect(res.status.firstCall.args[0]).to.equal(200);
- expect(
- res.json.calledOnceWith({
- success: true,
- msg: successMessages.MONITOR_RESUME,
- data: monitor,
- })
- ).to.be.true;
- });
-});
-
-describe("Monitor Controller - addDemoMonitors", function () {
- let req, res, next, stub;
-
- beforeEach(function () {
- stub = sinon.stub(jwt, "verify").callsFake(() => {
- return { _id: "123", teamId: "123" };
- });
- req = {
- headers: {
- authorization: "Bearer token",
- },
- params: {},
- query: {},
- body: {},
- db: {
- addDemoMonitors: sinon.stub(),
- },
- settingsService: {
- getSettings: sinon.stub(),
- },
- jobQueue: {
- addJob: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- });
-
- afterEach(function () {
- sinon.restore();
- stub.restore();
- });
-
- it("should reject with an error if getTokenFromHeaders fails", async function () {
- req.headers = {};
- await addDemoMonitors(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("No auth headers");
- expect(next.firstCall.args[0].status).to.equal(500);
- });
-
- it("should reject with an error if getting settings fails", async function () {
- req.settingsService.getSettings.throws(new Error("Settings error"));
- await addDemoMonitors(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("Settings error");
- });
-
- it("should reject with an error if JWT validation fails", async function () {
- stub.restore();
- req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
- await addDemoMonitors(req, res, next);
- expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError);
- });
-
- it("should reject with an error if addDemoMonitors operation fails", async function () {
- req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
- req.db.addDemoMonitors.throws(new Error("DB error"));
- await addDemoMonitors(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("DB error");
- });
-
- it("should reject with an error if addJob operation fails", async function () {
- req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
- req.db.addDemoMonitors.returns([{ _id: "123" }]);
- req.jobQueue.addJob.throws(new Error("Add Job error"));
- await addDemoMonitors(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("Add Job error");
- });
-
- it("should return success message with data if all operations succeed", async function () {
- const monitors = [{ _id: "123" }];
- req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
- req.db.addDemoMonitors.returns(monitors);
- await addDemoMonitors(req, res, next);
- expect(res.status.firstCall.args[0]).to.equal(200);
- expect(
- res.json.calledOnceWith({
- success: true,
- msg: successMessages.MONITOR_DEMO_ADDED,
- data: monitors.length,
- })
- ).to.be.true;
- });
-});
diff --git a/server/tests/controllers/queueController.test.js b/server/tests/controllers/queueController.test.js
deleted file mode 100755
index 6201bdb5a..000000000
--- a/server/tests/controllers/queueController.test.js
+++ /dev/null
@@ -1,176 +0,0 @@
-import { afterEach } from "node:test";
-import { getMetrics, getJobs, addJob, obliterateQueue } from "../../controllers/queueController.js";
-import { successMessages } from "../../utils/messages.js";
-import sinon from "sinon";
-
-describe("Queue Controller - getMetrics", function () {
- let req, res, next;
-
- beforeEach(function () {
- req = {
- headers: {},
- params: {},
- body: {},
- db: {},
- jobQueue: {
- getMetrics: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- });
-
- afterEach(() => {
- sinon.restore();
- });
-
- it("should throw an error if getMetrics throws an error", async function () {
- req.jobQueue.getMetrics.throws(new Error("getMetrics error"));
- await getMetrics(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("getMetrics error");
- });
-
- it("should return a success message and data if getMetrics is successful", async function () {
- const data = { data: "metrics" };
- req.jobQueue.getMetrics.returns(data);
- await getMetrics(req, res, next);
- expect(res.status.firstCall.args[0]).to.equal(200);
- expect(res.json.firstCall.args[0]).to.deep.equal({
- success: true,
- msg: successMessages.QUEUE_GET_METRICS,
- data,
- });
- });
-});
-
-describe("Queue Controller - getJobs", function () {
- let req, res, next;
-
- beforeEach(function () {
- req = {
- headers: {},
- params: {},
- body: {},
- db: {},
- jobQueue: {
- getJobStats: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- });
-
- afterEach(() => {
- sinon.restore();
- });
-
- it("should reject with an error if getJobs throws an error", async function () {
- req.jobQueue.getJobStats.throws(new Error("getJobs error"));
- await getJobs(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("getJobs error");
- });
-
- it("should return a success message and data if getJobs is successful", async function () {
- const data = { data: "jobs" };
- req.jobQueue.getJobStats.returns(data);
- await getJobs(req, res, next);
- expect(res.status.firstCall.args[0]).to.equal(200);
- expect(res.json.firstCall.args[0]).to.deep.equal({
- success: true,
- msg: successMessages.QUEUE_GET_METRICS,
- data,
- });
- });
-});
-
-describe("Queue Controller - addJob", function () {
- let req, res, next;
-
- beforeEach(function () {
- req = {
- headers: {},
- params: {},
- body: {},
- db: {},
- jobQueue: {
- addJob: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- });
-
- afterEach(() => {
- sinon.restore();
- });
-
- it("should reject with an error if addJob throws an error", async function () {
- req.jobQueue.addJob.throws(new Error("addJob error"));
- await addJob(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("addJob error");
- });
-
- it("should return a success message if addJob is successful", async function () {
- req.jobQueue.addJob.resolves();
- await addJob(req, res, next);
- expect(res.status.firstCall.args[0]).to.equal(200);
- expect(res.json.firstCall.args[0]).to.deep.equal({
- success: true,
- msg: successMessages.QUEUE_ADD_JOB,
- });
- });
-});
-
-describe("Queue Controller - obliterateQueue", function () {
- let req, res, next;
-
- beforeEach(function () {
- req = {
- headers: {},
- params: {},
- body: {},
- db: {},
- jobQueue: {
- obliterate: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- });
-
- afterEach(() => {
- sinon.restore();
- });
-
- it("should reject with an error if obliterateQueue throws an error", async function () {
- req.jobQueue.obliterate.throws(new Error("obliterateQueue error"));
- await obliterateQueue(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("obliterateQueue error");
- });
-
- it("should return a success message if obliterateQueue is successful", async function () {
- req.jobQueue.obliterate.resolves();
- await obliterateQueue(req, res, next);
- expect(res.status.firstCall.args[0]).to.equal(200);
- expect(res.json.firstCall.args[0]).to.deep.equal({
- success: true,
- msg: successMessages.QUEUE_OBLITERATE,
- });
- });
-});
diff --git a/server/tests/controllers/settingsController.test.js b/server/tests/controllers/settingsController.test.js
deleted file mode 100755
index 721c41c02..000000000
--- a/server/tests/controllers/settingsController.test.js
+++ /dev/null
@@ -1,109 +0,0 @@
-import { afterEach } from "node:test";
-import { getAppSettings, updateAppSettings } from "../../controllers/settingsController.js";
-
-import { successMessages } from "../../utils/messages.js";
-import sinon from "sinon";
-
-describe("Settings Controller - getAppSettings", function () {
- let req, res, next;
-
- beforeEach(function () {
- req = {
- headers: {},
- params: {},
- body: {},
- db: {},
- settingsService: {
- getSettings: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- });
-
- afterEach(() => {
- sinon.restore();
- });
-
- it("should throw an error if getSettings throws an error", async function () {
- req.settingsService.getSettings.throws(new Error("getSettings error"));
- await getAppSettings(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("getSettings error");
- });
-
- it("should return a success message and data if getSettings is successful", async function () {
- const data = { data: "settings" };
- req.settingsService.getSettings.returns(data);
- await getAppSettings(req, res, next);
- expect(res.status.firstCall.args[0]).to.equal(200);
- expect(res.json.firstCall.args[0]).to.deep.equal({
- success: true,
- msg: successMessages.GET_APP_SETTINGS,
- data,
- });
- });
-});
-
-describe("Settings Controller - updateAppSettings", function () {
- let req, res, next;
-
- beforeEach(function () {
- req = {
- headers: {},
- params: {},
- body: {},
- db: {
- updateAppSettings: sinon.stub(),
- },
- settingsService: {
- reloadSettings: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- });
-
- afterEach(() => {
- sinon.restore();
- });
-
- it("should reject with an error if body validation fails", async function () {
- req.body = { invalid: 1 };
- await updateAppSettings(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
-
- it("should reject with an error if updateAppSettings throws an error", async function () {
- req.db.updateAppSettings.throws(new Error("updateAppSettings error"));
- await updateAppSettings(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("updateAppSettings error");
- });
-
- it("should reject with an error if reloadSettings throws an error", async function () {
- req.settingsService.reloadSettings.throws(new Error("reloadSettings error"));
- await updateAppSettings(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].message).to.equal("reloadSettings error");
- });
-
- it("should return a success message and data if updateAppSettings is successful", async function () {
- const data = { data: "settings" };
- req.settingsService.reloadSettings.returns(data);
- await updateAppSettings(req, res, next);
- expect(res.status.firstCall.args[0]).to.equal(200);
- expect(res.json.firstCall.args[0]).to.deep.equal({
- success: true,
- msg: successMessages.UPDATE_APP_SETTINGS,
- data,
- });
- });
-});
diff --git a/server/tests/controllers/statusPageController.test.js b/server/tests/controllers/statusPageController.test.js
deleted file mode 100755
index cada662e0..000000000
--- a/server/tests/controllers/statusPageController.test.js
+++ /dev/null
@@ -1,127 +0,0 @@
-import sinon from "sinon";
-import { createStatusPage, getStatusPageByUrl } from "../../controllers/statusPageController.js";
-
-describe("statusPageController", function () {
- let req, res, next;
-
- beforeEach(function () {
- req = {
- params: {},
- body: {},
- db: {
- createStatusPage: sinon.stub(),
- getStatusPageByUrl: sinon.stub(),
- },
- };
- res = {
- status: sinon.stub().returnsThis(),
- json: sinon.stub(),
- };
- next = sinon.stub();
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- describe("createStatusPage", function () {
- beforeEach(function () {
- req.body = {
- companyName: "Test Company",
- url: "123456",
- timezone: "America/Toronto",
- color: "#000000",
- theme: "light",
- monitors: ["67309ca673788e808884c8ac", "67309ca673788e808884c8ac"],
- };
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should handle a validation error", async function () {
- req.body = {
- // Invalid data that will trigger validation error
- companyName: "",
- url: "",
- timezone: "",
- color: "invalid",
- theme: "invalid",
- monitors: ["invalid-id"],
- };
- try {
- await createStatusPage(req, res, next);
- } catch (error) {
- expect(error).to.be.an.instanceOf(Error);
- expect(error.message).to.equal("Validation error");
- }
- });
-
- it("should handle a db error", async function () {
- const err = new Error("DB error");
- req.db.createStatusPage.throws(err);
-
- try {
- await createStatusPage(req, res, next);
- } catch (error) {
- expect(error).to.deep.equal(err);
- }
- });
-
- it("should insert a properly formatted status page", async function () {
- const result = await createStatusPage(req, res, next);
- expect(res.status.firstCall.args[0]).to.equal(200);
- expect(res.json.firstCall.args[0].success).to.be.true;
- });
- });
-
- describe("getStatusPageByUrl", function () {
- beforeEach(function () {
- req.params = {
- url: "123456",
- };
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should handle a validation error", async function () {
- req.params = {
- url: "",
- };
-
- try {
- await getStatusPageByUrl(req, res, next);
- } catch (error) {
- expect(error).to.be.an.instanceOf(Error);
- expect(error.message).to.equal("Validation error");
- }
- });
-
- it("should handle a DB error", async function () {
- const err = new Error("DB error");
- req.db.getStatusPageByUrl.throws(err);
-
- try {
- await getStatusPageByUrl(req, res, next);
- } catch (error) {
- expect(error).to.deep.equal(err);
- }
- });
-
- it("should return a status page", async function () {
- const statusPage = {
- _id: "123456",
- companyName: "Test Company",
- url: "123456",
- };
- req.db.getStatusPageByUrl.resolves(statusPage);
- const result = await getStatusPageByUrl(req, res, next);
- expect(res.status.firstCall.args[0]).to.equal(200);
- expect(res.json.firstCall.args[0].success).to.be.true;
- expect(res.json.firstCall.args[0].data).to.deep.equal(statusPage);
- });
- });
-});
diff --git a/server/tests/db/checkModule.test.js b/server/tests/db/checkModule.test.js
deleted file mode 100755
index 6eef12445..000000000
--- a/server/tests/db/checkModule.test.js
+++ /dev/null
@@ -1,589 +0,0 @@
-import sinon from "sinon";
-import {
- createCheck,
- getChecksCount,
- getChecks,
- getTeamChecks,
- deleteChecks,
- deleteChecksByTeamId,
- updateChecksTTL,
-} from "../../db/mongo/modules/checkModule.js";
-import Check from "../../db/models/Check.js";
-import Monitor from "../../db/models/Monitor.js";
-import User from "../../db/models/User.js";
-import logger from "../../utils/logger.js";
-
-describe("checkModule", function () {
- describe("createCheck", function () {
- let checkCountDocumentsStub, checkSaveStub, monitorFindByIdStub, monitorSaveStub;
- const mockMonitor = {
- _id: "123",
- uptimePercentage: 0.5,
- status: true,
- save: () => this,
- };
- const mockCheck = { active: true };
-
- beforeEach(function () {
- checkSaveStub = sinon.stub(Check.prototype, "save");
- checkCountDocumentsStub = sinon.stub(Check, "countDocuments");
- monitorFindByIdStub = sinon.stub(Monitor, "findById");
- monitorSaveStub = sinon.stub(Monitor.prototype, "save");
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should return undefined early if no monitor is found", async function () {
- monitorFindByIdStub.returns(null);
- const check = await createCheck({ monitorId: "123" });
- expect(check).to.be.undefined;
- });
-
- it("should return a check", async function () {
- monitorFindByIdStub.returns(mockMonitor);
- checkSaveStub.returns(mockCheck);
- monitorSaveStub.returns(mockMonitor);
- const check = await createCheck({ monitorId: "123", status: true });
- expect(check).to.deep.equal(mockCheck);
- });
-
- it("should return a check if status is down", async function () {
- mockMonitor.status = false;
- monitorFindByIdStub.returns(mockMonitor);
- checkSaveStub.returns(mockCheck);
- monitorSaveStub.returns(mockMonitor);
- const check = await createCheck({ monitorId: "123", status: false });
- expect(check).to.deep.equal(mockCheck);
- });
-
- it("should return a check if uptimePercentage is undefined", async function () {
- mockMonitor.uptimePercentage = undefined;
- monitorFindByIdStub.returns(mockMonitor);
- checkSaveStub.returns(mockCheck);
- monitorSaveStub.returns(mockMonitor);
- const check = await createCheck({ monitorId: "123", status: true });
- expect(check).to.deep.equal(mockCheck);
- });
-
- it("should return a check if uptimePercentage is undefined and status is down", async function () {
- mockMonitor.uptimePercentage = undefined;
- monitorFindByIdStub.returns(mockMonitor);
- checkSaveStub.returns(mockCheck);
- monitorSaveStub.returns(mockMonitor);
- const check = await createCheck({ monitorId: "123", status: false });
- expect(check).to.deep.equal(mockCheck);
- });
-
- it("should monitor save error", async function () {
- const err = new Error("Save Error");
- monitorSaveStub.throws(err);
- try {
- await createCheck({ monitorId: "123" });
- } catch (error) {
- expect(error).to.deep.equal(err);
- }
- });
-
- it("should handle errors", async function () {
- const err = new Error("DB Error");
- checkCountDocumentsStub.throws(err);
- try {
- await createCheck({ monitorId: "123" });
- } catch (error) {
- expect(error).to.deep.equal(err);
- }
- });
- });
-
- describe("getChecksCount", function () {
- let checkCountDocumentStub;
-
- beforeEach(function () {
- checkCountDocumentStub = sinon.stub(Check, "countDocuments");
- });
-
- afterEach(function () {
- checkCountDocumentStub.restore();
- });
-
- it("should return count with basic monitorId query", async function () {
- const req = {
- params: { monitorId: "test123" },
- query: {},
- };
- checkCountDocumentStub.resolves(5);
-
- const result = await getChecksCount(req);
-
- expect(result).to.equal(5);
- expect(checkCountDocumentStub.calledOnce).to.be.true;
- expect(checkCountDocumentStub.firstCall.args[0]).to.deep.equal({
- monitorId: "test123",
- });
- });
-
- it("should include dateRange in query when provided", async function () {
- const req = {
- params: { monitorId: "test123" },
- query: { dateRange: "day" },
- };
- checkCountDocumentStub.resolves(3);
-
- const result = await getChecksCount(req);
-
- expect(result).to.equal(3);
- expect(checkCountDocumentStub.firstCall.args[0]).to.have.property("createdAt");
- expect(checkCountDocumentStub.firstCall.args[0].createdAt).to.have.property("$gte");
- });
-
- it('should handle "all" filter correctly', async function () {
- const req = {
- params: { monitorId: "test123" },
- query: { filter: "all" },
- };
- checkCountDocumentStub.resolves(2);
-
- const result = await getChecksCount(req);
-
- expect(result).to.equal(2);
- expect(checkCountDocumentStub.firstCall.args[0]).to.deep.equal({
- monitorId: "test123",
- status: false,
- });
- });
-
- it('should handle "down" filter correctly', async function () {
- const req = {
- params: { monitorId: "test123" },
- query: { filter: "down" },
- };
- checkCountDocumentStub.resolves(2);
-
- const result = await getChecksCount(req);
-
- expect(result).to.equal(2);
- expect(checkCountDocumentStub.firstCall.args[0]).to.deep.equal({
- monitorId: "test123",
- status: false,
- });
- });
-
- it('should handle "resolve" filter correctly', async function () {
- const req = {
- params: { monitorId: "test123" },
- query: { filter: "resolve" },
- };
- checkCountDocumentStub.resolves(1);
-
- const result = await getChecksCount(req);
-
- expect(result).to.equal(1);
- expect(checkCountDocumentStub.firstCall.args[0]).to.deep.equal({
- monitorId: "test123",
- status: false,
- statusCode: 5000,
- });
- });
-
- it("should handle unknown filter correctly", async function () {
- const req = {
- params: { monitorId: "test123" },
- query: { filter: "unknown" },
- };
- checkCountDocumentStub.resolves(1);
-
- const result = await getChecksCount(req);
-
- expect(result).to.equal(1);
- expect(checkCountDocumentStub.firstCall.args[0]).to.deep.equal({
- monitorId: "test123",
- status: false,
- });
- });
-
- it("should combine dateRange and filter in query", async function () {
- const req = {
- params: { monitorId: "test123" },
- query: {
- dateRange: "week",
- filter: "down",
- },
- };
- checkCountDocumentStub.resolves(4);
-
- const result = await getChecksCount(req);
-
- expect(result).to.equal(4);
- expect(checkCountDocumentStub.firstCall.args[0]).to.have.all.keys("monitorId", "createdAt", "status");
- });
- });
-
- describe("getChecks", function () {
- let checkFindStub, monitorFindStub;
-
- beforeEach(function () {
- checkFindStub = sinon.stub(Check, "find").returns({
- skip: sinon.stub().returns({
- limit: sinon.stub().returns({
- sort: sinon.stub().returns([{ id: 1 }, { id: 2 }]),
- }),
- }),
- });
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should return checks with basic monitorId query", async function () {
- const req = {
- params: { monitorId: "test123" },
- query: {},
- };
-
- const result = await getChecks(req);
-
- expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]);
- });
-
- it("should return checks with limit query", async function () {
- const req = {
- params: { monitorId: "test123" },
- query: { limit: 10 },
- };
-
- const result = await getChecks(req);
-
- expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]);
- });
-
- it("should handle pagination correctly", async function () {
- const req = {
- params: { monitorId: "test123" },
- query: {
- page: 2,
- rowsPerPage: 10,
- },
- };
-
- const result = await getChecks(req);
-
- expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]);
- });
-
- it("should handle dateRange filter", async function () {
- const req = {
- params: { monitorId: "test123" },
- query: { dateRange: "week" },
- };
- const result = await getChecks(req);
-
- expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]);
- });
-
- it('should handle "all" filter', async function () {
- const req = {
- params: { monitorId: "test123" },
- query: { filter: "all" },
- };
-
- await getChecks(req);
- const result = await getChecks(req);
- expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]);
- });
-
- it('should handle "down" filter', async function () {
- const req = {
- params: { monitorId: "test123" },
- query: { filter: "down" },
- };
-
- await getChecks(req);
- const result = await getChecks(req);
- expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]);
- });
-
- it('should handle "resolve" filter', async function () {
- const req = {
- params: { monitorId: "test123" },
- query: { filter: "resolve" },
- };
-
- await getChecks(req);
- const result = await getChecks(req);
- expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]);
- });
-
- it('should handle "unknown" filter', async function () {
- const req = {
- params: { monitorId: "test123" },
- query: { filter: "unknown" },
- };
-
- await getChecks(req);
- const result = await getChecks(req);
- expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]);
- });
-
- it("should handle ascending sort order", async function () {
- const req = {
- params: { monitorId: "test123" },
- query: { sortOrder: "asc" },
- };
-
- await getChecks(req);
- const result = await getChecks(req);
- expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]);
- });
-
- it("should handle error case", async function () {
- const req = {
- params: { monitorId: "test123" },
- query: {},
- };
-
- checkFindStub.throws(new Error("Database error"));
-
- try {
- await getChecks(req);
- } catch (error) {
- expect(error.message).to.equal("Database error");
- expect(error.service).to.equal("checkModule");
- expect(error.method).to.equal("getChecks");
- }
- });
- });
-
- describe("getTeamChecks", function () {
- let checkFindStub, checkCountDocumentsStub, monitorFindStub;
- const mockMonitors = [{ _id: "123" }];
-
- beforeEach(function () {
- monitorFindStub = sinon.stub(Monitor, "find").returns({
- select: sinon.stub().returns(mockMonitors),
- });
- checkCountDocumentsStub = sinon.stub(Check, "countDocuments").returns(2);
- checkFindStub = sinon.stub(Check, "find").returns({
- skip: sinon.stub().returns({
- limit: sinon.stub().returns({
- sort: sinon.stub().returns({
- select: sinon.stub().returns([{ id: 1 }, { id: 2 }]),
- }),
- }),
- }),
- });
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should return checks with basic monitorId query", async function () {
- const req = {
- params: { teamId: "test123" },
- query: {},
- };
-
- const result = await getTeamChecks(req);
- expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] });
- });
-
- it("should handle pagination correctly", async function () {
- const req = {
- params: { monitorId: "test123" },
- query: { limit: 1, page: 2, rowsPerPage: 10 },
- };
-
- const result = await getTeamChecks(req);
- expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] });
- });
-
- it("should handle dateRange filter", async function () {
- const req = {
- params: { monitorId: "test123" },
- query: { dateRange: "week" },
- };
- const result = await getTeamChecks(req);
- expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] });
- });
-
- it('should handle "all" filter', async function () {
- const req = {
- params: { monitorId: "test123" },
- query: { filter: "all" },
- };
-
- await getChecks(req);
- const result = await getTeamChecks(req);
- expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] });
- });
-
- it('should handle "down" filter', async function () {
- const req = {
- params: { monitorId: "test123" },
- query: { filter: "down" },
- };
-
- await getChecks(req);
- const result = await getTeamChecks(req);
- expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] });
- });
-
- it('should handle "resolve" filter', async function () {
- const req = {
- params: { monitorId: "test123" },
- query: { filter: "resolve" },
- };
-
- await getChecks(req);
- const result = await getTeamChecks(req);
- expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] });
- });
-
- it('should handle "unknown" filter', async function () {
- const req = {
- params: { monitorId: "test123" },
- query: { filter: "unknown" },
- };
-
- await getChecks(req);
- const result = await getTeamChecks(req);
- expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] });
- });
-
- it("should handle ascending sort order", async function () {
- const req = {
- params: { monitorId: "test123" },
- query: { sortOrder: "asc" },
- };
-
- await getChecks(req);
- const result = await getTeamChecks(req);
- expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] });
- });
-
- it("should handle error case", async function () {
- const req = {
- params: { monitorId: "test123" },
- query: {},
- };
-
- checkFindStub.throws(new Error("Database error"));
-
- try {
- await getTeamChecks(req);
- } catch (error) {
- expect(error.message).to.equal("Database error");
- expect(error.service).to.equal("checkModule");
- expect(error.method).to.equal("getTeamChecks");
- }
- });
- });
-
- describe("deleteChecks", function () {
- let checkDeleteManyStub;
-
- beforeEach(function () {
- checkDeleteManyStub = sinon.stub(Check, "deleteMany").resolves({ deletedCount: 1 });
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should return a value if a check is deleted", async function () {
- const result = await deleteChecks("123");
- expect(result).to.equal(1);
- });
-
- it("should handle an error", async function () {
- checkDeleteManyStub.throws(new Error("Database error"));
- try {
- await deleteChecks("123");
- } catch (error) {
- expect(error.message).to.equal("Database error");
- expect(error.method).to.equal("deleteChecks");
- }
- });
- });
-
- describe("deleteChecksByTeamId", function () {
- let mockMonitors = [{ _id: 123, save: () => this }];
- let monitorFindStub, monitorSaveStub, checkDeleteManyStub;
-
- beforeEach(function () {
- monitorSaveStub = sinon.stub(Monitor.prototype, "save");
- monitorFindStub = sinon.stub(Monitor, "find").returns(mockMonitors);
- checkDeleteManyStub = sinon.stub(Check, "deleteMany").resolves({ deletedCount: 1 });
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should return a deleted count", async function () {
- const result = await deleteChecksByTeamId("123");
- expect(result).to.equal(1);
- });
-
- it("should handle errors", async function () {
- const err = new Error("DB Error");
- monitorFindStub.throws(err);
- try {
- const result = await deleteChecksByTeamId("123");
- } catch (error) {
- expect(error).to.deep.equal(err);
- }
- });
- });
-
- describe("updateChecksTTL", function () {
- let userUpdateManyStub;
- let loggerStub;
-
- beforeEach(function () {
- loggerStub = sinon.stub(logger, "error");
- userUpdateManyStub = sinon.stub(User, "updateMany");
- Check.collection = { dropIndex: sinon.stub(), createIndex: sinon.stub() };
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should return undefined", async function () {
- const result = await updateChecksTTL("123", 10);
- expect(result).to.be.undefined;
- });
-
- it("should log an error if dropIndex throws an error", async function () {
- const err = new Error("Drop Index Error");
- Check.collection.dropIndex.throws(err);
- await updateChecksTTL("123", 10);
- expect(loggerStub.calledOnce).to.be.true;
- expect(loggerStub.firstCall.args[0].message).to.equal(err.message);
- });
-
- it("should throw an error if createIndex throws an error", async function () {
- const err = new Error("Create Index Error");
- Check.collection.createIndex.throws(err);
- try {
- await updateChecksTTL("123", 10);
- } catch (error) {
- expect(error).to.deep.equal(err);
- }
- });
-
- it("should throw an error if User.updateMany throws an error", async function () {
- const err = new Error("Update Many Error");
- userUpdateManyStub.throws(err);
- try {
- await updateChecksTTL("123", 10);
- } catch (error) {
- expect(error).to.deep.equal(err);
- }
- });
- });
-});
diff --git a/server/tests/db/hardwareCheckModule.test.js b/server/tests/db/hardwareCheckModule.test.js
deleted file mode 100755
index 20ce197fb..000000000
--- a/server/tests/db/hardwareCheckModule.test.js
+++ /dev/null
@@ -1,136 +0,0 @@
-import sinon from "sinon";
-import HardwareCheck from "../../db/models/HardwareCheck.js";
-import { createHardwareCheck } from "../../db/mongo/modules/hardwareCheckModule.js";
-import Monitor from "../../db/models/Monitor.js";
-import logger from "../../utils/logger.js";
-
-const mockHardwareCheck = {
- data: {
- cpu: {
- physical_core: 4,
- logical_core: 8,
- frequency: 4800,
- current_frequency: 1411,
- temperature: [45, 50, 46, 47, 45, 50, 46, 47],
- free_percent: 0.8552990910595134,
- usage_percent: 0.14470090894048657,
- },
- memory: {
- total_bytes: 16467628032,
- available_bytes: 7895044096,
- used_bytes: 6599561216,
- usage_percent: 0.4008,
- },
- disk: [
- {
- read_speed_bytes: null,
- write_speed_bytes: null,
- total_bytes: 931258499072,
- free_bytes: 737097256960,
- usage_percent: 0.1661,
- },
- ],
- host: {
- os: "linux",
- platform: "ubuntu",
- kernel_version: "6.8.0-48-generic",
- },
- },
- errors: [
- {
- metric: ["cpu.temperature"],
- err: "unable to read CPU temperature",
- },
- ],
-};
-
-const mockMonitor = {
- _id: "123",
- uptimePercentage: 1,
- status: true,
- save: () => this,
-};
-
-describe("HardwareCheckModule", function () {
- let hardwareCheckSaveStub, hardwareCheckCountDocumentsStub, monitorFindByIdStub, loggerStub;
-
- beforeEach(function () {
- loggerStub = sinon.stub(logger, "error");
- hardwareCheckSaveStub = sinon.stub(HardwareCheck.prototype, "save");
- monitorFindByIdStub = sinon.stub(Monitor, "findById");
- hardwareCheckCountDocumentsStub = sinon.stub(HardwareCheck, "countDocuments");
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- describe("createHardwareCheck", function () {
- it("should return a hardware check", async function () {
- hardwareCheckSaveStub.resolves(mockHardwareCheck);
- monitorFindByIdStub.resolves(mockMonitor);
- hardwareCheckCountDocumentsStub.resolves(1);
- const hardwareCheck = await createHardwareCheck({ status: true });
- expect(hardwareCheck).to.exist;
- expect(hardwareCheck).to.deep.equal(mockHardwareCheck);
- });
-
- it("should return a hardware check for a check with status false", async function () {
- hardwareCheckSaveStub.resolves(mockHardwareCheck);
- monitorFindByIdStub.resolves(mockMonitor);
- hardwareCheckCountDocumentsStub.resolves(1);
- const hardwareCheck = await createHardwareCheck({ status: false });
- expect(hardwareCheck).to.exist;
- expect(hardwareCheck).to.deep.equal(mockHardwareCheck);
- });
-
- it("should handle an error", async function () {
- const err = new Error("test error");
- monitorFindByIdStub.resolves(mockMonitor);
- hardwareCheckSaveStub.rejects(err);
- try {
- await createHardwareCheck({});
- } catch (error) {
- expect(error).to.exist;
- expect(error).to.deep.equal(err);
- }
- });
-
- it("should log an error if a monitor is not found", async function () {
- monitorFindByIdStub.resolves(null);
- const res = await createHardwareCheck({});
- expect(loggerStub.calledOnce).to.be.true;
- expect(res).to.be.null;
- });
-
- it("should handle a monitor with undefined uptimePercentage", async function () {
- monitorFindByIdStub.resolves({ ...mockMonitor, uptimePercentage: undefined });
- hardwareCheckSaveStub.resolves(mockHardwareCheck);
- hardwareCheckCountDocumentsStub.resolves(1);
- const res = await createHardwareCheck({});
- expect(res).to.exist;
- });
-
- it("should handle a monitor with undefined uptimePercentage and true status", async function () {
- monitorFindByIdStub.resolves({
- ...mockMonitor,
- uptimePercentage: undefined,
- });
- hardwareCheckSaveStub.resolves(mockHardwareCheck);
- hardwareCheckCountDocumentsStub.resolves(1);
- const res = await createHardwareCheck({ status: true });
- expect(res).to.exist;
- });
-
- it("should handle a monitor with undefined uptimePercentage and false status", async function () {
- monitorFindByIdStub.resolves({
- ...mockMonitor,
- uptimePercentage: undefined,
- });
- hardwareCheckSaveStub.resolves(mockHardwareCheck);
- hardwareCheckCountDocumentsStub.resolves(1);
- const res = await createHardwareCheck({ status: false });
- expect(res).to.exist;
- });
- });
-});
diff --git a/server/tests/db/inviteModule.test.js b/server/tests/db/inviteModule.test.js
deleted file mode 100755
index e8913c983..000000000
--- a/server/tests/db/inviteModule.test.js
+++ /dev/null
@@ -1,103 +0,0 @@
-import sinon from "sinon";
-import InviteToken from "../../db/models/InviteToken.js";
-import { requestInviteToken, getInviteToken, getInviteTokenAndDelete } from "../../db/mongo/modules/inviteModule.js";
-import { errorMessages } from "../../utils/messages.js";
-
-describe("Invite Module", function () {
- const mockUserData = {
- email: "test@test.com",
- teamId: "123",
- role: ["admin"],
- token: "123",
- };
- const mockInviteToken = { _id: 123, time: 123 };
- let inviteTokenDeleteManyStub, inviteTokenSaveStub, inviteTokenFindOneStub, inviteTokenFindOneAndDeleteStub;
-
- beforeEach(function () {
- inviteTokenDeleteManyStub = sinon.stub(InviteToken, "deleteMany");
- inviteTokenSaveStub = sinon.stub(InviteToken.prototype, "save");
- inviteTokenFindOneStub = sinon.stub(InviteToken, "findOne");
- inviteTokenFindOneAndDeleteStub = sinon.stub(InviteToken, "findOneAndDelete");
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- describe("requestInviteToken", function () {
- it("should return a new invite token", async function () {
- inviteTokenDeleteManyStub.resolves();
- inviteTokenSaveStub.resolves();
- const inviteToken = await requestInviteToken(mockUserData);
- expect(inviteToken.email).to.equal(mockUserData.email);
- expect(inviteToken.role).to.deep.equal(mockUserData.role);
- expect(inviteToken.token).to.exist;
- });
-
- it("should handle an error", async function () {
- const err = new Error("test error");
- inviteTokenDeleteManyStub.rejects(err);
- try {
- await requestInviteToken(mockUserData);
- } catch (error) {
- expect(error).to.deep.equal(err);
- }
- });
- });
-
- describe("getInviteToken", function () {
- it("should return an invite token", async function () {
- inviteTokenFindOneStub.resolves(mockInviteToken);
- const inviteToken = await getInviteToken(mockUserData.token);
- expect(inviteToken).to.deep.equal(mockInviteToken);
- });
-
- it("should handle a token not found", async function () {
- inviteTokenFindOneStub.resolves(null);
- try {
- await getInviteToken(mockUserData.token);
- } catch (error) {
- expect(error.message).to.equal(errorMessages.AUTH_INVITE_NOT_FOUND);
- }
- });
-
- it("should handle DB errors", async function () {
- const err = new Error("test error");
- inviteTokenFindOneStub.rejects(err);
- try {
- await getInviteToken(mockUserData.token);
- } catch (error) {
- expect(error).to.deep.equal(err);
- expect(error.method).to.equal("getInviteToken");
- }
- });
- });
-
- describe("getInviteTokenAndDelete", function () {
- it("should return a deleted invite", async function () {
- inviteTokenFindOneAndDeleteStub.resolves(mockInviteToken);
- const deletedInvite = await getInviteTokenAndDelete(mockUserData.token);
- expect(deletedInvite).to.deep.equal(mockInviteToken);
- });
-
- it("should handle a token not found", async function () {
- inviteTokenFindOneAndDeleteStub.resolves(null);
- try {
- await getInviteTokenAndDelete(mockUserData.token);
- } catch (error) {
- expect(error.message).to.equal(errorMessages.AUTH_INVITE_NOT_FOUND);
- }
- });
-
- it("should handle DB errors", async function () {
- const err = new Error("test error");
- inviteTokenFindOneAndDeleteStub.rejects(err);
- try {
- await getInviteTokenAndDelete(mockUserData.token);
- } catch (error) {
- expect(error).to.deep.equal(err);
- expect(error.method).to.equal("getInviteTokenAndDelete");
- }
- });
- });
-});
diff --git a/server/tests/db/maintenanceWindowModule.test.js b/server/tests/db/maintenanceWindowModule.test.js
deleted file mode 100755
index 576c97f84..000000000
--- a/server/tests/db/maintenanceWindowModule.test.js
+++ /dev/null
@@ -1,255 +0,0 @@
-import sinon from "sinon";
-import MaintenanceWindow from "../../db/models/MaintenanceWindow.js";
-import {
- createMaintenanceWindow,
- getMaintenanceWindowById,
- getMaintenanceWindowsByTeamId,
- getMaintenanceWindowsByMonitorId,
- deleteMaintenanceWindowById,
- deleteMaintenanceWindowByMonitorId,
- deleteMaintenanceWindowByUserId,
- editMaintenanceWindowById,
-} from "../../db/mongo/modules/maintenanceWindowModule.js";
-
-describe("MaintenanceWindow Module", function () {
- const mockMaintenanceWindow = {
- monitorId: "123",
- active: true,
- oneTime: true,
- start: 1,
- end: 20000,
- };
-
- let mockMaintenanceWindows = [mockMaintenanceWindow];
- let maintenanceWindowSaveStub,
- maintenanceWindowFindByIdStub,
- maintenanceWindowCountDocumentsStub,
- maintenanceWindowFindStub,
- maintenanceWindowFindByIdAndDeleteStub,
- maintenanceWindowDeleteManyStub,
- maintenanceWindowFindByIdAndUpdateStub;
-
- beforeEach(function () {
- maintenanceWindowSaveStub = sinon.stub(MaintenanceWindow.prototype, "save");
- maintenanceWindowFindByIdStub = sinon.stub(MaintenanceWindow, "findById");
- maintenanceWindowCountDocumentsStub = sinon.stub(MaintenanceWindow, "countDocuments");
- maintenanceWindowFindStub = sinon.stub(MaintenanceWindow, "find").returns({
- skip: sinon.stub().returns({
- limit: sinon.stub().returns({
- sort: sinon.stub().returns(mockMaintenanceWindows),
- }),
- }),
- });
- maintenanceWindowFindByIdAndDeleteStub = sinon.stub(MaintenanceWindow, "findByIdAndDelete");
- maintenanceWindowDeleteManyStub = sinon.stub(MaintenanceWindow, "deleteMany");
- maintenanceWindowFindByIdAndUpdateStub = sinon.stub(MaintenanceWindow, "findByIdAndUpdate");
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- describe("createMaintenanceWindow", function () {
- it("should save a new maintenance window", async function () {
- maintenanceWindowSaveStub.resolves(mockMaintenanceWindow);
- const result = await createMaintenanceWindow(mockMaintenanceWindow);
- expect(result).to.deep.equal(mockMaintenanceWindow);
- });
-
- it("should handle an error", async function () {
- const err = new Error("test error");
- maintenanceWindowSaveStub.rejects(err);
- try {
- await createMaintenanceWindow(mockMaintenanceWindow);
- } catch (error) {
- expect(error).to.deep.equal(err);
- }
- });
- });
-
- describe("getMaintenanceWindowById", function () {
- it("should return a maintenance window", async function () {
- maintenanceWindowFindByIdStub.resolves(mockMaintenanceWindow);
- const result = await getMaintenanceWindowById(mockMaintenanceWindow.id);
- expect(result).to.deep.equal(mockMaintenanceWindow);
- });
-
- it("should handle an error", async function () {
- const err = new Error("test error");
- maintenanceWindowFindByIdStub.rejects(err);
- try {
- await getMaintenanceWindowById(mockMaintenanceWindow.id);
- } catch (error) {
- expect(error).to.deep.equal(err);
- }
- });
- });
-
- describe("getMaintenanceWindowsByTeamId", function () {
- let query;
-
- beforeEach(function () {
- query = {
- active: true,
- page: 1,
- rowsPerPage: 10,
- field: "name",
- order: "asc",
- };
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should return a list of maintenance windows and count", async function () {
- maintenanceWindowCountDocumentsStub.resolves(1);
- const result = await getMaintenanceWindowsByTeamId(mockMaintenanceWindow.teamId, query);
- expect(result).to.deep.equal({
- maintenanceWindows: mockMaintenanceWindows,
- maintenanceWindowCount: 1,
- });
- });
-
- it("should return a list of maintenance windows and count with empty query", async function () {
- query = undefined;
- maintenanceWindowCountDocumentsStub.resolves(1);
- const result = await getMaintenanceWindowsByTeamId(mockMaintenanceWindow.teamId, query);
- expect(result).to.deep.equal({
- maintenanceWindows: mockMaintenanceWindows,
- maintenanceWindowCount: 1,
- });
- });
-
- it("should return a list of maintenance windows and count with no pagination provided", async function () {
- query.page = undefined;
- query.rowsPerPage = undefined;
- maintenanceWindowCountDocumentsStub.resolves(1);
- const result = await getMaintenanceWindowsByTeamId(mockMaintenanceWindow.teamId, query);
- expect(result).to.deep.equal({
- maintenanceWindows: mockMaintenanceWindows,
- maintenanceWindowCount: 1,
- });
- });
-
- it("should return a list of maintenance windows and count with field and desc order", async function () {
- query.order = "desc";
- maintenanceWindowCountDocumentsStub.resolves(1);
- const result = await getMaintenanceWindowsByTeamId(mockMaintenanceWindow.teamId, query);
- expect(result).to.deep.equal({
- maintenanceWindows: mockMaintenanceWindows,
- maintenanceWindowCount: 1,
- });
- });
-
- it("should return a list of maintenance windows and count no field", async function () {
- query.field = undefined;
- maintenanceWindowCountDocumentsStub.resolves(1);
- const result = await getMaintenanceWindowsByTeamId(mockMaintenanceWindow.teamId, query);
- expect(result).to.deep.equal({
- maintenanceWindows: mockMaintenanceWindows,
- maintenanceWindowCount: 1,
- });
- });
-
- it("should handle an error", async function () {
- const err = new Error("test error");
- maintenanceWindowCountDocumentsStub.rejects(err);
- try {
- await getMaintenanceWindowsByTeamId(mockMaintenanceWindow.teamId, query);
- } catch (error) {
- expect(error).to.deep.equal(err);
- }
- });
- });
-
- describe("getMaintenanceWindowsByMonitorId", function () {
- it("should return a list of maintenance windows", async function () {
- maintenanceWindowFindStub.resolves(mockMaintenanceWindows);
- const result = await getMaintenanceWindowsByMonitorId(mockMaintenanceWindow.monitorId);
- expect(result).to.deep.equal(mockMaintenanceWindows);
- });
-
- it("should handle an error", async function () {
- const err = new Error("test error");
- maintenanceWindowFindStub.rejects(err);
- try {
- await getMaintenanceWindowsByMonitorId(mockMaintenanceWindow.monitorId);
- } catch (error) {
- expect(error).to.deep.equal(err);
- }
- });
- });
-
- describe("deleteMaintenanceWindowById", function () {
- it("should delete a maintenance window", async function () {
- maintenanceWindowFindByIdAndDeleteStub.resolves(mockMaintenanceWindow);
- const result = await deleteMaintenanceWindowById(mockMaintenanceWindow.id);
- expect(result).to.deep.equal(mockMaintenanceWindow);
- });
-
- it("should handle an error", async function () {
- const err = new Error("test error");
- maintenanceWindowFindByIdAndDeleteStub.rejects(err);
- try {
- await deleteMaintenanceWindowById(mockMaintenanceWindow.id);
- } catch (error) {
- expect(error).to.deep.equal(err);
- }
- });
- });
-
- describe("deleteMaintenanceWindowByMonitorId", function () {
- it("should return the number of documents deleted", async function () {
- maintenanceWindowDeleteManyStub.resolves({ deletedCount: 1 });
- const result = await deleteMaintenanceWindowByMonitorId(mockMaintenanceWindow.monitorId);
- expect(result).to.deep.equal({ deletedCount: 1 });
- });
-
- it("should handle an error", async function () {
- const err = new Error("test error");
- maintenanceWindowDeleteManyStub.rejects(err);
- try {
- await deleteMaintenanceWindowByMonitorId(mockMaintenanceWindow.monitorId);
- } catch (error) {
- expect(error).to.deep.equal(err);
- }
- });
- });
-
- describe("deleteMaintenanceWindowByUserId", function () {
- it("should return the number of documents deleted", async function () {
- maintenanceWindowDeleteManyStub.resolves({ deletedCount: 1 });
- const result = await deleteMaintenanceWindowByUserId(mockMaintenanceWindow.userId);
- expect(result).to.deep.equal({ deletedCount: 1 });
- });
-
- it("should handle an error", async function () {
- const err = new Error("test error");
- maintenanceWindowDeleteManyStub.rejects(err);
- try {
- await deleteMaintenanceWindowByUserId(mockMaintenanceWindow.userId);
- } catch (error) {
- expect(error).to.deep.equal(err);
- }
- });
- });
-
- describe("editMaintenanceWindowById", function () {
- it("should return the updated maintenance window", async function () {
- maintenanceWindowFindByIdAndUpdateStub.resolves(mockMaintenanceWindow);
- const result = await editMaintenanceWindowById(mockMaintenanceWindow.id, mockMaintenanceWindow);
- expect(result).to.deep.equal(mockMaintenanceWindow);
- });
-
- it("should handle an error", async function () {
- const err = new Error("test error");
- maintenanceWindowFindByIdAndUpdateStub.rejects(err);
- try {
- await editMaintenanceWindowById(mockMaintenanceWindow.id, mockMaintenanceWindow);
- } catch (error) {
- expect(error).to.deep.equal(err);
- }
- });
- });
-});
diff --git a/server/tests/db/monitorModule.test.js b/server/tests/db/monitorModule.test.js
deleted file mode 100755
index e59c352cf..000000000
--- a/server/tests/db/monitorModule.test.js
+++ /dev/null
@@ -1,1865 +0,0 @@
-import sinon from "sinon";
-import Monitor from "../../db/models/Monitor.js";
-import Check from "../../db/models/Check.js";
-import PageSpeedCheck from "../../db/models/PageSpeedCheck.js";
-import HardwareCheck from "../../db/models/HardwareCheck.js";
-import Notification from "../../db/models/Notification.js";
-
-import { errorMessages } from "../../utils/messages.js";
-import {
- getAllMonitors,
- getAllMonitorsWithUptimeStats,
- getMonitorStatsById,
- getMonitorById,
- getMonitorsAndSummaryByTeamId,
- getMonitorsByTeamId,
- createMonitor,
- deleteMonitor,
- deleteAllMonitors,
- deleteMonitorsByUserId,
- editMonitor,
- addDemoMonitors,
- calculateUptimeDuration,
- getLastChecked,
- getLatestResponseTime,
- getAverageResponseTime,
- getUptimePercentage,
- getIncidents,
- getMonitorChecks,
- processChecksForDisplay,
- groupChecksByTime,
- calculateGroupStats,
-} from "../../db/mongo/modules/monitorModule.js";
-
-describe("monitorModule", function () {
- let monitorFindStub,
- monitorFindByIdStub,
- monitorFindByIdAndUpdateStub,
- monitorFindByIdAndDeleteStub,
- monitorDeleteManyStub,
- monitorCountStub,
- monitorInsertManyStub,
- checkFindStub,
- pageSpeedCheckFindStub,
- hardwareCheckFindStub;
-
- beforeEach(function () {
- monitorFindStub = sinon.stub(Monitor, "find");
- monitorFindByIdStub = sinon.stub(Monitor, "findById");
- monitorFindByIdAndUpdateStub = sinon.stub(Monitor, "findByIdAndUpdate");
- monitorFindByIdAndDeleteStub = sinon.stub(Monitor, "findByIdAndDelete");
- monitorDeleteManyStub = sinon.stub(Monitor, "deleteMany");
- monitorCountStub = sinon.stub(Monitor, "countDocuments");
-
- monitorInsertManyStub = sinon.stub(Monitor, "insertMany");
- checkFindStub = sinon.stub(Check, "find").returns({
- sort: sinon.stub(),
- });
- pageSpeedCheckFindStub = sinon.stub(PageSpeedCheck, "find").returns({
- sort: sinon.stub(),
- });
- hardwareCheckFindStub = sinon.stub(HardwareCheck, "find").returns({
- sort: sinon.stub(),
- });
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- describe("getAllMonitors", function () {
- it("should return all monitors", async function () {
- const mockMonitors = [
- { _id: "1", name: "Monitor 1", url: "test1.com" },
- { _id: "2", name: "Monitor 2", url: "test2.com" },
- ];
- monitorFindStub.returns(mockMonitors);
- const result = await getAllMonitors();
-
- expect(result).to.deep.equal(mockMonitors);
- expect(monitorFindStub.calledOnce).to.be.true;
- expect(monitorFindStub.firstCall.args).to.deep.equal([]);
- });
-
- it("should handle empty results", async function () {
- monitorFindStub.returns([]);
- const result = await getAllMonitors();
- expect(result).to.be.an("array").that.is.empty;
- });
-
- it("should throw error when database fails", async function () {
- // Arrange
- const error = new Error("Database error");
- error.service = "MonitorModule";
- error.method = "getAllMonitors";
- monitorFindStub.rejects(error);
- // Act & Assert
- try {
- await getAllMonitors();
- expect.fail("Should have thrown an error");
- } catch (err) {
- expect(err).to.equal(error);
- expect(err.service).to.equal("monitorModule");
- expect(err.method).to.equal("getAllMonitors");
- }
- });
- });
-
- describe("getAllMonitorsWithUptimeStats", function () {
- it("should return monitors with uptime stats for different time periods", async function () {
- // Mock data
- const mockMonitors = [
- {
- _id: "monitor1",
- type: "http",
- toObject: () => ({
- _id: "monitor1",
- type: "http",
- name: "Test Monitor",
- }),
- },
- ];
-
- const mockChecks = [{ status: true }, { status: true }, { status: false }, { status: true }];
-
- monitorFindStub.resolves(mockMonitors);
- checkFindStub.resolves(mockChecks);
-
- const result = await getAllMonitorsWithUptimeStats();
-
- expect(result).to.be.an("array");
- expect(result).to.have.lengthOf(1);
-
- const monitor = result[0];
- expect(monitor).to.have.property("_id", "monitor1");
- expect(monitor).to.have.property("name", "Test Monitor");
-
- // Check uptime percentages exist for all time periods
- expect(monitor).to.have.property("1");
- expect(monitor).to.have.property("7");
- expect(monitor).to.have.property("30");
- expect(monitor).to.have.property("90");
-
- // Verify uptime percentage calculation (3 successful out of 4 = 75%)
- expect(monitor["1"]).to.equal(75);
- expect(monitor["7"]).to.equal(75);
- expect(monitor["30"]).to.equal(75);
- expect(monitor["90"]).to.equal(75);
- });
-
- it("should return monitors with stats for pagespeed type", async function () {
- // Mock data
- const mockMonitors = [
- {
- _id: "monitor1",
- type: "pagespeed",
- toObject: () => ({
- _id: "monitor1",
- type: "pagespeed",
- name: "Test Monitor",
- }),
- },
- ];
-
- const mockChecks = [{ status: true }, { status: true }, { status: false }, { status: true }];
-
- monitorFindStub.resolves(mockMonitors);
- pageSpeedCheckFindStub.resolves(mockChecks);
-
- const result = await getAllMonitorsWithUptimeStats();
-
- expect(result).to.be.an("array");
- expect(result).to.have.lengthOf(1);
-
- const monitor = result[0];
- expect(monitor).to.have.property("_id", "monitor1");
- expect(monitor).to.have.property("name", "Test Monitor");
-
- // Check uptime percentages exist for all time periods
- expect(monitor).to.have.property("1");
- expect(monitor).to.have.property("7");
- expect(monitor).to.have.property("30");
- expect(monitor).to.have.property("90");
-
- // Verify uptime percentage calculation (3 successful out of 4 = 75%)
- expect(monitor["1"]).to.equal(75);
- expect(monitor["7"]).to.equal(75);
- expect(monitor["30"]).to.equal(75);
- expect(monitor["90"]).to.equal(75);
- });
-
- it("should return monitors with stats for hardware type", async function () {
- // Mock data
- const mockMonitors = [
- {
- _id: "monitor1",
- type: "hardware",
- toObject: () => ({
- _id: "monitor1",
- type: "hardware",
- name: "Test Monitor",
- }),
- },
- ];
-
- const mockChecks = [{ status: true }, { status: true }, { status: false }, { status: true }];
-
- monitorFindStub.resolves(mockMonitors);
- hardwareCheckFindStub.resolves(mockChecks);
-
- const result = await getAllMonitorsWithUptimeStats();
-
- expect(result).to.be.an("array");
- expect(result).to.have.lengthOf(1);
-
- const monitor = result[0];
- expect(monitor).to.have.property("_id", "monitor1");
- expect(monitor).to.have.property("name", "Test Monitor");
-
- // Check uptime percentages exist for all time periods
- expect(monitor).to.have.property("1");
- expect(monitor).to.have.property("7");
- expect(monitor).to.have.property("30");
- expect(monitor).to.have.property("90");
-
- // Verify uptime percentage calculation (3 successful out of 4 = 75%)
- expect(monitor["1"]).to.equal(75);
- expect(monitor["7"]).to.equal(75);
- expect(monitor["30"]).to.equal(75);
- expect(monitor["90"]).to.equal(75);
- });
-
- it("should handle errors appropriately", async function () {
- // Setup stub to throw error
- monitorFindStub.rejects(new Error("Database error"));
-
- try {
- await getAllMonitorsWithUptimeStats();
- } catch (error) {
- expect(error).to.be.an("error");
- expect(error.message).to.equal("Database error");
- expect(error.service).to.equal("monitorModule");
- expect(error.method).to.equal("getAllMonitorsWithUptimeStats");
- }
- });
-
- it("should handle empty monitor list", async function () {
- monitorFindStub.resolves([]);
-
- const result = await getAllMonitorsWithUptimeStats();
-
- expect(result).to.be.an("array");
- expect(result).to.have.lengthOf(0);
- });
-
- it("should handle monitor with no checks", async function () {
- const mockMonitors = [
- {
- _id: "monitor1",
- type: "http",
- toObject: () => ({
- _id: "monitor1",
- type: "http",
- name: "Test Monitor",
- }),
- },
- ];
-
- monitorFindStub.resolves(mockMonitors);
- checkFindStub.resolves([]);
-
- const result = await getAllMonitorsWithUptimeStats();
-
- expect(result[0]).to.have.property("1", 0);
- expect(result[0]).to.have.property("7", 0);
- expect(result[0]).to.have.property("30", 0);
- expect(result[0]).to.have.property("90", 0);
- });
- });
-
- describe("calculateUptimeDuration", function () {
- let clock;
- const NOW = new Date("2024-01-01T12:00:00Z").getTime();
-
- beforeEach(function () {
- // Fix the current time
- clock = sinon.useFakeTimers(NOW);
- });
-
- afterEach(function () {
- clock.restore();
- });
-
- it("should return 0 when checks array is empty", function () {
- expect(calculateUptimeDuration([])).to.equal(0);
- });
-
- it("should return 0 when checks array is null", function () {
- expect(calculateUptimeDuration(null)).to.equal(0);
- });
-
- it("should calculate uptime from last down check to most recent check", function () {
- const checks = [
- { status: true, createdAt: "2024-01-01T11:00:00Z" }, // Most recent
- { status: true, createdAt: "2024-01-01T10:00:00Z" },
- { status: false, createdAt: "2024-01-01T09:00:00Z" }, // Last down
- { status: true, createdAt: "2024-01-01T08:00:00Z" },
- ];
-
- // Expected: 2 hours (from 09:00 to 11:00) = 7200000ms
- expect(calculateUptimeDuration(checks)).to.equal(7200000);
- });
-
- it("should calculate uptime from first check when no down checks exist", function () {
- const checks = [
- { status: true, createdAt: "2024-01-01T11:00:00Z" },
- { status: true, createdAt: "2024-01-01T10:00:00Z" },
- { status: true, createdAt: "2024-01-01T09:00:00Z" },
- ];
-
- // Expected: Current time (12:00) - First check (09:00) = 3 hours = 10800000ms
- expect(calculateUptimeDuration(checks)).to.equal(10800000);
- });
- });
-
- describe("getLastChecked", function () {
- let clock;
- const NOW = new Date("2024-01-01T12:00:00Z").getTime();
-
- beforeEach(function () {
- // Fix the current time
- clock = sinon.useFakeTimers(NOW);
- });
-
- afterEach(function () {
- clock.restore();
- });
-
- it("should return 0 when checks array is empty", function () {
- expect(getLastChecked([])).to.equal(0);
- });
-
- it("should return 0 when checks array is null", function () {
- expect(getLastChecked(null)).to.equal(0);
- });
-
- it("should return time difference between now and most recent check", function () {
- const checks = [
- { createdAt: "2024-01-01T11:30:00Z" }, // 30 minutes ago
- { createdAt: "2024-01-01T11:00:00Z" },
- { createdAt: "2024-01-01T10:30:00Z" },
- ];
-
- // Expected: 30 minutes = 1800000ms
- expect(getLastChecked(checks)).to.equal(1800000);
- });
-
- it("should handle checks from different days", function () {
- const checks = [
- { createdAt: "2023-12-31T12:00:00Z" }, // 24 hours ago
- { createdAt: "2023-12-30T12:00:00Z" },
- ];
-
- // Expected: 24 hours = 86400000ms
- expect(getLastChecked(checks)).to.equal(86400000);
- });
- });
-
- describe("getLatestResponseTime", function () {
- it("should return 0 when checks array is empty", function () {
- expect(getLatestResponseTime([])).to.equal(0);
- });
-
- it("should return 0 when checks array is null", function () {
- expect(getLatestResponseTime(null)).to.equal(0);
- });
-
- it("should return response time from most recent check", function () {
- const checks = [
- { responseTime: 150, createdAt: "2024-01-01T11:30:00Z" }, // Most recent
- { responseTime: 200, createdAt: "2024-01-01T11:00:00Z" },
- { responseTime: 250, createdAt: "2024-01-01T10:30:00Z" },
- ];
-
- expect(getLatestResponseTime(checks)).to.equal(150);
- });
-
- it("should handle missing responseTime in checks", function () {
- const checks = [{ createdAt: "2024-01-01T11:30:00Z" }, { responseTime: 200, createdAt: "2024-01-01T11:00:00Z" }];
-
- expect(getLatestResponseTime(checks)).to.equal(0);
- });
- });
-
- describe("getAverageResponseTime", function () {
- it("should return 0 when checks array is empty", function () {
- expect(getAverageResponseTime([])).to.equal(0);
- });
-
- it("should return 0 when checks array is null", function () {
- expect(getAverageResponseTime(null)).to.equal(0);
- });
-
- it("should calculate average response time from all checks", function () {
- const checks = [
- { responseTime: 100, createdAt: "2024-01-01T11:30:00Z" },
- { responseTime: 200, createdAt: "2024-01-01T11:00:00Z" },
- { responseTime: 300, createdAt: "2024-01-01T10:30:00Z" },
- ];
-
- // Average: (100 + 200 + 300) / 3 = 200
- expect(getAverageResponseTime(checks)).to.equal(200);
- });
-
- it("should handle missing responseTime in some checks", function () {
- const checks = [
- { responseTime: 100, createdAt: "2024-01-01T11:30:00Z" },
- { createdAt: "2024-01-01T11:00:00Z" },
- { responseTime: 300, createdAt: "2024-01-01T10:30:00Z" },
- ];
-
- // Average: (100 + 300) / 2 = 200
- expect(getAverageResponseTime(checks)).to.equal(200);
- });
-
- it("should return 0 when no checks have responseTime", function () {
- const checks = [{ createdAt: "2024-01-01T11:30:00Z" }, { createdAt: "2024-01-01T11:00:00Z" }];
-
- expect(getAverageResponseTime(checks)).to.equal(0);
- });
- });
-
- describe("getUptimePercentage", function () {
- it("should return 0 when checks array is empty", function () {
- expect(getUptimePercentage([])).to.equal(0);
- });
-
- it("should return 0 when checks array is null", function () {
- expect(getUptimePercentage(null)).to.equal(0);
- });
-
- it("should return 100 when all checks are up", function () {
- const checks = [{ status: true }, { status: true }, { status: true }];
- expect(getUptimePercentage(checks)).to.equal(100);
- });
-
- it("should return 0 when all checks are down", function () {
- const checks = [{ status: false }, { status: false }, { status: false }];
- expect(getUptimePercentage(checks)).to.equal(0);
- });
-
- it("should calculate correct percentage for mixed status checks", function () {
- const checks = [{ status: true }, { status: false }, { status: true }, { status: true }];
- // 3 up out of 4 total = 75%
- expect(getUptimePercentage(checks)).to.equal(75);
- });
-
- it("should handle undefined status values", function () {
- const checks = [{ status: true }, { status: undefined }, { status: true }];
- // 2 up out of 3 total ≈ 66.67%
- expect(getUptimePercentage(checks)).to.equal((2 / 3) * 100);
- });
- });
-
- describe("getIncidents", function () {
- it("should return 0 when checks array is empty", function () {
- expect(getIncidents([])).to.equal(0);
- });
-
- it("should return 0 when checks array is null", function () {
- expect(getIncidents(null)).to.equal(0);
- });
-
- it("should return 0 when all checks are up", function () {
- const checks = [{ status: true }, { status: true }, { status: true }];
- expect(getIncidents(checks)).to.equal(0);
- });
-
- it("should count all incidents when all checks are down", function () {
- const checks = [{ status: false }, { status: false }, { status: false }];
- expect(getIncidents(checks)).to.equal(3);
- });
-
- it("should count correct number of incidents for mixed status checks", function () {
- const checks = [{ status: true }, { status: false }, { status: true }, { status: false }, { status: true }];
- expect(getIncidents(checks)).to.equal(2);
- });
-
- it("should handle undefined status values", function () {
- const checks = [{ status: true }, { status: undefined }, { status: false }, { status: false }];
- // Only counts explicit false values
- expect(getIncidents(checks)).to.equal(2);
- });
- });
-
- describe("getMonitorChecks", function () {
- let mockModel;
-
- beforeEach(function () {
- // Create a mock model with chainable methods
- const mockChecks = [
- { monitorId: "123", createdAt: new Date("2024-01-01") },
- { monitorId: "123", createdAt: new Date("2024-01-02") },
- ];
-
- mockModel = {
- find: sinon.stub().returns({
- sort: sinon.stub().returns(mockChecks),
- }),
- };
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should return all checks and date-ranged checks", async function () {
- // Arrange
- const monitorId = "123";
- const dateRange = {
- start: new Date("2024-01-01"),
- end: new Date("2024-01-02"),
- };
- const sortOrder = -1;
-
- // Act
- const result = await getMonitorChecks(monitorId, mockModel, dateRange, sortOrder);
-
- // Assert
- expect(result).to.have.keys(["checksAll", "checksForDateRange"]);
-
- // Verify find was called with correct parameters
- expect(mockModel.find.firstCall.args[0]).to.deep.equal({ monitorId });
- expect(mockModel.find.secondCall.args[0]).to.deep.equal({
- monitorId,
- createdAt: { $gte: dateRange.start, $lte: dateRange.end },
- });
-
- // Verify sort was called with correct parameters
- const sortCalls = mockModel.find().sort.getCalls();
- sortCalls.forEach((call) => {
- expect(call.args[0]).to.deep.equal({ createdAt: sortOrder });
- });
- });
-
- it("should handle empty results", async function () {
- // Arrange
- const emptyModel = {
- find: sinon.stub().returns({
- sort: sinon.stub().returns([]),
- }),
- };
-
- // Act
- const result = await getMonitorChecks(
- "123",
- emptyModel,
- {
- start: new Date(),
- end: new Date(),
- },
- -1
- );
-
- // Assert
- expect(result.checksAll).to.be.an("array").that.is.empty;
- expect(result.checksForDateRange).to.be.an("array").that.is.empty;
- });
-
- it("should maintain sort order", async function () {
- // Arrange
- const sortedChecks = [
- { monitorId: "123", createdAt: new Date("2024-01-02") },
- { monitorId: "123", createdAt: new Date("2024-01-01") },
- ];
-
- const sortedModel = {
- find: sinon.stub().returns({
- sort: sinon.stub().returns(sortedChecks),
- }),
- };
-
- // Act
- const result = await getMonitorChecks(
- "123",
- sortedModel,
- {
- start: new Date("2024-01-01"),
- end: new Date("2024-01-02"),
- },
- -1
- );
-
- // Assert
- expect(result.checksAll[0].createdAt).to.be.greaterThan(result.checksAll[1].createdAt);
- expect(result.checksForDateRange[0].createdAt).to.be.greaterThan(result.checksForDateRange[1].createdAt);
- });
- });
-
- describe("processChecksForDisplay", function () {
- let normalizeStub;
-
- beforeEach(function () {
- normalizeStub = sinon.stub();
- });
-
- it("should return original checks when numToDisplay is not provided", function () {
- const checks = [1, 2, 3, 4, 5];
- const result = processChecksForDisplay(normalizeStub, checks);
- expect(result).to.deep.equal(checks);
- });
-
- it("should return original checks when numToDisplay is greater than checks length", function () {
- const checks = [1, 2, 3];
- const result = processChecksForDisplay(normalizeStub, checks, 5);
- expect(result).to.deep.equal(checks);
- });
-
- it("should filter checks based on numToDisplay", function () {
- const checks = [1, 2, 3, 4, 5, 6];
- const result = processChecksForDisplay(normalizeStub, checks, 3);
- // Should return [1, 3, 5] as n = ceil(6/3) = 2
- expect(result).to.deep.equal([1, 3, 5]);
- });
-
- it("should handle empty checks array", function () {
- const checks = [];
- const result = processChecksForDisplay(normalizeStub, checks, 3);
- expect(result).to.be.an("array").that.is.empty;
- });
-
- it("should call normalizeData when normalize is true", function () {
- const checks = [1, 2, 3];
- normalizeStub.returns([10, 20, 30]);
-
- const result = processChecksForDisplay(normalizeStub, checks, null, true);
-
- expect(normalizeStub.args[0]).to.deep.equal([checks, 1, 100]);
- expect(result).to.deep.equal([10, 20, 30]);
- });
-
- it("should handle both filtering and normalization", function () {
- const checks = [1, 2, 3, 4, 5, 6];
- normalizeStub.returns([10, 30, 50]);
-
- const result = processChecksForDisplay(normalizeStub, checks, 3, true);
-
- expect(normalizeStub.args[0][0]).to.deep.equal([1, 3, 5]);
- expect(result).to.deep.equal([10, 30, 50]);
- });
- });
-
- describe("groupChecksByTime", function () {
- const mockChecks = [
- { createdAt: "2024-01-15T10:30:45Z" },
- { createdAt: "2024-01-15T10:45:15Z" },
- { createdAt: "2024-01-15T11:15:00Z" },
- { createdAt: "2024-01-16T10:30:00Z" },
- ];
-
- it("should group checks by hour when dateRange is 'day'", function () {
- const result = groupChecksByTime(mockChecks, "day");
-
- // Get timestamps for 10:00 and 11:00 on Jan 15
- const time1 = new Date("2024-01-15T10:00:00Z").getTime();
- const time2 = new Date("2024-01-15T11:00:00Z").getTime();
- const time3 = new Date("2024-01-16T10:00:00Z").getTime();
-
- expect(Object.keys(result)).to.have.lengthOf(3);
-
- expect(result[time1].checks).to.have.lengthOf(2);
- expect(result[time2].checks).to.have.lengthOf(1);
- expect(result[time3].checks).to.have.lengthOf(1);
- });
-
- it("should group checks by day when dateRange is not 'day'", function () {
- const result = groupChecksByTime(mockChecks, "week");
-
- expect(Object.keys(result)).to.have.lengthOf(2);
- expect(result["2024-01-15"].checks).to.have.lengthOf(3);
- expect(result["2024-01-16"].checks).to.have.lengthOf(1);
- });
-
- it("should handle empty checks array", function () {
- const result = groupChecksByTime([], "day");
- expect(result).to.deep.equal({});
- });
-
- it("should handle single check", function () {
- const singleCheck = [{ createdAt: "2024-01-15T10:30:45Z" }];
- const result = groupChecksByTime(singleCheck, "day");
-
- const expectedTime = new Date("2024-01-15T10:00:00Z").getTime();
- expect(Object.keys(result)).to.have.lengthOf(1);
- expect(result[expectedTime].checks).to.have.lengthOf(1);
- });
-
- it("should skip invalid dates and process valid ones", function () {
- const checksWithInvalidDate = [
- { createdAt: "invalid-date" },
- { createdAt: "2024-01-15T10:30:45Z" },
- { createdAt: null },
- { createdAt: undefined },
- { createdAt: "" },
- ];
-
- const result = groupChecksByTime(checksWithInvalidDate, "day");
-
- const expectedTime = new Date("2024-01-15T10:00:00Z").getTime();
- expect(Object.keys(result)).to.have.lengthOf(1);
- expect(result[expectedTime].checks).to.have.lengthOf(1);
- expect(result[expectedTime].checks[0].createdAt).to.equal("2024-01-15T10:30:45Z");
- });
-
- it("should handle checks in same time group", function () {
- const checksInSameHour = [{ createdAt: "2024-01-15T10:15:00Z" }, { createdAt: "2024-01-15T10:45:00Z" }];
-
- const result = groupChecksByTime(checksInSameHour, "day");
-
- const expectedTime = new Date("2024-01-15T10:00:00Z").getTime();
- expect(Object.keys(result)).to.have.lengthOf(1);
- expect(result[expectedTime].checks).to.have.lengthOf(2);
- });
- });
-
- describe("calculateGroupStats", function () {
- // Mock getUptimePercentage function
- let uptimePercentageStub;
-
- beforeEach(function () {
- uptimePercentageStub = sinon.stub();
- uptimePercentageStub.returns(95); // Default return value
- });
-
- it("should calculate stats correctly for a group of checks", function () {
- const mockGroup = {
- time: "2024-01-15",
- checks: [
- { status: true, responseTime: 100 },
- { status: false, responseTime: 200 },
- { status: true, responseTime: 300 },
- ],
- };
-
- const result = calculateGroupStats(mockGroup, uptimePercentageStub);
-
- expect(result).to.deep.equal({
- time: "2024-01-15",
- uptimePercentage: (2 / 3) * 100,
- totalChecks: 3,
- totalIncidents: 1,
- avgResponseTime: 200, // (100 + 200 + 300) / 3
- });
- });
-
- it("should handle empty checks array", function () {
- const mockGroup = {
- time: "2024-01-15",
- checks: [],
- };
-
- const result = calculateGroupStats(mockGroup, uptimePercentageStub);
-
- expect(result).to.deep.equal({
- time: "2024-01-15",
- uptimePercentage: 0,
- totalChecks: 0,
- totalIncidents: 0,
- avgResponseTime: 0,
- });
- });
-
- it("should handle missing responseTime values", function () {
- const mockGroup = {
- time: "2024-01-15",
- checks: [{ status: true }, { status: false, responseTime: 200 }, { status: true, responseTime: undefined }],
- };
-
- const result = calculateGroupStats(mockGroup, uptimePercentageStub);
-
- expect(result).to.deep.equal({
- time: "2024-01-15",
- uptimePercentage: (2 / 3) * 100,
- totalChecks: 3,
- totalIncidents: 1,
- avgResponseTime: 200, // 200 / 1
- });
- });
-
- it("should handle all checks with status false", function () {
- const mockGroup = {
- time: "2024-01-15",
- checks: [
- { status: false, responseTime: 100 },
- { status: false, responseTime: 200 },
- { status: false, responseTime: 300 },
- ],
- };
-
- const result = calculateGroupStats(mockGroup, uptimePercentageStub);
-
- expect(result).to.deep.equal({
- time: "2024-01-15",
- uptimePercentage: 0,
- totalChecks: 3,
- totalIncidents: 3,
- avgResponseTime: 200,
- });
- });
-
- it("should handle all checks with status true", function () {
- const mockGroup = {
- time: "2024-01-15",
- checks: [
- { status: true, responseTime: 100 },
- { status: true, responseTime: 200 },
- { status: true, responseTime: 300 },
- ],
- };
-
- const result = calculateGroupStats(mockGroup, uptimePercentageStub);
-
- expect(result).to.deep.equal({
- time: "2024-01-15",
- uptimePercentage: 100,
- totalChecks: 3,
- totalIncidents: 0,
- avgResponseTime: 200,
- });
- });
- });
-
- describe("getMonitorStatsById", function () {
- const now = new Date();
- const oneHourAgo = new Date(now - 3600000);
- const twoHoursAgo = new Date(now - 7200000);
-
- const mockMonitor = {
- _id: "monitor123",
- type: "http",
- name: "Test Monitor",
- url: "https://test.com",
- toObject: () => ({
- _id: "monitor123",
- type: "http",
- name: "Test Monitor",
- url: "https://test.com",
- }),
- };
-
- const mockMonitorPing = {
- _id: "monitor123",
- type: "ping",
- name: "Test Monitor",
- url: "https://test.com",
- toObject: () => ({
- _id: "monitor123",
- type: "http",
- name: "Test Monitor",
- url: "https://test.com",
- }),
- };
- const mockMonitorDocker = {
- _id: "monitor123",
- type: "docker",
- name: "Test Monitor",
- url: "https://test.com",
- toObject: () => ({
- _id: "monitor123",
- type: "http",
- name: "Test Monitor",
- url: "https://test.com",
- }),
- };
-
- const checkDocs = [
- {
- monitorId: "monitor123",
- status: true,
- responseTime: 100,
- createdAt: new Date("2024-01-01T12:00:00Z"),
- toObject: function () {
- return {
- monitorId: this.monitorId,
- status: this.status,
- responseTime: this.responseTime,
- createdAt: this.createdAt,
- };
- },
- },
- {
- monitorId: "monitor123",
- status: true,
- responseTime: 150,
- createdAt: new Date("2024-01-01T11:00:00Z"),
- toObject: function () {
- return {
- monitorId: this.monitorId,
- status: this.status,
- responseTime: this.responseTime,
- createdAt: this.createdAt,
- };
- },
- },
- {
- monitorId: "monitor123",
- status: false,
- responseTime: 200,
- createdAt: new Date("2024-01-01T10:00:00Z"),
- toObject: function () {
- return {
- monitorId: this.monitorId,
- status: this.status,
- responseTime: this.responseTime,
- createdAt: this.createdAt,
- };
- },
- },
- ];
- const req = {
- params: { monitorId: "monitor123" },
- query: {
- dateRange: "day",
- sortOrder: "desc",
- numToDisplay: 10,
- normalize: true,
- },
- };
-
- beforeEach(function () {
- checkFindStub.returns({
- sort: () => checkDocs,
- });
- monitorFindByIdStub.returns(mockMonitor);
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should return monitor stats with calculated values, sort order desc", async function () {
- req.query.sortOrder = "desc";
- const result = await getMonitorStatsById(req);
- expect(result).to.include.keys([
- "_id",
- "type",
- "name",
- "url",
- "uptimeDuration",
- "lastChecked",
- "latestResponseTime",
- "periodIncidents",
- "periodTotalChecks",
- "periodAvgResponseTime",
- "periodUptime",
- "aggregateData",
- ]);
- expect(result.latestResponseTime).to.equal(100);
- expect(result.periodTotalChecks).to.equal(3);
- expect(result.periodIncidents).to.equal(1);
- expect(result.periodUptime).to.be.a("number");
- expect(result.aggregateData).to.be.an("array");
- });
-
- it("should return monitor stats with calculated values, ping type", async function () {
- monitorFindByIdStub.returns(mockMonitorPing);
- req.query.sortOrder = "desc";
- const result = await getMonitorStatsById(req);
- expect(result).to.include.keys([
- "_id",
- "type",
- "name",
- "url",
- "uptimeDuration",
- "lastChecked",
- "latestResponseTime",
- "periodIncidents",
- "periodTotalChecks",
- "periodAvgResponseTime",
- "periodUptime",
- "aggregateData",
- ]);
- expect(result.latestResponseTime).to.equal(100);
- expect(result.periodTotalChecks).to.equal(3);
- expect(result.periodIncidents).to.equal(1);
- expect(result.periodUptime).to.be.a("number");
- expect(result.aggregateData).to.be.an("array");
- });
-
- it("should return monitor stats with calculated values, docker type", async function () {
- monitorFindByIdStub.returns(mockMonitorDocker);
- req.query.sortOrder = "desc";
- const result = await getMonitorStatsById(req);
- expect(result).to.include.keys([
- "_id",
- "type",
- "name",
- "url",
- "uptimeDuration",
- "lastChecked",
- "latestResponseTime",
- "periodIncidents",
- "periodTotalChecks",
- "periodAvgResponseTime",
- "periodUptime",
- "aggregateData",
- ]);
- expect(result.latestResponseTime).to.equal(100);
- expect(result.periodTotalChecks).to.equal(3);
- expect(result.periodIncidents).to.equal(1);
- expect(result.periodUptime).to.be.a("number");
- expect(result.aggregateData).to.be.an("array");
- });
-
- it("should return monitor stats with calculated values", async function () {
- req.query.sortOrder = "asc";
- const result = await getMonitorStatsById(req);
- expect(result).to.include.keys([
- "_id",
- "type",
- "name",
- "url",
- "uptimeDuration",
- "lastChecked",
- "latestResponseTime",
- "periodIncidents",
- "periodTotalChecks",
- "periodAvgResponseTime",
- "periodUptime",
- "aggregateData",
- ]);
- expect(result.latestResponseTime).to.equal(100);
- expect(result.periodTotalChecks).to.equal(3);
- expect(result.periodIncidents).to.equal(1);
- expect(result.periodUptime).to.be.a("number");
- expect(result.aggregateData).to.be.an("array");
- });
-
- it("should throw error when monitor is not found", async function () {
- monitorFindByIdStub.returns(Promise.resolve(null));
-
- const req = {
- params: { monitorId: "nonexistent" },
- };
-
- try {
- await getMonitorStatsById(req);
- expect.fail("Should have thrown an error");
- } catch (error) {
- expect(error).to.be.an("Error");
- expect(error.service).to.equal("monitorModule");
- expect(error.method).to.equal("getMonitorStatsById");
- }
- });
- });
-
- describe("getMonitorById", function () {
- let notificationFindStub;
- let monitorSaveStub;
-
- beforeEach(function () {
- // Create stubs
- notificationFindStub = sinon.stub(Notification, "find");
- monitorSaveStub = sinon.stub().resolves();
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should return monitor with notifications when found", async function () {
- // Arrange
- const monitorId = "123";
- const mockMonitor = {
- _id: monitorId,
- name: "Test Monitor",
- save: monitorSaveStub,
- };
- const mockNotifications = [
- { _id: "notif1", message: "Test notification 1" },
- { _id: "notif2", message: "Test notification 2" },
- ];
-
- monitorFindByIdStub.resolves(mockMonitor);
- notificationFindStub.resolves(mockNotifications);
-
- const result = await getMonitorById(monitorId);
- expect(result._id).to.equal(monitorId);
- expect(result.name).to.equal("Test Monitor");
- expect(monitorFindByIdStub.calledWith(monitorId)).to.be.true;
- expect(notificationFindStub.calledWith({ monitorId })).to.be.true;
- expect(monitorSaveStub.calledOnce).to.be.true;
- });
-
- it("should throw 404 error when monitor not found", async function () {
- // Arrange
- const monitorId = "nonexistent";
- monitorFindByIdStub.resolves(null);
-
- // Act & Assert
- try {
- await getMonitorById(monitorId);
- expect.fail("Should have thrown an error");
- } catch (error) {
- expect(error.message).to.equal(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId));
- expect(error.status).to.equal(404);
- expect(error.service).to.equal("monitorModule");
- expect(error.method).to.equal("getMonitorById");
- }
- });
-
- it("should handle database errors properly", async function () {
- // Arrange
- const monitorId = "123";
- const dbError = new Error("Database connection failed");
- monitorFindByIdStub.rejects(dbError);
-
- // Act & Assert
- try {
- await getMonitorById(monitorId);
- expect.fail("Should have thrown an error");
- } catch (error) {
- expect(error.service).to.equal("monitorModule");
- expect(error.method).to.equal("getMonitorById");
- expect(error.message).to.equal("Database connection failed");
- }
- });
-
- it("should handle notification fetch errors", async function () {
- // Arrange
- const monitorId = "123";
- const mockMonitor = {
- _id: monitorId,
- name: "Test Monitor",
- save: monitorSaveStub,
- };
- const notificationError = new Error("Notification fetch failed");
-
- monitorFindByIdStub.resolves(mockMonitor);
- notificationFindStub.rejects(notificationError);
-
- // Act & Assert
- try {
- await getMonitorById(monitorId);
- expect.fail("Should have thrown an error");
- } catch (error) {
- expect(error.service).to.equal("monitorModule");
- expect(error.method).to.equal("getMonitorById");
- expect(error.message).to.equal("Notification fetch failed");
- }
- });
-
- it("should handle monitor save errors", async function () {
- // Arrange
- const monitorId = "123";
- const mockMonitor = {
- _id: monitorId,
- name: "Test Monitor",
- save: sinon.stub().rejects(new Error("Save failed")),
- };
- const mockNotifications = [];
-
- monitorFindByIdStub.resolves(mockMonitor);
- notificationFindStub.resolves(mockNotifications);
-
- // Act & Assert
- try {
- await getMonitorById(monitorId);
- expect.fail("Should have thrown an error");
- } catch (error) {
- expect(error.service).to.equal("monitorModule");
- expect(error.method).to.equal("getMonitorById");
- expect(error.message).to.equal("Save failed");
- }
- });
- });
-
- describe("getMonitorsAndSummaryByTeamId", function () {
- it("should return monitors and correct summary counts", async function () {
- // Arrange
- const teamId = "team123";
- const type = "http";
- const mockMonitors = [
- { teamId, type, status: true, isActive: true }, // up
- { teamId, type, status: false, isActive: true }, // down
- { teamId, type, status: null, isActive: false }, // paused
- { teamId, type, status: true, isActive: true }, // up
- ];
- monitorFindStub.resolves(mockMonitors);
-
- // Act
- const result = await getMonitorsAndSummaryByTeamId(teamId, type);
-
- // Assert
- expect(result.monitors).to.have.lengthOf(4);
- expect(result.monitorCounts).to.deep.equal({
- up: 2,
- down: 1,
- paused: 1,
- total: 4,
- });
- expect(monitorFindStub.calledOnceWith({ teamId, type })).to.be.true;
- });
-
- it("should return empty results for non-existent team", async function () {
- // Arrange
- monitorFindStub.resolves([]);
-
- // Act
- const result = await getMonitorsAndSummaryByTeamId("nonexistent", "http");
-
- // Assert
- expect(result.monitors).to.have.lengthOf(0);
- expect(result.monitorCounts).to.deep.equal({
- up: 0,
- down: 0,
- paused: 0,
- total: 0,
- });
- });
-
- it("should handle database errors", async function () {
- // Arrange
- const error = new Error("Database error");
- error.service = "MonitorModule";
- error.method = "getMonitorsAndSummaryByTeamId";
- monitorFindStub.rejects(error);
-
- // Act & Assert
- try {
- await getMonitorsAndSummaryByTeamId("team123", "http");
- expect.fail("Should have thrown an error");
- } catch (err) {
- expect(err).to.equal(error);
- expect(err.service).to.equal("monitorModule");
- expect(err.method).to.equal("getMonitorsAndSummaryByTeamId");
- }
- });
- });
-
- describe("getMonitorsByTeamId", function () {
- beforeEach(function () {
- // Chain stubs for Monitor.find().skip().limit().sort()
-
- // Stub for CHECK_MODEL_LOOKUP model find
- checkFindStub.returns({
- sort: sinon.stub().returns({
- limit: sinon.stub().returns([]),
- }),
- });
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should return monitors with basic query parameters", async function () {
- const mockMonitors = [
- { _id: "1", type: "http", toObject: () => ({ _id: "1", type: "http" }) },
- { _id: "2", type: "ping", toObject: () => ({ _id: "2", type: "ping" }) },
- ];
- monitorFindStub.returns({
- skip: sinon.stub().returns({
- limit: sinon.stub().returns({
- sort: sinon.stub().returns(mockMonitors),
- }),
- }),
- });
-
- const req = {
- params: { teamId: "team123" },
- query: {
- type: "http",
- page: 0,
- rowsPerPage: 10,
- field: "name",
- status: false,
- checkOrder: "desc",
- },
- };
-
- monitorCountStub.resolves(2);
-
- const result = await getMonitorsByTeamId(req);
-
- expect(result).to.have.property("monitors");
- expect(result).to.have.property("monitorCount", 2);
- });
-
- it("should return monitors with basic query parameters", async function () {
- const mockMonitors = [
- { _id: "1", type: "http", toObject: () => ({ _id: "1", type: "http" }) },
- { _id: "2", type: "ping", toObject: () => ({ _id: "2", type: "ping" }) },
- ];
- monitorFindStub.returns({
- skip: sinon.stub().returns({
- limit: sinon.stub().returns({
- sort: sinon.stub().returns(mockMonitors),
- }),
- }),
- });
-
- const req = {
- params: { teamId: "team123" },
- query: {
- type: "http",
- page: 0,
- rowsPerPage: 10,
- field: "name",
- status: true,
- checkOrder: "asc",
- },
- };
-
- monitorCountStub.resolves(2);
-
- const result = await getMonitorsByTeamId(req);
-
- expect(result).to.have.property("monitors");
- expect(result).to.have.property("monitorCount", 2);
- });
-
- it("should handle type filter with array input", async function () {
- const req = {
- params: { teamId: "team123" },
- query: {
- type: ["http", "ping"],
- },
- };
-
- monitorFindStub.returns({
- skip: sinon.stub().returns({
- limit: sinon.stub().returns({
- sort: sinon.stub().returns([]),
- }),
- }),
- });
- monitorCountStub.resolves(0);
-
- await getMonitorsByTeamId(req);
-
- expect(Monitor.find.firstCall.args[0]).to.deep.equal({
- teamId: "team123",
- type: { $in: ["http", "ping"] },
- });
- });
-
- it("should handle text search filter", async function () {
- const req = {
- params: { teamId: "team123" },
- query: {
- filter: "search",
- },
- };
-
- monitorFindStub.returns({
- skip: sinon.stub().returns({
- limit: sinon.stub().returns({
- sort: sinon.stub().returns([]),
- }),
- }),
- });
- monitorCountStub.resolves(0);
-
- await getMonitorsByTeamId(req);
-
- expect(Monitor.find.firstCall.args[0]).to.deep.equal({
- teamId: "team123",
- $or: [{ name: { $regex: "search", $options: "i" } }, { url: { $regex: "search", $options: "i" } }],
- });
- });
-
- it("should handle pagination parameters", async function () {
- const req = {
- params: { teamId: "team123" },
- query: {
- page: 2,
- rowsPerPage: 5,
- },
- };
-
- monitorFindStub.returns({
- skip: sinon.stub().returns({
- limit: sinon.stub().returns({
- sort: sinon.stub().returns([]),
- }),
- }),
- });
- monitorCountStub.resolves(0);
-
- const result = await getMonitorsByTeamId(req);
- expect(result).to.deep.equal({
- monitors: [],
- monitorCount: 0,
- });
- });
-
- it("should handle sorting parameters", async function () {
- const req = {
- params: { teamId: "team123" },
- query: {
- field: "name",
- order: "asc",
- },
- };
-
- monitorFindStub.returns({
- skip: sinon.stub().returns({
- limit: sinon.stub().returns({
- sort: sinon.stub().returns([]),
- }),
- }),
- });
- monitorCountStub.resolves(0);
-
- await getMonitorsByTeamId(req);
-
- const result = await getMonitorsByTeamId(req);
- expect(result).to.deep.equal({
- monitors: [],
- monitorCount: 0,
- });
- });
-
- it("should return early when limit is -1", async function () {
- // Arrange
- const req = {
- params: { teamId: "team123" },
- query: {
- limit: "-1",
- },
- };
-
- const mockMonitors = [
- { _id: "1", type: "http" },
- { _id: "2", type: "ping" },
- ];
-
- monitorFindStub.returns({
- skip: sinon.stub().returns({
- limit: sinon.stub().returns({
- sort: sinon.stub().returns(mockMonitors),
- }),
- }),
- });
-
- monitorCountStub.resolves(2);
-
- // Act
- const result = await getMonitorsByTeamId(req);
-
- // Assert
- expect(result).to.deep.equal({
- monitors: mockMonitors,
- monitorCount: 2,
- });
- });
-
- it("should normalize checks when normalize parameter is provided", async function () {
- const req = {
- params: { teamId: "team123" },
- query: { normalize: "true" },
- };
- monitorCountStub.resolves(2);
-
- const mockMonitors = [
- { _id: "1", type: "http", toObject: () => ({ _id: "1", type: "http" }) },
- { _id: "2", type: "ping", toObject: () => ({ _id: "2", type: "ping" }) },
- ];
-
- monitorFindStub.returns({
- skip: sinon.stub().returns({
- limit: sinon.stub().returns({
- sort: sinon.stub().returns(mockMonitors),
- }),
- }),
- });
-
- const result = await getMonitorsByTeamId(req);
- expect(result.monitorCount).to.equal(2);
- expect(result.monitors).to.have.lengthOf(2);
- });
-
- it("should handle database errors", async function () {
- const req = {
- params: { teamId: "team123" },
- query: {},
- };
-
- const error = new Error("Database error");
- monitorFindStub.returns({
- skip: sinon.stub().returns({
- limit: sinon.stub().returns({
- sort: sinon.stub().throws(error),
- }),
- }),
- });
-
- try {
- await getMonitorsByTeamId(req);
- expect.fail("Should have thrown an error");
- } catch (err) {
- expect(err.service).to.equal("monitorModule");
- expect(err.method).to.equal("getMonitorsByTeamId");
- expect(err.message).to.equal("Database error");
- }
- });
- });
-
- describe("createMonitor", function () {
- it("should create a monitor without notifications", async function () {
- let monitorSaveStub = sinon.stub(Monitor.prototype, "save").resolves();
-
- const req = {
- body: {
- name: "Test Monitor",
- url: "http://test.com",
- type: "http",
- notifications: ["someNotification"],
- },
- };
-
- const expectedMonitor = {
- name: "Test Monitor",
- url: "http://test.com",
- type: "http",
- notifications: undefined,
- save: monitorSaveStub,
- };
-
- const result = await createMonitor(req);
- expect(result.name).to.equal(expectedMonitor.name);
- expect(result.url).to.equal(expectedMonitor.url);
- });
-
- it("should handle database errors", async function () {
- const req = {
- body: {
- name: "Test Monitor",
- },
- };
-
- try {
- await createMonitor(req);
- expect.fail("Should have thrown an error");
- } catch (err) {
- expect(err.service).to.equal("monitorModule");
- expect(err.method).to.equal("createMonitor");
- }
- });
- });
-
- describe("deleteMonitor", function () {
- it("should delete a monitor successfully", async function () {
- const monitorId = "123456789";
- const mockMonitor = {
- _id: monitorId,
- name: "Test Monitor",
- url: "http://test.com",
- };
-
- const req = {
- params: { monitorId },
- };
-
- monitorFindByIdAndDeleteStub.resolves(mockMonitor);
-
- const result = await deleteMonitor(req);
-
- expect(result).to.deep.equal(mockMonitor);
- sinon.assert.calledWith(monitorFindByIdAndDeleteStub, monitorId);
- });
-
- it("should throw error when monitor not found", async function () {
- const monitorId = "nonexistent123";
- const req = {
- params: { monitorId },
- };
-
- monitorFindByIdAndDeleteStub.resolves(null);
-
- try {
- await deleteMonitor(req);
- expect.fail("Should have thrown an error");
- } catch (err) {
- expect(err.message).to.equal(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId));
- expect(err.service).to.equal("monitorModule");
- expect(err.method).to.equal("deleteMonitor");
- }
- });
-
- it("should handle database errors", async function () {
- const monitorId = "123456789";
- const req = {
- params: { monitorId },
- };
-
- const dbError = new Error("Database connection error");
- monitorFindByIdAndDeleteStub.rejects(dbError);
-
- try {
- await deleteMonitor(req);
- expect.fail("Should have thrown an error");
- } catch (err) {
- expect(err.message).to.equal("Database connection error");
- expect(err.service).to.equal("monitorModule");
- expect(err.method).to.equal("deleteMonitor");
- }
- });
- });
-
- describe("deleteAllMonitors", function () {
- it("should delete all monitors for a team successfully", async function () {
- const teamId = "team123";
- const mockMonitors = [
- { _id: "1", name: "Monitor 1", teamId },
- { _id: "2", name: "Monitor 2", teamId },
- ];
-
- monitorFindStub.resolves(mockMonitors);
- monitorDeleteManyStub.resolves({ deletedCount: 2 });
-
- const result = await deleteAllMonitors(teamId);
-
- expect(result).to.deep.equal({
- monitors: mockMonitors,
- deletedCount: 2,
- });
- sinon.assert.calledWith(monitorFindStub, { teamId });
- sinon.assert.calledWith(monitorDeleteManyStub, { teamId });
- });
-
- it("should return empty array when no monitors found", async function () {
- const teamId = "emptyTeam";
-
- monitorFindStub.resolves([]);
- monitorDeleteManyStub.resolves({ deletedCount: 0 });
-
- const result = await deleteAllMonitors(teamId);
-
- expect(result).to.deep.equal({
- monitors: [],
- deletedCount: 0,
- });
- sinon.assert.calledWith(monitorFindStub, { teamId });
- sinon.assert.calledWith(monitorDeleteManyStub, { teamId });
- });
-
- it("should handle database errors", async function () {
- const teamId = "team123";
- const dbError = new Error("Database connection error");
- monitorFindStub.rejects(dbError);
-
- try {
- await deleteAllMonitors(teamId);
- } catch (err) {
- expect(err.message).to.equal("Database connection error");
- expect(err.service).to.equal("monitorModule");
- expect(err.method).to.equal("deleteAllMonitors");
- }
- });
-
- it("should handle deleteMany errors", async function () {
- const teamId = "team123";
- monitorFindStub.resolves([{ _id: "1", name: "Monitor 1" }]);
- monitorDeleteManyStub.rejects(new Error("Delete operation failed"));
-
- try {
- await deleteAllMonitors(teamId);
- } catch (err) {
- expect(err.message).to.equal("Delete operation failed");
- expect(err.service).to.equal("monitorModule");
- expect(err.method).to.equal("deleteAllMonitors");
- }
- });
- });
-
- describe("deleteMonitorsByUserId", function () {
- beforeEach(function () {});
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should delete all monitors for a user successfully", async function () {
- // Arrange
- const userId = "user123";
- const mockResult = {
- deletedCount: 3,
- acknowledged: true,
- };
-
- monitorDeleteManyStub.resolves(mockResult);
-
- // Act
- const result = await deleteMonitorsByUserId(userId);
-
- // Assert
- expect(result).to.deep.equal(mockResult);
- sinon.assert.calledWith(monitorDeleteManyStub, { userId: userId });
- });
-
- it("should return zero deletedCount when no monitors found", async function () {
- // Arrange
- const userId = "nonexistentUser";
- const mockResult = {
- deletedCount: 0,
- acknowledged: true,
- };
-
- monitorDeleteManyStub.resolves(mockResult);
-
- // Act
- const result = await deleteMonitorsByUserId(userId);
-
- // Assert
- expect(result.deletedCount).to.equal(0);
- sinon.assert.calledWith(monitorDeleteManyStub, { userId: userId });
- });
-
- it("should handle database errors", async function () {
- // Arrange
- const userId = "user123";
- const dbError = new Error("Database connection error");
- monitorDeleteManyStub.rejects(dbError);
-
- // Act & Assert
- try {
- await deleteMonitorsByUserId(userId);
- expect.fail("Should have thrown an error");
- } catch (err) {
- expect(err.message).to.equal("Database connection error");
- expect(err.service).to.equal("monitorModule");
- expect(err.method).to.equal("deleteMonitorsByUserId");
- }
- });
- });
-
- describe("editMonitor", function () {
- it("should edit a monitor successfully", async function () {
- // Arrange
- const candidateId = "monitor123";
- const candidateMonitor = {
- name: "Updated Monitor",
- url: "http://updated.com",
- type: "http",
- notifications: ["someNotification"],
- };
-
- const expectedUpdateData = {
- name: "Updated Monitor",
- url: "http://updated.com",
- type: "http",
- notifications: undefined,
- };
-
- const mockUpdatedMonitor = {
- _id: candidateId,
- ...expectedUpdateData,
- };
-
- monitorFindByIdAndUpdateStub.resolves(mockUpdatedMonitor);
-
- // Act
- const result = await editMonitor(candidateId, candidateMonitor);
-
- // Assert
- expect(result).to.deep.equal(mockUpdatedMonitor);
- sinon.assert.calledWith(monitorFindByIdAndUpdateStub, candidateId, expectedUpdateData, {
- new: true,
- });
- });
-
- it("should return null when monitor not found", async function () {
- // Arrange
- const candidateId = "nonexistent123";
- const candidateMonitor = {
- name: "Updated Monitor",
- };
-
- monitorFindByIdAndUpdateStub.resolves(null);
-
- // Act
- const result = await editMonitor(candidateId, candidateMonitor);
-
- // Assert
- expect(result).to.be.null;
- sinon.assert.calledWith(monitorFindByIdAndUpdateStub, candidateId, { name: "Updated Monitor", notifications: undefined }, { new: true });
- });
-
- it("should remove notifications from update data", async function () {
- // Arrange
- const candidateId = "monitor123";
- const candidateMonitor = {
- name: "Updated Monitor",
- notifications: ["notification1", "notification2"],
- };
-
- const expectedUpdateData = {
- name: "Updated Monitor",
- notifications: undefined,
- };
-
- monitorFindByIdAndUpdateStub.resolves({
- _id: candidateId,
- ...expectedUpdateData,
- });
-
- // Act
- await editMonitor(candidateId, candidateMonitor);
-
- // Assert
- sinon.assert.calledWith(monitorFindByIdAndUpdateStub, candidateId, expectedUpdateData, {
- new: true,
- });
- });
-
- it("should handle database errors", async function () {
- // Arrange
- const candidateId = "monitor123";
- const candidateMonitor = {
- name: "Updated Monitor",
- };
-
- const dbError = new Error("Database connection error");
- monitorFindByIdAndUpdateStub.rejects(dbError);
-
- // Act & Assert
- try {
- await editMonitor(candidateId, candidateMonitor);
- expect.fail("Should have thrown an error");
- } catch (err) {
- expect(err.message).to.equal("Database connection error");
- expect(err.service).to.equal("monitorModule");
- expect(err.method).to.equal("editMonitor");
- }
- });
- });
-
- describe("addDemoMonitors", function () {
- it("should add demo monitors successfully", async function () {
- // Arrange
- const userId = "user123";
- const teamId = "team123";
- monitorInsertManyStub.resolves([{ _id: "123" }]);
- const result = await addDemoMonitors(userId, teamId);
- expect(result).to.deep.equal([{ _id: "123" }]);
- });
-
- it("should handle database errors", async function () {
- const userId = "user123";
- const teamId = "team123";
-
- const dbError = new Error("Database connection error");
- monitorInsertManyStub.rejects(dbError);
-
- try {
- await addDemoMonitors(userId, teamId);
- } catch (err) {
- expect(err.message).to.equal("Database connection error");
- expect(err.service).to.equal("monitorModule");
- expect(err.method).to.equal("addDemoMonitors");
- }
- });
- });
-});
diff --git a/server/tests/db/notificationModule.test.js b/server/tests/db/notificationModule.test.js
deleted file mode 100755
index fbb97f33a..000000000
--- a/server/tests/db/notificationModule.test.js
+++ /dev/null
@@ -1,76 +0,0 @@
-import sinon from "sinon";
-import Notification from "../../db/models/Notification.js";
-import { createNotification, getNotificationsByMonitorId, deleteNotificationsByMonitorId } from "../../db/mongo/modules/notificationModule.js";
-
-describe("notificationModule", function () {
- const mockNotification = {
- monitorId: "123",
- };
- const mockNotifications = [mockNotification];
- let notificationSaveStub, notificationFindStub, notificationDeleteManyStub;
-
- beforeEach(function () {
- notificationSaveStub = sinon.stub(Notification.prototype, "save").resolves();
- notificationFindStub = sinon.stub(Notification, "find").resolves();
- notificationDeleteManyStub = sinon.stub(Notification, "deleteMany").resolves();
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- describe("createNotification", function () {
- it("should create a new notification", async function () {
- const notificationData = { _id: "123", name: "test" };
- notificationSaveStub.resolves(notificationData);
- const res = await createNotification(notificationData);
- expect(res).to.deep.equal(notificationData);
- });
-
- it("should handle an error", async function () {
- const err = new Error("test error");
- notificationSaveStub.rejects(err);
- try {
- await createNotification(mockNotification);
- } catch (error) {
- expect(error).to.deep.equal(err);
- }
- });
- });
-
- describe("getNotificationsByMonitorId", function () {
- it("should return notifications by monitor ID", async function () {
- notificationFindStub.resolves(mockNotifications);
- const res = await getNotificationsByMonitorId(mockNotification.monitorId);
- expect(res).to.deep.equal(mockNotifications);
- });
-
- it("should handle an error", async function () {
- const err = new Error("test error");
- notificationFindStub.rejects(err);
- try {
- await getNotificationsByMonitorId(mockNotification.monitorId);
- } catch (error) {
- expect(error).to.deep.equal(err);
- }
- });
- });
-
- describe("deleteNotificationsByMonitorId", function () {
- it("should delete notifications by monitor ID", async function () {
- notificationDeleteManyStub.resolves({ deletedCount: mockNotifications.length });
- const res = await deleteNotificationsByMonitorId(mockNotification.monitorId);
- expect(res).to.deep.equal(mockNotifications.length);
- });
-
- it("should handle an error", async function () {
- const err = new Error("test error");
- notificationDeleteManyStub.rejects(err);
- try {
- await deleteNotificationsByMonitorId(mockNotification.monitorId);
- } catch (error) {
- expect(error).to.deep.equal(err);
- }
- });
- });
-});
diff --git a/server/tests/db/pageSpeedCheckModule.test.js b/server/tests/db/pageSpeedCheckModule.test.js
deleted file mode 100755
index 45135135d..000000000
--- a/server/tests/db/pageSpeedCheckModule.test.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import sinon from "sinon";
-import PageSpeedCheck from "../../db/models/PageSpeedCheck.js";
-import { createPageSpeedCheck, deletePageSpeedChecksByMonitorId } from "../../db/mongo/modules/pageSpeedCheckModule.js";
-
-const mockPageSpeedCheck = {
- monitorId: "monitorId",
- bestPractices: 1,
- seo: 1,
- performance: 1,
-};
-
-const mockDeletedResult = { deletedCount: 1 };
-
-describe("pageSpeedCheckModule", function () {
- let pageSpeedCheckSaveStub, pageSpeedCheckDeleteManyStub;
-
- beforeEach(function () {
- pageSpeedCheckSaveStub = sinon.stub(PageSpeedCheck.prototype, "save");
- pageSpeedCheckDeleteManyStub = sinon.stub(PageSpeedCheck, "deleteMany");
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- describe("createPageSpeedCheck", function () {
- it("should return a page speed check", async function () {
- pageSpeedCheckSaveStub.resolves(mockPageSpeedCheck);
- const pageSpeedCheck = await createPageSpeedCheck(mockPageSpeedCheck);
- expect(pageSpeedCheck).to.deep.equal(mockPageSpeedCheck);
- });
-
- it("should handle an error", async function () {
- const err = new Error("test error");
- pageSpeedCheckSaveStub.rejects(err);
- try {
- await expect(createPageSpeedCheck(mockPageSpeedCheck));
- } catch (error) {
- expect(error).to.exist;
- expect(error).to.deep.equal(err);
- }
- });
- });
-
- describe("deletePageSpeedChecksByMonitorId", function () {
- it("should return the number of deleted checks", async function () {
- pageSpeedCheckDeleteManyStub.resolves(mockDeletedResult);
- const result = await deletePageSpeedChecksByMonitorId("monitorId");
- expect(result).to.deep.equal(mockDeletedResult.deletedCount);
- });
-
- it("should handle an error", async function () {
- const err = new Error("test error");
- pageSpeedCheckDeleteManyStub.rejects(err);
- try {
- await expect(deletePageSpeedChecksByMonitorId("monitorId"));
- } catch (error) {
- expect(error).to.exist;
- expect(error).to.deep.equal(err);
- }
- });
- });
-});
diff --git a/server/tests/db/recoveryModule.test.js b/server/tests/db/recoveryModule.test.js
deleted file mode 100755
index 88afa257a..000000000
--- a/server/tests/db/recoveryModule.test.js
+++ /dev/null
@@ -1,168 +0,0 @@
-import sinon from "sinon";
-import RecoveryToken from "../../db/models/RecoveryToken.js";
-import User from "../../db/models/User.js";
-import { requestRecoveryToken, validateRecoveryToken, resetPassword } from "../../db/mongo/modules/recoveryModule.js";
-import { errorMessages } from "../../utils/messages.js";
-
-const mockRecoveryToken = {
- email: "test@test.com",
- token: "1234567890",
-};
-
-const mockUser = {
- email: "test@test.com",
- password: "oldPassword",
-};
-
-const mockUserWithoutPassword = {
- email: "test@test.com",
-};
-
-// Create a query builder that logs
-const createQueryChain = (finalResult, comparePasswordResult = false) => ({
- select: () => ({
- select: async () => {
- if (finalResult === mockUser) {
- // Return a new object with all required methods
- return {
- email: "test@test.com",
- password: "oldPassword",
- comparePassword: sinon.stub().resolves(comparePasswordResult),
- save: sinon.stub().resolves(),
- };
- }
- return finalResult;
- },
- }),
- // Add methods to the top level too
- comparePassword: sinon.stub().resolves(comparePasswordResult),
- save: sinon.stub().resolves(),
-});
-
-describe("recoveryModule", function () {
- let deleteManyStub, saveStub, findOneStub, userCompareStub, userSaveStub, userFindOneStub;
- let req, res;
-
- beforeEach(function () {
- req = {
- body: { email: "test@test.com" },
- };
- deleteManyStub = sinon.stub(RecoveryToken, "deleteMany");
- saveStub = sinon.stub(RecoveryToken.prototype, "save");
- findOneStub = sinon.stub(RecoveryToken, "findOne");
- userCompareStub = sinon.stub(User.prototype, "comparePassword");
- userSaveStub = sinon.stub(User.prototype, "save");
- userFindOneStub = sinon.stub().resolves();
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- describe("requestRecoveryToken", function () {
- it("should return a recovery token", async function () {
- deleteManyStub.resolves();
- saveStub.resolves(mockRecoveryToken);
- const result = await requestRecoveryToken(req, res);
- expect(result.email).to.equal(mockRecoveryToken.email);
- });
-
- it("should handle an error", async function () {
- const err = new Error("Test error");
- deleteManyStub.rejects(err);
- try {
- await requestRecoveryToken(req, res);
- } catch (error) {
- expect(error).to.exist;
- expect(error).to.deep.equal(err);
- }
- });
- });
-
- describe("validateRecoveryToken", function () {
- it("should return a recovery token if found", async function () {
- findOneStub.resolves(mockRecoveryToken);
- const result = await validateRecoveryToken(req, res);
- expect(result).to.deep.equal(mockRecoveryToken);
- });
-
- it("should thrown an error if a token is not found", async function () {
- findOneStub.resolves(null);
- try {
- await validateRecoveryToken(req, res);
- } catch (error) {
- expect(error).to.exist;
- expect(error.message).to.equal(errorMessages.DB_TOKEN_NOT_FOUND);
- }
- });
-
- it("should handle DB errors", async function () {
- const err = new Error("Test error");
- findOneStub.rejects(err);
- try {
- await validateRecoveryToken(req, res);
- } catch (error) {
- expect(error).to.exist;
- expect(error).to.deep.equal(err);
- }
- });
- });
-
- describe("resetPassword", function () {
- beforeEach(function () {
- req.body = {
- password: "test",
- newPassword: "test1",
- };
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should thrown an error if a recovery token is not found", async function () {
- findOneStub.resolves(null);
- try {
- await resetPassword(req, res);
- } catch (error) {
- expect(error).to.exist;
- expect(error.message).to.equal(errorMessages.DB_TOKEN_NOT_FOUND);
- }
- });
-
- it("should throw an error if a user is not found", async function () {
- findOneStub.resolves(mockRecoveryToken);
- userFindOneStub = sinon.stub(User, "findOne").resolves(null);
- try {
- await resetPassword(req, res);
- } catch (error) {
- expect(error).to.exist;
- expect(error.message).to.equal(errorMessages.DB_USER_NOT_FOUND);
- }
- });
-
- it("should throw an error if the passwords match", async function () {
- findOneStub.resolves(mockRecoveryToken);
- saveStub.resolves();
- userFindOneStub = sinon.stub(User, "findOne").returns(createQueryChain(mockUser, true));
- try {
- await resetPassword(req, res);
- } catch (error) {
- expect(error).to.exist;
- expect(error.message).to.equal(errorMessages.DB_RESET_PASSWORD_BAD_MATCH);
- }
- });
-
- it("should return a user without password if successful", async function () {
- findOneStub.resolves(mockRecoveryToken);
- saveStub.resolves();
- userFindOneStub = sinon
- .stub(User, "findOne")
- .returns(createQueryChain(mockUser)) // First call will resolve to mockUser
- .onSecondCall()
- .returns(createQueryChain(mockUserWithoutPassword));
- const result = await resetPassword(req, res);
- expect(result).to.deep.equal(mockUserWithoutPassword);
- });
- });
-});
diff --git a/server/tests/db/settingsModule.test.js b/server/tests/db/settingsModule.test.js
deleted file mode 100755
index 9d1a80c3e..000000000
--- a/server/tests/db/settingsModule.test.js
+++ /dev/null
@@ -1,56 +0,0 @@
-import sinon from "sinon";
-import { getAppSettings, updateAppSettings } from "../../db/mongo/modules/settingsModule.js";
-import AppSettings from "../../db/models/AppSettings.js";
-
-const mockAppSettings = {
- appName: "Test App",
-};
-
-describe("SettingsModule", function () {
- let appSettingsFindOneStub, appSettingsFindOneAndUpdateStub;
-
- beforeEach(function () {
- appSettingsFindOneStub = sinon.stub(AppSettings, "findOne");
- appSettingsFindOneAndUpdateStub = sinon.stub(AppSettings, "findOneAndUpdate");
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- describe("getAppSettings", function () {
- it("should return app settings", async function () {
- appSettingsFindOneStub.resolves(mockAppSettings);
- const result = await getAppSettings();
- expect(result).to.deep.equal(mockAppSettings);
- });
-
- it("should handle an error", async function () {
- const err = new Error("Test error");
- appSettingsFindOneStub.throws(err);
- try {
- await getAppSettings();
- } catch (error) {
- expect(error).to.deep.equal(err);
- }
- });
- });
-
- describe("updateAppSettings", function () {
- it("should update app settings", async function () {
- appSettingsFindOneAndUpdateStub.resolves(mockAppSettings);
- const result = await updateAppSettings(mockAppSettings);
- expect(result).to.deep.equal(mockAppSettings);
- });
-
- it("should handle an error", async function () {
- const err = new Error("Test error");
- appSettingsFindOneAndUpdateStub.throws(err);
- try {
- await updateAppSettings(mockAppSettings);
- } catch (error) {
- expect(error).to.deep.equal(err);
- }
- });
- });
-});
diff --git a/server/tests/db/statusPageModule.test.js b/server/tests/db/statusPageModule.test.js
deleted file mode 100755
index 54e1a287f..000000000
--- a/server/tests/db/statusPageModule.test.js
+++ /dev/null
@@ -1,70 +0,0 @@
-import sinon from "sinon";
-import { createStatusPage, getStatusPageByUrl } from "../../db/mongo/modules/statusPageModule.js";
-import StatusPage from "../../db/models/StatusPage.js";
-import { errorMessages } from "../../utils/messages.js";
-
-describe("statusPageModule", function () {
- let statusPageFindOneStub, statusPageSaveStub, statusPageFindStub;
-
- beforeEach(function () {
- statusPageSaveStub = sinon.stub(StatusPage.prototype, "save");
- statusPageFindOneStub = sinon.stub(StatusPage, "findOne");
- statusPageFindStub = sinon.stub(StatusPage, "find");
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- describe("createStatusPage", function () {
- it("should throw an error if a non-unique url is provided", async function () {
- statusPageFindOneStub.resolves(true);
- try {
- await createStatusPage({ url: "test" });
- } catch (error) {
- expect(error.status).to.equal(400);
- expect(error.message).to.equal(errorMessages.STATUS_PAGE_URL_NOT_UNIQUE);
- }
- });
-
- it("should handle duplicate URL errors", async function () {
- const err = new Error("test");
- err.code = 11000;
- statusPageSaveStub.rejects(err);
- try {
- await createStatusPage({ url: "test" });
- } catch (error) {
- expect(error).to.deep.equal(err);
- }
- });
-
- it("should return a status page if a unique url is provided", async function () {
- statusPageFindOneStub.resolves(null);
- statusPageFindStub.resolves([]);
- const mockStatusPage = { url: "test" };
- const statusPage = await createStatusPage(mockStatusPage);
- expect(statusPage).to.exist;
- expect(statusPage.url).to.equal(mockStatusPage.url);
- });
- });
-
- describe("getStatusPageByUrl", function () {
- it("should throw an error if a status page is not found", async function () {
- statusPageFindOneStub.resolves(null);
- try {
- await getStatusPageByUrl("test");
- } catch (error) {
- expect(error.status).to.equal(404);
- expect(error.message).to.equal(errorMessages.STATUS_PAGE_NOT_FOUND);
- }
- });
-
- it("should return a status page if a status page is found", async function () {
- const mockStatusPage = { url: "test" };
- statusPageFindOneStub.resolves(mockStatusPage);
- const statusPage = await getStatusPageByUrl(mockStatusPage.url);
- expect(statusPage).to.exist;
- expect(statusPage).to.deep.equal(mockStatusPage);
- });
- });
-});
diff --git a/server/tests/db/userModule.test.js b/server/tests/db/userModule.test.js
deleted file mode 100755
index d0f5ecb3e..000000000
--- a/server/tests/db/userModule.test.js
+++ /dev/null
@@ -1,294 +0,0 @@
-import sinon from "sinon";
-import UserModel from "../../db/models/User.js";
-import TeamModel from "../../db/models/Team.js";
-import {
- insertUser,
- getUserByEmail,
- updateUser,
- deleteUser,
- deleteTeam,
- deleteAllOtherUsers,
- getAllUsers,
- logoutUser,
-} from "../../db/mongo/modules/userModule.js";
-import { errorMessages } from "../../utils/messages.js";
-
-const mockUser = {
- email: "test@test.com",
- password: "password",
- role: ["user"],
-};
-const mockSuperUser = {
- email: "test@test.com",
- password: "password",
- role: ["superadmin"],
-};
-const imageFile = {
- image: 1,
-};
-
-describe("userModule", function () {
- let teamSaveStub,
- teamFindByIdAndDeleteStub,
- userSaveStub,
- userFindStub,
- userFindOneStub,
- userFindByIdAndUpdateStub,
- userFindByIdAndDeleteStub,
- userDeleteManyStub,
- userUpdateOneStub,
- generateAvatarImageStub,
- parseBooleanStub;
-
- beforeEach(function () {
- teamSaveStub = sinon.stub(TeamModel.prototype, "save");
- teamFindByIdAndDeleteStub = sinon.stub(TeamModel, "findByIdAndDelete");
- userSaveStub = sinon.stub(UserModel.prototype, "save");
- userFindStub = sinon.stub(UserModel, "find");
- userFindOneStub = sinon.stub(UserModel, "findOne");
- userFindByIdAndUpdateStub = sinon.stub(UserModel, "findByIdAndUpdate");
- userFindByIdAndDeleteStub = sinon.stub(UserModel, "findByIdAndDelete");
- userDeleteManyStub = sinon.stub(UserModel, "deleteMany");
- userUpdateOneStub = sinon.stub(UserModel, "updateOne");
- generateAvatarImageStub = sinon.stub().resolves({ image: 2 });
- parseBooleanStub = sinon.stub().returns(true);
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- describe("insertUser", function () {
- it("should insert a regular user", async function () {
- userSaveStub.resolves(mockUser);
- userFindOneStub.returns({
- select: sinon.stub().returns({
- select: sinon.stub().resolves(mockUser),
- }),
- });
- const result = await insertUser(mockUser, imageFile, generateAvatarImageStub);
- expect(result).to.deep.equal(mockUser);
- });
-
- it("should insert a superadmin user", async function () {
- userSaveStub.resolves(mockSuperUser);
- userFindOneStub.returns({
- select: sinon.stub().returns({
- select: sinon.stub().resolves(mockSuperUser),
- }),
- });
- const result = await insertUser(mockSuperUser, imageFile, generateAvatarImageStub);
- expect(result).to.deep.equal(mockSuperUser);
- });
-
- it("should handle an error", async function () {
- const err = new Error("test error");
- userSaveStub.rejects(err);
- try {
- await insertUser(mockUser, imageFile, generateAvatarImageStub);
- } catch (error) {
- expect(error).to.exist;
- expect(error).to.deep.equal(err);
- }
- });
-
- it("should handle a duplicate key error", async function () {
- const err = new Error("test error");
- err.code = 11000;
- userSaveStub.rejects(err);
- try {
- await insertUser(mockUser, imageFile, generateAvatarImageStub);
- } catch (error) {
- expect(error).to.exist;
- expect(error).to.deep.equal(err);
- }
- });
- });
-
- describe("getUserByEmail", function () {
- it("should return a user", async function () {
- userFindOneStub.returns({
- select: sinon.stub().resolves(mockUser),
- });
- const result = await getUserByEmail(mockUser.email);
- expect(result).to.deep.equal(mockUser);
- });
- });
-
- describe("getUserByEmail", function () {
- it("throw an error if a user is not found", async function () {
- userFindOneStub.returns({
- select: sinon.stub().resolves(null),
- });
- try {
- await getUserByEmail(mockUser.email);
- } catch (error) {
- expect(error.message).to.equal(errorMessages.DB_USER_NOT_FOUND);
- }
- });
- });
-
- describe("updateUser", function () {
- let req, res;
-
- beforeEach(function () {
- req = {
- params: {
- userId: "testId",
- },
- body: {
- deleteProfileImage: "false",
- email: "test@test.com",
- },
- file: {
- buffer: "test",
- mimetype: "test",
- },
- };
- res = {};
- });
-
- afterEach(function () {});
-
- it("should update a user", async function () {
- parseBooleanStub.returns(false);
- userFindByIdAndUpdateStub.returns({
- select: sinon.stub().returns({
- select: sinon.stub().resolves(mockUser),
- }),
- });
- const result = await updateUser(req, res, parseBooleanStub, generateAvatarImageStub);
- expect(result).to.deep.equal(mockUser);
- });
-
- it("should delete a user profile image", async function () {
- req.body.deleteProfileImage = "true";
- userFindByIdAndUpdateStub.returns({
- select: sinon.stub().returns({
- select: sinon.stub().resolves(mockUser),
- }),
- });
- const result = await updateUser(req, res, parseBooleanStub, generateAvatarImageStub);
- expect(result).to.deep.equal(mockUser);
- });
-
- it("should handle an error", async function () {
- const err = new Error("test error");
- userFindByIdAndUpdateStub.throws(err);
- try {
- await updateUser(req, res, parseBooleanStub, generateAvatarImageStub);
- } catch (error) {
- expect(error).to.exist;
- expect(error).to.deep.equal(err);
- }
- });
- });
-
- describe("deleteUser", function () {
- it("should return a deleted user", async function () {
- userFindByIdAndDeleteStub.resolves(mockUser);
- const result = await deleteUser("testId");
- expect(result).to.deep.equal(mockUser);
- });
-
- it("should throw an error if a user is not found", async function () {
- try {
- await deleteUser("testId");
- } catch (error) {
- expect(error).to.exist;
- expect(error.message).to.equal(errorMessages.DB_USER_NOT_FOUND);
- }
- });
-
- it("should handle an error", async function () {
- const err = new Error("test error");
- userFindByIdAndDeleteStub.throws(err);
- try {
- await deleteUser("testId");
- } catch (error) {
- expect(error).to.exist;
- expect(error).to.deep.equal(err);
- }
- });
- });
-
- describe("deleteTeam", function () {
- it("should return true if team deleted", async function () {
- teamFindByIdAndDeleteStub.resolves();
- const result = await deleteTeam("testId");
- expect(result).to.equal(true);
- });
-
- it("should handle an error", async function () {
- const err = new Error("test error");
- teamFindByIdAndDeleteStub.throws(err);
- try {
- await deleteTeam("testId");
- } catch (error) {
- expect(error).to.exist;
- expect(error).to.deep.equal(err);
- }
- });
- });
-
- describe("deleteAllOtherUsers", function () {
- it("should return true if all other users deleted", async function () {
- userDeleteManyStub.resolves(true);
- const result = await deleteAllOtherUsers();
- expect(result).to.equal(true);
- });
-
- it("should handle an error", async function () {
- const err = new Error("test error");
- userDeleteManyStub.throws(err);
- try {
- await deleteAllOtherUsers();
- } catch (error) {
- expect(error).to.exist;
- expect(error).to.deep.equal(err);
- }
- });
- });
-
- describe("getAllUsers", function () {
- it("should return all users", async function () {
- userFindStub.returns({
- select: sinon.stub().returns({
- select: sinon.stub().resolves([mockUser]),
- }),
- });
- const result = await getAllUsers();
- expect(result).to.deep.equal([mockUser]);
- });
-
- it("should handle an error", async function () {
- const err = new Error("test error");
- userFindStub.throws(err);
- try {
- await getAllUsers();
- } catch (error) {
- expect(error).to.exist;
- expect(error).to.deep.equal(err);
- }
- });
- });
-
- describe("logoutUser", function () {
- it("should return true if user logged out", async function () {
- userUpdateOneStub.resolves(true);
- const result = await logoutUser("testId");
- expect(result).to.equal(true);
- });
-
- it("should handle an error", async function () {
- const err = new Error("test error");
- userUpdateOneStub.throws(err);
- try {
- await logoutUser("testId");
- } catch (error) {
- expect(error).to.exist;
- expect(error).to.deep.equal(err);
- }
- });
- });
-});
diff --git a/server/tests/services/emailService.test.js b/server/tests/services/emailService.test.js
deleted file mode 100755
index cca86896a..000000000
--- a/server/tests/services/emailService.test.js
+++ /dev/null
@@ -1,168 +0,0 @@
-import sinon from "sinon";
-import EmailService from "../../service/emailService.js";
-
-describe("EmailService - Constructor", function () {
- let settingsServiceMock;
- let fsMock;
- let pathMock;
- let compileMock;
- let mjml2htmlMock;
- let nodemailerMock;
- let loggerMock;
-
- beforeEach(function () {
- settingsServiceMock = {
- getSettings: sinon.stub().returns({
- systemEmailHost: "smtp.example.com",
- systemEmailPort: 465,
- systemEmailAddress: "test@example.com",
- systemEmailPassword: "password",
- }),
- };
-
- fsMock = {
- readFileSync: sinon.stub().returns(""),
- };
-
- pathMock = {
- join: sinon.stub().callsFake((...args) => args.join("/")),
- };
-
- compileMock = sinon.stub().returns(() => "");
-
- mjml2htmlMock = sinon.stub().returns({ html: "" });
-
- nodemailerMock = {
- createTransport: sinon.stub().returns({
- sendMail: sinon.stub().resolves({ messageId: "12345" }),
- }),
- };
-
- loggerMock = {
- error: sinon.stub(),
- };
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should initialize template loaders and email transporter", function () {
- const emailService = new EmailService(settingsServiceMock, fsMock, pathMock, compileMock, mjml2htmlMock, nodemailerMock, loggerMock);
-
- // Verify that the settingsService is assigned correctly
- expect(emailService.settingsService).to.equal(settingsServiceMock);
-
- // Verify that the template loaders are initialized
- expect(emailService.templateLookup.welcomeEmailTemplate).to.be.a("function");
- expect(emailService.templateLookup.employeeActivationTemplate).to.be.a("function");
-
- // Verify that the email transporter is initialized
- expect(nodemailerMock.createTransport.calledOnce).to.be.true;
- const emailConfig = nodemailerMock.createTransport.getCall(0).args[0];
- expect(emailConfig).to.deep.equal({
- host: "smtp.example.com",
- port: 465,
- secure: true,
- auth: {
- user: "test@example.com",
- pass: "password",
- },
- });
- });
-
- it("should have undefined templates if FS fails", function () {
- fsMock = {
- readFileSync: sinon.stub().throws(new Error("File read error")),
- };
- const emailService = new EmailService(settingsServiceMock, fsMock, pathMock, compileMock, mjml2htmlMock, nodemailerMock, loggerMock);
- expect(loggerMock.error.called).to.be.true;
- expect(loggerMock.error.firstCall.args[0].message).to.equal("File read error");
- });
-});
-
-describe("EmailService - buildAndSendEmail", function () {
- let settingsServiceMock;
- let fsMock;
- let pathMock;
- let compileMock;
- let mjml2htmlMock;
- let nodemailerMock;
- let loggerMock;
- let emailService;
-
- beforeEach(function () {
- settingsServiceMock = {
- getSettings: sinon.stub().returns({
- systemEmailHost: "smtp.example.com",
- systemEmailPort: 465,
- systemEmailAddress: "test@example.com",
- systemEmailPassword: "password",
- }),
- };
-
- fsMock = {
- readFileSync: sinon.stub().returns(""),
- };
-
- pathMock = {
- join: sinon.stub().callsFake((...args) => args.join("/")),
- };
-
- compileMock = sinon.stub().returns(() => "");
-
- mjml2htmlMock = sinon.stub().returns({ html: "" });
-
- nodemailerMock = {
- createTransport: sinon.stub().returns({
- sendMail: sinon.stub().resolves({ messageId: "12345" }),
- }),
- };
-
- loggerMock = {
- error: sinon.stub(),
- };
-
- emailService = new EmailService(settingsServiceMock, fsMock, pathMock, compileMock, mjml2htmlMock, nodemailerMock, loggerMock);
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should build and send email successfully", async function () {
- const messageId = await emailService.buildAndSendEmail("welcomeEmailTemplate", {}, "recipient@example.com", "Welcome");
-
- expect(messageId).to.equal("12345");
- expect(nodemailerMock.createTransport().sendMail.calledOnce).to.be.true;
- });
-
- it("should log error if building HTML fails", async function () {
- mjml2htmlMock.throws(new Error("MJML error"));
-
- const messageId = await emailService.buildAndSendEmail("welcomeEmailTemplate", {}, "recipient@example.com", "Welcome");
- expect(loggerMock.error.calledOnce).to.be.true;
- expect(loggerMock.error.getCall(0).args[0].message).to.equal("MJML error");
- });
-
- it("should log error if sending email fails", async function () {
- nodemailerMock.createTransport().sendMail.rejects(new Error("SMTP error"));
- await emailService.buildAndSendEmail("welcomeEmailTemplate", {}, "recipient@example.com", "Welcome");
- expect(loggerMock.error.calledOnce).to.be.true;
- expect(loggerMock.error.getCall(0).args[0].message).to.equal("SMTP error");
- });
-
- it("should log error if both building HTML and sending email fail", async function () {
- mjml2htmlMock.throws(new Error("MJML error"));
- nodemailerMock.createTransport().sendMail.rejects(new Error("SMTP error"));
-
- const messageId = await emailService.buildAndSendEmail("welcomeEmailTemplate", {}, "recipient@example.com", "Welcome");
-
- expect(messageId).to.be.undefined;
- expect(loggerMock.error.calledTwice).to.be.true;
- expect(loggerMock.error.getCall(0).args[0].message).to.equal("MJML error");
- expect(loggerMock.error.getCall(1).args[0].message).to.equal("SMTP error");
- });
-
- it("should log an error if buildHtml fails", async function () {});
-});
diff --git a/server/tests/services/jobQueue.test.js b/server/tests/services/jobQueue.test.js
deleted file mode 100755
index 155f9119e..000000000
--- a/server/tests/services/jobQueue.test.js
+++ /dev/null
@@ -1,813 +0,0 @@
-import sinon from "sinon";
-import JobQueue from "../../service/jobQueue.js";
-import { log } from "console";
-
-class QueueStub {
- constructor(queueName, options) {
- this.queueName = queueName;
- this.options = options;
- this.workers = [];
- this.jobs = [];
- }
-
- // Add any methods that are expected to be called on the Queue instance
- add(job) {
- this.jobs.push(job);
- }
-
- removeRepeatable(id) {
- const removedJob = this.jobs.find((job) => job.data._id === id);
- this.jobs = this.jobs.filter((job) => job.data._id !== id);
- if (removedJob) {
- return true;
- }
- return false;
- }
-
- getRepeatableJobs() {
- return this.jobs;
- }
- async getJobs() {
- return this.jobs;
- }
-
- async pause() {
- return true;
- }
-
- async obliterate() {
- return true;
- }
-}
-
-class WorkerStub {
- constructor(QUEUE_NAME, workerTask) {
- this.queueName = QUEUE_NAME;
- this.workerTask = async () => workerTask({ data: { _id: 1 } });
- }
-
- async close() {
- return true;
- }
-}
-
-describe("JobQueue", function () {
- let settingsService, logger, db, networkService, statusService, notificationService, jobQueue;
-
- beforeEach(async function () {
- settingsService = { getSettings: sinon.stub() };
- statusService = { updateStatus: sinon.stub() };
- notificationService = { handleNotifications: sinon.stub() };
-
- logger = { error: sinon.stub(), info: sinon.stub() };
- db = {
- getAllMonitors: sinon.stub().returns([]),
- getMaintenanceWindowsByMonitorId: sinon.stub().returns([]),
- };
- networkService = { getStatus: sinon.stub() };
- jobQueue = await JobQueue.createJobQueue(db, networkService, statusService, notificationService, settingsService, logger, QueueStub, WorkerStub);
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- describe("createJobQueue", function () {
- it("should create a new JobQueue and add jobs for active monitors", async function () {
- db.getAllMonitors.returns([
- { id: 1, isActive: true },
- { id: 2, isActive: true },
- ]);
- const jobQueue = await JobQueue.createJobQueue(
- db,
- networkService,
- statusService,
- notificationService,
- settingsService,
- logger,
- QueueStub,
- WorkerStub
- );
- // There should be double the jobs, as one is meant to be instantly executed
- // And one is meant to be enqueued
- expect(jobQueue.queue.jobs.length).to.equal(4);
- });
-
- it("should reject with an error if an error occurs", async function () {
- db.getAllMonitors.throws("Error");
- try {
- const jobQueue = await JobQueue.createJobQueue(
- db,
- networkService,
- statusService,
- notificationService,
- settingsService,
- logger,
- QueueStub,
- WorkerStub
- );
- } catch (error) {
- expect(error.service).to.equal("JobQueue");
- expect(error.method).to.equal("createJobQueue");
- }
- });
-
- it("should reject with an error if an error occurs, should not overwrite error data", async function () {
- const error = new Error("Error");
- error.service = "otherService";
- error.method = "otherMethod";
- db.getAllMonitors.throws(error);
-
- try {
- const jobQueue = await JobQueue.createJobQueue(
- db,
- networkService,
- statusService,
- notificationService,
- settingsService,
- logger,
- QueueStub,
- WorkerStub
- );
- } catch (error) {
- expect(error.service).to.equal("otherService");
- expect(error.method).to.equal("otherMethod");
- }
- });
- });
-
- describe("Constructor", function () {
- it("should construct a new JobQueue with default port and host if not provided", async function () {
- settingsService.getSettings.returns({});
-
- expect(jobQueue.connection.host).to.equal("127.0.0.1");
- expect(jobQueue.connection.port).to.equal(6379);
- });
-
- it("should construct a new JobQueue with provided port and host", async function () {
- settingsService.getSettings.returns({ redisHost: "localhost", redisPort: 1234 });
- const jobQueue = await JobQueue.createJobQueue(
- db,
- networkService,
- statusService,
- notificationService,
- settingsService,
- logger,
- QueueStub,
- WorkerStub
- );
- expect(jobQueue.connection.host).to.equal("localhost");
- expect(jobQueue.connection.port).to.equal(1234);
- });
- });
-
- describe("isMaintenanceWindow", function () {
- it("should throw an error if error occurs", async function () {
- db.getMaintenanceWindowsByMonitorId.throws("Error");
- const jobQueue = await JobQueue.createJobQueue(
- db,
- networkService,
- statusService,
- notificationService,
- settingsService,
- logger,
- QueueStub,
- WorkerStub
- );
- try {
- jobQueue.isInMaintenanceWindow(1);
- } catch (error) {
- expect(error.service).to.equal("JobQueue");
- expect(error.method).to.equal("createWorker");
- }
- });
-
- it("should return true if in maintenance window with no repeat", async function () {
- db.getMaintenanceWindowsByMonitorId.returns([
- {
- active: true,
- start: new Date(Date.now() - 1000).toISOString(),
- end: new Date(Date.now() + 1000).toISOString(),
- repeat: 0,
- },
- ]);
- const jobQueue = await JobQueue.createJobQueue(
- db,
- networkService,
- statusService,
- notificationService,
- settingsService,
- logger,
- QueueStub,
- WorkerStub
- );
- const inWindow = await jobQueue.isInMaintenanceWindow(1);
- expect(inWindow).to.be.true;
- });
-
- it("should return true if in maintenance window with repeat", async function () {
- db.getMaintenanceWindowsByMonitorId.returns([
- {
- active: true,
- start: new Date(Date.now() - 10000).toISOString(),
- end: new Date(Date.now() - 5000).toISOString(),
- repeat: 1000,
- },
- ]);
- const jobQueue = await JobQueue.createJobQueue(
- db,
- networkService,
- statusService,
- notificationService,
- settingsService,
- logger,
- QueueStub,
- WorkerStub
- );
- const inWindow = await jobQueue.isInMaintenanceWindow(1);
- expect(inWindow).to.be.true;
- });
-
- it("should return false if in end < start", async function () {
- db.getMaintenanceWindowsByMonitorId.returns([
- {
- active: true,
- start: new Date(Date.now() - 5000).toISOString(),
- end: new Date(Date.now() - 10000).toISOString(),
- repeat: 1000,
- },
- ]);
- const jobQueue = await JobQueue.createJobQueue(
- db,
- networkService,
- statusService,
- notificationService,
- settingsService,
- logger,
- QueueStub,
- WorkerStub
- );
- const inWindow = await jobQueue.isInMaintenanceWindow(1);
- expect(inWindow).to.be.false;
- });
-
- it("should return false if not in maintenance window", async function () {
- db.getMaintenanceWindowsByMonitorId.returns([
- {
- active: false,
- start: new Date(Date.now() - 5000).toISOString(),
- end: new Date(Date.now() - 10000).toISOString(),
- repeat: 1000,
- },
- ]);
- const jobQueue = await JobQueue.createJobQueue(
- db,
- networkService,
- statusService,
- notificationService,
- settingsService,
- logger,
- QueueStub,
- WorkerStub
- );
- const inWindow = await jobQueue.isInMaintenanceWindow(1);
- expect(inWindow).to.be.false;
- });
- });
-
- describe("createJobHandler", function () {
- it("resolve to an error if an error is thrown within", async function () {
- const jobQueue = await JobQueue.createJobQueue(
- db,
- networkService,
- statusService,
- notificationService,
- settingsService,
- logger,
- QueueStub,
- WorkerStub
- );
- jobQueue.isInMaintenanceWindow = sinon.stub().throws("Error");
- try {
- const handler = jobQueue.createJobHandler();
- await handler({ data: { _id: 1 } });
- } catch (error) {
- expect(error.service).to.equal("JobQueue");
- expect(error.details).to.equal(`Error processing job 1: Error`);
- }
- });
-
- it("should log info if job is in maintenance window", async function () {
- const jobQueue = await JobQueue.createJobQueue(
- db,
- networkService,
- statusService,
- notificationService,
- settingsService,
- logger,
- QueueStub,
- WorkerStub
- );
- jobQueue.isInMaintenanceWindow = sinon.stub().returns(true);
- const handler = jobQueue.createJobHandler();
- await handler({ data: { _id: 1 } });
- expect(logger.info.calledOnce).to.be.true;
- expect(logger.info.firstCall.args[0].message).to.equal("Monitor 1 is in maintenance window");
- });
-
- it("should return if status has not changed", async function () {
- const jobQueue = await JobQueue.createJobQueue(
- db,
- networkService,
- statusService,
- notificationService,
- settingsService,
- logger,
- QueueStub,
- WorkerStub
- );
- jobQueue.isInMaintenanceWindow = sinon.stub().returns(false);
- statusService.updateStatus = sinon.stub().returns({ statusChanged: false });
- const handler = jobQueue.createJobHandler();
- await handler({ data: { _id: 1 } });
- expect(jobQueue.notificationService.handleNotifications.notCalled).to.be.true;
- });
-
- it("should return if status has changed, but prevStatus was undefined (monitor paused)", async function () {
- const jobQueue = await JobQueue.createJobQueue(
- db,
- networkService,
- statusService,
- notificationService,
- settingsService,
- logger,
- QueueStub,
- WorkerStub
- );
- jobQueue.isInMaintenanceWindow = sinon.stub().returns(false);
- statusService.updateStatus = sinon.stub().returns({ statusChanged: true, prevStatus: undefined });
- const handler = jobQueue.createJobHandler();
- await handler({ data: { _id: 1 } });
- expect(jobQueue.notificationService.handleNotifications.notCalled).to.be.true;
- });
-
- it("should call notification service if status changed and monitor was not paused", async function () {
- const jobQueue = await JobQueue.createJobQueue(
- db,
- networkService,
- statusService,
- notificationService,
- settingsService,
- logger,
- QueueStub,
- WorkerStub
- );
- jobQueue.isInMaintenanceWindow = sinon.stub().returns(false);
- statusService.updateStatus = sinon.stub().returns({ statusChanged: true, prevStatus: false });
- const handler = jobQueue.createJobHandler();
- await handler({ data: { _id: 1 } });
- expect(jobQueue.notificationService.handleNotifications.calledOnce).to.be.true;
- });
- });
-
- describe("getWorkerStats", function () {
- it("should throw an error if getRepeatable Jobs fails", async function () {
- const jobQueue = await JobQueue.createJobQueue(
- db,
- networkService,
- statusService,
- notificationService,
- settingsService,
- logger,
- QueueStub,
- WorkerStub
- );
- jobQueue.queue.getRepeatableJobs = async () => {
- throw new Error("Error");
- };
- try {
- const stats = await jobQueue.getWorkerStats();
- } catch (error) {
- expect(error.service).to.equal("JobQueue");
- expect(error.method).to.equal("getWorkerStats");
- }
- });
-
- it("should throw an error if getRepeatable Jobs fails but respect existing error data", async function () {
- const jobQueue = await JobQueue.createJobQueue(
- db,
- networkService,
- statusService,
- notificationService,
- settingsService,
- logger,
- QueueStub,
- WorkerStub
- );
- jobQueue.queue.getRepeatableJobs = async () => {
- const error = new Error("Existing Error");
- error.service = "otherService";
- error.method = "otherMethod";
- throw error;
- };
- try {
- await jobQueue.getWorkerStats();
- } catch (error) {
- expect(error.service).to.equal("otherService");
- expect(error.method).to.equal("otherMethod");
- }
- });
- });
-
- describe("scaleWorkers", function () {
- it("should scale workers to 5 if no workers", async function () {
- const jobQueue = await JobQueue.createJobQueue(
- db,
- networkService,
- statusService,
- notificationService,
- settingsService,
- logger,
- QueueStub,
- WorkerStub
- );
- expect(jobQueue.workers.length).to.equal(5);
- });
-
- it("should scale workers up", async function () {
- const jobQueue = await JobQueue.createJobQueue(
- db,
- networkService,
- statusService,
- notificationService,
- settingsService,
- logger,
- QueueStub,
- WorkerStub
- );
- jobQueue.scaleWorkers({
- load: 100,
- jobs: Array.from({ length: 100 }, (_, i) => i + 1),
- });
- expect(jobQueue.workers.length).to.equal(20);
- });
-
- it("should scale workers down, even with error of worker.close fails", async function () {
- WorkerStub.prototype.close = async () => {
- throw new Error("Error");
- };
- const jobQueue = await JobQueue.createJobQueue(
- db,
- networkService,
- statusService,
- notificationService,
- settingsService,
- logger,
- QueueStub,
- WorkerStub
- );
- await jobQueue.scaleWorkers({
- load: 100,
- jobs: Array.from({ length: 100 }, (_, i) => i + 1),
- });
-
- const res = await jobQueue.scaleWorkers({
- load: 0,
- jobs: [],
- });
- expect(jobQueue.workers.length).to.equal(5);
- });
-
- it("should scale workers down", async function () {
- WorkerStub.prototype.close = async () => {
- return true;
- };
- const jobQueue = await JobQueue.createJobQueue(
- db,
- networkService,
- statusService,
- notificationService,
- settingsService,
- logger,
- QueueStub,
- WorkerStub
- );
- await jobQueue.scaleWorkers({
- load: 40,
- jobs: Array.from({ length: 40 }, (_, i) => i + 1),
- });
-
- const res = await jobQueue.scaleWorkers({
- load: 0,
- jobs: [],
- });
- expect(jobQueue.workers.length).to.equal(5);
- });
-
- it("should return false if scaling doesn't happen", async function () {
- const jobQueue = await JobQueue.createJobQueue(
- db,
- networkService,
- statusService,
- notificationService,
- settingsService,
- logger,
- QueueStub,
- WorkerStub
- );
- const res = await jobQueue.scaleWorkers({ load: 5 });
- expect(jobQueue.workers.length).to.equal(5);
- expect(res).to.be.false;
- });
- });
-
- describe("getJobs", function () {
- it("should return jobs", async function () {
- const jobQueue = await JobQueue.createJobQueue(
- db,
- networkService,
- statusService,
- notificationService,
- settingsService,
- logger,
- QueueStub,
- WorkerStub
- );
- const jobs = await jobQueue.getJobs();
- expect(jobs.length).to.equal(0);
- });
-
- it("should throw an error if getRepeatableJobs fails", async function () {
- const jobQueue = await JobQueue.createJobQueue(
- db,
- networkService,
- statusService,
- notificationService,
- settingsService,
- logger,
- QueueStub,
- WorkerStub
- );
- try {
- jobQueue.queue.getRepeatableJobs = async () => {
- throw new Error("error");
- };
-
- await jobQueue.getJobs(true);
- } catch (error) {
- expect(error.service).to.equal("JobQueue");
- expect(error.method).to.equal("getJobs");
- }
- });
-
- it("should throw an error if getRepeatableJobs fails but respect existing error data", async function () {
- const jobQueue = await JobQueue.createJobQueue(
- db,
- networkService,
- statusService,
- notificationService,
- settingsService,
- logger,
- QueueStub,
- WorkerStub
- );
- try {
- jobQueue.queue.getRepeatableJobs = async () => {
- const error = new Error("Existing error");
- error.service = "otherService";
- error.method = "otherMethod";
- throw error;
- };
-
- await jobQueue.getJobs(true);
- } catch (error) {
- expect(error.service).to.equal("otherService");
- expect(error.method).to.equal("otherMethod");
- }
- });
- });
-
- describe("getJobStats", function () {
- it("should return job stats for no jobs", async function () {
- const jobStats = await jobQueue.getJobStats();
- expect(jobStats).to.deep.equal({ jobs: [], workers: 5 });
- });
-
- it("should return job stats for jobs", async function () {
- jobQueue.queue.getJobs = async () => {
- return [{ data: { url: "test" }, getState: async () => "completed" }];
- };
- const jobStats = await jobQueue.getJobStats();
- expect(jobStats).to.deep.equal({
- jobs: [{ url: "test", state: "completed" }],
- workers: 5,
- });
- });
-
- it("should reject with an error if mapping jobs fails", async function () {
- jobQueue.queue.getJobs = async () => {
- return [
- {
- data: { url: "test" },
- getState: async () => {
- throw new Error("Mapping Error");
- },
- },
- ];
- };
- try {
- await jobQueue.getJobStats();
- } catch (error) {
- expect(error.message).to.equal("Mapping Error");
- expect(error.service).to.equal("JobQueue");
- expect(error.method).to.equal("getJobStats");
- }
- });
-
- it("should reject with an error if mapping jobs fails but respect existing error data", async function () {
- jobQueue.queue.getJobs = async () => {
- return [
- {
- data: { url: "test" },
- getState: async () => {
- const error = new Error("Mapping Error");
- error.service = "otherService";
- error.method = "otherMethod";
- throw error;
- },
- },
- ];
- };
- try {
- await jobQueue.getJobStats();
- } catch (error) {
- expect(error.message).to.equal("Mapping Error");
- expect(error.service).to.equal("otherService");
- expect(error.method).to.equal("otherMethod");
- }
- });
- });
-
- describe("addJob", function () {
- it("should add a job to the queue", async function () {
- jobQueue.addJob("test", { url: "test" });
- expect(jobQueue.queue.jobs.length).to.equal(1);
- });
-
- it("should reject with an error if adding fails", async function () {
- jobQueue.queue.add = async () => {
- throw new Error("Error adding job");
- };
- try {
- await jobQueue.addJob("test", { url: "test" });
- } catch (error) {
- expect(error.message).to.equal("Error adding job");
- expect(error.service).to.equal("JobQueue");
- expect(error.method).to.equal("addJob");
- }
- });
-
- it("should reject with an error if adding fails but respect existing error data", async function () {
- jobQueue.queue.add = async () => {
- const error = new Error("Error adding job");
- error.service = "otherService";
- error.method = "otherMethod";
- throw error;
- };
- try {
- await jobQueue.addJob("test", { url: "test" });
- } catch (error) {
- expect(error.message).to.equal("Error adding job");
- expect(error.service).to.equal("otherService");
- expect(error.method).to.equal("otherMethod");
- }
- });
- });
-
- describe("deleteJob", function () {
- it("should delete a job from the queue", async function () {
- jobQueue.getWorkerStats = sinon.stub().returns({ load: 1, jobs: [{}] });
- jobQueue.scaleWorkers = sinon.stub();
- const monitor = { _id: 1 };
- const job = { data: monitor };
- jobQueue.queue.jobs = [job];
- await jobQueue.deleteJob(monitor);
- // expect(jobQueue.queue.jobs.length).to.equal(0);
- // expect(logger.info.calledOnce).to.be.true;
- // expect(jobQueue.getWorkerStats.calledOnce).to.be.true;
- // expect(jobQueue.scaleWorkers.calledOnce).to.be.true;
- });
-
- it("should log an error if job is not found", async function () {
- jobQueue.getWorkerStats = sinon.stub().returns({ load: 1, jobs: [{}] });
- jobQueue.scaleWorkers = sinon.stub();
- const monitor = { _id: 1 };
- const job = { data: monitor };
- jobQueue.queue.jobs = [job];
- await jobQueue.deleteJob({ id_: 2 });
- expect(logger.error.calledOnce).to.be.true;
- });
-
- it("should reject with an error if removeRepeatable fails", async function () {
- jobQueue.queue.removeRepeatable = async () => {
- const error = new Error("removeRepeatable error");
- throw error;
- };
-
- try {
- await jobQueue.deleteJob({ _id: 1 });
- } catch (error) {
- expect(error.message).to.equal("removeRepeatable error");
- expect(error.service).to.equal("JobQueue");
- expect(error.method).to.equal("deleteJob");
- }
- });
-
- it("should reject with an error if removeRepeatable fails but respect existing error data", async function () {
- jobQueue.queue.removeRepeatable = async () => {
- const error = new Error("removeRepeatable error");
- error.service = "otherService";
- error.method = "otherMethod";
- throw error;
- };
-
- try {
- await jobQueue.deleteJob({ _id: 1 });
- } catch (error) {
- expect(error.message).to.equal("removeRepeatable error");
- expect(error.service).to.equal("otherService");
- expect(error.method).to.equal("otherMethod");
- }
- });
- });
-
- describe("getMetrics", function () {
- it("should return metrics for the job queue", async function () {
- jobQueue.queue.getWaitingCount = async () => 1;
- jobQueue.queue.getActiveCount = async () => 2;
- jobQueue.queue.getCompletedCount = async () => 3;
- jobQueue.queue.getFailedCount = async () => 4;
- jobQueue.queue.getDelayedCount = async () => 5;
- jobQueue.queue.getRepeatableJobs = async () => [1, 2, 3];
- const metrics = await jobQueue.getMetrics();
- expect(metrics).to.deep.equal({
- waiting: 1,
- active: 2,
- completed: 3,
- failed: 4,
- delayed: 5,
- repeatableJobs: 3,
- });
- });
-
- it("should log an error if metrics operations fail", async function () {
- jobQueue.queue.getWaitingCount = async () => {
- throw new Error("Error");
- };
- await jobQueue.getMetrics();
- expect(logger.error.calledOnce).to.be.true;
- expect(logger.error.firstCall.args[0].message).to.equal("Error");
- });
- });
-
- describe("obliterate", function () {
- it("should return true if obliteration is successful", async function () {
- jobQueue.queue.pause = async () => true;
- jobQueue.getJobs = async () => [{ key: 1, id: 1 }];
- jobQueue.queue.removeRepeatableByKey = async () => true;
- jobQueue.queue.remove = async () => true;
- jobQueue.queue.obliterate = async () => true;
- const obliteration = await jobQueue.obliterate();
- expect(obliteration).to.be.true;
- });
-
- it("should throw an error if obliteration fails", async function () {
- jobQueue.getMetrics = async () => {
- throw new Error("Error");
- };
-
- try {
- await jobQueue.obliterate();
- } catch (error) {
- expect(error.service).to.equal("JobQueue");
- expect(error.method).to.equal("obliterate");
- }
- });
-
- it("should throw an error if obliteration fails but respect existing error data", async function () {
- jobQueue.getMetrics = async () => {
- const error = new Error("Error");
- error.service = "otherService";
- error.method = "otherMethod";
- throw error;
- };
-
- try {
- await jobQueue.obliterate();
- } catch (error) {
- expect(error.service).to.equal("otherService");
- expect(error.method).to.equal("otherMethod");
- }
- });
- });
-});
diff --git a/server/tests/services/networkService.test.js b/server/tests/services/networkService.test.js
deleted file mode 100755
index cb48ed9b2..000000000
--- a/server/tests/services/networkService.test.js
+++ /dev/null
@@ -1,439 +0,0 @@
-import sinon from "sinon";
-import NetworkService from "../../service/networkService.js";
-import { expect } from "chai";
-import http from "http";
-import { errorMessages } from "../../utils/messages.js";
-describe("Network Service", function () {
- let axios, ping, Docker, logger, networkService;
-
- beforeEach(function () {
- axios = {
- get: sinon.stub().resolves({
- data: { foo: "bar" },
- status: 200,
- }),
- };
- Docker = class {
- listContainers = sinon.stub().resolves([
- {
- Names: ["http://test.com"],
- Id: "http://test.com",
- },
- ]);
- getContainer = sinon.stub().returns({
- inspect: sinon.stub().resolves({ State: { Status: "running" } }),
- });
- };
- ping = {
- promise: {
- probe: sinon.stub().resolves({ response: { alive: true }, responseTime: 100, alive: true }),
- },
- };
- logger = { error: sinon.stub() };
- networkService = new NetworkService(axios, ping, logger, http, Docker);
- });
-
- describe("constructor", function () {
- it("should create a new NetworkService instance", function () {
- const networkService = new NetworkService();
- expect(networkService).to.be.an.instanceOf(NetworkService);
- });
- });
-
- describe("timeRequest", function () {
- it("should time an asynchronous operation", async function () {
- const operation = sinon.stub().resolves("success");
- const { response, responseTime } = await networkService.timeRequest(operation);
- expect(response).to.equal("success");
- expect(responseTime).to.be.a("number");
- });
-
- it("should handle errors if operation throws error", async function () {
- const error = new Error("Test error");
- const operation = sinon.stub().throws(error);
- const { response, responseTime } = await networkService.timeRequest(operation);
- expect(response).to.be.null;
- expect(responseTime).to.be.a("number");
- expect(error.message).to.equal("Test error");
- });
- });
-
- describe("requestPing", function () {
- it("should return a response object if ping successful", async function () {
- const pingResult = await networkService.requestPing({
- data: { url: "http://test.com", _id: "123" },
- });
- expect(pingResult.monitorId).to.equal("123");
- expect(pingResult.type).to.equal("ping");
- expect(pingResult.responseTime).to.be.a("number");
- expect(pingResult.status).to.be.true;
- });
-
- it("should return a response object if ping unsuccessful", async function () {
- const error = new Error("Test error");
- networkService.timeRequest = sinon.stub().resolves({ response: null, responseTime: 1, error });
- const pingResult = await networkService.requestPing({
- data: { url: "http://test.com", _id: "123" },
- });
- expect(pingResult.monitorId).to.equal("123");
- expect(pingResult.type).to.equal("ping");
- expect(pingResult.responseTime).to.be.a("number");
- expect(pingResult.status).to.be.false;
- expect(pingResult.code).to.equal(networkService.PING_ERROR);
- });
-
- it("should throw an error if ping cannot resolve", async function () {
- const error = new Error("test error");
- networkService.timeRequest = sinon.stub().throws(error);
- try {
- await networkService.requestPing({
- data: { url: "http://test.com", _id: "123" },
- });
- } catch (error) {
- expect(error).to.exist;
- expect(error.method).to.equal("requestPing");
- }
- });
- });
-
- describe("requestHttp", function () {
- it("should return a response object if http successful", async function () {
- const job = { data: { url: "http://test.com", _id: "123", type: "http" } };
- const httpResult = await networkService.requestHttp(job);
- expect(httpResult.monitorId).to.equal("123");
- expect(httpResult.type).to.equal("http");
- expect(httpResult.responseTime).to.be.a("number");
- expect(httpResult.status).to.be.true;
- });
-
- it("should return a response object if http unsuccessful", async function () {
- const error = new Error("Test error");
- error.response = { status: 404 };
- networkService.timeRequest = sinon.stub().resolves({ response: null, responseTime: 1, error });
- const job = { data: { url: "http://test.com", _id: "123", type: "http" } };
- const httpResult = await networkService.requestHttp(job);
- expect(httpResult.monitorId).to.equal("123");
- expect(httpResult.type).to.equal("http");
- expect(httpResult.responseTime).to.be.a("number");
- expect(httpResult.status).to.be.false;
- expect(httpResult.code).to.equal(404);
- });
-
- it("should return a response object if http unsuccessful with unknown code", async function () {
- const error = new Error("Test error");
- error.response = {};
- networkService.timeRequest = sinon.stub().resolves({ response: null, responseTime: 1, error });
- const job = { data: { url: "http://test.com", _id: "123", type: "http" } };
- const httpResult = await networkService.requestHttp(job);
- expect(httpResult.monitorId).to.equal("123");
- expect(httpResult.type).to.equal("http");
- expect(httpResult.responseTime).to.be.a("number");
- expect(httpResult.status).to.be.false;
- expect(httpResult.code).to.equal(networkService.NETWORK_ERROR);
- });
-
- it("should throw an error if an error occurs", async function () {
- const error = new Error("test error");
- networkService.timeRequest = sinon.stub().throws(error);
- try {
- await networkService.requestHttp({
- data: { url: "http://test.com", _id: "123" },
- });
- } catch (error) {
- expect(error).to.exist;
- expect(error.method).to.equal("requestHttp");
- }
- });
- });
-
- describe("requestPagespeed", function () {
- it("should return a response object if pagespeed successful", async function () {
- const job = { data: { url: "http://test.com", _id: "123", type: "pagespeed" } };
- const pagespeedResult = await networkService.requestPagespeed(job);
- expect(pagespeedResult.monitorId).to.equal("123");
- expect(pagespeedResult.type).to.equal("pagespeed");
- expect(pagespeedResult.responseTime).to.be.a("number");
- expect(pagespeedResult.status).to.be.true;
- });
-
- it("should return a response object if pagespeed unsuccessful", async function () {
- const error = new Error("Test error");
- error.response = { status: 404 };
- networkService.timeRequest = sinon.stub().resolves({ response: null, responseTime: 1, error });
- const job = { data: { url: "http://test.com", _id: "123", type: "pagespeed" } };
- const pagespeedResult = await networkService.requestPagespeed(job);
- expect(pagespeedResult.monitorId).to.equal("123");
- expect(pagespeedResult.type).to.equal("pagespeed");
- expect(pagespeedResult.responseTime).to.be.a("number");
- expect(pagespeedResult.status).to.be.false;
- expect(pagespeedResult.code).to.equal(404);
- });
-
- it("should return a response object if pagespeed unsuccessful with an unknown code", async function () {
- const error = new Error("Test error");
- error.response = {};
- networkService.timeRequest = sinon.stub().resolves({ response: null, responseTime: 1, error });
- const job = { data: { url: "http://test.com", _id: "123", type: "pagespeed" } };
- const pagespeedResult = await networkService.requestPagespeed(job);
- expect(pagespeedResult.monitorId).to.equal("123");
- expect(pagespeedResult.type).to.equal("pagespeed");
- expect(pagespeedResult.responseTime).to.be.a("number");
- expect(pagespeedResult.status).to.be.false;
- expect(pagespeedResult.code).to.equal(networkService.NETWORK_ERROR);
- });
-
- it("should throw an error if pagespeed cannot resolve", async function () {
- const error = new Error("test error");
- networkService.timeRequest = sinon.stub().throws(error);
- try {
- await networkService.requestPagespeed({
- data: { url: "http://test.com", _id: "123" },
- });
- } catch (error) {
- expect(error).to.exist;
- expect(error.method).to.equal("requestPagespeed");
- }
- });
- });
-
- describe("requestHardware", function () {
- it("should return a response object if hardware successful", async function () {
- const job = { data: { url: "http://test.com", _id: "123", type: "hardware" } };
- const httpResult = await networkService.requestHardware(job);
- expect(httpResult.monitorId).to.equal("123");
- expect(httpResult.type).to.equal("hardware");
- expect(httpResult.responseTime).to.be.a("number");
- expect(httpResult.status).to.be.true;
- });
-
- it("should return a response object if hardware successful and job has a secret", async function () {
- const job = {
- data: {
- url: "http://test.com",
- _id: "123",
- type: "hardware",
- secret: "my_secret",
- },
- };
- const httpResult = await networkService.requestHardware(job);
- expect(httpResult.monitorId).to.equal("123");
- expect(httpResult.type).to.equal("hardware");
- expect(httpResult.responseTime).to.be.a("number");
- expect(httpResult.status).to.be.true;
- });
-
- it("should return a response object if hardware unsuccessful", async function () {
- const error = new Error("Test error");
- error.response = { status: 404 };
- networkService.timeRequest = sinon.stub().resolves({ response: null, responseTime: 1, error });
- const job = { data: { url: "http://test.com", _id: "123", type: "hardware" } };
- const httpResult = await networkService.requestHardware(job);
- expect(httpResult.monitorId).to.equal("123");
- expect(httpResult.type).to.equal("hardware");
- expect(httpResult.responseTime).to.be.a("number");
- expect(httpResult.status).to.be.false;
- expect(httpResult.code).to.equal(404);
- });
-
- it("should return a response object if hardware unsuccessful with unknown code", async function () {
- const error = new Error("Test error");
- error.response = {};
- networkService.timeRequest = sinon.stub().resolves({ response: null, responseTime: 1, error });
- const job = { data: { url: "http://test.com", _id: "123", type: "hardware" } };
- const httpResult = await networkService.requestHardware(job);
- expect(httpResult.monitorId).to.equal("123");
- expect(httpResult.type).to.equal("hardware");
- expect(httpResult.responseTime).to.be.a("number");
- expect(httpResult.status).to.be.false;
- expect(httpResult.code).to.equal(networkService.NETWORK_ERROR);
- });
-
- it("should throw an error if hardware cannot resolve", async function () {
- const error = new Error("test error");
- networkService.timeRequest = sinon.stub().throws(error);
- try {
- await networkService.requestHardware({
- data: { url: "http://test.com", _id: "123" },
- });
- } catch (error) {
- expect(error).to.exist;
- expect(error.method).to.equal("requestHardware");
- }
- });
- });
-
- describe("requestDocker", function () {
- it("should return a response object if docker successful", async function () {
- const job = { data: { url: "http://test.com", _id: "123", type: "docker" } };
- const dockerResult = await networkService.requestDocker(job);
- expect(dockerResult.monitorId).to.equal("123");
- expect(dockerResult.type).to.equal("docker");
- expect(dockerResult.responseTime).to.be.a("number");
- expect(dockerResult.status).to.be.true;
- });
-
- it("should return a response object with status false if container not running", async function () {
- Docker = class {
- listContainers = sinon.stub().resolves([
- {
- Names: ["/my_container"],
- Id: "abc123",
- },
- ]);
- getContainer = sinon.stub().returns({
- inspect: sinon.stub().resolves({ State: { Status: "stopped" } }),
- });
- };
- networkService = new NetworkService(axios, ping, logger, http, Docker);
- const job = { data: { url: "abc123", _id: "123", type: "docker" } };
- const dockerResult = await networkService.requestDocker(job);
- expect(dockerResult.status).to.be.false;
- expect(dockerResult.code).to.equal(200);
- });
-
- it("should handle an error when fetching the container", async function () {
- Docker = class {
- listContainers = sinon.stub().resolves([
- {
- Names: ["/my_container"],
- Id: "abc123",
- },
- ]);
- getContainer = sinon.stub().returns({
- inspect: sinon.stub().throws(new Error("test error")),
- });
- };
- networkService = new NetworkService(axios, ping, logger, http, Docker);
- const job = { data: { url: "abc123", _id: "123", type: "docker" } };
- const dockerResult = await networkService.requestDocker(job);
- expect(dockerResult.status).to.be.false;
- expect(dockerResult.code).to.equal(networkService.NETWORK_ERROR);
- });
-
- it("should throw an error if operations fail", async function () {
- Docker = class {
- listContainers = sinon.stub().resolves([
- {
- Names: ["/my_container"],
- Id: "abc123",
- },
- ]);
- getContainer = sinon.stub().throws(new Error("test error"));
- };
- networkService = new NetworkService(axios, ping, logger, http, Docker);
- const job = { data: { url: "abc123", _id: "123", type: "docker" } };
- try {
- await networkService.requestDocker(job);
- } catch (error) {
- expect(error.message).to.equal("test error");
- }
- });
-
- it("should throw an error if no matching images found", async function () {
- Docker = class {
- listContainers = sinon.stub().resolves([]);
- getContainer = sinon.stub().throws(new Error("test error"));
- };
- networkService = new NetworkService(axios, ping, logger, http, Docker);
- const job = { data: { url: "abc123", _id: "123", type: "docker" } };
- try {
- await networkService.requestDocker(job);
- } catch (error) {
- expect(error.message).to.equal(errorMessages.DOCKER_NOT_FOUND);
- }
- });
- });
-
- describe("getStatus", function () {
- beforeEach(function () {
- networkService.requestPing = sinon.stub();
- networkService.requestHttp = sinon.stub();
- networkService.requestPagespeed = sinon.stub();
- networkService.requestHardware = sinon.stub();
- networkService.requestDocker = sinon.stub();
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should call requestPing if type is ping", async function () {
- await networkService.getStatus({ data: { type: "ping" } });
- expect(networkService.requestPing.calledOnce).to.be.true;
- expect(networkService.requestDocker.notCalled).to.be.true;
- expect(networkService.requestHttp.notCalled).to.be.true;
- expect(networkService.requestPagespeed.notCalled).to.be.true;
- });
-
- it("should call requestHttp if type is http", async function () {
- await networkService.getStatus({ data: { type: "http" } });
- expect(networkService.requestPing.notCalled).to.be.true;
- expect(networkService.requestDocker.notCalled).to.be.true;
- expect(networkService.requestHttp.calledOnce).to.be.true;
- expect(networkService.requestPagespeed.notCalled).to.be.true;
- });
-
- it("should call requestPagespeed if type is pagespeed", async function () {
- await networkService.getStatus({ data: { type: "pagespeed" } });
- expect(networkService.requestPing.notCalled).to.be.true;
- expect(networkService.requestDocker.notCalled).to.be.true;
- expect(networkService.requestHttp.notCalled).to.be.true;
- expect(networkService.requestPagespeed.calledOnce).to.be.true;
- });
-
- it("should call requestHardware if type is hardware", async function () {
- await networkService.getStatus({ data: { type: "hardware" } });
- expect(networkService.requestHardware.calledOnce).to.be.true;
- expect(networkService.requestDocker.notCalled).to.be.true;
- expect(networkService.requestPing.notCalled).to.be.true;
- expect(networkService.requestPagespeed.notCalled).to.be.true;
- });
-
- it("should call requestDocker if type is Docker", async function () {
- await networkService.getStatus({ data: { type: "docker" } });
- expect(networkService.requestDocker.calledOnce).to.be.true;
- expect(networkService.requestHardware.notCalled).to.be.true;
- expect(networkService.requestPing.notCalled).to.be.true;
- expect(networkService.requestPagespeed.notCalled).to.be.true;
- });
-
- it("should throw an error if an unknown type is provided", async function () {
- try {
- await networkService.getStatus({ data: { type: "unknown" } });
- } catch (error) {
- expect(error.service).to.equal("NetworkService");
- expect(error.method).to.equal("getStatus");
- expect(error.message).to.equal("Unsupported type: unknown");
- }
- });
-
- it("should throw an error if job type is undefined", async function () {
- try {
- await networkService.getStatus({ data: { type: undefined } });
- } catch (error) {
- expect(error.service).to.equal("NetworkService");
- expect(error.method).to.equal("getStatus");
- expect(error.message).to.equal("Unsupported type: unknown");
- }
- });
-
- it("should throw an error if job is empty", async function () {
- try {
- await networkService.getStatus({});
- } catch (error) {
- expect(error.method).to.equal("getStatus");
- expect(error.message).to.equal("Unsupported type: unknown");
- }
- });
-
- it("should throw an error if job is null", async function () {
- try {
- await networkService.getStatus(null);
- } catch (error) {
- expect(error.service).to.equal("NetworkService");
- expect(error.method).to.equal("getStatus");
- expect(error.message).to.equal("Unsupported type: unknown");
- }
- });
- });
-});
diff --git a/server/tests/services/notificationService.test.js b/server/tests/services/notificationService.test.js
deleted file mode 100755
index 211cc8b46..000000000
--- a/server/tests/services/notificationService.test.js
+++ /dev/null
@@ -1,285 +0,0 @@
-import sinon from "sinon";
-import NotificationService from "../../service/notificationService.js";
-import { expect } from "chai";
-
-describe("NotificationService", function () {
- let emailService, db, logger, notificationService;
-
- beforeEach(function () {
- db = {
- getNotificationsByMonitorId: sinon.stub(),
- };
- emailService = {
- buildAndSendEmail: sinon.stub(),
- };
- logger = {
- warn: sinon.stub(),
- };
-
- notificationService = new NotificationService(emailService, db, logger);
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- describe("constructor", function () {
- it("should create a new instance of NotificationService", function () {
- expect(notificationService).to.be.an.instanceOf(NotificationService);
- });
- });
-
- describe("sendEmail", function () {
- it("should send an email notification with Up Template", async function () {
- const networkResponse = {
- monitor: {
- name: "Test Monitor",
- url: "http://test.com",
- },
- status: true,
- prevStatus: false,
- };
- const address = "test@test.com";
- await notificationService.sendEmail(networkResponse, address);
- expect(notificationService.emailService.buildAndSendEmail.calledOnce).to.be.true;
- expect(
- notificationService.emailService.buildAndSendEmail.calledWith("serverIsUpTemplate", { monitor: "Test Monitor", url: "http://test.com" })
- );
- });
-
- it("should send an email notification with Down Template", async function () {
- const networkResponse = {
- monitor: {
- name: "Test Monitor",
- url: "http://test.com",
- },
- status: false,
- prevStatus: true,
- };
- const address = "test@test.com";
- await notificationService.sendEmail(networkResponse, address);
- expect(notificationService.emailService.buildAndSendEmail.calledOnce).to.be.true;
- });
-
- it("should send an email notification with Up Template", async function () {
- const networkResponse = {
- monitor: {
- name: "Test Monitor",
- url: "http://test.com",
- },
- status: true,
- prevStatus: false,
- };
- const address = "test@test.com";
- await notificationService.sendEmail(networkResponse, address);
- expect(notificationService.emailService.buildAndSendEmail.calledOnce).to.be.true;
- });
- });
-
- describe("handleNotifications", function () {
- it("should handle notifications based on the network response", async function () {
- notificationService.sendEmail = sinon.stub();
- const res = await notificationService.handleNotifications({
- monitor: {
- type: "email",
- address: "www.google.com",
- },
- });
- expect(res).to.be.true;
- });
-
- it("should handle hardware notifications", async function () {
- notificationService.sendEmail = sinon.stub();
- const res = await notificationService.handleNotifications({
- monitor: {
- type: "hardware",
- address: "www.google.com",
- },
- });
- expect(res).to.be.true;
- });
-
- it("should handle an error when getting notifications", async function () {
- const testError = new Error("Test Error");
- notificationService.db.getNotificationsByMonitorId.rejects(testError);
- await notificationService.handleNotifications({ monitorId: "123" });
- expect(notificationService.logger.warn.calledOnce).to.be.true;
- });
- });
-
- describe("sendHardwareEmail", function () {
- let networkResponse, address, alerts;
-
- beforeEach(function () {
- networkResponse = {
- monitor: {
- name: "Test Monitor",
- url: "http://test.com",
- },
- status: true,
- prevStatus: false,
- };
- address = "test@test.com";
- alerts = ["test"];
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should send an email notification with Hardware Template", async function () {
- emailService.buildAndSendEmail.resolves(true);
- const res = await notificationService.sendHardwareEmail(networkResponse, address, alerts);
- expect(res).to.be.true;
- });
-
- it("should return false if no alerts are provided", async function () {
- alerts = [];
- emailService.buildAndSendEmail.resolves(true);
- const res = await notificationService.sendHardwareEmail(networkResponse, address, alerts);
- expect(res).to.be.false;
- });
- });
-
- describe("handleStatusNotifications", function () {
- let networkResponse;
-
- beforeEach(function () {
- networkResponse = {
- monitor: {
- name: "Test Monitor",
- url: "http://test.com",
- },
- statusChanged: true,
- status: true,
- prevStatus: false,
- };
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should handle status notifications", async function () {
- db.getNotificationsByMonitorId.resolves([{ type: "email", address: "test@test.com" }]);
- const res = await notificationService.handleStatusNotifications(networkResponse);
- expect(res).to.be.true;
- });
-
- it("should return false if status hasn't changed", async function () {
- networkResponse.statusChanged = false;
- const res = await notificationService.handleStatusNotifications(networkResponse);
- expect(res).to.be.false;
- });
-
- it("should return false if prevStatus is undefined", async function () {
- networkResponse.prevStatus = undefined;
- const res = await notificationService.handleStatusNotifications(networkResponse);
- expect(res).to.be.false;
- });
-
- it("should handle an error", async function () {
- const testError = new Error("Test Error");
- db.getNotificationsByMonitorId.rejects(testError);
- try {
- await notificationService.handleStatusNotifications(networkResponse);
- } catch (error) {
- expect(error).to.be.an.instanceOf(Error);
- expect(error.message).to.equal("Test Error");
- }
- });
- });
-
- describe("handleHardwareNotifications", function () {
- let networkResponse;
-
- beforeEach(function () {
- networkResponse = {
- monitor: {
- name: "Test Monitor",
- url: "http://test.com",
- thresholds: {
- usage_cpu: 1,
- usage_memory: 1,
- usage_disk: 1,
- },
- },
- payload: {
- data: {
- cpu: {
- usage_percent: 0.655,
- },
- memory: {
- usage_percent: 0.783,
- },
- disk: [
- {
- name: "/dev/sda1",
- usage_percent: 0.452,
- },
- {
- name: "/dev/sdb1",
- usage_percent: 0.627,
- },
- ],
- },
- },
- };
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- describe("it should return false if no thresholds are set", function () {
- it("should return false if no thresholds are set", async function () {
- networkResponse.monitor.thresholds = undefined;
- const res = await notificationService.handleHardwareNotifications(networkResponse);
- expect(res).to.be.false;
- });
-
- it("should return false if metrics are null", async function () {
- networkResponse.payload.data = null;
- const res = await notificationService.handleHardwareNotifications(networkResponse);
- expect(res).to.be.false;
- });
-
- it("should return true if request is well formed and thresholds > 0", async function () {
- db.getNotificationsByMonitorId.resolves([
- {
- type: "email",
- address: "test@test.com",
- alertThreshold: 1,
- cpuAlertThreshold: 1,
- memoryAlertThreshold: 1,
- diskAlertThreshold: 1,
- save: sinon.stub().resolves(),
- },
- ]);
- const res = await notificationService.handleHardwareNotifications(networkResponse);
- expect(res).to.be.true;
- });
-
- it("should return true if thresholds are exceeded", async function () {
- db.getNotificationsByMonitorId.resolves([
- {
- type: "email",
- address: "test@test.com",
- alertThreshold: 1,
- cpuAlertThreshold: 1,
- memoryAlertThreshold: 1,
- diskAlertThreshold: 1,
- save: sinon.stub().resolves(),
- },
- ]);
- networkResponse.monitor.thresholds = {
- usage_cpu: 0.01,
- usage_memory: 0.01,
- usage_disk: 0.01,
- };
- const res = await notificationService.handleHardwareNotifications(networkResponse);
- expect(res).to.be.true;
- });
- });
- });
-});
diff --git a/server/tests/services/settingsService.test.js b/server/tests/services/settingsService.test.js
deleted file mode 100755
index 6c9960e5e..000000000
--- a/server/tests/services/settingsService.test.js
+++ /dev/null
@@ -1,140 +0,0 @@
-import sinon from "sinon";
-import SettingsService from "../../service/settingsService.js";
-import { expect } from "chai";
-import NetworkService from "../../service/networkService.js";
-const SERVICE_NAME = "SettingsService";
-
-describe("SettingsService", function () {
- let sandbox, mockAppSettings;
-
- beforeEach(function () {
- sandbox = sinon.createSandbox();
- sandbox.stub(process.env, "CLIENT_HOST").value("http://localhost");
- sandbox.stub(process.env, "JWT_SECRET").value("secret");
- sandbox.stub(process.env, "REFRESH_TOKEN_SECRET").value("refreshSecret");
- sandbox.stub(process.env, "DB_TYPE").value("postgres");
- sandbox.stub(process.env, "DB_CONNECTION_STRING").value("postgres://user:pass@localhost/db");
- sandbox.stub(process.env, "REDIS_HOST").value("localhost");
- sandbox.stub(process.env, "REDIS_PORT").value("6379");
- sandbox.stub(process.env, "TOKEN_TTL").value("3600");
- sandbox.stub(process.env, "REFRESH_TOKEN_TTL").value("86400");
- sandbox.stub(process.env, "PAGESPEED_API_KEY").value("apiKey");
- sandbox.stub(process.env, "SYSTEM_EMAIL_HOST").value("smtp.mailtrap.io");
- sandbox.stub(process.env, "SYSTEM_EMAIL_PORT").value("2525");
- sandbox.stub(process.env, "SYSTEM_EMAIL_ADDRESS").value("test@example.com");
- sandbox.stub(process.env, "SYSTEM_EMAIL_PASSWORD").value("password");
- });
-
- mockAppSettings = {
- settingOne: 123,
- settingTwo: 456,
- };
-
- afterEach(function () {
- sandbox.restore();
- sinon.restore();
- });
-
- describe("constructor", function () {
- it("should construct a new SettingsService", function () {
- const settingsService = new SettingsService(mockAppSettings);
- expect(settingsService.appSettings).to.equal(mockAppSettings);
- });
- });
-
- describe("loadSettings", function () {
- it("should load settings from DB when environment variables are not set", async function () {
- const dbSettings = { logLevel: "debug", apiBaseUrl: "http://localhost" };
- const appSettings = { findOne: sinon.stub().returns(dbSettings) };
- const settingsService = new SettingsService(appSettings);
- settingsService.settings = {};
- const result = await settingsService.loadSettings();
- expect(result).to.deep.equal(dbSettings);
- });
-
- it("should throw an error if settings are not found", async function () {
- const appSettings = { findOne: sinon.stub().returns(null) };
- const settingsService = new SettingsService(appSettings);
- settingsService.settings = null;
-
- try {
- await settingsService.loadSettings();
- } catch (error) {
- expect(error.message).to.equal("Settings not found");
- expect(error.service).to.equal(SERVICE_NAME);
- expect(error.method).to.equal("loadSettings");
- }
- });
-
- it("should add its method and service name to error if not present", async function () {
- const appSettings = { findOne: sinon.stub().throws(new Error("Test error")) };
- const settingsService = new SettingsService(appSettings);
- try {
- await settingsService.loadSettings();
- } catch (error) {
- expect(error.message).to.equal("Test error");
- expect(error.service).to.equal(SERVICE_NAME);
- expect(error.method).to.equal("loadSettings");
- }
- });
-
- it("should not add its method and service name to error if present", async function () {
- const error = new Error("Test error");
- error.method = "otherMethod";
- error.service = "OTHER_SERVICE";
- const appSettings = { findOne: sinon.stub().throws(error) };
- const settingsService = new SettingsService(appSettings);
- try {
- await settingsService.loadSettings();
- } catch (error) {
- expect(error.message).to.equal("Test error");
- expect(error.service).to.equal("OTHER_SERVICE");
- expect(error.method).to.equal("otherMethod");
- }
- });
-
- it("should merge DB settings with environment variables", async function () {
- const dbSettings = { logLevel: "debug", apiBaseUrl: "http://localhost" };
- const appSettings = { findOne: sinon.stub().returns(dbSettings) };
- const settingsService = new SettingsService(appSettings);
- const result = await settingsService.loadSettings();
- expect(result).to.deep.equal(settingsService.settings);
- expect(settingsService.settings.logLevel).to.equal("debug");
- expect(settingsService.settings.apiBaseUrl).to.equal("http://localhost");
- });
- });
-
- describe("reloadSettings", function () {
- it("should call loadSettings", async function () {
- const dbSettings = { logLevel: "debug", apiBaseUrl: "http://localhost" };
- const appSettings = { findOne: sinon.stub().returns(dbSettings) };
- const settingsService = new SettingsService(appSettings);
- settingsService.settings = {};
- const result = await settingsService.reloadSettings();
- expect(result).to.deep.equal(dbSettings);
- });
- });
-
- describe("getSettings", function () {
- it("should return the current settings", function () {
- const dbSettings = { logLevel: "debug", apiBaseUrl: "http://localhost" };
- const appSettings = { findOne: sinon.stub().returns(dbSettings) };
- const settingsService = new SettingsService(appSettings);
- settingsService.settings = dbSettings;
- const result = settingsService.getSettings();
- expect(result).to.deep.equal(dbSettings);
- });
-
- it("should throw an error if settings have not been loaded", function () {
- const appSettings = { findOne: sinon.stub().returns(null) };
- const settingsService = new SettingsService(appSettings);
- settingsService.settings = null;
-
- try {
- settingsService.getSettings();
- } catch (error) {
- expect(error.message).to.equal("Settings have not been loaded");
- }
- });
- });
-});
diff --git a/server/tests/services/statusService.test.js b/server/tests/services/statusService.test.js
deleted file mode 100755
index 289a36884..000000000
--- a/server/tests/services/statusService.test.js
+++ /dev/null
@@ -1,255 +0,0 @@
-import sinon from "sinon";
-import StatusService from "../../service/statusService.js";
-import { afterEach, describe } from "node:test";
-
-describe("StatusService", () => {
- let db, logger, statusService;
-
- beforeEach(function () {
- db = {
- getMonitorById: sinon.stub(),
- createCheck: sinon.stub(),
- createPagespeedCheck: sinon.stub(),
- };
- logger = {
- info: sinon.stub(),
- error: sinon.stub(),
- };
- statusService = new StatusService(db, logger);
- });
-
- afterEach(() => {
- sinon.restore();
- });
-
- describe("constructor", () => {
- it("should create an instance of StatusService", function () {
- expect(statusService).to.be.an.instanceOf(StatusService);
- });
- });
-
- describe("getStatusString", () => {
- it("should return 'up' if status is true", function () {
- expect(statusService.getStatusString(true)).to.equal("up");
- });
-
- it("should return 'down' if status is false", function () {
- expect(statusService.getStatusString(false)).to.equal("down");
- });
-
- it("should return 'unknown' if status is undefined or null", function () {
- expect(statusService.getStatusString(undefined)).to.equal("unknown");
- });
- });
-
- describe("updateStatus", () => {
- beforeEach(function () {
- // statusService.insertCheck = sinon.stub().resolves;
- });
-
- afterEach(() => {
- sinon.restore();
- });
-
- it("should throw an error if an error occurs", async function () {
- const error = new Error("Test error");
- statusService.db.getMonitorById = sinon.stub().throws(error);
- try {
- await statusService.updateStatus({ monitorId: "test", status: true });
- } catch (error) {
- expect(error.message).to.equal("Test error");
- }
- // expect(statusService.insertCheck.calledOnce).to.be.true;
- });
-
- it("should return {statusChanged: false} if status hasn't changed", async function () {
- statusService.db.getMonitorById = sinon.stub().returns({ status: true });
- const result = await statusService.updateStatus({
- monitorId: "test",
- status: true,
- });
- expect(result).to.deep.equal({ statusChanged: false });
- // expect(statusService.insertCheck.calledOnce).to.be.true;
- });
-
- it("should return {statusChanged: true} if status has changed from down to up", async function () {
- statusService.db.getMonitorById = sinon.stub().returns({ status: false, save: sinon.stub() });
- const result = await statusService.updateStatus({
- monitorId: "test",
- status: true,
- });
- expect(result.statusChanged).to.be.true;
- expect(result.monitor.status).to.be.true;
- expect(result.prevStatus).to.be.false;
- // expect(statusService.insertCheck.calledOnce).to.be.true;
- });
-
- it("should return {statusChanged: true} if status has changed from up to down", async function () {
- statusService.db.getMonitorById = sinon.stub().returns({ status: true, save: sinon.stub() });
- const result = await statusService.updateStatus({
- monitorId: "test",
- status: false,
- });
- expect(result.statusChanged).to.be.true;
- expect(result.monitor.status).to.be.false;
- expect(result.prevStatus).to.be.true;
- // expect(statusService.insertCheck.calledOnce).to.be.true;
- });
- });
-
- describe("buildCheck", () => {
- it("should build a check object", function () {
- const check = statusService.buildCheck({
- monitorId: "test",
- type: "test",
- status: true,
- responseTime: 100,
- code: 200,
- message: "Test message",
- payload: { test: "test" },
- });
- expect(check.monitorId).to.equal("test");
- expect(check.status).to.be.true;
- expect(check.statusCode).to.equal(200);
- expect(check.responseTime).to.equal(100);
- expect(check.message).to.equal("Test message");
- });
-
- it("should build a check object for pagespeed type", function () {
- const check = statusService.buildCheck({
- monitorId: "test",
- type: "pagespeed",
- status: true,
- responseTime: 100,
- code: 200,
- message: "Test message",
- payload: {
- lighthouseResult: {
- categories: {
- accessibility: { score: 1 },
- "best-practices": { score: 1 },
- performance: { score: 1 },
- seo: { score: 1 },
- },
- audits: {
- "cumulative-layout-shift": { score: 1 },
- "speed-index": { score: 1 },
- "first-contentful-paint": { score: 1 },
- "largest-contentful-paint": { score: 1 },
- "total-blocking-time": { score: 1 },
- },
- },
- },
- });
- expect(check.monitorId).to.equal("test");
- expect(check.status).to.be.true;
- expect(check.statusCode).to.equal(200);
- expect(check.responseTime).to.equal(100);
- expect(check.message).to.equal("Test message");
- expect(check.accessibility).to.equal(100);
- expect(check.bestPractices).to.equal(100);
- expect(check.performance).to.equal(100);
- expect(check.seo).to.equal(100);
- expect(check.audits).to.deep.equal({
- cls: { score: 1 },
- si: { score: 1 },
- fcp: { score: 1 },
- lcp: { score: 1 },
- tbt: { score: 1 },
- });
- });
-
- it("should build a check object for pagespeed type with missing data", function () {
- const check = statusService.buildCheck({
- monitorId: "test",
- type: "pagespeed",
- status: true,
- responseTime: 100,
- code: 200,
- message: "Test message",
- payload: {
- lighthouseResult: {
- categories: {},
- audits: {},
- },
- },
- });
- expect(check.monitorId).to.equal("test");
- expect(check.status).to.be.true;
- expect(check.statusCode).to.equal(200);
- expect(check.responseTime).to.equal(100);
- expect(check.message).to.equal("Test message");
- expect(check.accessibility).to.equal(0);
- expect(check.bestPractices).to.equal(0);
- expect(check.performance).to.equal(0);
- expect(check.seo).to.equal(0);
- expect(check.audits).to.deep.equal({
- cls: 0,
- si: 0,
- fcp: 0,
- lcp: 0,
- tbt: 0,
- });
- });
-
- it("should build a check for hardware type", function () {
- const check = statusService.buildCheck({
- monitorId: "test",
- type: "hardware",
- status: true,
- responseTime: 100,
- code: 200,
- message: "Test message",
- payload: { data: { cpu: "cpu", memory: "memory", disk: "disk", host: "host" } },
- });
- expect(check.monitorId).to.equal("test");
- expect(check.status).to.be.true;
- expect(check.statusCode).to.equal(200);
- expect(check.responseTime).to.equal(100);
- expect(check.message).to.equal("Test message");
- expect(check.cpu).to.equal("cpu");
- expect(check.memory).to.equal("memory");
- expect(check.disk).to.equal("disk");
- expect(check.host).to.equal("host");
- });
-
- it("should build a check for hardware type with missing data", function () {
- const check = statusService.buildCheck({
- monitorId: "test",
- type: "hardware",
- status: true,
- responseTime: 100,
- code: 200,
- message: "Test message",
- payload: {},
- });
- expect(check.monitorId).to.equal("test");
- expect(check.status).to.be.true;
- expect(check.statusCode).to.equal(200);
- expect(check.responseTime).to.equal(100);
- expect(check.message).to.equal("Test message");
- expect(check.cpu).to.deep.equal({});
- expect(check.memory).to.deep.equal({});
- expect(check.disk).to.deep.equal({});
- expect(check.host).to.deep.equal({});
- });
- });
-
- describe("insertCheck", () => {
- it("should log an error if one is thrown", async function () {
- const testError = new Error("Test error");
- statusService.db.createCheck = sinon.stub().throws(testError);
- try {
- await statusService.insertCheck({ monitorId: "test" });
- } catch (error) {
- expect(error.message).to.equal(testError.message);
- }
- expect(statusService.logger.error.calledOnce).to.be.true;
- });
-
- it("should insert a check into the database", async function () {
- await statusService.insertCheck({ monitorId: "test", type: "http" });
- expect(statusService.db.createCheck.calledOnce).to.be.true;
- });
- });
-});
diff --git a/server/tests/utils/dataUtils.test.js b/server/tests/utils/dataUtils.test.js
deleted file mode 100755
index ec9eabb40..000000000
--- a/server/tests/utils/dataUtils.test.js
+++ /dev/null
@@ -1,61 +0,0 @@
-import { NormalizeData, calculatePercentile } from "../../utils/dataUtils.js";
-import sinon from "sinon";
-
-describe("NormalizeData", function () {
- it("should normalize response times when checks length is greater than 1", function () {
- const checks = [
- { responseTime: 20, _doc: { id: 1 } },
- { responseTime: 40, _doc: { id: 2 } },
- { responseTime: 60, _doc: { id: 3 } },
- ];
- const rangeMin = 1;
- const rangeMax = 100;
-
- const result = NormalizeData(checks, rangeMin, rangeMax);
-
- expect(result).to.be.an("array");
- expect(result).to.have.lengthOf(3);
- result.forEach((check) => {
- expect(check).to.have.property("responseTime").that.is.a("number");
- expect(check).to.have.property("originalResponseTime").that.is.a("number");
- });
- });
-
- it("should return checks with original response times when checks length is 1", function () {
- const checks = [{ responseTime: 20, _doc: { id: 1 } }];
- const rangeMin = 1;
- const rangeMax = 100;
-
- const result = NormalizeData(checks, rangeMin, rangeMax);
- expect(result).to.be.an("array");
- expect(result).to.have.lengthOf(1);
- expect(result[0]).to.have.property("originalResponseTime", 20);
- });
-
- it("should handle edge cases with extreme response times", function () {
- const checks = [
- { responseTime: 5, _doc: { id: 1 } },
- { responseTime: 95, _doc: { id: 2 } },
- ];
- const rangeMin = 1;
- const rangeMax = 100;
-
- const result = NormalizeData(checks, rangeMin, rangeMax);
-
- expect(result).to.be.an("array");
- expect(result).to.have.lengthOf(2);
- expect(result[0]).to.have.property("responseTime").that.is.at.least(rangeMin);
- expect(result[1]).to.have.property("responseTime").that.is.at.most(rangeMax);
- });
-});
-
-describe("calculatePercentile", function () {
- it("should return the lower value when upper is greater than or equal to the length of the sorted array", function () {
- const checks = [{ responseTime: 10 }, { responseTime: 20 }, { responseTime: 30 }, { responseTime: 40 }, { responseTime: 50 }];
-
- const percentile = 100;
- const result = calculatePercentile(checks, percentile);
- const expected = 50;
- expect(result).to.equal(expected);
- });
-});
diff --git a/server/tests/utils/imageProcessing.test.js b/server/tests/utils/imageProcessing.test.js
deleted file mode 100755
index 09330c317..000000000
--- a/server/tests/utils/imageProcessing.test.js
+++ /dev/null
@@ -1,53 +0,0 @@
-import { expect } from "chai";
-import sinon from "sinon";
-import sharp from "sharp";
-import { GenerateAvatarImage } from "../../utils/imageProcessing.js";
-
-describe("imageProcessing - GenerateAvatarImage", function () {
- it("should resize the image to 64x64 and return a base64 string", async function () {
- const file = {
- buffer: Buffer.from("test image buffer"),
- };
-
- // Stub the sharp function
- const toBufferStub = sinon.stub().resolves(Buffer.from("resized image buffer"));
- const resizeStub = sinon.stub().returns({ toBuffer: toBufferStub });
- const sharpStub = sinon.stub(sharp.prototype, "resize").returns({ toBuffer: toBufferStub });
-
- const result = await GenerateAvatarImage(file);
-
- // Verify the result
- const expected = Buffer.from("resized image buffer").toString("base64");
- expect(result).to.equal(expected);
-
- // Verify that the sharp function was called with the correct arguments
- expect(sharpStub.calledOnceWith({ width: 64, height: 64, fit: "cover" })).to.be.true;
- expect(toBufferStub.calledOnce).to.be.true;
-
- // Restore the stubbed functions
- sharpStub.restore();
- });
-
- it("should throw an error if resizing fails", async function () {
- const file = {
- buffer: Buffer.from("test image buffer"),
- };
-
- // Stub the sharp function to throw an error
- const toBufferStub = sinon.stub().rejects(new Error("Resizing failed"));
- const resizeStub = sinon.stub().returns({ toBuffer: toBufferStub });
- const sharpStub = sinon.stub(sharp.prototype, "resize").returns({ toBuffer: toBufferStub });
-
- try {
- await GenerateAvatarImage(file);
- // If no error is thrown, fail the test
- expect.fail("Expected error to be thrown");
- } catch (error) {
- // Verify that the error message is correct
- expect(error.message).to.equal("Resizing failed");
- }
-
- // Restore the stubbed functions
- sharpStub.restore();
- });
-});
diff --git a/server/tests/utils/logger.test.js b/server/tests/utils/logger.test.js
deleted file mode 100755
index 2d5bd82a9..000000000
--- a/server/tests/utils/logger.test.js
+++ /dev/null
@@ -1,139 +0,0 @@
-import sinon from "sinon";
-import logger from "../../utils/logger.js";
-import { Logger } from "../../utils/logger.js";
-import winston from "winston";
-
-describe("Logger", function () {
- let infoStub, warnStub, errorStub;
-
- beforeEach(function () {
- infoStub = sinon.stub(logger.logger, "info");
- warnStub = sinon.stub(logger.logger, "warn");
- errorStub = sinon.stub(logger.logger, "error");
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- describe("constructor", function () {
- let createLoggerStub;
-
- beforeEach(function () {
- createLoggerStub = sinon.stub(winston, "createLogger");
- });
-
- afterEach(function () {
- sinon.restore();
- });
-
- it("should convert message to JSON string if it is an object", function () {
- const logMessage = { key: "value" };
- const expectedMessage = JSON.stringify(logMessage, null, 2);
-
- createLoggerStub.callsFake((config) => {
- const consoleTransport = config.transports[0];
- const logEntry = {
- level: "info",
- message: logMessage,
- timestamp: new Date().toISOString(),
- };
- const formattedMessage = consoleTransport.format.transform(logEntry);
- expect(formattedMessage).to.include(expectedMessage);
- return { log: sinon.spy() };
- });
-
- const logger = new Logger();
- logger.logger.info(logMessage);
- });
-
- it("should convert details to JSON string if it is an object", function () {
- const logDetails = { key: "value" };
- const expectedDetails = JSON.stringify(logDetails, null, 2); // Removed .s
-
- createLoggerStub.callsFake((config) => {
- const consoleTransport = config.transports[0];
- const logEntry = {
- level: "info",
- message: "", // Add empty message since it's required
- details: logDetails,
- timestamp: new Date().toISOString(),
- };
- const formattedMessage = consoleTransport.format.transform(logEntry);
- expect(formattedMessage).to.include(expectedDetails);
- return { info: sinon.spy() }; // Changed to return info method
- });
-
- const logger = new Logger();
- logger.logger.info("", { details: logDetails }); // Updated to pass details properly
- });
- });
-
- describe("info", function () {
- it("should log an informational message", function () {
- const config = {
- message: "Info message",
- service: "TestService",
- method: "TestMethod",
- details: { key: "value" },
- };
-
- logger.info(config);
-
- expect(infoStub.calledOnce).to.be.true;
- expect(
- infoStub.calledWith(config.message, {
- service: config.service,
- method: config.method,
- details: config.details,
- })
- ).to.be.true;
- });
- });
-
- describe("warn", function () {
- it("should log a warning message", function () {
- const config = {
- message: "Warning message",
- service: "TestService",
- method: "TestMethod",
- details: { key: "value" },
- };
-
- logger.warn(config);
-
- expect(warnStub.calledOnce).to.be.true;
- expect(
- warnStub.calledWith(config.message, {
- service: config.service,
- method: config.method,
- details: config.details,
- })
- ).to.be.true;
- });
- });
-
- describe("error", function () {
- it("should log an error message", function () {
- const config = {
- message: "Error message",
- service: "TestService",
- method: "TestMethod",
- details: { key: "value" },
- stack: "Error stack trace",
- };
-
- logger.error(config);
-
- expect(errorStub.calledOnce).to.be.true;
- expect(
- errorStub.calledWith(config.message, {
- service: config.service,
- method: config.method,
- details: config.details,
- stack: config.stack,
- })
- ).to.be.true;
- });
- });
-});
diff --git a/server/tests/utils/messages.test.js b/server/tests/utils/messages.test.js
deleted file mode 100755
index a7e98c477..000000000
--- a/server/tests/utils/messages.test.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import { errorMessages, successMessages } from "../../utils/messages.js";
-describe("Messages", function () {
- describe("messages - errorMessages", function () {
- it("should have a DB_FIND_MONITOR_BY_ID function", function () {
- const monitorId = "12345";
- expect(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId)).to.equal(`Monitor with id ${monitorId} not found`);
- });
-
- it("should have a DB_DELETE_CHECKS function", function () {
- const monitorId = "12345";
- expect(errorMessages.DB_DELETE_CHECKS(monitorId)).to.equal(`No checks found for monitor with id ${monitorId}`);
- });
- });
-
- describe("messages - successMessages", function () {
- it("should have a MONITOR_GET_BY_USER_ID function", function () {
- const userId = "12345";
- expect(successMessages.MONITOR_GET_BY_USER_ID(userId)).to.equal(`Got monitor for ${userId} successfully"`);
- });
-
- // Add more tests for other success messages as needed
- });
-});
diff --git a/server/tests/utils/utils.test.js b/server/tests/utils/utils.test.js
deleted file mode 100755
index 2630bb8e6..000000000
--- a/server/tests/utils/utils.test.js
+++ /dev/null
@@ -1,50 +0,0 @@
-import { ParseBoolean, getTokenFromHeaders } from "../../utils/utils.js";
-
-describe("utils - ParseBoolean", function () {
- it("should return true", function () {
- const result = ParseBoolean("true");
- expect(result).to.be.true;
- });
-
- it("should return false", function () {
- const result = ParseBoolean("false");
- expect(result).to.be.false;
- });
-
- it("should return false", function () {
- const result = ParseBoolean(null);
- expect(result).to.be.false;
- });
-
- it("should return false", function () {
- const result = ParseBoolean(undefined);
- expect(result).to.be.false;
- });
-});
-
-describe("utils - getTokenFromHeaders", function () {
- it("should throw an error if authorization header is missing", function () {
- const headers = {};
- expect(() => getTokenFromHeaders(headers)).to.throw("No auth headers");
- });
-
- it("should throw an error if authorization header does not start with Bearer", function () {
- const headers = { authorization: "Basic abcdef" };
- expect(() => getTokenFromHeaders(headers)).to.throw("Invalid auth headers");
- });
-
- it("should return the token if authorization header is correctly formatted", function () {
- const headers = { authorization: "Bearer abcdef" };
- expect(getTokenFromHeaders(headers)).to.equal("abcdef");
- });
-
- it("should throw an error if authorization header has more than two parts", function () {
- const headers = { authorization: "Bearer abc def" };
- expect(() => getTokenFromHeaders(headers)).to.throw("Invalid auth headers");
- });
-
- it("should throw an error if authorization header has less than two parts", function () {
- const headers = { authorization: "Bearer" };
- expect(() => getTokenFromHeaders(headers)).to.throw("Invalid auth headers");
- });
-});
diff --git a/server/tsconfig.json b/server/tsconfig.json
new file mode 100644
index 000000000..260ccb964
--- /dev/null
+++ b/server/tsconfig.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "rootDir": "./src",
+ "outDir": "./dist",
+ "module": "nodenext",
+ "target": "esnext",
+ "types": ["node"],
+ "sourceMap": true,
+ "declaration": false,
+ "declarationMap": false,
+ "noUncheckedIndexedAccess": true,
+ "exactOptionalPropertyTypes": true,
+ "strict": true,
+ "isolatedModules": true,
+ "noUncheckedSideEffectImports": true,
+ "moduleDetection": "force",
+ "skipLibCheck": true,
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": true,
+ "resolveJsonModule": true,
+ "forceConsistentCasingInFileNames": true,
+ "allowJs": true,
+ "checkJs": false
+ },
+ "exclude": ["node_modules", "dist/", "**/*.config.js"]
+}