diff --git a/Client/package-lock.json b/Client/package-lock.json
index d6ac1146c..cf739d2f4 100644
--- a/Client/package-lock.json
+++ b/Client/package-lock.json
@@ -11,9 +11,9 @@
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@fontsource/roboto": "^5.0.13",
- "@mui/icons-material": "6.3.0",
- "@mui/lab": "6.0.0-beta.21",
- "@mui/material": "6.3.0",
+ "@mui/icons-material": "6.3.1",
+ "@mui/lab": "6.0.0-beta.22",
+ "@mui/material": "6.3.1",
"@mui/x-charts": "^7.5.1",
"@mui/x-data-grid": "7.23.5",
"@mui/x-date-pickers": "7.23.3",
@@ -1106,9 +1106,9 @@
}
},
"node_modules/@mui/core-downloads-tracker": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.3.0.tgz",
- "integrity": "sha512-/d8NwSuC3rMwCjswmGB3oXC4sdDuhIUJ8inVQAxGrADJhf0eq/kmy+foFKvpYhHl2siOZR+MLdFttw6/Bzqtqg==",
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.3.1.tgz",
+ "integrity": "sha512-2OmnEyoHpj5//dJJpMuxOeLItCCHdf99pjMFfUFdBteCunAK9jW+PwEo4mtdGcLs7P+IgZ+85ypd52eY4AigoQ==",
"license": "MIT",
"funding": {
"type": "opencollective",
@@ -1116,9 +1116,9 @@
}
},
"node_modules/@mui/icons-material": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.3.0.tgz",
- "integrity": "sha512-3uWws6DveDn5KxCS34p+sUNMxehuclQY6OmoJeJJ+Sfg9L7LGBpksY/nX5ywKAqickTZnn+sQyVcp963ep9jvw==",
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.3.1.tgz",
+ "integrity": "sha512-nJmWj1PBlwS3t1PnoqcixIsftE+7xrW3Su7f0yrjPw4tVjYrgkhU0hrRp+OlURfZ3ptdSkoBkalee9Bhf1Erfw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.0"
@@ -1131,7 +1131,7 @@
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
- "@mui/material": "^6.3.0",
+ "@mui/material": "^6.3.1",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
@@ -1142,16 +1142,16 @@
}
},
"node_modules/@mui/lab": {
- "version": "6.0.0-beta.21",
- "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-6.0.0-beta.21.tgz",
- "integrity": "sha512-hiFZgTwBNhJMUlEhmqfW4+5wy3C8UF9KFuzSOux6x4kgc9hsC0l+motXcF1Vyh+jhJYGeZ6yUoImqCf9RWzEvw==",
+ "version": "6.0.0-beta.22",
+ "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-6.0.0-beta.22.tgz",
+ "integrity": "sha512-9nwUfBj+UzoQJOCbqV+JcCSJ74T+gGWrM1FMlXzkahtYUcMN+5Zmh2ArlttW3zv2dZyCzp7K5askcnKF0WzFQg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.0",
"@mui/base": "5.0.0-beta.68",
- "@mui/system": "^6.3.0",
- "@mui/types": "^7.2.20",
- "@mui/utils": "^6.3.0",
+ "@mui/system": "^6.3.1",
+ "@mui/types": "^7.2.21",
+ "@mui/utils": "^6.3.1",
"clsx": "^2.1.1",
"prop-types": "^15.8.1"
},
@@ -1165,8 +1165,8 @@
"peerDependencies": {
"@emotion/react": "^11.5.0",
"@emotion/styled": "^11.3.0",
- "@mui/material": "^6.3.0",
- "@mui/material-pigment-css": "^6.3.0",
+ "@mui/material": "^6.3.1",
+ "@mui/material-pigment-css": "^6.3.1",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
@@ -1187,16 +1187,16 @@
}
},
"node_modules/@mui/material": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.3.0.tgz",
- "integrity": "sha512-qhlTFyRMxfoVPxUtA5e8IvqxP0dWo2Ij7cvot7Orag+etUlZH+3UwD8gZGt+3irOoy7Ms3UNBflYjwEikUXtAQ==",
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.3.1.tgz",
+ "integrity": "sha512-ynG9ayhxgCsHJ/dtDcT1v78/r2GwQyP3E0hPz3GdPRl0uFJz/uUTtI5KFYwadXmbC+Uv3bfB8laZ6+Cpzh03gA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.0",
- "@mui/core-downloads-tracker": "^6.3.0",
- "@mui/system": "^6.3.0",
- "@mui/types": "^7.2.20",
- "@mui/utils": "^6.3.0",
+ "@mui/core-downloads-tracker": "^6.3.1",
+ "@mui/system": "^6.3.1",
+ "@mui/types": "^7.2.21",
+ "@mui/utils": "^6.3.1",
"@popperjs/core": "^2.11.8",
"@types/react-transition-group": "^4.4.12",
"clsx": "^2.1.1",
@@ -1215,7 +1215,7 @@
"peerDependencies": {
"@emotion/react": "^11.5.0",
"@emotion/styled": "^11.3.0",
- "@mui/material-pigment-css": "^6.3.0",
+ "@mui/material-pigment-css": "^6.3.1",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
@@ -1242,13 +1242,13 @@
"license": "MIT"
},
"node_modules/@mui/private-theming": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.3.0.tgz",
- "integrity": "sha512-tdS8jvqMokltNTXg6ioRCCbVdDmZUJZa/T9VtTnX2Lwww3FTgCakst9tWLZSxm1fEE9Xp0m7onZJmgeUmWQYVw==",
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.3.1.tgz",
+ "integrity": "sha512-g0u7hIUkmXmmrmmf5gdDYv9zdAig0KoxhIQn1JN8IVqApzf/AyRhH3uDGx5mSvs8+a1zb4+0W6LC260SyTTtdQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.0",
- "@mui/utils": "^6.3.0",
+ "@mui/utils": "^6.3.1",
"prop-types": "^15.8.1"
},
"engines": {
@@ -1269,9 +1269,9 @@
}
},
"node_modules/@mui/styled-engine": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.3.0.tgz",
- "integrity": "sha512-iWA6eyiPkO07AlHxRUvI7dwVRSc+84zV54kLmjUms67GJeOWVuXlu8ZO+UhCnwJxHacghxnabsMEqet5PYQmHg==",
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.3.1.tgz",
+ "integrity": "sha512-/7CC0d2fIeiUxN5kCCwYu4AWUDd9cCTxWCyo0v/Rnv6s8uk6hWgJC3VLZBoDENBHf/KjqDZuYJ2CR+7hD6QYww==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.0",
@@ -1303,16 +1303,16 @@
}
},
"node_modules/@mui/system": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.3.0.tgz",
- "integrity": "sha512-L+8hUHMNlfReKSqcnVslFrVhoNfz/jw7Fe9NfDE85R3KarvZ4O3MU9daF/lZeqEAvnYxEilkkTfDwQ7qCgJdFg==",
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.3.1.tgz",
+ "integrity": "sha512-AwqQ3EAIT2np85ki+N15fF0lFXX1iFPqenCzVOSl3QXKy2eifZeGd9dGtt7pGMoFw5dzW4dRGGzRpLAq9rkl7A==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.0",
- "@mui/private-theming": "^6.3.0",
- "@mui/styled-engine": "^6.3.0",
- "@mui/types": "^7.2.20",
- "@mui/utils": "^6.3.0",
+ "@mui/private-theming": "^6.3.1",
+ "@mui/styled-engine": "^6.3.1",
+ "@mui/types": "^7.2.21",
+ "@mui/utils": "^6.3.1",
"clsx": "^2.1.1",
"csstype": "^3.1.3",
"prop-types": "^15.8.1"
@@ -1343,9 +1343,9 @@
}
},
"node_modules/@mui/types": {
- "version": "7.2.20",
- "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.20.tgz",
- "integrity": "sha512-straFHD7L8v05l/N5vcWk+y7eL9JF0C2mtph/y4BPm3gn2Eh61dDwDB65pa8DLss3WJfDXYC7Kx5yjP0EmXpgw==",
+ "version": "7.2.21",
+ "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.21.tgz",
+ "integrity": "sha512-6HstngiUxNqLU+/DPqlUJDIPbzUBxIVHb1MmXP0eTWDIROiCR2viugXpEif0PPe2mLqqakPzzRClWAnK+8UJww==",
"license": "MIT",
"peerDependencies": {
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
@@ -1357,13 +1357,13 @@
}
},
"node_modules/@mui/utils": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.3.0.tgz",
- "integrity": "sha512-MkDBF08OPVwXhAjedyMykRojgvmf0y/jxkBWjystpfI/pasyTYrfdv4jic6s7j3y2+a+SJzS9qrD6X8ZYj/8AQ==",
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.3.1.tgz",
+ "integrity": "sha512-sjGjXAngoio6lniQZKJ5zGfjm+LD2wvLwco7FbKe1fu8A7VIFmz2SwkLb+MDPLNX1lE7IscvNNyh1pobtZg2tw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.0",
- "@mui/types": "^7.2.20",
+ "@mui/types": "^7.2.21",
"@types/prop-types": "^15.7.14",
"clsx": "^2.1.1",
"prop-types": "^15.8.1",
diff --git a/Client/package.json b/Client/package.json
index 671c93b64..31b3f5e62 100644
--- a/Client/package.json
+++ b/Client/package.json
@@ -14,9 +14,9 @@
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@fontsource/roboto": "^5.0.13",
- "@mui/icons-material": "6.3.0",
- "@mui/lab": "6.0.0-beta.21",
- "@mui/material": "6.3.0",
+ "@mui/icons-material": "6.3.1",
+ "@mui/lab": "6.0.0-beta.22",
+ "@mui/material": "6.3.1",
"@mui/x-charts": "^7.5.1",
"@mui/x-data-grid": "7.23.5",
"@mui/x-date-pickers": "7.23.3",
diff --git a/Client/src/Components/BasicTable/index.css b/Client/src/Components/BasicTable/index.css
deleted file mode 100644
index 3c1b0f2cd..000000000
--- a/Client/src/Components/BasicTable/index.css
+++ /dev/null
@@ -1,130 +0,0 @@
-.MuiTable-root .host {
- width: fit-content;
- text-overflow: ellipsis;
- overflow: hidden;
- white-space: nowrap;
-}
-.MuiTable-root .host span {
- font-size: 11px;
-}
-
-.MuiTable-root .label {
- line-height: 1;
- border-radius: var(--env-var-radius-2);
- padding: 7px;
- font-size: var(--env-var-font-size-small-plus);
-}
-
-.MuiPaper-root:has(table.MuiTable-root) {
- box-shadow: none;
-}
-.MuiTable-root .MuiTableBody-root .MuiTableRow-root:last-child .MuiTableCell-root {
- border: none;
-}
-
-.MuiTable-root .MuiTableHead-root .MuiTableCell-root,
-.MuiTable-root .MuiTableBody-root .MuiTableCell-root {
- font-size: var(--env-var-font-size-medium);
-}
-.MuiTable-root .MuiTableHead-root .MuiTableCell-root {
- padding: var(--env-var-spacing-1);
- font-weight: 500;
-}
-.MuiTable-root .MuiTableHead-root span {
- display: inline-block;
- height: 17px;
- width: 20px;
- overflow: hidden;
- margin-bottom: -3px;
- margin-left: 3px;
-}
-.MuiTable-root .MuiTableHead-root span svg {
- width: 20px;
- height: 20px;
-}
-.MuiTable-root .MuiTableBody-root .MuiTableCell-root {
- padding: var(--env-var-spacing-1);
-}
-.MuiTable-root .MuiTableBody-root .MuiTableRow-root {
- height: 50px;
-}
-
-.MuiPaper-root + .MuiPagination-root {
- border-radius: var(--env-var-radius-1);
- padding: var(--env-var-spacing-1-plus) var(--env-var-spacing-2);
-}
-.MuiPaper-root + .MuiPagination-root ul {
- justify-content: center;
-}
-.MuiPaper-root + .MuiPagination-root button {
- font-size: var(--env-var-font-size-medium);
- font-weight: 500;
-}
-.MuiPaper-root + .MuiPagination-root ul li:first-child {
- margin-right: auto;
-}
-.MuiPaper-root + .MuiPagination-root ul li:last-child {
- margin-left: auto;
-}
-.MuiPaper-root + .MuiPagination-root ul li:first-child button {
- padding: 0 var(--env-var-spacing-1) 0 var(--env-var-spacing-1-plus);
-}
-.MuiPaper-root + .MuiPagination-root ul li:last-child button {
- padding: 0 var(--env-var-spacing-1-plus) 0 var(--env-var-spacing-1);
-}
-.MuiPaper-root + .MuiPagination-root ul li:first-child button::after,
-.MuiPaper-root + .MuiPagination-root ul li:last-child button::before {
- position: relative;
- display: inline-block;
-}
-.MuiPaper-root + .MuiPagination-root ul li:first-child button::after {
- content: "Previous";
- margin-left: 15px;
-}
-.MuiPaper-root + .MuiPagination-root ul li:last-child button::before {
- content: "Next";
- margin-right: 15px;
-}
-.MuiPaper-root + .MuiPagination-root div.MuiPaginationItem-root {
- user-select: none;
-}
-
-.MuiTablePagination-root p {
- font-weight: 500;
- font-size: var(--env-var-font-size-small-plus);
-}
-.MuiTablePagination-root .MuiTablePagination-select.MuiSelect-select {
- text-align: left;
- text-align-last: left;
-}
-.MuiTablePagination-root button {
- min-width: 0;
- padding: 4px;
- margin-left: 5px;
-}
-.MuiTablePagination-root svg {
- width: 22px;
- height: 22px;
-}
-.MuiTablePagination-root .MuiSelect-icon {
- width: 16px;
- height: 16px;
- top: 50%;
- right: 8%;
- transform: translateY(-50%);
-}
-.MuiTablePagination-root button.Mui-disabled {
- opacity: 0.4;
-}
-
-.table-container .MuiTable-root .MuiTableHead-root .MuiTableCell-root {
- text-transform: uppercase;
- opacity: 0.8;
- font-size: var(--env-var-font-size-small-plus);
- font-weight: 400;
-}
-.monitors .MuiTableCell-root:not(:first-of-type):not(:last-of-type),
-.monitors .MuiTableCell-root:not(:first-of-type):not(:last-of-type) {
- padding-left: var(--env-var-spacing-1);
- padding-right: var(--env-var-spacing-1);
-}
diff --git a/Client/src/Components/BasicTable/index.jsx b/Client/src/Components/BasicTable/index.jsx
deleted file mode 100644
index cfc3030b0..000000000
--- a/Client/src/Components/BasicTable/index.jsx
+++ /dev/null
@@ -1,335 +0,0 @@
-import PropTypes from "prop-types";
-import { useState, useEffect } from "react";
-import { useTheme } from "@emotion/react";
-import {
- TableContainer,
- Paper,
- Table,
- TableHead,
- TableRow,
- TableCell,
- TableBody,
- TablePagination,
- Box,
- Typography,
- Stack,
- Button,
-} from "@mui/material";
-import { useDispatch, useSelector } from "react-redux";
-import { setRowsPerPage } from "../../Features/UI/uiSlice";
-import LeftArrowDouble from "../../assets/icons/left-arrow-double.svg?react";
-import RightArrowDouble from "../../assets/icons/right-arrow-double.svg?react";
-import LeftArrow from "../../assets/icons/left-arrow.svg?react";
-import RightArrow from "../../assets/icons/right-arrow.svg?react";
-import SelectorVertical from "../../assets/icons/selector-vertical.svg?react";
-import "./index.css";
-/**
- * Component for pagination actions (first, previous, next, last).
- *
- * @component
- * @param {Object} props
- * @param {number} props.count - Total number of items.
- * @param {number} props.page - Current page number.
- * @param {number} props.rowsPerPage - Number of rows per page.
- * @param {function} props.onPageChange - Callback function to handle page change.
- *
- * @returns {JSX.Element} Pagination actions component.
- */
-const TablePaginationActions = (props) => {
- const { count, page, rowsPerPage, onPageChange } = props;
-
- const handleFirstPageButtonClick = (event) => {
- onPageChange(event, 0);
- };
- const handleBackButtonClick = (event) => {
- onPageChange(event, page - 1);
- };
- const handleNextButtonClick = (event) => {
- onPageChange(event, page + 1);
- };
- const handleLastPageButtonClick = (event) => {
- onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1));
- };
-
- return (
-
-
-
-
-
-
- );
-};
-
-TablePaginationActions.propTypes = {
- count: PropTypes.number.isRequired,
- page: PropTypes.number.isRequired,
- rowsPerPage: PropTypes.number.isRequired,
- onPageChange: PropTypes.func.isRequired,
-};
-
-/**
- * BasicTable Component
- * Renders a table with optional pagination.
- *
- * @component
- * @param {Object} props - Component props.
- * @param {Object} props.data - Data for the table including columns and rows.
- * @param {Array} props.data.cols - Array of objects for column headers.
- * @param {number} props.data.cols[].id - Unique identifier for the column.
- * @param {string} props.data.cols[].name - Name of the column to display as header.
- * @param {Array} props.data.rows - Array of row objects.
- * @param {number} props.data.rows[].id - Unique identifier for the row.
- * @param {Array} props.data.rows[].data - Array of cell data objects for the row.
- * @param {number} props.data.rows[].data[].id - Unique identifier for the cell.
- * @param {JSX.Element} props.data.rows[].data[].data - The content to display in the cell.
- * @param {function} props.data.rows.data.handleClick - Function to call when the row is clicked.
- * @param {boolean} [props.paginated=false] - Flag to enable pagination.
- * @param {boolean} [props.reversed=false] - Flag to enable reverse order.
- * @param {number} props.rowsPerPage- Number of rows per page (table).
- * @param {string} props.emptyMessage - Message to display when there is no data.
- * @example
- * const data = {
- * cols: [
- * { id: 1, name: "First Col" },
- * { id: 2, name: "Second Col" },
- * { id: 3, name: "Third Col" },
- * { id: 4, name: "Fourth Col" },
- * ],
- * rows: [
- * {
- * id: 1,
- * data: [
- * { id: 1, data:
Data for Row 1 Col 1
},
- * { id: 2, data: Data for Row 1 Col 2
},
- * { id: 3, data: Data for Row 1 Col 3
},
- * { id: 4, data: Data for Row 1 Col 4
},
- * ],
- * },
- * {
- * id: 2,
- * data: [
- * { id: 5, data: Data for Row 2 Col 1
},
- * { id: 6, data: Data for Row 2 Col 2
},
- * { id: 7, data: Data for Row 2 Col 3
},
- * { id: 8, data: Data for Row 2 Col 4
},
- * ],
- * },
- * ],
- * };
- *
- *
- */
-
-const BasicTable = ({ data, paginated, reversed, table, emptyMessage = "No data" }) => {
- const DEFAULT_ROWS_PER_PAGE = 5;
- const theme = useTheme();
- const dispatch = useDispatch();
- const uiState = useSelector((state) => state.ui);
- let rowsPerPage = uiState?.[table]?.rowsPerPage ?? DEFAULT_ROWS_PER_PAGE;
- const [page, setPage] = useState(0);
-
- useEffect(() => {
- setPage(0);
- }, [data]);
-
- const handleChangePage = (event, newPage) => {
- setPage(newPage);
- };
-
- const handleChangeRowsPerPage = (event) => {
- dispatch(
- setRowsPerPage({
- value: parseInt(event.target.value, 10),
- table: table,
- })
- );
- setPage(0);
- };
-
- let displayData = [];
-
- if (data && data.rows) {
- let rows = reversed ? [...data.rows].reverse() : data.rows;
- displayData = paginated
- ? rows.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
- : rows;
- }
-
- if (!data || !data.cols || !data.rows) {
- return No data
;
- }
-
- /**
- * Helper function to calculate the range of displayed rows.
- * @returns {string}
- */
- const getRange = () => {
- let start = page * rowsPerPage + 1;
- let end = Math.min(page * rowsPerPage + rowsPerPage, data.rows.length);
- return `${start} - ${end}`;
- };
-
- return (
- <>
-
-
-
-
- {data.cols.map((col) => (
- {col.name}
- ))}
-
-
-
- {displayData.map((row) => {
- return (
-
- {row.data.map((cell) => {
- return {cell.data};
- })}
-
- );
- })}
- {displayData.length === 0 && (
-
-
- {emptyMessage}
-
-
- )}
-
-
-
- {paginated && (
-
-
- Showing {getRange()} of {data.rows.length} monitor(s)
-
-
- `Page ${page + 1} of ${Math.max(0, Math.ceil(count / rowsPerPage))}`
- }
- slotProps={{
- select: {
- MenuProps: {
- keepMounted: true,
- PaperProps: {
- className: "pagination-dropdown",
- sx: {
- mt: 0,
- mb: theme.spacing(2),
- },
- },
- transformOrigin: { vertical: "bottom", horizontal: "left" },
- anchorOrigin: { vertical: "top", horizontal: "left" },
- sx: { mt: theme.spacing(-2) },
- },
- inputProps: { id: "pagination-dropdown" },
- IconComponent: SelectorVertical,
- sx: {
- ml: theme.spacing(4),
- mr: theme.spacing(12),
- minWidth: theme.spacing(20),
- textAlign: "left",
- "&.Mui-focused > div": {
- backgroundColor: theme.palette.background.main,
- },
- },
- },
- }}
- sx={{
- mt: theme.spacing(6),
- color: theme.palette.text.secondary,
- "& svg path": {
- stroke: theme.palette.text.tertiary,
- strokeWidth: 1.3,
- },
- "& .MuiSelect-select": {
- border: 1,
- borderColor: theme.palette.border.light,
- borderRadius: theme.shape.borderRadius,
- },
- }}
- />
-
- )}
- >
- );
-};
-
-BasicTable.propTypes = {
- data: PropTypes.object.isRequired,
- paginated: PropTypes.bool,
- reversed: PropTypes.bool,
- rowPerPage: PropTypes.number,
- table: PropTypes.string,
- emptyMessage: PropTypes.string,
-};
-
-export default BasicTable;
diff --git a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx
index 2fefd3daa..91cb1bf22 100644
--- a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx
+++ b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx
@@ -13,9 +13,14 @@ import { useTheme } from "@emotion/react";
import { useMemo, useState } from "react";
import { useSelector } from "react-redux";
import { formatDateWithTz } from "../../../Utils/timeUtils";
-import "./index.css";
+import {
+ tooltipDateFormatLookup,
+ tickDateFormatLookup,
+} from "../Utils/chartUtilFunctions";
-const CustomToolTip = ({ active, payload, label }) => {
+import "./index.css";
+const CustomToolTip = ({ active, payload, label, dateRange }) => {
+ const format = tooltipDateFormatLookup(dateRange);
const uiTimezone = useSelector((state) => state.ui.timezone);
const theme = useTheme();
if (active && payload && payload.length) {
@@ -41,7 +46,7 @@ const CustomToolTip = ({ active, payload, label }) => {
fontWeight: 500,
}}
>
- {formatDateWithTz(label, "ddd, MMMM D, YYYY, h:mm A", uiTimezone)}
+ {formatDateWithTz(label, format, uiTimezone)}
{
+const CustomTick = ({ x, y, payload, index, dateRange }) => {
+ const format = tickDateFormatLookup(dateRange);
const theme = useTheme();
-
const uiTimezone = useSelector((state) => state.ui.timezone);
- // Render nothing for the first tick
- if (index === 0) return null;
return (
{
fontSize={11}
fontWeight={400}
>
- {formatDateWithTz(payload?.value, "h:mm a", uiTimezone)}
+ {formatDateWithTz(payload?.value, format, uiTimezone)}
);
};
@@ -128,9 +132,10 @@ CustomTick.propTypes = {
y: PropTypes.number,
payload: PropTypes.object,
index: PropTypes.number,
+ dateRange: PropTypes.string,
};
-const MonitorDetailsAreaChart = ({ checks }) => {
+const MonitorDetailsAreaChart = ({ checks, dateRange }) => {
const theme = useTheme();
const memoizedChecks = useMemo(() => checks, [checks[0]]);
const [isHovered, setIsHovered] = useState(false);
@@ -184,16 +189,14 @@ const MonitorDetailsAreaChart = ({ checks }) => {
}
- minTickGap={0}
+ tick={}
axisLine={false}
tickLine={false}
height={20}
- interval="equidistantPreserveStart"
/>
}
+ content={}
wrapperStyle={{ pointerEvents: "none" }}
/>
{
MonitorDetailsAreaChart.propTypes = {
checks: PropTypes.array,
+ dateRange: PropTypes.string,
};
export default MonitorDetailsAreaChart;
diff --git a/Client/src/Components/Charts/Utils/chartUtilFunctions.js b/Client/src/Components/Charts/Utils/chartUtilFunctions.js
new file mode 100644
index 000000000..70f5ac9c3
--- /dev/null
+++ b/Client/src/Components/Charts/Utils/chartUtilFunctions.js
@@ -0,0 +1,25 @@
+export const tooltipDateFormatLookup = (dateRange) => {
+ const dateFormatLookup = {
+ day: "ddd. MMMM D, YYYY, hh:mm A",
+ week: "ddd. MMMM D, YYYY, hh:mm A",
+ month: "ddd. MMMM D, YYYY",
+ };
+ const format = dateFormatLookup[dateRange];
+ if (format === undefined) {
+ return "";
+ }
+ return format;
+};
+
+export const tickDateFormatLookup = (dateRange) => {
+ const tickFormatLookup = {
+ day: "h:mm A",
+ week: "MM/D, h:mm A",
+ month: "ddd. M/D",
+ };
+ const format = tickFormatLookup[dateRange];
+ if (format === undefined) {
+ return "";
+ }
+ return format;
+};
diff --git a/Client/src/Components/Charts/Utils/chartUtils.jsx b/Client/src/Components/Charts/Utils/chartUtils.jsx
index 1d82f10b4..1776c75d9 100644
--- a/Client/src/Components/Charts/Utils/chartUtils.jsx
+++ b/Client/src/Components/Charts/Utils/chartUtils.jsx
@@ -4,7 +4,7 @@ import { useTheme } from "@mui/material";
import { Text } from "recharts";
import { formatDateWithTz } from "../../../Utils/timeUtils";
import { Box, Stack, Typography } from "@mui/material";
-
+import { tickDateFormatLookup, tooltipDateFormatLookup } from "./chartUtilFunctions";
/**
* Custom tick component for rendering time with timezone.
*
@@ -15,9 +15,10 @@ import { Box, Stack, Typography } from "@mui/material";
* @param {number} props.index - The index of the tick.
* @returns {JSX.Element} The rendered tick component.
*/
-export const TzTick = ({ x, y, payload, index }) => {
+export const TzTick = ({ x, y, payload, index, dateRange }) => {
const theme = useTheme();
const uiTimezone = useSelector((state) => state.ui.timezone);
+ const format = tickDateFormatLookup(dateRange);
return (
{
fontSize={11}
fontWeight={400}
>
- {formatDateWithTz(payload?.value, "h:mm a", uiTimezone)}
+ {formatDateWithTz(payload?.value, format, uiTimezone)}
);
};
@@ -37,6 +38,7 @@ TzTick.propTypes = {
y: PropTypes.number,
payload: PropTypes.object,
index: PropTypes.number,
+ dateRange: PropTypes.string,
};
/**
@@ -109,9 +111,12 @@ export const InfrastructureTooltip = ({
yIdx = -1,
yLabel,
dotColor,
+ dateRange,
}) => {
const uiTimezone = useSelector((state) => state.ui.timezone);
const theme = useTheme();
+
+ const format = tooltipDateFormatLookup(dateRange);
if (active && payload && payload.length) {
const [hardwareType, metric] = yKey.split(".");
return (
@@ -133,7 +138,7 @@ export const InfrastructureTooltip = ({
fontWeight: 500,
}}
>
- {formatDateWithTz(label, "ddd, MMMM D, YYYY, h:mm A", uiTimezone)}
+ {formatDateWithTz(label, format, uiTimezone)}
{
+export const TemperatureTooltip = ({
+ active,
+ payload,
+ label,
+ keys,
+ dotColor,
+ dateRange,
+}) => {
const uiTimezone = useSelector((state) => state.ui.timezone);
const theme = useTheme();
+ const format = tooltipDateFormatLookup(dateRange);
const formatCoreKey = (key) => {
return key.replace(/^core(\d+)$/, "Core $1");
};
@@ -213,7 +227,7 @@ export const TemperatureTooltip = ({ active, payload, label, keys, dotColor }) =
fontWeight: 500,
}}
>
- {formatDateWithTz(label, "ddd, MMMM D, YYYY, h:mm A", uiTimezone)}
+ {formatDateWithTz(label, format, uiTimezone)}
@@ -273,4 +287,6 @@ TemperatureTooltip.propTypes = {
PropTypes.string,
PropTypes.number,
]),
+ dotColor: PropTypes.string,
+ dateRange: PropTypes.string,
};
diff --git a/Client/src/Components/Inputs/Search/index.jsx b/Client/src/Components/Inputs/Search/index.jsx
index a2f35cac7..7a5154bb9 100644
--- a/Client/src/Components/Inputs/Search/index.jsx
+++ b/Client/src/Components/Inputs/Search/index.jsx
@@ -138,6 +138,7 @@ const Search = ({
}
return filtered;
}}
+ getOptionKey={(option) => option.id}
renderOption={(props, option) => {
const { key, ...optionProps } = props;
return (
diff --git a/Client/src/Components/TabPanels/Account/PasswordPanel.jsx b/Client/src/Components/TabPanels/Account/PasswordPanel.jsx
index a34c0000d..e5274f76a 100644
--- a/Client/src/Components/TabPanels/Account/PasswordPanel.jsx
+++ b/Client/src/Components/TabPanels/Account/PasswordPanel.jsx
@@ -126,8 +126,8 @@ const PasswordPanel = () => {
noValidate
spellCheck="false"
gap={theme.spacing(26)}
- maxWidth={"80ch"}
- marginInline={"auto"}
+ maxWidth={"80ch"} // Keep maxWidth
+
>
{
@@ -192,7 +192,7 @@ const PasswordPanel = () => {
@@ -255,4 +255,4 @@ PasswordPanel.propTypes = {
// No props are being passed to this component, hence no specific PropTypes are defined.
};
-export default PasswordPanel;
+export default PasswordPanel;
\ No newline at end of file
diff --git a/Client/src/Components/TabPanels/Account/TeamPanel.jsx b/Client/src/Components/TabPanels/Account/TeamPanel.jsx
index bbc6c4305..73d63e4b9 100644
--- a/Client/src/Components/TabPanels/Account/TeamPanel.jsx
+++ b/Client/src/Components/TabPanels/Account/TeamPanel.jsx
@@ -7,11 +7,10 @@ import { credentials } from "../../../Validation/validation";
import { networkService } from "../../../main";
import { createToast } from "../../../Utils/toastUtils";
import { useSelector } from "react-redux";
-import BasicTable from "../../BasicTable";
import Select from "../../Inputs/Select";
import LoadingButton from "@mui/lab/LoadingButton";
import { GenericDialog } from "../../Dialog/genericDialog";
-
+import DataTable from "../../Table/";
/**
* TeamPanel component manages the organization and team members,
* providing functionalities like renaming the organization, managing team members,
@@ -21,34 +20,47 @@ import { GenericDialog } from "../../Dialog/genericDialog";
*/
const TeamPanel = () => {
- const roleMap = {
- superadmin: "Super admin",
- admin: "Admin",
- user: "Team member",
- demo: "Demo User",
- };
-
const theme = useTheme();
const SPACING_GAP = theme.spacing(12);
- const { authToken, user } = useSelector((state) => state.auth);
- //TODO
- // const [orgStates, setOrgStates] = useState({
- // name: "Bluewave Labs",
- // isEdit: false,
- // });
+ const { authToken } = useSelector((state) => state.auth);
const [toInvite, setToInvite] = useState({
email: "",
role: ["0"],
});
- const [tableData, setTableData] = useState({});
+ const [data, setData] = useState([]);
const [members, setMembers] = useState([]);
const [filter, setFilter] = useState("all");
const [isDisabled, setIsDisabled] = useState(true);
const [errors, setErrors] = useState({});
const [isSendingInvite, setIsSendingInvite] = useState(false);
+ const headers = [
+ {
+ id: "name",
+ content: "Name",
+ render: (row) => {
+ return (
+
+
+ {row.firstName + " " + row.lastName}
+
+
+ Created {new Date(row.createdAt).toLocaleDateString()}
+
+
+ );
+ },
+ },
+ { id: "email", content: "Email", render: (row) => row.email },
+ {
+ id: "role",
+ content: "Role",
+ render: (row) => row.role,
+ },
+ ];
+
useEffect(() => {
const fetchTeam = async () => {
try {
@@ -67,6 +79,12 @@ const TeamPanel = () => {
}, [authToken]);
useEffect(() => {
+ const ROLE_MAP = {
+ superadmin: "Super admin",
+ admin: "Admin",
+ user: "Team member",
+ demo: "Demo User",
+ };
let team = members;
if (filter !== "all")
team = members.filter((member) => {
@@ -76,42 +94,14 @@ const TeamPanel = () => {
return member.role.includes(filter);
});
- const data = {
- cols: [
- { id: 1, name: "NAME" },
- { id: 2, name: "EMAIL" },
- { id: 3, name: "ROLE" },
- ],
- rows: team?.map((member, idx) => {
- const roles = member.role.map((role) => roleMap[role]).join(",");
- return {
- id: member._id,
- data: [
- {
- id: idx,
- data: (
-
-
- {member.firstName + " " + member.lastName}
-
-
- Created {new Date(member.createdAt).toLocaleDateString()}
-
-
- ),
- },
- { id: idx + 1, data: member.email },
- {
- id: idx + 2,
- data: roles,
- },
- ],
- };
- }),
- };
+ team = team.map((member) => ({
+ ...member,
+ id: member._id,
+ role: member.role.map((role) => ROLE_MAP[role]).join(","),
+ }));
+ setData(team);
+ }, [filter, members]);
- setTableData(data);
- }, [filter, members, roleMap, theme]);
useEffect(() => {
setIsDisabled(Object.keys(errors).length !== 0 || toInvite.email === "");
}, [errors, toInvite.email]);
@@ -248,12 +238,11 @@ const TeamPanel = () => {
Invite a team member
-
diff --git a/Client/src/Pages/Infrastructure/components/TablePagination/Actions/index.jsx b/Client/src/Components/Table/TablePagination/Actions/index.jsx
similarity index 85%
rename from Client/src/Pages/Infrastructure/components/TablePagination/Actions/index.jsx
rename to Client/src/Components/Table/TablePagination/Actions/index.jsx
index c684da145..c607fcb8b 100644
--- a/Client/src/Pages/Infrastructure/components/TablePagination/Actions/index.jsx
+++ b/Client/src/Components/Table/TablePagination/Actions/index.jsx
@@ -1,9 +1,9 @@
import PropTypes from "prop-types";
import { Box, Button } from "@mui/material";
-import LeftArrowDouble from "../../../../../assets/icons/left-arrow-double.svg?react";
-import RightArrowDouble from "../../../../../assets/icons/right-arrow-double.svg?react";
-import LeftArrow from "../../../../../assets/icons/left-arrow.svg?react";
-import RightArrow from "../../../../../assets/icons/right-arrow.svg?react";
+import LeftArrowDouble from "../../../../assets/icons/left-arrow-double.svg?react";
+import RightArrowDouble from "../../../../assets/icons/right-arrow-double.svg?react";
+import LeftArrow from "../../../../assets/icons/left-arrow.svg?react";
+import RightArrow from "../../../../assets/icons/right-arrow.svg?react";
TablePaginationActions.propTypes = {
count: PropTypes.number.isRequired,
diff --git a/Client/src/Pages/Infrastructure/components/TablePagination/index.jsx b/Client/src/Components/Table/TablePagination/index.jsx
similarity index 92%
rename from Client/src/Pages/Infrastructure/components/TablePagination/index.jsx
rename to Client/src/Components/Table/TablePagination/index.jsx
index bb618add8..a2c7ad067 100644
--- a/Client/src/Pages/Infrastructure/components/TablePagination/index.jsx
+++ b/Client/src/Components/Table/TablePagination/index.jsx
@@ -2,7 +2,7 @@ import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { Stack, TablePagination, Typography } from "@mui/material";
import { TablePaginationActions } from "./Actions";
-import SelectorVertical from "../../../../assets/icons/selector-vertical.svg?react";
+import SelectorVertical from "../../../assets/icons/selector-vertical.svg?react";
Pagination.propTypes = {
monitorCount: PropTypes.number.isRequired, // Total number of items for pagination.
@@ -94,6 +94,13 @@ function Pagination({
"&.Mui-focused > div": {
backgroundColor: theme.palette.background.main,
},
+ "& .MuiSelect-icon": {
+ // Add this style override
+ position: "absolute",
+ right: 0,
+ top: "50%",
+ transform: "translateY(-50%)",
+ },
},
},
}}
diff --git a/Client/src/Components/Table/index.jsx b/Client/src/Components/Table/index.jsx
new file mode 100644
index 000000000..3cef178f1
--- /dev/null
+++ b/Client/src/Components/Table/index.jsx
@@ -0,0 +1,113 @@
+import {
+ Paper,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableRow,
+} from "@mui/material";
+import PropTypes from "prop-types";
+import { useTheme } from "@emotion/react";
+
+/**
+ * @typedef {Object} Header
+ * @property {number|string} id - The unique identifier for the header.
+ * @property {React.ReactNode} content - The content to display in the header cell.
+ * @property {Function} render - A function to render the cell content for a given row.
+ */
+
+/**
+ * @typedef {Object} Config
+ * @property {Function} onRowClick - A function to be called when a row is clicked, receiving the row data as an argument.
+ * @property {Object} rowSX - Style object for the table row.
+ */
+
+/**
+ * DataTable component renders a table with headers and data.
+ *
+ * @param {Object} props - The component props.
+ * @param {Header[]} props.headers - An array of header objects, each containing an `id`, `content`, and `render` function.
+ * @param {Array} props.data - An array of data objects, each representing a row.
+ * @returns {JSX.Element} The rendered table component.
+ */
+
+const DataTable = ({ headers, data, config = { emptyView: "No data" } }) => {
+ const theme = useTheme();
+ if ((headers?.length ?? 0) === 0) {
+ return "No data";
+ }
+
+ return (
+
+
+
+
+ {headers.map((header, index) => (
+
+ {header.content}
+
+ ))}
+
+
+
+ {(data?.length ?? 0) === 0 ? (
+
+
+ {config.emptyView}
+
+
+ ) : (
+ data.map((row) => {
+ return (
+ config?.onRowClick(row)}
+ >
+ {headers.map((header, index) => {
+ return (
+
+ {header.render(row)}
+
+ );
+ })}
+
+ );
+ })
+ )}
+
+
+
+ );
+};
+
+DataTable.propTypes = {
+ headers: PropTypes.arrayOf(
+ PropTypes.shape({
+ id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+ content: PropTypes.node.isRequired,
+ render: PropTypes.func.isRequired,
+ })
+ ).isRequired,
+ data: PropTypes.array.isRequired,
+ config: PropTypes.shape({
+ onRowClick: PropTypes.func.isRequired,
+ rowSX: PropTypes.object,
+ emptyView: PropTypes.node,
+ }),
+};
+
+export default DataTable;
diff --git a/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js b/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js
index 6cd91ac28..b2da2cbe6 100644
--- a/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js
+++ b/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js
@@ -40,7 +40,7 @@ export const checkEndpointResolution = createAsyncThunk(
const res = await networkService.checkEndpointResolution({
authToken: authToken,
monitorURL: monitorURL,
- })
+ });
return res.data;
} catch (error) {
if (error.response && error.response.data) {
@@ -53,7 +53,7 @@ export const checkEndpointResolution = createAsyncThunk(
return thunkApi.rejectWithValue(payload);
}
}
-)
+);
export const getPagespeedMonitorById = createAsyncThunk(
"monitors/getMonitorById",
@@ -88,7 +88,6 @@ export const getPageSpeedByTeamId = createAsyncThunk(
teamId: user.teamId,
types: ["pagespeed"],
});
-
return res.data;
} catch (error) {
if (error.response && error.response.data) {
diff --git a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js b/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js
index 78250c19a..7ea34b9ea 100644
--- a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js
+++ b/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js
@@ -78,30 +78,6 @@ export const getUptimeMonitorById = createAsyncThunk(
}
);
-export const getUptimeMonitorsByTeamId = createAsyncThunk(
- "monitors/getMonitorsByTeamId",
- async (token, thunkApi) => {
- const user = jwtDecode(token);
- try {
- const res = await networkService.getMonitorsAndSummaryByTeamId({
- authToken: token,
- teamId: user.teamId,
- types: ["http", "ping", "docker", "port"],
- });
- return res.data;
- } catch (error) {
- if (error.response && error.response.data) {
- return thunkApi.rejectWithValue(error.response.data);
- }
- const payload = {
- status: false,
- msg: error.message ? error.message : "Unknown error",
- };
- return thunkApi.rejectWithValue(payload);
- }
- }
-);
-
export const updateUptimeMonitor = createAsyncThunk(
"monitors/updateMonitor",
async (data, thunkApi) => {
@@ -257,26 +233,6 @@ const uptimeMonitorsSlice = createSlice({
},
extraReducers: (builder) => {
builder
- // *****************************************************
- // Monitors by teamId
- // *****************************************************
-
- .addCase(getUptimeMonitorsByTeamId.pending, (state) => {
- state.isLoading = true;
- })
- .addCase(getUptimeMonitorsByTeamId.fulfilled, (state, action) => {
- state.isLoading = false;
- state.success = action.payload.msg;
- state.monitorsSummary = action.payload.data;
- })
- .addCase(getUptimeMonitorsByTeamId.rejected, (state, action) => {
- state.isLoading = false;
- state.success = false;
- state.msg = action.payload
- ? action.payload.msg
- : "Getting uptime monitors failed";
- })
-
// *****************************************************
// Create Monitor
// *****************************************************
diff --git a/Client/src/Pages/Incidents/IncidentTable/index.jsx b/Client/src/Pages/Incidents/IncidentTable/index.jsx
index 1d0be7666..b4ef9fdb8 100644
--- a/Client/src/Pages/Incidents/IncidentTable/index.jsx
+++ b/Client/src/Pages/Incidents/IncidentTable/index.jsx
@@ -1,17 +1,5 @@
import PropTypes from "prop-types";
-import {
- TableContainer,
- Table,
- TableHead,
- TableRow,
- TableCell,
- TableBody,
- Pagination,
- PaginationItem,
- Paper,
- Typography,
- Box,
-} from "@mui/material";
+import { Pagination, PaginationItem, Typography, Box } from "@mui/material";
import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded";
import ArrowForwardRoundedIcon from "@mui/icons-material/ArrowForwardRounded";
@@ -27,7 +15,7 @@ import PlaceholderDark from "../../../assets/Images/data_placeholder_dark.svg?re
import { HttpStatusLabel } from "../../../Components/HttpStatusLabel";
import { Empty } from "./Empty/Empty";
import { IncidentSkeleton } from "./Skeleton/Skeleton";
-
+import DataTable from "../../../Components/Table";
const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
const uiTimezone = useSelector((state) => state.ui.timezone);
@@ -106,6 +94,46 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
});
};
+ const headers = [
+ {
+ id: "monitorName",
+ content: "Monitor Name",
+ render: (row) => monitors[row.monitorId]?.name ?? "N/A",
+ },
+ {
+ id: "status",
+ content: "Status",
+ render: (row) => {
+ const status = row.status === true ? "up" : "down";
+ return (
+
+ );
+ },
+ },
+ {
+ id: "dateTime",
+ content: "Date & Time",
+ render: (row) => {
+ const formattedDate = formatDateWithTz(
+ row.createdAt,
+ "YYYY-MM-DD HH:mm:ss A",
+ uiTimezone
+ );
+ return formattedDate;
+ },
+ },
+ {
+ id: "statusCode",
+ content: "Status Code",
+ render: (row) => ,
+ },
+ { id: "message", content: "Message", render: (row) => row.message },
+ ];
+
let paginationComponent = <>>;
if (checksCount > paginationController.rowsPerPage) {
paginationComponent = (
@@ -166,47 +194,11 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
) : (
<>
-
-
-
-
- Monitor Name
- Status
- Date & Time
- Status Code
- Message
-
-
-
- {checks.map((check) => {
- const status = check.status === true ? "up" : "down";
- const formattedDate = formatDateWithTz(
- check.createdAt,
- "YYYY-MM-DD HH:mm:ss A",
- uiTimezone
- );
-
- return (
-
- {monitors[check.monitorId]?.name}
-
-
-
- {formattedDate}
-
-
-
- {check.message}
-
- );
- })}
-
-
-
+
+
{paginationComponent}
>
)}
diff --git a/Client/src/Pages/Infrastructure/Details/index.jsx b/Client/src/Pages/Infrastructure/Details/index.jsx
index 29eddb9fc..5a0f59d5c 100644
--- a/Client/src/Pages/Infrastructure/Details/index.jsx
+++ b/Client/src/Pages/Infrastructure/Details/index.jsx
@@ -13,9 +13,8 @@ import { useNavigate } from "react-router-dom";
import Empty from "./empty";
import { logger } from "../../../Utils/Logger";
import { formatDurationRounded, formatDurationSplit } from "../../../Utils/timeUtils";
+import { TzTick, PercentTick } from "../../../Components/Charts/Utils/chartUtils";
import {
- TzTick,
- PercentTick,
InfrastructureTooltip,
TemperatureTooltip,
} from "../../../Components/Charts/Utils/chartUtils";
@@ -414,12 +413,13 @@ const InfrastructureDetails = () => {
yLabel: "Memory usage",
yDomain: [0, 1],
yTick: ,
- xTick: ,
+ xTick: ,
toolTip: (
),
},
@@ -433,12 +433,13 @@ const InfrastructureDetails = () => {
yLabel: "CPU usage",
yDomain: [0, 1],
yTick: ,
- xTick: ,
+ xTick: ,
toolTip: (
),
},
@@ -450,7 +451,7 @@ const InfrastructureDetails = () => {
gradientStartColor: theme.palette.error.main,
heading: "CPU Temperature",
yLabel: "Temperature",
- xTick: ,
+ xTick: ,
yDomain: [
0,
Math.max(
@@ -462,6 +463,7 @@ const InfrastructureDetails = () => {
),
},
@@ -476,13 +478,14 @@ const InfrastructureDetails = () => {
yLabel: "Disk Usage",
yDomain: [0, 1],
yTick: ,
- xTick: ,
+ xTick: ,
toolTip: (
),
})) || []),
diff --git a/Client/src/Pages/Infrastructure/index.jsx b/Client/src/Pages/Infrastructure/index.jsx
index aba4369e6..d9ef7528e 100644
--- a/Client/src/Pages/Infrastructure/index.jsx
+++ b/Client/src/Pages/Infrastructure/index.jsx
@@ -1,4 +1,4 @@
-import { useEffect, useState } from "react";
+import { useEffect, useState, useCallback } from "react";
import { useNavigate } from "react-router-dom";
import { /* useDispatch, */ useSelector } from "react-redux";
import { useTheme } from "@emotion/react";
@@ -8,23 +8,12 @@ import SkeletonLayout from "./skeleton";
import Fallback from "../../Components/Fallback";
// import GearIcon from "../../Assets/icons/settings-bold.svg?react";
import CPUChipIcon from "../../assets/icons/cpu-chip.svg?react";
-import {
- Box,
- Button,
- IconButton,
- Paper,
- Stack,
- Table,
- TableBody,
- TableCell,
- TableContainer,
- TableHead,
- TableRow,
-} from "@mui/material";
+import DataTable from "../../Components/Table";
+import { Box, Button, IconButton, Stack } from "@mui/material";
import Breadcrumbs from "../../Components/Breadcrumbs";
import { StatusLabel } from "../../Components/Label";
import { Heading } from "../../Components/Heading";
-import { Pagination } from "./components/TablePagination";
+import { Pagination } from "../../Components/Table/TablePagination/index.jsx";
// import { getInfrastructureMonitorsByTeamId } from "../../Features/InfrastructureMonitors/infrastructureMonitorsSlice";
import { networkService } from "../../Utils/NetworkService.js";
import CustomGauge from "../../Components/Charts/CustomGauge/index.jsx";
@@ -32,42 +21,8 @@ import Host from "../Uptime/Home/host.jsx";
import { useIsAdmin } from "../../Hooks/useIsAdmin.js";
import { InfrastructureMenu } from "./components/Menu";
-const columns = [
- { label: "Host" },
- { label: "Status" },
- { label: "Frequency" },
- { label: "CPU" },
- { label: "Mem" },
- { label: "Disk" },
- { label: "Actions" },
-];
-
const BREADCRUMBS = [{ name: `infrastructure`, path: "/infrastructure" }];
-/* TODO
-Create reusable table component.
-It should receive as a parameter the following object:
-tableData = [
- columns = [
- {
- id: example,
- label: Example Extendable,
- align: "center" | "left" (default)
- }
- ],
- rows: [
- {
- **Number of keys will be equal to number of columns**
- key1: string,
- key2: number,
- key3: Component
- }
- ]
-]
-Apply to Monitor Table, and Account/Team.
-Analyze existing BasicTable
-*/
-
/**
* This is the Infrastructure monitoring page. This is a work in progress
*
@@ -95,12 +50,13 @@ function Infrastructure() {
setRowsPerPage(parseInt(event.target.value));
setPage(0);
};
- const [monitorState, setMonitorState] = useState({ monitors: [], total: 0 });
+ const [monitors, setMonitors] = useState([]);
+ const [summary, setSummary] = useState({});
const { authToken } = useSelector((state) => state.auth);
const user = jwtDecode(authToken);
- const fetchMonitors = async () => {
+ const fetchMonitors = useCallback(async () => {
try {
setIsLoading(true);
const response = await networkService.getMonitorsByTeamId({
@@ -108,29 +64,23 @@ function Infrastructure() {
teamId: user.teamId,
limit: 1,
types: ["hardware"],
- status: null,
- checkOrder: "desc",
- normalize: true,
page: page,
rowsPerPage: rowsPerPage,
});
- setMonitorState({
- monitors: response?.data?.data?.monitors ?? [],
- total: response?.data?.data?.monitorCount ?? 0,
- });
+ setMonitors(response?.data?.data?.monitors ?? []);
+ setSummary(response?.data?.data?.summary ?? {});
} catch (error) {
console.error(error);
} finally {
setIsLoading(false);
}
- };
+ }, [page, rowsPerPage, authToken, user.teamId]);
useEffect(() => {
fetchMonitors();
- }, [page, rowsPerPage]);
+ }, [fetchMonitors]);
const { determineState } = useUtils();
- const { monitors, total: totalMonitors } = monitorState;
// do it here
function openDetails(id) {
navigate(`/infrastructure/${id}`);
@@ -139,6 +89,71 @@ function Infrastructure() {
fetchMonitors();
}
+ const headers = [
+ {
+ id: "host",
+ content: "Host",
+ render: (row) => (
+
+ ),
+ },
+ {
+ id: "status",
+ content: "Status",
+ render: (row) => (
+
+ ),
+ },
+ {
+ id: "frequency",
+ content: "Frequency",
+ render: (row) => (
+
+
+ {row.processor}
+
+ ),
+ },
+ { id: "cpu", content: "CPU", render: (row) => },
+ { id: "mem", content: "Mem", render: (row) => },
+ { id: "disk", content: "Disk", render: (row) => },
+ {
+ id: "actions",
+ content: "Actions",
+ render: (row) => (
+
+
+
+ ),
+ },
+ ];
+
const monitorsAsRows = monitors.map((monitor) => {
const processor =
((monitor.checks[0]?.cpu?.usage_frequency ?? 0) / 1000).toFixed(2) + " GHz";
@@ -171,7 +186,7 @@ function Infrastructure() {
};
});
- let isActuallyLoading = isLoading && monitorState.monitors?.length === 0;
+ let isActuallyLoading = isLoading && monitors?.length === 0;
return (
{isActuallyLoading ? (
- ) : monitorState.monitors?.length !== 0 ? (
+ ) : monitors?.length !== 0 ? (
@@ -234,108 +249,25 @@ function Infrastructure() {
borderColor={theme.palette.border.light}
backgroundColor={theme.palette.background.accent}
>
- {totalMonitors}
+ {summary?.totalMonitors ?? 0}
-
-
-
-
- {columns.map((column, index) => (
-
- {column.label}
-
- ))}
-
-
-
- {monitorsAsRows.map((row) => {
- return (
- openDetails(row.id)}
- sx={{
- cursor: "pointer",
- "&:hover": {
- backgroundColor: theme.palette.background.accent,
- },
- }}
- >
- {/* TODO iterate over column and get column id, applying row[column.id] */}
-
-
-
-
-
-
-
-
-
- {row.processor}
-
-
-
-
-
-
-
-
-
-
-
-
- {/* Get ActionsMenu from Monitor Table and create a component */}
-
-
- {/* */}
-
-
-
- );
- })}
-
-
-
+
+ openDetails(row.id),
+ }}
+ headers={headers}
+ data={monitorsAsRows}
+ />
{
sx={{ gridColumnStart: 1, gridColumnEnd: 4 }}
>
diff --git a/Client/src/Pages/PageSpeed/index.jsx b/Client/src/Pages/PageSpeed/index.jsx
index b18148aa8..98761be6f 100644
--- a/Client/src/Pages/PageSpeed/index.jsx
+++ b/Client/src/Pages/PageSpeed/index.jsx
@@ -1,8 +1,7 @@
import { Box, Button, Grid, Stack } from "@mui/material";
import { useEffect, useState } from "react";
import { useTheme } from "@emotion/react";
-import { useDispatch, useSelector } from "react-redux";
-import { getPageSpeedByTeamId } from "../../Features/PageSpeedMonitor/pageSpeedMonitorSlice";
+import { useSelector } from "react-redux";
import Fallback from "../../Components/Fallback";
import "./index.css";
import { useNavigate } from "react-router";
@@ -15,16 +14,12 @@ import { Heading } from "../../Components/Heading";
import { useIsAdmin } from "../../Hooks/useIsAdmin";
const PageSpeed = () => {
const theme = useTheme();
- const dispatch = useDispatch();
const navigate = useNavigate();
const isAdmin = useIsAdmin();
const { user, authToken } = useSelector((state) => state.auth);
const [isLoading, setIsLoading] = useState(true);
const [monitors, setMonitors] = useState([]);
- const [monitorCount, setMonitorCount] = useState(0);
- useEffect(() => {
- dispatch(getPageSpeedByTeamId(authToken));
- }, [authToken, dispatch]);
+ const [summary, setSummary] = useState({});
useEffect(() => {
const fetchMonitors = async () => {
@@ -35,9 +30,6 @@ const PageSpeed = () => {
teamId: user.teamId,
limit: 10,
types: ["pagespeed"],
- status: null,
- checkOrder: "desc",
- normalize: true,
page: null,
rowsPerPage: null,
filter: null,
@@ -46,7 +38,7 @@ const PageSpeed = () => {
});
if (res?.data?.data?.monitors) {
setMonitors(res.data.data.monitors);
- setMonitorCount(res.data.data.monitorCount);
+ setSummary(res.data.data.summary);
}
} catch (error) {
console.log(error);
@@ -118,7 +110,7 @@ const PageSpeed = () => {
borderColor={theme.palette.border.light}
backgroundColor={theme.palette.background.accent}
>
- {monitorCount}
+ {summary?.totalMonitors ?? 0}
{
const action = await dispatch(updateUptimeMonitor({ authToken, monitor: monitor }));
if (action.meta.requestStatus === "fulfilled") {
createToast({ body: "Monitor updated successfully!" });
- dispatch(getUptimeMonitorsByTeamId(authToken));
} else {
createToast({ body: "Failed to update monitor." });
}
diff --git a/Client/src/Pages/Uptime/CreateUptime/index.jsx b/Client/src/Pages/Uptime/CreateUptime/index.jsx
index c5a9df770..3499a9c73 100644
--- a/Client/src/Pages/Uptime/CreateUptime/index.jsx
+++ b/Client/src/Pages/Uptime/CreateUptime/index.jsx
@@ -58,7 +58,7 @@ const CreateMonitor = () => {
};
const { user, authToken } = useSelector((state) => state.auth);
- const { monitors, isLoading } = useSelector((state) => state.uptimeMonitors);
+ const { isLoading } = useSelector((state) => state.uptimeMonitors);
const dispatch = useDispatch();
const navigate = useNavigate();
const theme = useTheme();
@@ -205,7 +205,7 @@ const CreateMonitor = () => {
}
};
fetchMonitor();
- }, [monitorId, authToken, monitors, dispatch, navigate]);
+ }, [monitorId, authToken, dispatch, navigate]);
return (
diff --git a/Client/src/Pages/Uptime/Details/index.jsx b/Client/src/Pages/Uptime/Details/index.jsx
index 7a2aa6756..d1d40a8b1 100644
--- a/Client/src/Pages/Uptime/Details/index.jsx
+++ b/Client/src/Pages/Uptime/Details/index.jsx
@@ -308,7 +308,9 @@ const DetailsPage = () => {
{hoveredUptimeData !== null
? hoveredUptimeData.totalChecks
- : (monitor.stats?.upChecksAggregate?.totalChecks ?? 0)}
+ : (monitor.stats?.upChecks?.reduce((count, checkGroup) => {
+ return count + checkGroup.totalChecks;
+ }, 0) ?? 0)}
{hoveredUptimeData !== null && hoveredUptimeData.time !== null && (
{
{hoveredIncidentsData !== null
? hoveredIncidentsData.totalChecks
- : (monitor.stats?.downChecksAggregate?.totalChecks ?? 0)}
+ : (monitor.stats?.downChecks?.reduce((count, checkGroup) => {
+ return count + checkGroup.totalChecks;
+ }, 0) ?? 0)}
{hoveredIncidentsData !== null &&
hoveredIncidentsData.time !== null && (
@@ -410,7 +414,10 @@ const DetailsPage = () => {
Response Times
-
+
{
- const theme = useTheme();
- const [search, setSearch] = useState("");
- const [isSearching, setIsSearching] = useState(false);
- const debouncedFilter = useDebounce(search, 500);
- const handleSearch = (value) => {
- setIsSearching(true);
- setSearch(value);
- };
- return (
-
-
- Uptime monitors
-
-
- {totalMonitors}
-
-
-
-
-
-
-
- );
-};
-
-CurrentMonitoring.propTypes = {
- handlePause: PropTypes.func,
- totalMonitors: PropTypes.number,
- monitors: PropTypes.array,
- isAdmin: PropTypes.bool,
-};
-
-export { CurrentMonitoring };
diff --git a/Client/src/Pages/Uptime/Home/UptimeDataTable/Skeleton/index.jsx b/Client/src/Pages/Uptime/Home/UptimeDataTable/Skeleton/index.jsx
new file mode 100644
index 000000000..c17f6d74b
--- /dev/null
+++ b/Client/src/Pages/Uptime/Home/UptimeDataTable/Skeleton/index.jsx
@@ -0,0 +1,47 @@
+import { Skeleton } from "@mui/material";
+import DataTable from "../../../../../Components/Table";
+const ROWS_NUMBER = 7;
+const ROWS_ARRAY = Array.from({ length: ROWS_NUMBER }, (_, i) => i);
+
+const TableSkeleton = () => {
+ /* TODO Skeleton does not follow light and dark theme */
+
+ const headers = [
+ {
+ id: "name",
+
+ content: "Host",
+
+ render: () => ,
+ },
+ {
+ id: "status",
+ content: "Status",
+ render: () => ,
+ },
+ {
+ id: "responseTime",
+ content: "Response Time",
+ render: () => ,
+ },
+ {
+ id: "type",
+ content: "Type",
+ render: () => ,
+ },
+ {
+ id: "actions",
+ content: "Actions",
+ render: () => ,
+ },
+ ];
+
+ return (
+
+ );
+};
+
+export { TableSkeleton };
diff --git a/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx b/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx
new file mode 100644
index 000000000..31b6182fc
--- /dev/null
+++ b/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx
@@ -0,0 +1,315 @@
+// Components
+import { Box, Stack, CircularProgress } from "@mui/material";
+import Search from "../../../../Components/Inputs/Search";
+import { Heading } from "../../../../Components/Heading";
+import DataTable from "../../../../Components/Table";
+import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded";
+import ArrowUpwardRoundedIcon from "@mui/icons-material/ArrowUpwardRounded";
+import Host from "../host";
+import { StatusLabel } from "../../../../Components/Label";
+import BarChart from "../../../../Components/Charts/BarChart";
+import ActionsMenu from "../actionsMenu";
+
+// Utils
+import { useTheme } from "@emotion/react";
+import useUtils from "../../utils";
+import { useState, memo, useCallback } from "react";
+import { useNavigate } from "react-router-dom";
+import "../index.css";
+import PropTypes from "prop-types";
+
+const SearchComponent = memo(
+ ({ monitors, debouncedSearch, onSearchChange, setIsSearching }) => {
+ const [localSearch, setLocalSearch] = useState(debouncedSearch);
+ const handleSearch = useCallback(
+ (value) => {
+ setIsSearching(true);
+ setLocalSearch(value);
+ onSearchChange(value);
+ },
+ [onSearchChange, setIsSearching]
+ );
+
+ return (
+
+
+
+ );
+ }
+);
+SearchComponent.displayName = "SearchComponent";
+SearchComponent.propTypes = {
+ monitors: PropTypes.array,
+ debouncedSearch: PropTypes.string,
+ onSearchChange: PropTypes.func,
+ setIsSearching: PropTypes.func,
+};
+
+/**
+ * UptimeDataTable displays a table of uptime monitors with sorting, searching, and action capabilities
+ * @param {Object} props - Component props
+ * @param {boolean} props.isAdmin - Whether the current user has admin privileges
+ * @param {boolean} props.isLoading - Loading state of the table
+ * @param {Array<{
+ * _id: string,
+ * url: string,
+ * title: string,
+ * percentage: number,
+ * percentageColor: string,
+ * monitor: {
+ * _id: string,
+ * type: string,
+ * checks: Array
+ * }
+ * }>} props.monitors - Array of monitor objects to display
+ * @param {number} props.monitorCount - Total count of monitors
+ * @param {Object} props.sort - Current sort configuration
+ * @param {string} props.sort.field - Field to sort by
+ * @param {'asc'|'desc'} props.sort.order - Sort direction
+ * @param {Function} props.setSort - Callback to update sort configuration
+ * @param {string} props.search - Current search query
+ * @param {Function} props.setSearch - Callback to update search query
+ * @param {boolean} props.isSearching - Whether a search is in progress
+ * @param {Function} props.setIsSearching - Callback to update search state
+ * @param {Function} props.setIsLoading - Callback to update loading state
+ * @param {Function} props.triggerUpdate - Callback to trigger a data refresh
+ * @returns {JSX.Element} Rendered component
+ */
+const UptimeDataTable = ({
+ isAdmin,
+ isLoading,
+ monitors,
+ monitorCount,
+ sort,
+ setSort,
+ debouncedSearch,
+ setSearch,
+ isSearching,
+ setIsSearching,
+ setIsLoading,
+ triggerUpdate,
+}) => {
+ const { determineState } = useUtils();
+
+ const theme = useTheme();
+ const navigate = useNavigate();
+
+ const handleSort = (field) => {
+ let order = "";
+ if (sort.field !== field) {
+ order = "desc";
+ } else {
+ order = sort.order === "asc" ? "desc" : "asc";
+ }
+ setSort({ field, order });
+ };
+
+ const headers = [
+ {
+ id: "name",
+ content: (
+ handleSort("name")}
+ >
+ Host
+
+ {sort.order === "asc" ? (
+
+ ) : (
+
+ )}
+
+
+ ),
+ render: (row) => (
+
+ ),
+ },
+ {
+ id: "status",
+ content: (
+ handleSort("status")}
+ >
+ {" "}
+ Status
+
+ {sort.order === "asc" ? (
+
+ ) : (
+
+ )}
+
+
+ ),
+ render: (row) => {
+ const status = determineState(row.monitor);
+ return (
+
+ );
+ },
+ },
+ {
+ id: "responseTime",
+ content: "Response Time",
+ render: (row) => ,
+ },
+ {
+ id: "type",
+ content: "Type",
+ render: (row) => (
+ {row.monitor.type}
+ ),
+ },
+ {
+ id: "actions",
+ content: "Actions",
+ render: (row) => (
+
+ ),
+ },
+ ];
+
+ return (
+
+
+ Uptime monitors
+
+
+ {monitorCount}
+
+
+
+
+
+ {(isSearching || isLoading) && (
+ <>
+
+
+
+
+ >
+ )}
+ {
+ navigate(`/uptime/${row.id}`);
+ },
+ emptyView: "No monitors found",
+ }}
+ />
+
+
+ );
+};
+
+const MemoizedUptimeDataTable = memo(UptimeDataTable);
+export default MemoizedUptimeDataTable;
+
+UptimeDataTable.propTypes = {
+ isAdmin: PropTypes.bool,
+ isLoading: PropTypes.bool,
+ monitors: PropTypes.array,
+ monitorCount: PropTypes.number,
+ sort: PropTypes.shape({
+ field: PropTypes.string,
+ order: PropTypes.oneOf(["asc", "desc"]),
+ }),
+ setSort: PropTypes.func,
+ debouncedSearch: PropTypes.string,
+ setSearch: PropTypes.func,
+ isSearching: PropTypes.bool,
+ setIsSearching: PropTypes.func,
+ setIsLoading: PropTypes.func,
+ triggerUpdate: PropTypes.func,
+};
diff --git a/Client/src/Pages/Uptime/Home/UptimeTable/Skeleton/index.jsx b/Client/src/Pages/Uptime/Home/UptimeTable/Skeleton/index.jsx
deleted file mode 100644
index 22d885c97..000000000
--- a/Client/src/Pages/Uptime/Home/UptimeTable/Skeleton/index.jsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { Skeleton, TableCell, TableRow } from "@mui/material";
-const ROWS_NUMBER = 7;
-const ROWS_ARRAY = Array.from({ length: ROWS_NUMBER }, (_, i) => i);
-
-const TableBodySkeleton = () => {
- /* TODO Skeleton does not follow light and dark theme */
- return (
- <>
- {ROWS_ARRAY.map((row) => (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ))}
- >
- );
-};
-
-export { TableBodySkeleton };
diff --git a/Client/src/Pages/Uptime/Home/UptimeTable/index.jsx b/Client/src/Pages/Uptime/Home/UptimeTable/index.jsx
deleted file mode 100644
index a19da2a43..000000000
--- a/Client/src/Pages/Uptime/Home/UptimeTable/index.jsx
+++ /dev/null
@@ -1,336 +0,0 @@
-import PropTypes from "prop-types";
-import { useState, useEffect, memo, useCallback, useRef } from "react";
-import { useNavigate } from "react-router";
-import { useDispatch, useSelector } from "react-redux";
-import { useTheme } from "@emotion/react";
-import useUtils from "../../utils";
-
-import { setRowsPerPage } from "../../../../Features/UI/uiSlice";
-import { logger } from "../../../../Utils/Logger";
-import { jwtDecode } from "jwt-decode";
-import { networkService } from "../../../../main";
-
-import {
- TableContainer,
- Table,
- TableHead,
- TableRow,
- TableCell,
- TableBody,
- Paper,
- Box,
- CircularProgress,
-} from "@mui/material";
-import ActionsMenu from "../actionsMenu";
-import Host from "../host";
-import { StatusLabel } from "../../../../Components/Label";
-import { TableBodySkeleton } from "./Skeleton";
-import BarChart from "../../../../Components/Charts/BarChart";
-
-import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded";
-import ArrowUpwardRoundedIcon from "@mui/icons-material/ArrowUpwardRounded";
-
-import { Pagination } from "../../../Infrastructure/components/TablePagination";
-
-const MonitorTable = ({ isAdmin, filter, setIsSearching, isSearching, handlePause }) => {
- const theme = useTheme();
- const navigate = useNavigate();
- const dispatch = useDispatch();
- const { determineState } = useUtils();
-
- const { rowsPerPage } = useSelector((state) => state.ui.monitors);
- const authState = useSelector((state) => state.auth);
-
- const [page, setPage] = useState(0);
- const [monitors, setMonitors] = useState([]);
- const [monitorCount, setMonitorCount] = useState(0);
- const [updateTrigger, setUpdateTrigger] = useState(false);
- const [sort, setSort] = useState({});
- const prevFilter = useRef(filter);
-
- const handleRowUpdate = () => {
- setUpdateTrigger((prev) => !prev);
- };
-
- const handleChangePage = (event, newPage) => {
- setPage(newPage);
- };
-
- const handleChangeRowsPerPage = (event) => {
- dispatch(
- setRowsPerPage({
- value: parseInt(event.target.value, 10),
- table: "monitors",
- })
- );
- setPage(0);
- };
-
- const fetchPage = useCallback(async () => {
- try {
- const { authToken } = authState;
- const user = jwtDecode(authToken);
- const res = await networkService.getMonitorsByTeamId({
- authToken,
- teamId: user.teamId,
- limit: 25,
- types: ["http", "ping", "docker", "port"],
- status: null,
- checkOrder: "desc",
- normalize: true,
- page: page,
- rowsPerPage: rowsPerPage,
- filter: filter,
- field: sort.field,
- order: sort.order,
- });
- setMonitors(res?.data?.data?.monitors ?? []);
- setMonitorCount(res?.data?.data?.monitorCount ?? 0);
- } catch (error) {
- logger.error(error);
- } finally {
- setIsSearching(false);
- }
- }, [authState, page, rowsPerPage, filter, sort, setIsSearching]);
-
- useEffect(() => {
- fetchPage();
- }, [
- updateTrigger,
- authState,
- page,
- rowsPerPage,
- filter,
- sort,
- setIsSearching,
- fetchPage,
- ]);
-
- // Listen for changes in filter, if new value reset the page
- useEffect(() => {
- if (prevFilter.current !== filter) {
- setPage(0);
- fetchPage();
- }
- prevFilter.current = filter;
- }, [filter, fetchPage]);
-
- const handleSort = async (field) => {
- let order = "";
- if (sort.field !== field) {
- order = "desc";
- } else {
- order = sort.order === "asc" ? "desc" : "asc";
- }
- setSort({ field, order });
-
- const { authToken } = authState;
- const user = jwtDecode(authToken);
-
- const res = await networkService.getMonitorsByTeamId({
- authToken,
- teamId: user.teamId,
- limit: 25,
- types: ["http", "ping"],
- status: null,
- checkOrder: "desc",
- normalize: true,
- page: page,
- rowsPerPage: rowsPerPage,
- filter: null,
- field: field,
- order: order,
- });
- setMonitors(res?.data?.data?.monitors ?? []);
- setMonitorCount(res?.data?.data?.monitorCount ?? 0);
- };
- /* TODO Apply component basic table? */
- return (
-
- {isSearching && (
- <>
-
-
-
-
- >
- )}
-
-
-
-
- handleSort("name")}
- >
-
- Host
-
- {sort.order === "asc" ? (
-
- ) : (
-
- )}
-
-
-
- handleSort("status")}
- >
- {" "}
-
- {" "}
- Status
-
- {sort.order === "asc" ? (
-
- ) : (
-
- )}
-
-
-
- Response Time
- Type
- Actions
-
-
-
- {/* TODO add empty state. Check if is searching, and empty => skeleton. Is empty, not searching => skeleton */}
- {monitors.length > 0 ? (
- monitors.map((monitor) => {
- let uptimePercentage = "";
- let percentageColor = theme.palette.percentage.uptimeExcellent;
-
- // Determine uptime percentage and color based on the monitor's uptimePercentage value
- if (monitor.uptimePercentage !== undefined) {
- uptimePercentage =
- monitor.uptimePercentage === 0
- ? "0"
- : (monitor.uptimePercentage * 100).toFixed(2);
-
- percentageColor =
- monitor.uptimePercentage < 0.25
- ? theme.palette.percentage.uptimePoor
- : monitor.uptimePercentage < 0.5
- ? theme.palette.percentage.uptimeFair
- : monitor.uptimePercentage < 0.75
- ? theme.palette.percentage.uptimeGood
- : theme.palette.percentage.uptimeExcellent;
- }
-
- const params = {
- url: monitor.url,
- title: monitor.name,
- percentage: uptimePercentage,
- percentageColor,
- status: determineState(monitor),
- };
-
- return (
- {
- navigate(`/uptime/${monitor._id}`);
- }}
- >
-
-
-
-
-
-
-
-
-
-
- {monitor.type}
-
-
-
-
-
- );
- })
- ) : (
-
- )}
-
-
-
-
-
- );
-};
-
-MonitorTable.propTypes = {
- isAdmin: PropTypes.bool,
- filter: PropTypes.string,
- setIsSearching: PropTypes.func,
- isSearching: PropTypes.bool,
- setMonitorUpdateTrigger: PropTypes.func,
- handlePause: PropTypes.func,
-};
-
-const MemoizedMonitorTable = memo(MonitorTable);
-export default MemoizedMonitorTable;
diff --git a/Client/src/Pages/Uptime/Home/actionsMenu.jsx b/Client/src/Pages/Uptime/Home/actionsMenu.jsx
index 01452b718..fe4f4b2e8 100644
--- a/Client/src/Pages/Uptime/Home/actionsMenu.jsx
+++ b/Client/src/Pages/Uptime/Home/actionsMenu.jsx
@@ -8,13 +8,18 @@ import { IconButton, Menu, MenuItem } from "@mui/material";
import {
deleteUptimeMonitor,
pauseUptimeMonitor,
- getUptimeMonitorsByTeamId,
} from "../../../Features/UptimeMonitors/uptimeMonitorsSlice";
import Settings from "../../../assets/icons/settings-bold.svg?react";
import PropTypes from "prop-types";
import Dialog from "../../../Components/Dialog";
-const ActionsMenu = ({ monitor, isAdmin, updateRowCallback, pauseCallback }) => {
+const ActionsMenu = ({
+ monitor,
+ isAdmin,
+ updateRowCallback,
+ pauseCallback,
+ setIsLoading,
+}) => {
const [anchorEl, setAnchorEl] = useState(null);
const [actions, setActions] = useState({});
const [isOpen, setIsOpen] = useState(false);
@@ -33,8 +38,7 @@ const ActionsMenu = ({ monitor, isAdmin, updateRowCallback, pauseCallback }) =>
);
if (action.meta.requestStatus === "fulfilled") {
setIsOpen(false); // close modal
- dispatch(getUptimeMonitorsByTeamId(authState.authToken));
- updateCallback();
+ updateRowCallback();
createToast({ body: "Monitor deleted successfully." });
} else {
createToast({ body: "Failed to delete monitor." });
@@ -43,6 +47,7 @@ const ActionsMenu = ({ monitor, isAdmin, updateRowCallback, pauseCallback }) =>
const handlePause = async () => {
try {
+ setIsLoading(true);
const action = await dispatch(
pauseUptimeMonitor({ authToken, monitorId: monitor._id })
);
@@ -169,6 +174,8 @@ const ActionsMenu = ({ monitor, isAdmin, updateRowCallback, pauseCallback }) =>
{isAdmin && (
- {loading ? (
-
- ) : (
+ {
<>
- {noMonitors && }
- {hasMonitors && (
- <>
-
- }
+ {isLoading ? (
+
+ ) : (
+ hasMonitors && (
+ <>
+
+
+
+
+
+
-
-
-
-
- >
+ >
+ )
)}
>
- )}
+ }
);
};
-UptimeMonitors.propTypes = {
- isAdmin: PropTypes.bool,
-};
export default UptimeMonitors;
diff --git a/Client/src/Routes/index.jsx b/Client/src/Routes/index.jsx
index e72ac328d..e100216c1 100644
--- a/Client/src/Routes/index.jsx
+++ b/Client/src/Routes/index.jsx
@@ -49,6 +49,7 @@ const Routes = () => {
path="/uptime"
element={}
/>
+
}
diff --git a/Client/src/Utils/NetworkService.js b/Client/src/Utils/NetworkService.js
index 4cb7cf858..99c093d25 100644
--- a/Client/src/Utils/NetworkService.js
+++ b/Client/src/Utils/NetworkService.js
@@ -127,7 +127,7 @@ class NetworkService {
* @param {Array} config.types - Array of monitor types
* @returns {Promise} The response from the axios POST request.
*/
- async getMonitorsAndSummaryByTeamId(config) {
+ async getMonitorsSummaryByTeamId(config) {
const params = new URLSearchParams();
if (config.types) {
@@ -157,9 +157,6 @@ class NetworkService {
* @param {string} config.teamId - The ID of the team whose monitors are to be retrieved.
* @param {number} [config.limit] - The maximum number of checks to retrieve. 0 for all, -1 for none
* @param {Array} [config.types] - The types of monitors to retrieve.
- * @param {string} [config.status] - The status of the monitors to retrieve.
- * @param {string} [config.checkOrder] - The order in which to sort the retrieved monitors.
- * @param {boolean} [config.normalize] - Whether to normalize the retrieved monitors.
* @param {number} [config.page] - The page number for pagination.
* @param {number} [config.rowsPerPage] - The number of rows per page for pagination.
* @param {string} [config.filter] - The filter to apply to the monitors.
@@ -167,21 +164,10 @@ class NetworkService {
* @param {string} [config.order] - The order in which to sort the field.
* @returns {Promise} The response from the axios GET request.
*/
+
async getMonitorsByTeamId(config) {
- const {
- authToken,
- teamId,
- limit,
- types,
- status,
- checkOrder,
- normalize,
- page,
- rowsPerPage,
- filter,
- field,
- order,
- } = config;
+ const { authToken, teamId, limit, types, page, rowsPerPage, filter, field, order } =
+ config;
const params = new URLSearchParams();
@@ -191,9 +177,6 @@ class NetworkService {
params.append("type", type);
});
}
- if (status) params.append("status", status);
- if (checkOrder) params.append("checkOrder", checkOrder);
- if (normalize) params.append("normalize", normalize);
if (page) params.append("page", page);
if (rowsPerPage) params.append("rowsPerPage", rowsPerPage);
if (filter) params.append("filter", filter);
diff --git a/Client/src/Validation/validation.js b/Client/src/Validation/validation.js
index b16d31542..eca376b84 100644
--- a/Client/src/Validation/validation.js
+++ b/Client/src/Validation/validation.js
@@ -95,9 +95,48 @@ const monitorValidation = joi.object({
.string()
.trim()
.custom((value, helpers) => {
- const urlRegex =
- /^(https?:\/\/)?(([0-9]{1,3}\.){3}[0-9]{1,3}|[\da-z\.-]+)(\.[a-z\.]{2,6})?(:(\d+))?([\/\w \.-]*)*\/?$/i;
-
+ // Regex from https://gist.github.com/dperini/729294
+ var urlRegex = new RegExp(
+ "^" +
+ // protocol identifier (optional)
+ // short syntax // still required
+ "(?:(?:https?|ftp):\\/\\/)?" +
+ // user:pass BasicAuth (optional)
+ "(?:" +
+ // IP address exclusion
+ // private & local networks
+ "(?!(?:10|127)(?:\\.\\d{1,3}){3})" +
+ "(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})" +
+ "(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})" +
+ // IP address dotted notation octets
+ // excludes loopback network 0.0.0.0
+ // excludes reserved space >= 224.0.0.0
+ // excludes network & broadcast addresses
+ // (first & last IP address of each class)
+ "(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])" +
+ "(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}" +
+ "(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))" +
+ "|" +
+ // host & domain names, may end with dot
+ // can be replaced by a shortest alternative
+ // (?![-_])(?:[-\\w\\u00a1-\\uffff]{0,63}[^-_]\\.)+
+ "(?:" +
+ "(?:" +
+ "[a-z0-9\\u00a1-\\uffff]" +
+ "[a-z0-9\\u00a1-\\uffff_-]{0,62}" +
+ ")?" +
+ "[a-z0-9\\u00a1-\\uffff]\\." +
+ ")+" +
+ // TLD identifier name, may end with dot
+ "(?:[a-z\\u00a1-\\uffff]{2,}\\.?)" +
+ ")" +
+ // port number (optional)
+ "(?::\\d{2,5})?" +
+ // resource path (optional)
+ "(?:[/?#]\\S*)?" +
+ "$",
+ "i"
+ );
if (!urlRegex.test(value)) {
return helpers.error("string.invalidUrl");
}
diff --git a/Docker/dev/docker-compose.yaml b/Docker/dev/docker-compose.yaml
index d3b00adf6..f8b2c6936 100644
--- a/Docker/dev/docker-compose.yaml
+++ b/Docker/dev/docker-compose.yaml
@@ -24,6 +24,12 @@ services:
- "6379:6379"
volumes:
- ./redis/data:/data
+ healthcheck:
+ test: ["CMD", "redis-cli", "ping"]
+ interval: 30s
+ timeout: 10s
+ retries: 5
+ start_period: 5s
mongodb:
image: uptime_database_mongo:latest
restart: always
diff --git a/Docker/dist/docker-compose.yaml b/Docker/dist/docker-compose.yaml
index 8afa3c241..420941e7e 100644
--- a/Docker/dist/docker-compose.yaml
+++ b/Docker/dist/docker-compose.yaml
@@ -30,6 +30,12 @@ services:
- "6379:6379"
volumes:
- ./redis/data:/data
+ healthcheck:
+ test: ["CMD", "redis-cli", "ping"]
+ interval: 30s
+ timeout: 10s
+ retries: 5
+ start_period: 5s
mongodb:
image: bluewaveuptime/uptime_database_mongo:latest
restart: always
diff --git a/Docker/prod/docker-compose.yaml b/Docker/prod/docker-compose.yaml
index 1fb6a5d05..0da2da996 100644
--- a/Docker/prod/docker-compose.yaml
+++ b/Docker/prod/docker-compose.yaml
@@ -38,6 +38,12 @@ services:
- "6379:6379"
volumes:
- ./redis/data:/data
+ healthcheck:
+ test: ["CMD", "redis-cli", "ping"]
+ interval: 30s
+ timeout: 10s
+ retries: 5
+ start_period: 5s
mongodb:
image: uptime_database_mongo:latest
restart: always
diff --git a/Docker/test/docker-compose.yaml b/Docker/test/docker-compose.yaml
index 5a0080101..09ea94449 100644
--- a/Docker/test/docker-compose.yaml
+++ b/Docker/test/docker-compose.yaml
@@ -38,6 +38,12 @@ services:
- "6379:6379"
volumes:
- ./redis/data:/data
+ healthcheck:
+ test: ["CMD", "redis-cli", "ping"]
+ interval: 30s
+ timeout: 10s
+ retries: 5
+ start_period: 5s
mongodb:
image: uptime_database_mongo:latest
command: ["mongod", "--quiet", "--auth"]
diff --git a/README.md b/README.md
index 35e3f0fd5..8e9f34fb7 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,9 @@
-# **We're opening our $5000 grant funding announcement soon, powered by Checkmate and [UpRock](https://uprock.com) - check [our web page](https://checkmate.so) for preliminary details.**
+# **We're opening our $5000 grant funding announcement soon, in partnership with [UpRock](https://uprock.com) - check [our web page](https://checkmate.so) for preliminary details.**
-**If you would like to support us, please consider giving it a ⭐, and think about contributing or providing feedback. Need support or have a suggestion? Check our [Discord channel](https://discord.gg/NAb6H3UTjK) or [Discussions](https://github.com/bluewave-labs/checkmate/discussions) forum.**
-
-
+**If you would like to support us, please consider giving it a ⭐ and click on "watch" so you can latest news from us. Need support or have a suggestion? Check our [Discord channel](https://discord.gg/NAb6H3UTjK) or [Discussions](https://github.com/bluewave-labs/checkmate/discussions) forum.**
+


@@ -13,12 +12,14 @@



+[](https://www.bestpractices.dev/projects/9901)
+
-An open source server and infrastructure monitoring application
+An open source uptime and infrastructure monitoring application
-
+
Checkmate is an open source monitoring tool used to track the operational status and performance of servers and websites. It regularly checks whether a server/website is accessible and performs optimally, providing real-time alerts and reports on the monitored services' availability, downtime, and response time.
@@ -112,8 +113,8 @@ Here's how you can contribute:
Also check other developer and contributor-friendly projects of BlueWave:
-- [BlueWave DataRoom](https://github.com/bluewave-labs/bluewave-dataroom), an secure file sharing application, aka dataroom.
+- [DataRoom](https://github.com/bluewave-labs/bluewave-dataroom), an secure file sharing application, aka dataroom.
- [BlueWave HRM](https://github.com/bluewave-labs/bluewave-hrm), a complete Human Resource Management platform.
-- [BlueWave Onboarding](https://github.com/bluewave-labs/bluewave-onboarding), an application that helps new users learn how to use your product via hints, tours, popups and banners.
+- [Guidefox](https://github.com/bluewave-labs/guidefox), an application that helps new users learn how to use your product via hints, tours, popups and banners.
- [VerifyWise](https://github.com/bluewave-labs/verifywise), the first open source AI governance platform.
diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js
index 5e16b3b18..686661a00 100644
--- a/Server/controllers/monitorController.js
+++ b/Server/controllers/monitorController.js
@@ -1,13 +1,11 @@
import {
getMonitorByIdParamValidation,
getMonitorByIdQueryValidation,
- getMonitorsByTeamIdValidation,
+ getMonitorsByTeamIdParamValidation,
+ getMonitorsByTeamIdQueryValidation,
createMonitorBodyValidation,
getMonitorURLByQueryValidation,
editMonitorBodyValidation,
- getMonitorsAndSummaryByTeamIdParamValidation,
- getMonitorsAndSummaryByTeamIdQueryValidation,
- getMonitorsByTeamIdQueryValidation,
pauseMonitorParamValidation,
getMonitorStatsByIdParamValidation,
getMonitorStatsByIdQueryValidation,
@@ -204,77 +202,6 @@ class MonitorController {
}
};
- /**
- * Retrieves all monitors and a summary for a team based on the team ID.
- * @async
- * @param {Object} req - The Express request object.
- * @property {Object} req.params - The parameters of the request.
- * @property {string} req.params.teamId - The ID of the team.
- * @property {Object} req.query - The query parameters of the request.
- * @property {string} req.query.type - The type of the request.
- * @param {Object} res - The Express response object.
- * @param {function} next - The next middleware function.
- * @returns {Object} The response object with a success status, a message, and the data containing the monitors and summary for the team.
- * @throws {Error} If there is an error during the process, especially if there is a validation error (422).
- */
- getMonitorsAndSummaryByTeamId = async (req, res, next) => {
- try {
- await getMonitorsAndSummaryByTeamIdParamValidation.validateAsync(req.params);
- await getMonitorsAndSummaryByTeamIdQueryValidation.validateAsync(req.query);
- } catch (error) {
- next(handleValidationError(error, SERVICE_NAME));
- return;
- }
-
- try {
- const { teamId } = req.params;
- const { type } = req.query;
- const monitorsSummary = await this.db.getMonitorsAndSummaryByTeamId(teamId, type);
- return res.status(200).json({
- success: true,
- msg: successMessages.MONITOR_GET_BY_USER_ID(teamId),
- data: monitorsSummary,
- });
- } catch (error) {
- next(handleError(error, SERVICE_NAME, "getMonitorsAndSummaryByTeamId"));
- }
- };
-
- /**
- * Retrieves all monitors associated with a team by the team's ID.
- * @async
- * @param {Object} req - The Express request object.
- * @property {Object} req.params - The parameters of the request.
- * @property {string} req.params.teamId - The ID of the team.
- * @property {Object} req.query - The query parameters of the request.
- * @param {Object} res - The Express response object.
- * @param {function} next - The next middleware function.
- * @returns {Object} The response object with a success status, a message, and the data containing the monitors for the team.
- * @throws {Error} If there is an error during the process, especially if there is a validation error (422).
- */
- getMonitorsByTeamId = async (req, res, next) => {
- try {
- await getMonitorsByTeamIdValidation.validateAsync(req.params);
- await getMonitorsByTeamIdQueryValidation.validateAsync(req.query);
- } catch (error) {
- next(handleValidationError(error, SERVICE_NAME));
- return;
- }
-
- try {
- const teamId = req.params.teamId;
- const monitors = await this.db.getMonitorsByTeamId(req, res);
- return res.status(200).json({
- success: true,
- msg: successMessages.MONITOR_GET_BY_USER_ID(teamId),
- data: monitors,
- });
- } catch (error) {
- next(handleError(error, SERVICE_NAME, "getMonitorsByTeamId"));
- next(error);
- }
- };
-
/**
* Creates a new monitor and adds it to the job queue.
* @async
@@ -564,6 +491,26 @@ class MonitorController {
next(handleError(error, SERVICE_NAME, "addDemoMonitors"));
}
};
+
+ getMonitorsByTeamId = async (req, res, next) => {
+ try {
+ await getMonitorsByTeamIdParamValidation.validateAsync(req.params);
+ await getMonitorsByTeamIdQueryValidation.validateAsync(req.query);
+ } catch (error) {
+ next(handleValidationError(error, SERVICE_NAME));
+ }
+
+ try {
+ const monitors = await this.db.getMonitorsByTeamId(req);
+ return res.status(200).json({
+ success: true,
+ msg: "good",
+ data: monitors,
+ });
+ } catch (error) {
+ next(handleError(error, SERVICE_NAME, "getMonitorsForDisplay"));
+ }
+ };
}
export default MonitorController;
diff --git a/Server/db/mongo/modules/checkModule.js b/Server/db/mongo/modules/checkModule.js
index 773d6ef1e..0a552012d 100644
--- a/Server/db/mongo/modules/checkModule.js
+++ b/Server/db/mongo/modules/checkModule.js
@@ -175,6 +175,7 @@ const getTeamChecks = async (req) => {
case "all":
break;
case "down":
+ checksQuery.status = false;
break;
case "resolve":
checksQuery.statusCode = 5000;
diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js
index 1143b9eeb..7b554ff32 100644
--- a/Server/db/mongo/modules/monitorModule.js
+++ b/Server/db/mongo/modules/monitorModule.js
@@ -8,7 +8,11 @@ import { NormalizeData, NormalizeDataUptimeDetails } from "../../../utils/dataUt
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
-
+import {
+ buildUptimeDetailsPipeline,
+ buildHardwareDetailsPipeline,
+} from "./monitorModuleQueries.js";
+import { ObjectId } from "mongodb";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -336,272 +340,9 @@ const getUptimeDetailsById = async (req) => {
};
const dateString = formatLookup[dateRange];
- const monitorData = await Check.aggregate([
- {
- $match: {
- monitorId: monitor._id,
- },
- },
- {
- $sort: {
- createdAt: 1,
- },
- },
- {
- $facet: {
- aggregateData: [
- {
- $group: {
- _id: null,
- avgResponseTime: {
- $avg: "$responseTime",
- },
- firstCheck: {
- $first: "$$ROOT",
- },
- lastCheck: {
- $last: "$$ROOT",
- },
- totalChecks: {
- $sum: 1,
- },
- },
- },
- ],
- uptimeDuration: [
- {
- $match: {
- status: false,
- },
- },
- {
- $sort: {
- createdAt: 1,
- },
- },
- {
- $group: {
- _id: null,
- lastFalseCheck: {
- $last: "$$ROOT",
- },
- },
- },
- ],
- groupChecks: [
- {
- $match: {
- createdAt: { $gte: dates.start, $lte: dates.end },
- },
- },
- {
- $group: {
- _id: {
- $dateToString: {
- format: dateString,
- date: "$createdAt",
- },
- },
- avgResponseTime: {
- $avg: "$responseTime",
- },
- totalChecks: {
- $sum: 1,
- },
- },
- },
- {
- $sort: {
- _id: 1,
- },
- },
- ],
- groupAggregate: [
- {
- $match: {
- createdAt: { $gte: dates.start, $lte: dates.end },
- },
- },
- {
- $group: {
- _id: null,
- avgResponseTime: {
- $avg: "$responseTime",
- },
- },
- },
- ],
- upChecksAggregate: [
- {
- $match: {
- status: true,
- },
- },
- {
- $group: {
- _id: null,
- avgResponseTime: {
- $avg: "$responseTime",
- },
- totalChecks: {
- $sum: 1,
- },
- },
- },
- ],
- upChecks: [
- {
- $match: {
- status: true,
- createdAt: { $gte: dates.start, $lte: dates.end },
- },
- },
- {
- $group: {
- _id: {
- $dateToString: {
- format: dateString,
- date: "$createdAt",
- },
- },
- totalChecks: {
- $sum: 1,
- },
- avgResponseTime: {
- $avg: "$responseTime",
- },
- },
- },
- {
- $sort: { _id: 1 },
- },
- ],
- downChecksAggregate: [
- {
- $match: {
- status: false,
- },
- },
- {
- $group: {
- _id: null,
- avgResponseTime: {
- $avg: "$responseTime",
- },
- totalChecks: {
- $sum: 1,
- },
- },
- },
- ],
- downChecks: [
- {
- $match: {
- status: false,
- createdAt: { $gte: dates.start, $lte: dates.end },
- },
- },
- {
- $group: {
- _id: {
- $dateToString: {
- format: dateString,
- date: "$createdAt",
- },
- },
- totalChecks: {
- $sum: 1,
- },
- avgResponseTime: {
- $avg: "$responseTime",
- },
- },
- },
- {
- $sort: { _id: 1 },
- },
- ],
- },
- },
- {
- $project: {
- avgResponseTime: {
- $arrayElemAt: ["$aggregateData.avgResponseTime", 0],
- },
- totalChecks: {
- $arrayElemAt: ["$aggregateData.totalChecks", 0],
- },
- latestResponseTime: {
- $arrayElemAt: ["$aggregateData.lastCheck.responseTime", 0],
- },
- timeSinceLastCheck: {
- $let: {
- vars: {
- lastCheck: {
- $arrayElemAt: ["$aggregateData.lastCheck", 0],
- },
- },
- in: {
- $cond: [
- {
- $ifNull: ["$$lastCheck", false],
- },
- {
- $subtract: [new Date(), "$$lastCheck.createdAt"],
- },
- 0,
- ],
- },
- },
- },
- timeSinceLastFalseCheck: {
- $let: {
- vars: {
- lastFalseCheck: {
- $arrayElemAt: ["$uptimeDuration.lastFalseCheck", 0],
- },
- firstCheck: {
- $arrayElemAt: ["$aggregateData.firstCheck", 0],
- },
- },
- in: {
- $cond: [
- {
- $ifNull: ["$$lastFalseCheck", false],
- },
- {
- $subtract: [new Date(), "$$lastFalseCheck.createdAt"],
- },
- {
- $cond: [
- {
- $ifNull: ["$$firstCheck", false],
- },
- {
- $subtract: [new Date(), "$$firstCheck.createdAt"],
- },
- 0,
- ],
- },
- ],
- },
- },
- },
- groupChecks: "$groupChecks",
- groupAggregate: {
- $arrayElemAt: ["$groupAggregate", 0],
- },
- upChecksAggregate: {
- $arrayElemAt: ["$upChecksAggregate", 0],
- },
- upChecks: "$upChecks",
- downChecksAggregate: {
- $arrayElemAt: ["$downChecksAggregate", 0],
- },
- downChecks: "$downChecks",
- },
- },
- ]);
+ const monitorData = await Check.aggregate(
+ buildUptimeDetailsPipeline(monitor, dates, dateString)
+ );
const normalizedGroupChecks = NormalizeDataUptimeDetails(
monitorData[0].groupChecks,
@@ -715,261 +456,9 @@ const getHardwareDetailsById = async (req) => {
month: "%Y-%m-%dT00:00:00Z",
};
const dateString = formatLookup[dateRange];
- const hardwareStats = await HardwareCheck.aggregate([
- {
- $match: {
- monitorId: monitor._id,
- createdAt: { $gte: dates.start, $lte: dates.end },
- },
- },
- {
- $sort: {
- createdAt: 1,
- },
- },
- {
- $facet: {
- aggregateData: [
- {
- $group: {
- _id: null,
- latestCheck: {
- $last: "$$ROOT",
- },
- totalChecks: {
- $sum: 1,
- },
- },
- },
- ],
- upChecks: [
- {
- $match: {
- status: true,
- },
- },
- {
- $group: {
- _id: null,
- totalChecks: {
- $sum: 1,
- },
- },
- },
- ],
- checks: [
- {
- $limit: 1,
- },
- {
- $project: {
- diskCount: {
- $size: "$disk",
- },
- },
- },
- {
- $lookup: {
- from: "hardwarechecks",
- let: {
- diskCount: "$diskCount",
- },
- pipeline: [
- {
- $match: {
- $expr: {
- $and: [
- { $eq: ["$monitorId", monitor._id] },
- { $gte: ["$createdAt", dates.start] },
- { $lte: ["$createdAt", dates.end] },
- ],
- },
- },
- },
- {
- $group: {
- _id: {
- $dateToString: {
- format: dateString,
- date: "$createdAt",
- },
- },
- avgCpuUsage: {
- $avg: "$cpu.usage_percent",
- },
- avgMemoryUsage: {
- $avg: "$memory.usage_percent",
- },
- avgTemperatures: {
- $push: {
- $ifNull: ["$cpu.temperature", [0]],
- },
- },
- disks: {
- $push: "$disk",
- },
- },
- },
- {
- $project: {
- _id: 1,
- avgCpuUsage: 1,
- avgMemoryUsage: 1,
- avgTemperature: {
- $map: {
- input: {
- $range: [
- 0,
- {
- $size: {
- // Handle null temperatures array
- $ifNull: [
- { $arrayElemAt: ["$avgTemperatures", 0] },
- [0], // Default to single-element array if null
- ],
- },
- },
- ],
- },
- as: "index",
- in: {
- $avg: {
- $map: {
- input: "$avgTemperatures",
- as: "tempArray",
- in: {
- $ifNull: [
- { $arrayElemAt: ["$$tempArray", "$$index"] },
- 0, // Default to 0 if element is null
- ],
- },
- },
- },
- },
- },
- },
- disks: {
- $map: {
- input: {
- $range: [0, "$$diskCount"],
- },
- as: "diskIndex",
- in: {
- name: {
- $concat: [
- "disk",
- {
- $toString: "$$diskIndex",
- },
- ],
- },
- readSpeed: {
- $avg: {
- $map: {
- input: "$disks",
- as: "diskArray",
- in: {
- $arrayElemAt: [
- "$$diskArray.read_speed_bytes",
- "$$diskIndex",
- ],
- },
- },
- },
- },
- writeSpeed: {
- $avg: {
- $map: {
- input: "$disks",
- as: "diskArray",
- in: {
- $arrayElemAt: [
- "$$diskArray.write_speed_bytes",
- "$$diskIndex",
- ],
- },
- },
- },
- },
- totalBytes: {
- $avg: {
- $map: {
- input: "$disks",
- as: "diskArray",
- in: {
- $arrayElemAt: [
- "$$diskArray.total_bytes",
- "$$diskIndex",
- ],
- },
- },
- },
- },
- freeBytes: {
- $avg: {
- $map: {
- input: "$disks",
- as: "diskArray",
- in: {
- $arrayElemAt: [
- "$$diskArray.free_bytes",
- "$$diskIndex",
- ],
- },
- },
- },
- },
- usagePercent: {
- $avg: {
- $map: {
- input: "$disks",
- as: "diskArray",
- in: {
- $arrayElemAt: [
- "$$diskArray.usage_percent",
- "$$diskIndex",
- ],
- },
- },
- },
- },
- },
- },
- },
- },
- },
- ],
- as: "hourlyStats",
- },
- },
- {
- $unwind: "$hourlyStats",
- },
- {
- $replaceRoot: {
- newRoot: "$hourlyStats",
- },
- },
- ],
- },
- },
- {
- $project: {
- aggregateData: {
- $arrayElemAt: ["$aggregateData", 0],
- },
- upChecks: {
- $arrayElemAt: ["$upChecks", 0],
- },
- checks: {
- $sortArray: {
- input: "$checks",
- sortBy: { _id: 1 },
- },
- },
- },
- },
- ]);
+ const hardwareStats = await HardwareCheck.aggregate(
+ buildHardwareDetailsPipeline(monitor, dates, dateString)
+ );
const monitorStats = {
...monitor.toObject(),
@@ -1016,121 +505,167 @@ const getMonitorById = async (monitorId) => {
}
};
-/**
- * Get monitors and Summary by TeamID
- * @async
- * @param {Express.Request} req
- * @param {Express.Response} res
- * @returns {Promise>}
- * @throws {Error}
- */
+const getMonitorsByTeamId = async (req) => {
+ let { limit, type, page, rowsPerPage, filter, field, order } = req.query;
-const getMonitorsAndSummaryByTeamId = async (teamId, type) => {
- try {
- const monitors = await Monitor.find({ teamId, type });
- const monitorCounts = monitors.reduce(
- (acc, monitor) => {
- if (monitor.status === true) {
- acc.up += 1;
- } else if (monitor.status === false) {
- acc.down += 1;
- } else if (monitor.isActive === false) {
- acc.paused += 1;
- }
- return acc;
+ // Parse ints
+ limit = parseInt(limit);
+ page = parseInt(page);
+ rowsPerPage = parseInt(rowsPerPage);
+
+ // Build the match stage
+ const matchStage = { teamId: ObjectId.createFromHexString(req.params.teamId) };
+ if (type !== undefined) {
+ matchStage.type = Array.isArray(type) ? { $in: type } : type;
+ }
+
+ const skip = page && rowsPerPage ? page * rowsPerPage : 0;
+
+ const sort = { [field]: order === "asc" ? 1 : -1 };
+
+ const results = await Monitor.aggregate([
+ { $match: matchStage },
+ {
+ $facet: {
+ summary: [
+ {
+ $group: {
+ _id: null,
+ totalMonitors: { $sum: 1 },
+ upMonitors: {
+ $sum: {
+ $cond: [{ $eq: ["$status", true] }, 1, 0],
+ },
+ },
+ downMonitors: {
+ $sum: {
+ $cond: [{ $eq: ["$status", false] }, 1, 0],
+ },
+ },
+ pausedMonitors: {
+ $sum: {
+ $cond: [{ $eq: ["$isActive", false] }, 1, 0],
+ },
+ },
+ },
+ },
+ {
+ $project: {
+ _id: 0,
+ },
+ },
+ ],
+ monitors: [
+ ...(filter !== undefined
+ ? [
+ {
+ $match: {
+ $or: [
+ { name: { $regex: filter, $options: "i" } },
+ { url: { $regex: filter, $options: "i" } },
+ ],
+ },
+ },
+ ]
+ : []),
+ { $sort: sort },
+ { $skip: skip },
+ ...(rowsPerPage ? [{ $limit: rowsPerPage }] : []),
+ {
+ $lookup: {
+ from: "checks",
+ let: { monitorId: "$_id" },
+ pipeline: [
+ {
+ $match: {
+ $expr: { $eq: ["$monitorId", "$$monitorId"] },
+ },
+ },
+ { $sort: { createdAt: -1 } },
+ ...(limit ? [{ $limit: limit }] : []),
+ ],
+ as: "standardchecks",
+ },
+ },
+ {
+ $lookup: {
+ from: "pagespeedchecks",
+ let: { monitorId: "$_id" },
+ pipeline: [
+ {
+ $match: {
+ $expr: { $eq: ["$monitorId", "$$monitorId"] },
+ },
+ },
+ { $sort: { createdAt: -1 } },
+ ...(limit ? [{ $limit: limit }] : []),
+ ],
+ as: "pagespeedchecks",
+ },
+ },
+ {
+ $lookup: {
+ from: "hardwarechecks",
+ let: { monitorId: "$_id" },
+ pipeline: [
+ {
+ $match: {
+ $expr: { $eq: ["$monitorId", "$$monitorId"] },
+ },
+ },
+ { $sort: { createdAt: -1 } },
+ ...(limit ? [{ $limit: limit }] : []),
+ ],
+ as: "hardwarechecks",
+ },
+ },
+ {
+ $addFields: {
+ checks: {
+ $switch: {
+ branches: [
+ {
+ case: { $in: ["$type", ["http", "ping", "docker", "port"]] },
+ then: "$standardchecks",
+ },
+ {
+ case: { $eq: ["$type", "pagespeed"] },
+ then: "$pagespeedchecks",
+ },
+ {
+ case: { $eq: ["$type", "hardware"] },
+ then: "$hardwarechecks",
+ },
+ ],
+ default: [],
+ },
+ },
+ },
+ },
+ {
+ $project: {
+ standardchecks: 0,
+ pagespeedchecks: 0,
+ hardwarechecks: 0,
+ },
+ },
+ ],
},
- { up: 0, down: 0, paused: 0 }
- );
- monitorCounts.total = monitors.length;
- return { monitors, monitorCounts };
- } catch (error) {
- error.service = SERVICE_NAME;
- error.method = "getMonitorsAndSummaryByTeamId";
- throw error;
- }
-};
+ },
+ {
+ $project: {
+ summary: { $arrayElemAt: ["$summary", 0] },
+ monitors: 1,
+ },
+ },
+ ]);
-/**
- * Get monitors by TeamID
- * @async
- * @param {Express.Request} req
- * @param {Express.Response} res
- * @returns {Promise>}
- * @throws {Error}
- */
-const getMonitorsByTeamId = async (req, res) => {
- try {
- let {
- limit,
- type,
- status,
- checkOrder,
- normalize,
- page,
- rowsPerPage,
- filter,
- field,
- order,
- } = req.query;
-
- const monitorQuery = { teamId: req.params.teamId };
-
- if (type !== undefined) {
- monitorQuery.type = Array.isArray(type) ? { $in: type } : type;
- }
- // Add filter if provided
- // $options: "i" makes the search case-insensitive
- if (filter !== undefined) {
- monitorQuery.$or = [
- { name: { $regex: filter, $options: "i" } },
- { url: { $regex: filter, $options: "i" } },
- ];
- }
- const monitorCount = await Monitor.countDocuments(monitorQuery);
-
- // Pagination
- const skip = page && rowsPerPage ? page * rowsPerPage : 0;
-
- // Build Sort option
- const sort = field ? { [field]: order === "asc" ? 1 : -1 } : {};
-
- const monitors = await Monitor.find(monitorQuery)
- .skip(skip)
- .limit(rowsPerPage)
- .sort(sort);
-
- // Early return if limit is set to -1, indicating we don't want any checks
- if (limit === "-1") {
- return { monitors, monitorCount };
- }
- // Map each monitor to include its associated checks
- const monitorsWithChecks = await Promise.all(
- monitors.map(async (monitor) => {
- let model = CHECK_MODEL_LOOKUP[monitor.type];
-
- // Checks are order newest -> oldest
- let checks = await model
- .find({
- monitorId: monitor._id,
- ...(status && { status }),
- })
- .sort({ createdAt: checkOrder === "asc" ? 1 : -1 })
-
- .limit(limit || 0);
-
- //Normalize checks if requested
- if (normalize !== undefined) {
- checks = NormalizeData(checks, 10, 100);
- }
- return { ...monitor.toObject(), checks };
- })
- );
- return { monitors: monitorsWithChecks, monitorCount };
- } catch (error) {
- error.service = SERVICE_NAME;
- error.method = "getMonitorsByTeamId";
- throw error;
- }
+ let { monitors, summary } = results[0];
+ monitors = monitors.map((monitor) => {
+ monitor.checks = NormalizeData(monitor.checks, 10, 100);
+ return monitor;
+ });
+ return { monitors, summary };
};
/**
@@ -1262,9 +797,8 @@ export {
getAllMonitorsWithUptimeStats,
getMonitorStatsById,
getMonitorById,
- getUptimeDetailsById,
- getMonitorsAndSummaryByTeamId,
getMonitorsByTeamId,
+ getUptimeDetailsById,
createMonitor,
deleteMonitor,
deleteAllMonitors,
diff --git a/Server/db/mongo/modules/monitorModuleQueries.js b/Server/db/mongo/modules/monitorModuleQueries.js
new file mode 100644
index 000000000..ccafbcbdc
--- /dev/null
+++ b/Server/db/mongo/modules/monitorModuleQueries.js
@@ -0,0 +1,525 @@
+const buildUptimeDetailsPipeline = (monitor, dates, dateString) => {
+ return [
+ {
+ $match: {
+ monitorId: monitor._id,
+ },
+ },
+ {
+ $sort: {
+ createdAt: 1,
+ },
+ },
+ {
+ $facet: {
+ aggregateData: [
+ {
+ $group: {
+ _id: null,
+ avgResponseTime: {
+ $avg: "$responseTime",
+ },
+ firstCheck: {
+ $first: "$$ROOT",
+ },
+ lastCheck: {
+ $last: "$$ROOT",
+ },
+ totalChecks: {
+ $sum: 1,
+ },
+ },
+ },
+ ],
+ uptimeDuration: [
+ {
+ $match: {
+ status: false,
+ },
+ },
+ {
+ $sort: {
+ createdAt: 1,
+ },
+ },
+ {
+ $group: {
+ _id: null,
+ lastFalseCheck: {
+ $last: "$$ROOT",
+ },
+ },
+ },
+ ],
+ groupChecks: [
+ {
+ $match: {
+ createdAt: { $gte: dates.start, $lte: dates.end },
+ },
+ },
+ {
+ $group: {
+ _id: {
+ $dateToString: {
+ format: dateString,
+ date: "$createdAt",
+ },
+ },
+ avgResponseTime: {
+ $avg: "$responseTime",
+ },
+ totalChecks: {
+ $sum: 1,
+ },
+ },
+ },
+ {
+ $sort: {
+ _id: 1,
+ },
+ },
+ ],
+ groupAggregate: [
+ {
+ $match: {
+ createdAt: { $gte: dates.start, $lte: dates.end },
+ },
+ },
+ {
+ $group: {
+ _id: null,
+ avgResponseTime: {
+ $avg: "$responseTime",
+ },
+ },
+ },
+ ],
+ upChecksAggregate: [
+ {
+ $match: {
+ status: true,
+ },
+ },
+ {
+ $group: {
+ _id: null,
+ avgResponseTime: {
+ $avg: "$responseTime",
+ },
+ totalChecks: {
+ $sum: 1,
+ },
+ },
+ },
+ ],
+ upChecks: [
+ {
+ $match: {
+ status: true,
+ createdAt: { $gte: dates.start, $lte: dates.end },
+ },
+ },
+ {
+ $group: {
+ _id: {
+ $dateToString: {
+ format: dateString,
+ date: "$createdAt",
+ },
+ },
+ totalChecks: {
+ $sum: 1,
+ },
+ avgResponseTime: {
+ $avg: "$responseTime",
+ },
+ },
+ },
+ {
+ $sort: { _id: 1 },
+ },
+ ],
+ downChecksAggregate: [
+ {
+ $match: {
+ status: false,
+ },
+ },
+ {
+ $group: {
+ _id: null,
+ avgResponseTime: {
+ $avg: "$responseTime",
+ },
+ totalChecks: {
+ $sum: 1,
+ },
+ },
+ },
+ ],
+ downChecks: [
+ {
+ $match: {
+ status: false,
+ createdAt: { $gte: dates.start, $lte: dates.end },
+ },
+ },
+ {
+ $group: {
+ _id: {
+ $dateToString: {
+ format: dateString,
+ date: "$createdAt",
+ },
+ },
+ totalChecks: {
+ $sum: 1,
+ },
+ avgResponseTime: {
+ $avg: "$responseTime",
+ },
+ },
+ },
+ {
+ $sort: { _id: 1 },
+ },
+ ],
+ },
+ },
+ {
+ $project: {
+ avgResponseTime: {
+ $arrayElemAt: ["$aggregateData.avgResponseTime", 0],
+ },
+ totalChecks: {
+ $arrayElemAt: ["$aggregateData.totalChecks", 0],
+ },
+ latestResponseTime: {
+ $arrayElemAt: ["$aggregateData.lastCheck.responseTime", 0],
+ },
+ timeSinceLastCheck: {
+ $let: {
+ vars: {
+ lastCheck: {
+ $arrayElemAt: ["$aggregateData.lastCheck", 0],
+ },
+ },
+ in: {
+ $cond: [
+ {
+ $ifNull: ["$$lastCheck", false],
+ },
+ {
+ $subtract: [new Date(), "$$lastCheck.createdAt"],
+ },
+ 0,
+ ],
+ },
+ },
+ },
+ timeSinceLastFalseCheck: {
+ $let: {
+ vars: {
+ lastFalseCheck: {
+ $arrayElemAt: ["$uptimeDuration.lastFalseCheck", 0],
+ },
+ firstCheck: {
+ $arrayElemAt: ["$aggregateData.firstCheck", 0],
+ },
+ },
+ in: {
+ $cond: [
+ {
+ $ifNull: ["$$lastFalseCheck", false],
+ },
+ {
+ $subtract: [new Date(), "$$lastFalseCheck.createdAt"],
+ },
+ {
+ $cond: [
+ {
+ $ifNull: ["$$firstCheck", false],
+ },
+ {
+ $subtract: [new Date(), "$$firstCheck.createdAt"],
+ },
+ 0,
+ ],
+ },
+ ],
+ },
+ },
+ },
+ groupChecks: "$groupChecks",
+ groupAggregate: {
+ $arrayElemAt: ["$groupAggregate", 0],
+ },
+ upChecksAggregate: {
+ $arrayElemAt: ["$upChecksAggregate", 0],
+ },
+ upChecks: "$upChecks",
+ downChecksAggregate: {
+ $arrayElemAt: ["$downChecksAggregate", 0],
+ },
+ downChecks: "$downChecks",
+ },
+ },
+ ];
+};
+
+const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
+ return [
+ {
+ $match: {
+ monitorId: monitor._id,
+ createdAt: { $gte: dates.start, $lte: dates.end },
+ },
+ },
+ {
+ $sort: {
+ createdAt: 1,
+ },
+ },
+ {
+ $facet: {
+ aggregateData: [
+ {
+ $group: {
+ _id: null,
+ latestCheck: {
+ $last: "$$ROOT",
+ },
+ totalChecks: {
+ $sum: 1,
+ },
+ },
+ },
+ ],
+ upChecks: [
+ {
+ $match: {
+ status: true,
+ },
+ },
+ {
+ $group: {
+ _id: null,
+ totalChecks: {
+ $sum: 1,
+ },
+ },
+ },
+ ],
+ checks: [
+ {
+ $limit: 1,
+ },
+ {
+ $project: {
+ diskCount: {
+ $size: "$disk",
+ },
+ },
+ },
+ {
+ $lookup: {
+ from: "hardwarechecks",
+ let: {
+ diskCount: "$diskCount",
+ },
+ pipeline: [
+ {
+ $match: {
+ $expr: {
+ $and: [
+ { $eq: ["$monitorId", monitor._id] },
+ { $gte: ["$createdAt", dates.start] },
+ { $lte: ["$createdAt", dates.end] },
+ ],
+ },
+ },
+ },
+ {
+ $group: {
+ _id: {
+ $dateToString: {
+ format: dateString,
+ date: "$createdAt",
+ },
+ },
+ avgCpuUsage: {
+ $avg: "$cpu.usage_percent",
+ },
+ avgMemoryUsage: {
+ $avg: "$memory.usage_percent",
+ },
+ avgTemperatures: {
+ $push: {
+ $ifNull: ["$cpu.temperature", [0]],
+ },
+ },
+ disks: {
+ $push: "$disk",
+ },
+ },
+ },
+ {
+ $project: {
+ _id: 1,
+ avgCpuUsage: 1,
+ avgMemoryUsage: 1,
+ avgTemperature: {
+ $map: {
+ input: {
+ $range: [
+ 0,
+ {
+ $size: {
+ // Handle null temperatures array
+ $ifNull: [
+ { $arrayElemAt: ["$avgTemperatures", 0] },
+ [0], // Default to single-element array if null
+ ],
+ },
+ },
+ ],
+ },
+ as: "index",
+ in: {
+ $avg: {
+ $map: {
+ input: "$avgTemperatures",
+ as: "tempArray",
+ in: {
+ $ifNull: [
+ { $arrayElemAt: ["$$tempArray", "$$index"] },
+ 0, // Default to 0 if element is null
+ ],
+ },
+ },
+ },
+ },
+ },
+ },
+ disks: {
+ $map: {
+ input: {
+ $range: [0, "$$diskCount"],
+ },
+ as: "diskIndex",
+ in: {
+ name: {
+ $concat: [
+ "disk",
+ {
+ $toString: "$$diskIndex",
+ },
+ ],
+ },
+ readSpeed: {
+ $avg: {
+ $map: {
+ input: "$disks",
+ as: "diskArray",
+ in: {
+ $arrayElemAt: [
+ "$$diskArray.read_speed_bytes",
+ "$$diskIndex",
+ ],
+ },
+ },
+ },
+ },
+ writeSpeed: {
+ $avg: {
+ $map: {
+ input: "$disks",
+ as: "diskArray",
+ in: {
+ $arrayElemAt: [
+ "$$diskArray.write_speed_bytes",
+ "$$diskIndex",
+ ],
+ },
+ },
+ },
+ },
+ totalBytes: {
+ $avg: {
+ $map: {
+ input: "$disks",
+ as: "diskArray",
+ in: {
+ $arrayElemAt: [
+ "$$diskArray.total_bytes",
+ "$$diskIndex",
+ ],
+ },
+ },
+ },
+ },
+ freeBytes: {
+ $avg: {
+ $map: {
+ input: "$disks",
+ as: "diskArray",
+ in: {
+ $arrayElemAt: ["$$diskArray.free_bytes", "$$diskIndex"],
+ },
+ },
+ },
+ },
+ usagePercent: {
+ $avg: {
+ $map: {
+ input: "$disks",
+ as: "diskArray",
+ in: {
+ $arrayElemAt: [
+ "$$diskArray.usage_percent",
+ "$$diskIndex",
+ ],
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ as: "hourlyStats",
+ },
+ },
+ {
+ $unwind: "$hourlyStats",
+ },
+ {
+ $replaceRoot: {
+ newRoot: "$hourlyStats",
+ },
+ },
+ ],
+ },
+ },
+ {
+ $project: {
+ aggregateData: {
+ $arrayElemAt: ["$aggregateData", 0],
+ },
+ upChecks: {
+ $arrayElemAt: ["$upChecks", 0],
+ },
+ checks: {
+ $sortArray: {
+ input: "$checks",
+ sortBy: { _id: 1 },
+ },
+ },
+ },
+ },
+ ];
+};
+
+export { buildUptimeDetailsPipeline, buildHardwareDetailsPipeline };
diff --git a/Server/package-lock.json b/Server/package-lock.json
index 01ca7d510..30306bf29 100644
--- a/Server/package-lock.json
+++ b/Server/package-lock.json
@@ -11,9 +11,9 @@
"dependencies": {
"axios": "^1.7.2",
"bcrypt": "5.1.1",
- "bullmq": "5.34.6",
+ "bullmq": "5.34.8",
"cors": "^2.8.5",
- "dockerode": "4.0.2",
+ "dockerode": "4.0.3",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"handlebars": "^4.7.8",
@@ -352,6 +352,37 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
+ "node_modules/@grpc/grpc-js": {
+ "version": "1.12.5",
+ "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.12.5.tgz",
+ "integrity": "sha512-d3iiHxdpg5+ZcJ6jnDSOT8Z0O0VMVGy34jAnYLUX8yd36b1qn8f1TwOA/Lc7TsOh03IkPJ38eGI5qD2EjNkoEA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@grpc/proto-loader": "^0.7.13",
+ "@js-sdsl/ordered-map": "^4.4.2"
+ },
+ "engines": {
+ "node": ">=12.10.0"
+ }
+ },
+ "node_modules/@grpc/proto-loader": {
+ "version": "0.7.13",
+ "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz",
+ "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "lodash.camelcase": "^4.3.0",
+ "long": "^5.0.0",
+ "protobufjs": "^7.2.5",
+ "yargs": "^17.7.2"
+ },
+ "bin": {
+ "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/@hapi/hoek": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
@@ -850,6 +881,16 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@js-sdsl/ordered-map": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz",
+ "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/js-sdsl"
+ }
+ },
"node_modules/@mapbox/node-pre-gyp": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
@@ -967,6 +1008,70 @@
"node": ">=14"
}
},
+ "node_modules/@protobufjs/aspromise": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
+ "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/base64": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
+ "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/codegen": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
+ "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/eventemitter": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
+ "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/fetch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
+ "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@protobufjs/aspromise": "^1.1.1",
+ "@protobufjs/inquire": "^1.1.0"
+ }
+ },
+ "node_modules/@protobufjs/float": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
+ "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/inquire": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
+ "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/path": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
+ "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/pool": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
+ "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/utf8": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
+ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
+ "license": "BSD-3-Clause"
+ },
"node_modules/@scarf/scarf": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz",
@@ -1132,7 +1237,6 @@
"version": "22.10.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.4.tgz",
"integrity": "sha512-99l6wv4HEzBQhvaU/UGoeBoCK61SCROQaCCGyQSgX2tEQ3rKkNZ2S7CEWnS/4s1LV+8ODdK21UeyR1fHP2mXug==",
- "dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.20.0"
@@ -1640,9 +1744,9 @@
}
},
"node_modules/bullmq": {
- "version": "5.34.6",
- "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.34.6.tgz",
- "integrity": "sha512-pRCYyO9RlkQWxdmKlrNnUthyFwurYXRYLVXD1YIx+nCCdhAOiHatD8FDHbsT/w2I31c0NWoMcfZiIGuipiF7Lg==",
+ "version": "5.34.8",
+ "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.34.8.tgz",
+ "integrity": "sha512-id5mmPg3K8tNXQ9VVlmUxBSeLmliIWUrB8Hd5c62PFrIiHywz4TN1PEqU6OWvYXEvoFCr8/BlnbE4JCrGqPVmg==",
"license": "MIT",
"dependencies": {
"cron-parser": "^4.9.0",
@@ -2654,9 +2758,9 @@
}
},
"node_modules/docker-modem": {
- "version": "5.0.3",
- "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-5.0.3.tgz",
- "integrity": "sha512-89zhop5YVhcPEt5FpUFGr3cDyceGhq/F9J+ZndQ4KfqNvfbJpPMfgeixFgUj5OjCYAboElqODxY5Z1EBsSa6sg==",
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-5.0.5.tgz",
+ "integrity": "sha512-Cxw8uEcvNTRmsQuGqzzfiCnfGgf96tVJItLh8taOX0miTcIBALKH5TckCSuZbpbjP7uhAl81dOL9sxfa6HgCIg==",
"license": "Apache-2.0",
"dependencies": {
"debug": "^4.1.1",
@@ -2669,19 +2773,36 @@
}
},
"node_modules/dockerode": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-4.0.2.tgz",
- "integrity": "sha512-9wM1BVpVMFr2Pw3eJNXrYYt6DT9k0xMcsSCjtPvyQ+xa1iPg/Mo3T/gUcwI0B2cczqCeCYRPF8yFYDwtFXT0+w==",
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-4.0.3.tgz",
+ "integrity": "sha512-QSXJFcBQNaGZO6U3qWW4B7p8yRIJn/dWmvL2AQWfO/bjptBBO6QYdVkYSYFz9qoivP2jsOHZfmXMAfrK0BMKyg==",
"license": "Apache-2.0",
"dependencies": {
"@balena/dockerignore": "^1.0.2",
- "docker-modem": "^5.0.3",
- "tar-fs": "~2.0.1"
+ "@grpc/grpc-js": "^1.11.1",
+ "@grpc/proto-loader": "^0.7.13",
+ "docker-modem": "^5.0.5",
+ "protobufjs": "^7.3.2",
+ "tar-fs": "~2.0.1",
+ "uuid": "^10.0.0"
},
"engines": {
"node": ">= 8.0"
}
},
+ "node_modules/dockerode/node_modules/uuid": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
+ "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "license": "MIT",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
"node_modules/dom-serializer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
@@ -4650,6 +4771,12 @@
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"license": "MIT"
},
+ "node_modules/lodash.camelcase": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
+ "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
+ "license": "MIT"
+ },
"node_modules/lodash.clonedeep": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
@@ -4770,6 +4897,12 @@
"node": ">= 12.0.0"
}
},
+ "node_modules/long": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz",
+ "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==",
+ "license": "Apache-2.0"
+ },
"node_modules/loupe": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz",
@@ -6958,6 +7091,30 @@
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"license": "MIT"
},
+ "node_modules/protobufjs": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz",
+ "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==",
+ "hasInstallScript": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@protobufjs/aspromise": "^1.1.2",
+ "@protobufjs/base64": "^1.1.2",
+ "@protobufjs/codegen": "^2.0.4",
+ "@protobufjs/eventemitter": "^1.1.0",
+ "@protobufjs/fetch": "^1.1.0",
+ "@protobufjs/float": "^1.0.2",
+ "@protobufjs/inquire": "^1.1.0",
+ "@protobufjs/path": "^1.1.2",
+ "@protobufjs/pool": "^1.1.0",
+ "@protobufjs/utf8": "^1.1.0",
+ "@types/node": ">=13.7.0",
+ "long": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -8111,7 +8268,6 @@
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
- "dev": true,
"license": "MIT"
},
"node_modules/unfetch": {
diff --git a/Server/package.json b/Server/package.json
index d93c6c640..222f95f16 100644
--- a/Server/package.json
+++ b/Server/package.json
@@ -18,9 +18,9 @@
"dependencies": {
"axios": "^1.7.2",
"bcrypt": "5.1.1",
- "bullmq": "5.34.6",
+ "bullmq": "5.34.8",
"cors": "^2.8.5",
- "dockerode": "4.0.2",
+ "dockerode": "4.0.3",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"handlebars": "^4.7.8",
diff --git a/Server/routes/monitorRoute.js b/Server/routes/monitorRoute.js
index d47351486..92b529417 100644
--- a/Server/routes/monitorRoute.js
+++ b/Server/routes/monitorRoute.js
@@ -17,6 +17,7 @@ class MonitorRoutes {
"/hardware/details/:monitorId",
this.monitorController.getHardwareDetailsById
);
+
this.router.get(
"/uptime/details/:monitorId",
this.monitorController.getUptimeDetailsById
@@ -30,10 +31,7 @@ class MonitorRoutes {
);
});
this.router.get("/:monitorId", this.monitorController.getMonitorById);
- this.router.get(
- "/team/summary/:teamId",
- this.monitorController.getMonitorsAndSummaryByTeamId
- );
+
this.router.get("/team/:teamId", this.monitorController.getMonitorsByTeamId);
this.router.get(
diff --git a/Server/service/jobQueue.js b/Server/service/jobQueue.js
index 9b5663350..177ca34c5 100644
--- a/Server/service/jobQueue.js
+++ b/Server/service/jobQueue.js
@@ -217,14 +217,51 @@ class NewJobQueue {
connection: this.connection,
concurrency: 5,
});
- worker.on("failed", (job, err) => {
- this.logger.error({
- message: `Worker failed job: ${job.id}`,
- service: SERVICE_NAME,
- method: "createWorker",
- stack: err.stack,
- });
- });
+
+ // worker.on("active", (job) => {
+ // this.logger.info({
+ // message: `Worker started processing job: ${job.id}`,
+ // service: SERVICE_NAME,
+ // method: "createWorker",
+ // });
+ // });
+
+ // worker.on("completed", (job) => {
+ // this.logger.info({
+ // message: `Worker completed job: ${job.id}`,
+ // service: SERVICE_NAME,
+ // method: "createWorker",
+ // });
+ // });
+
+ // // Log job progress updates
+ // worker.on("progress", (job, progress) => {
+ // this.logger.info({
+ // message: `Job progress: ${job.id}`,
+ // service: SERVICE_NAME,
+ // method: "createWorker",
+ // details: `Progress: ${progress}%`,
+ // });
+ // });
+
+ // // Log when a job fails
+ // worker.on("failed", (job, err) => {
+ // this.logger.error({
+ // message: `Worker failed job: ${job.id}`,
+ // service: SERVICE_NAME,
+ // method: "createWorker",
+ // details: `Error: ${err.message}`,
+ // stack: err.stack,
+ // });
+ // });
+
+ // worker.on("stalled", (jobId) => {
+ // this.logger.warn({
+ // message: `Worker stalled job: ${jobId}`,
+ // service: SERVICE_NAME,
+ // method: "createWorker",
+ // });
+ // });
return worker;
}
@@ -353,8 +390,9 @@ class NewJobQueue {
const jobs = await queue.getJobs();
const ret = await Promise.all(
jobs.map(async (job) => {
+ console.log(job);
const state = await job.getState();
- return { url: job.data.url, state };
+ return { url: job.data.url, state, progress: job.progress };
})
);
stats[name] = { jobs: ret, workers: this.workers[name].length };
diff --git a/Server/service/settingsService.js b/Server/service/settingsService.js
index fb85939ca..24931f6ce 100644
--- a/Server/service/settingsService.js
+++ b/Server/service/settingsService.js
@@ -2,7 +2,7 @@ const SERVICE_NAME = "SettingsService";
import dotenv from "dotenv";
dotenv.config();
const envConfig = {
- logLevel: undefined,
+ logLevel: process.env.LOG_LEVEL,
apiBaseUrl: undefined,
clientHost: process.env.CLIENT_HOST,
jwtSecret: process.env.JWT_SECRET,
diff --git a/Server/utils/dataUtils.js b/Server/utils/dataUtils.js
index fd10a4257..ca6ca66df 100644
--- a/Server/utils/dataUtils.js
+++ b/Server/utils/dataUtils.js
@@ -38,7 +38,7 @@ const NormalizeData = (checks, rangeMin, rangeMax) => {
Math.min(rangeMax, normalizedResponseTime)
);
return {
- ...check._doc,
+ ...check,
responseTime: normalizedResponseTime,
originalResponseTime: originalResponseTime,
};
@@ -47,7 +47,7 @@ const NormalizeData = (checks, rangeMin, rangeMax) => {
return normalizedChecks;
} else {
return checks.map((check) => {
- return { ...check._doc, originalResponseTime: check.responseTime };
+ return { ...check, originalResponseTime: check.responseTime };
});
}
};
diff --git a/Server/validation/joi.js b/Server/validation/joi.js
index dbffef937..d30235048 100644
--- a/Server/validation/joi.js
+++ b/Server/validation/joi.js
@@ -136,32 +136,12 @@ const getMonitorByIdQueryValidation = joi.object({
normalize: joi.boolean(),
});
-const getMonitorsAndSummaryByTeamIdParamValidation = joi.object({
- teamId: joi.string().required(),
-});
-
-const getMonitorsAndSummaryByTeamIdQueryValidation = joi.object({
- type: joi
- .alternatives()
- .try(
- joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port"),
- joi
- .array()
- .items(
- joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port")
- )
- ),
-});
-
-const getMonitorsByTeamIdValidation = joi.object({
+const getMonitorsByTeamIdParamValidation = joi.object({
teamId: joi.string().required(),
});
const getMonitorsByTeamIdQueryValidation = joi.object({
- status: joi.boolean(),
- checkOrder: joi.string().valid("asc", "desc"),
limit: joi.number(),
- normalize: joi.boolean(),
type: joi
.alternatives()
.try(
@@ -467,9 +447,7 @@ export {
createMonitorBodyValidation,
getMonitorByIdParamValidation,
getMonitorByIdQueryValidation,
- getMonitorsAndSummaryByTeamIdParamValidation,
- getMonitorsAndSummaryByTeamIdQueryValidation,
- getMonitorsByTeamIdValidation,
+ getMonitorsByTeamIdParamValidation,
getMonitorsByTeamIdQueryValidation,
getMonitorStatsByIdParamValidation,
getMonitorStatsByIdQueryValidation,