diff --git a/Client/package-lock.json b/Client/package-lock.json index a267d7bdc..76f31993f 100644 --- a/Client/package-lock.json +++ b/Client/package-lock.json @@ -29,6 +29,7 @@ "react-router": "^6.23.0", "react-router-dom": "^6.23.1", "react-toastify": "^10.0.5", + "recharts": "2.12.7", "redux-persist": "6.0.0" }, "devDependencies": { @@ -2055,6 +2056,69 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", + "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz", + "integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", + "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -2658,6 +2722,15 @@ "node": ">=12" } }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, "node_modules/d3-format": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", @@ -2740,6 +2813,15 @@ "node": ">=12" } }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/data-view-buffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", @@ -2817,6 +2899,12 @@ } } }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -3473,6 +3561,12 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3480,6 +3574,15 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-equals": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz", + "integrity": "sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -4538,6 +4641,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -5101,6 +5210,21 @@ "react-dom": ">=16.8" } }, + "node_modules/react-smooth": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.1.tgz", + "integrity": "sha512-OE4hm7XqR0jNOq3Qmk9mFLyd6p2+j6bvbPJ7qlB7+oo0eNcL2l7WQzG6MBnT3EXY6xzkLMUBec3AfewJdA0J8w==", + "license": "MIT", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-toastify": { "version": "10.0.5", "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.5.tgz", @@ -5130,6 +5254,44 @@ "react-dom": ">=16.6.0" } }, + "node_modules/recharts": { + "version": "2.12.7", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.12.7.tgz", + "integrity": "sha512-hlLJMhPQfv4/3NBSAyq3gzGg4h2v69RJh6KU7b3pXYNNAELs9kEoXOjbkxdXpALqKBoVmVptGfLpxdaVYqjmXQ==", + "license": "MIT", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^16.10.2", + "react-smooth": "^4.0.0", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "license": "MIT", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, + "node_modules/recharts/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/redux": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", @@ -5620,6 +5782,12 @@ "dev": true, "license": "MIT" }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -5798,6 +5966,28 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/victory-vendor": { + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, "node_modules/vite": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.1.tgz", diff --git a/Client/package.json b/Client/package.json index 2edb972b7..9c54daf7f 100644 --- a/Client/package.json +++ b/Client/package.json @@ -31,6 +31,7 @@ "react-router": "^6.23.0", "react-router-dom": "^6.23.1", "react-toastify": "^10.0.5", + "recharts": "2.12.7", "redux-persist": "6.0.0" }, "devDependencies": { diff --git a/Client/src/Components/Charts/BarChart/BarChart.jsx b/Client/src/Components/Charts/BarChart/BarChart.jsx deleted file mode 100644 index a1f3d48b0..000000000 --- a/Client/src/Components/Charts/BarChart/BarChart.jsx +++ /dev/null @@ -1,63 +0,0 @@ -import "./barChart.css"; -import PropTypes from "prop-types"; - -const RANGE_MAX = 100; -const RANGE_MIN = 1; - -/** - * Normalizes the response times of a set of checks to a specified range. - * This function calculates the minimum and maximum response times from the input, - * then scales each check's response time linearly between `RANGE_MIN` and `RANGE_MAX`. - * - * @param {Array} checks - An array of check objects. Each check should have a `responseTime` property. - * @returns {Array} An array of check objects with normalized `responseTime` values. - */ -const normalizeData = (checks) => { - const min = checks.reduce((accum, cur) => { - return Math.min(accum, cur.responseTime); - }, Infinity); - - const max = checks.reduce((accum, cur) => { - return Math.max(accum, cur.responseTime); - }, 0); - - const normalizedChecks = checks.map((check) => { - let normailzedResponseTime = - RANGE_MIN + - ((check.responseTime - min) * (RANGE_MAX - RANGE_MIN)) / (max - min); - const normalizedCheck = { ...check, responseTime: normailzedResponseTime }; - return normalizedCheck; - }); - return normalizedChecks; -}; - -/** - * BarChart renders a bar chart visualization for a set of checks. Each bar represents a check's response time, - * with the color indicating the check's status (green for successful checks, red for failed ones). - * - * @component - * @param {Array} checks - An array of check objects to be visualized. Each check object should have an `_id`, - * a `status` indicating success (true) or failure (false), and a `responseTime` representing the height of the bar in percentage. - */ -const BarChart = ({ checks = [] }) => { - const normailzedChecks = normalizeData(checks); - return ( -
- {normailzedChecks.map((check) => { - return ( -
- ); - })} -
- ); -}; - -BarChart.propTypes = { - checks: PropTypes.array, -}; - -export default BarChart; diff --git a/Client/src/Components/Charts/BarChart/barChart.css b/Client/src/Components/Charts/BarChart/barChart.css deleted file mode 100644 index 8dd2a2b22..000000000 --- a/Client/src/Components/Charts/BarChart/barChart.css +++ /dev/null @@ -1,20 +0,0 @@ -.bar-chart { - display: flex; - justify-content: space-around; - align-items: flex-end; - height: 50px; - width: 300px; -} - -.bar { - width: 7%; - transition: height 0.3s ease; -} - -.green { - background-color: var(--env-var-color-23); -} - -.red { - background-color: var(--env-var-color-24); -} diff --git a/Client/src/Components/Charts/ResponseTimeChart/index.css b/Client/src/Components/Charts/ResponseTimeChart/index.css new file mode 100644 index 000000000..86b7a1d1b --- /dev/null +++ b/Client/src/Components/Charts/ResponseTimeChart/index.css @@ -0,0 +1,7 @@ +.chart-container { + display: flex; + justify-content: space-around; + align-items: flex-end; + height: 50px; + width: 300px; +} diff --git a/Client/src/Components/Charts/ResponseTimeChart/index.jsx b/Client/src/Components/Charts/ResponseTimeChart/index.jsx new file mode 100644 index 000000000..2121169b6 --- /dev/null +++ b/Client/src/Components/Charts/ResponseTimeChart/index.jsx @@ -0,0 +1,78 @@ +import "./index.css"; +import PropTypes from "prop-types"; +import { BarChart, Bar, ResponsiveContainer, Cell } from "recharts"; + +const RANGE_MAX = 100; +const RANGE_MIN = 1; + +const calculatePercentile = (arr, percentile) => { + const sorted = arr.slice().sort((a, b) => a.responseTime - b.responseTime); + const index = (percentile / 100) * (sorted.length - 1); + const lower = Math.floor(index); + const upper = lower + 1; + const weight = index % 1; + if (upper >= sorted.length) return sorted[lower].responseTime; + return ( + sorted[lower].responseTime * (1 - weight) + + sorted[upper].responseTime * weight + ); +}; + +const normalizeData = (checks) => { + if (checks.length > 1) { + // Get the 5th and 95th percentile + const min = calculatePercentile(checks, 5); + const max = calculatePercentile(checks, 95); + + const normalizedChecks = checks.map((check) => { + // Normalize the response time between 1 and 100 + let normalizedResponseTime = + RANGE_MIN + + ((check.responseTime - min) * (RANGE_MAX - RANGE_MIN)) / (max - min); + + // Put a floor on the response times so we don't have extreme outliers + // Better visuals + normalizedResponseTime = Math.max( + RANGE_MIN, + Math.min(RANGE_MAX, normalizedResponseTime) + ); + return { + ...check, + responseTime: normalizedResponseTime, + }; + }); + + return normalizedChecks; + } else { + return checks; + } +}; + +const ResponseTimeChart = ({ checks = [] }) => { + const normalizedChecks = normalizeData(checks); + return ( +
+ + + + {normalizedChecks.map((check, index) => ( + + ))} + + + +
+ ); +}; + +ResponseTimeChart.propTypes = { + checks: PropTypes.array, +}; +export default ResponseTimeChart; diff --git a/Client/src/Components/CurrentMonitors/index.jsx b/Client/src/Components/CurrentMonitors/index.jsx deleted file mode 100644 index 2357c850e..000000000 --- a/Client/src/Components/CurrentMonitors/index.jsx +++ /dev/null @@ -1,38 +0,0 @@ -import SearchTextField from "../TextFields/Search/SearchTextField"; -import "./index.css"; -import HostsTable from "../HostsTable"; -import Pagination from "../Pagination"; -import PropTypes from "prop-types"; - -/** - * CurrentMonitors displays the current status of monitor - * - * @component - * @param {Array} monitors - An array of monitor objects to be displayed. - */ - -const CurrentMonitors = ({ monitors }) => { - return ( -
-
-
-
Current monitors
-
{monitors.length}
-
-
- -
-
-
- -
- -
- ); -}; - -CurrentMonitors.propTypes = { - monitors: PropTypes.arrayOf(PropTypes.object).isRequired, -}; - -export default CurrentMonitors; diff --git a/Client/src/Components/HostsTable/index.jsx b/Client/src/Components/HostsTable/index.jsx index 8e5af7164..bc1dc1ff9 100644 --- a/Client/src/Components/HostsTable/index.jsx +++ b/Client/src/Components/HostsTable/index.jsx @@ -1,7 +1,7 @@ -import BarChart from "../Charts/BarChart/BarChart"; import PropTypes from "prop-types"; import Host from "../Host/"; import HostStatus from "../HostStatus"; +import ResponseTimeChart from "../Charts/ResponseTimeChart"; /** * HostsTable displays the current status of monitor * @@ -59,7 +59,7 @@ const HostsTable = ({ monitors }) => { {host} {status} - + {monitor.actions} diff --git a/Client/src/Components/TabPanels/Account/PasswordPanel.jsx b/Client/src/Components/TabPanels/Account/PasswordPanel.jsx index 14b9a016f..1e04ecaea 100644 --- a/Client/src/Components/TabPanels/Account/PasswordPanel.jsx +++ b/Client/src/Components/TabPanels/Account/PasswordPanel.jsx @@ -1,6 +1,7 @@ import TabPanel from "@mui/lab/TabPanel"; import React, { useState } from "react"; -import AnnouncementsDualButtonWithIcon from "../../Announcements/AnnouncementsDualButtonWithIcon/AnnouncementsDualButtonWithIcon"; +// import AnnouncementsDualButtonWithIcon from "../../Announcements/AnnouncementsDualButtonWithIcon/AnnouncementsDualButtonWithIcon"; +import AnnouncementsDualButtonWithIcon from "../../Toast/AnnouncementsDualButtonWithIcon/AnnouncementsDualButtonWithIcon"; import { useTheme } from "@emotion/react"; import { Box, Divider, Stack, Typography } from "@mui/material"; import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; diff --git a/Client/src/Components/Announcements/AnnouncementUpdateSubscription/AnnouncementUpdateSubscription.jsx b/Client/src/Components/Toast/AnnouncementUpdateSubscription/AnnouncementUpdateSubscription.jsx similarity index 100% rename from Client/src/Components/Announcements/AnnouncementUpdateSubscription/AnnouncementUpdateSubscription.jsx rename to Client/src/Components/Toast/AnnouncementUpdateSubscription/AnnouncementUpdateSubscription.jsx diff --git a/Client/src/Components/Announcements/AnnouncementUpdateSubscription/announcementUpdateSubscription.css b/Client/src/Components/Toast/AnnouncementUpdateSubscription/announcementUpdateSubscription.css similarity index 100% rename from Client/src/Components/Announcements/AnnouncementUpdateSubscription/announcementUpdateSubscription.css rename to Client/src/Components/Toast/AnnouncementUpdateSubscription/announcementUpdateSubscription.css diff --git a/Client/src/Components/Announcements/AnnouncementsDualButton/AnnouncementsDualButton.jsx b/Client/src/Components/Toast/AnnouncementsDualButton/AnnouncementsDualButton.jsx similarity index 100% rename from Client/src/Components/Announcements/AnnouncementsDualButton/AnnouncementsDualButton.jsx rename to Client/src/Components/Toast/AnnouncementsDualButton/AnnouncementsDualButton.jsx diff --git a/Client/src/Components/Announcements/AnnouncementsDualButton/announcementsDualButton.css b/Client/src/Components/Toast/AnnouncementsDualButton/announcementsDualButton.css similarity index 100% rename from Client/src/Components/Announcements/AnnouncementsDualButton/announcementsDualButton.css rename to Client/src/Components/Toast/AnnouncementsDualButton/announcementsDualButton.css diff --git a/Client/src/Components/Announcements/AnnouncementsDualButtonWithIcon/AnnouncementsDualButtonWithIcon.jsx b/Client/src/Components/Toast/AnnouncementsDualButtonWithIcon/AnnouncementsDualButtonWithIcon.jsx similarity index 100% rename from Client/src/Components/Announcements/AnnouncementsDualButtonWithIcon/AnnouncementsDualButtonWithIcon.jsx rename to Client/src/Components/Toast/AnnouncementsDualButtonWithIcon/AnnouncementsDualButtonWithIcon.jsx diff --git a/Client/src/Components/Announcements/AnnouncementsDualButtonWithIcon/announcementsDualButtonWithIcon.css b/Client/src/Components/Toast/AnnouncementsDualButtonWithIcon/announcementsDualButtonWithIcon.css similarity index 100% rename from Client/src/Components/Announcements/AnnouncementsDualButtonWithIcon/announcementsDualButtonWithIcon.css rename to Client/src/Components/Toast/AnnouncementsDualButtonWithIcon/announcementsDualButtonWithIcon.css diff --git a/Client/src/Components/Announcements/AnnouncementsMessageBar/AnnouncementsMessageBar.jsx b/Client/src/Components/Toast/AnnouncementsMessageBar/AnnouncementsMessageBar.jsx similarity index 100% rename from Client/src/Components/Announcements/AnnouncementsMessageBar/AnnouncementsMessageBar.jsx rename to Client/src/Components/Toast/AnnouncementsMessageBar/AnnouncementsMessageBar.jsx diff --git a/Client/src/Components/Announcements/AnnouncementsMessageBar/announcementsMessageBar.css b/Client/src/Components/Toast/AnnouncementsMessageBar/announcementsMessageBar.css similarity index 100% rename from Client/src/Components/Announcements/AnnouncementsMessageBar/announcementsMessageBar.css rename to Client/src/Components/Toast/AnnouncementsMessageBar/announcementsMessageBar.css diff --git a/Client/src/Components/Toast/index.jsx b/Client/src/Components/Toast/index.jsx index 87e6c2ea9..41367ec11 100644 --- a/Client/src/Components/Toast/index.jsx +++ b/Client/src/Components/Toast/index.jsx @@ -1,6 +1,6 @@ import PropTypes from "prop-types"; import ComplexAlert from "../../Components/Icons/ComplexAlert/ComplexAlert"; -import AnnouncementsDualButtonWithIcon from "../../Components/Announcements/AnnouncementsDualButtonWithIcon/AnnouncementsDualButtonWithIcon"; +import AnnouncementsDualButtonWithIcon from "./AnnouncementsDualButtonWithIcon/AnnouncementsDualButtonWithIcon"; import "react-toastify/dist/ReactToastify.css"; import { toast, ToastContainer } from "react-toastify"; import "./index.css"; diff --git a/Client/src/Pages/Monitors/CurrentStats.jsx b/Client/src/Pages/Monitors/CurrentStats.jsx deleted file mode 100644 index ef15ae6cc..000000000 --- a/Client/src/Pages/Monitors/CurrentStats.jsx +++ /dev/null @@ -1,65 +0,0 @@ -import Button from "../../Components/Button"; -import ServerStatus from "../../Components/Charts/Servers/ServerStatus"; -import CurrentMonitors from "../../Components/CurrentMonitors"; -import PropTypes from "prop-types"; -import { useSelector } from "react-redux"; -import { useNavigate } from "react-router-dom"; - -/** - * CurrentStats displays the current status of monitor - * - * @component - * @param {Array} monitors - An array of monitor objects to be displayed. - */ - -const CurrentStats = ({ monitors }) => { - const navigate = useNavigate(); - const authState = useSelector((state) => state.auth); - const up = monitors.reduce((acc, cur) => { - if (cur.checks.length > 0) { - return cur.checks[cur.checks.length - 1].status === true ? acc + 1 : acc; - } - return 0; - }, 0); - - const down = monitors.reduce((acc, cur) => { - if (cur.checks.length > 0) { - return cur.checks[cur.checks.length - 1].status === false ? acc + 1 : acc; - } - return 0; - }, 0); - - return ( -
-
-
-
-
- Hello, {authState.user.firstname} -
-
-
-
- - - -
-
- -
- ); -}; - -CurrentStats.propTypes = { - monitors: PropTypes.arrayOf(PropTypes.object).isRequired, -}; - -export default CurrentStats; diff --git a/Client/src/Pages/Monitors/index.css b/Client/src/Pages/Monitors/index.css index 6efd05617..987bbd7cd 100644 --- a/Client/src/Pages/Monitors/index.css +++ b/Client/src/Pages/Monitors/index.css @@ -9,6 +9,7 @@ } .monitors-bar { + margin-top: calc(2 * var(--env-var-spacing-2)); display: flex; justify-content: space-between; align-items: center; @@ -20,6 +21,7 @@ } .monitors-stats { + margin-top: var(--env-var-spacing-2); display: flex; gap: 24px; } diff --git a/Client/src/Pages/Monitors/index.jsx b/Client/src/Pages/Monitors/index.jsx index 5f7b4b9af..4c6d18606 100644 --- a/Client/src/Pages/Monitors/index.jsx +++ b/Client/src/Pages/Monitors/index.jsx @@ -1,22 +1,70 @@ -import CurrentStats from "./CurrentStats"; import "./index.css"; +import "./monitors.css"; import { useEffect } from "react"; -import { useSelector } from "react-redux"; -import { useDispatch } from "react-redux"; +import { useSelector, useDispatch } from "react-redux"; import { getMonitorsByUserId } from "../../Features/Monitors/monitorsSlice"; +import { useNavigate } from "react-router-dom"; +import Button from "../../Components/Button"; +import ServerStatus from "../../Components/Charts/Servers/ServerStatus"; +import SearchTextField from "../../Components/TextFields/Search/SearchTextField"; +import HostsTable from "../../Components/HostsTable"; +import Pagination from "../../Components/Pagination"; const Monitors = () => { + const navigate = useNavigate(); const monitorState = useSelector((state) => state.monitors); - const authstate = useSelector((state) => state.auth); + const authState = useSelector((state) => state.auth); const dispatch = useDispatch(); useEffect(() => { - dispatch(getMonitorsByUserId(authstate.authToken)); + dispatch(getMonitorsByUserId(authState.authToken)); }, []); + const up = monitorState.monitors.reduce((acc, cur) => { + return cur.status === true ? acc + 1 : acc; + }, 0); + + const down = monitorState.monitors.length - up; + return (
- +
+
+ Hello, {authState.user.firstname} +
+
+ +
+ + + +
+ +
+
+
+
Current monitors
+
+ {monitorState.monitors.length} +
+
+
+ +
+
+
+ +
+ +
); }; diff --git a/Client/src/Components/CurrentMonitors/index.css b/Client/src/Pages/Monitors/monitors.css similarity index 98% rename from Client/src/Components/CurrentMonitors/index.css rename to Client/src/Pages/Monitors/monitors.css index 8580bfcd0..c2ed8532f 100644 --- a/Client/src/Components/CurrentMonitors/index.css +++ b/Client/src/Pages/Monitors/monitors.css @@ -1,4 +1,5 @@ .current-monitors { + margin-top: var(--env-var-spacing-2); border: 1px solid #eaecf0; padding: 40px; border-radius: 4px; diff --git a/Client/src/Pages/PlayGround/PlayGround.jsx b/Client/src/Pages/PlayGround/PlayGround.jsx index 5f46af08d..bde0b6761 100644 --- a/Client/src/Pages/PlayGround/PlayGround.jsx +++ b/Client/src/Pages/PlayGround/PlayGround.jsx @@ -4,12 +4,12 @@ import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline"; import HelpOutlineIcon from "@mui/icons-material/HelpOutline"; import WebsiteTextField from "../../Components/TextFields/Website/WebsiteTextField"; import DescriptionTextField from "../../Components/TextFields/Description/DescriptionTextField"; -import AnnouncementsDualButton from "../../Components/Announcements/AnnouncementsDualButton/AnnouncementsDualButton"; -import AnnouncementsDualButtonWithIcon from "../../Components/Announcements/AnnouncementsDualButtonWithIcon/AnnouncementsDualButtonWithIcon"; +import AnnouncementsDualButton from "../../Components/Toast/AnnouncementsDualButton/AnnouncementsDualButton"; +import AnnouncementsDualButtonWithIcon from "../../Components/Toast/AnnouncementsDualButtonWithIcon/AnnouncementsDualButtonWithIcon"; import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; import ComplexAlert from "../../Components/Icons/ComplexAlert/ComplexAlert"; -import AnnouncementsMessageBar from "../../Components/Announcements/AnnouncementsMessageBar/AnnouncementsMessageBar"; -import AnnouncementUpdateSubscription from "../../Components/Announcements/AnnouncementUpdateSubscription/AnnouncementUpdateSubscription"; +import AnnouncementsMessageBar from "../../Components/Toast/AnnouncementsMessageBar/AnnouncementsMessageBar"; +import AnnouncementUpdateSubscription from "../../Components/Toast/AnnouncementUpdateSubscription/AnnouncementUpdateSubscription"; import PlayGroundCharts from "./PlayGround-Charts"; import PlayGroundPopupModals from "./PlayGround-Popup-Modals"; import PlayGroundTooltips from "./PlayGround-Tooltips"; diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index 800ec994c..3cb1711c7 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -214,6 +214,10 @@ const editMonitor = async (req, res, next) => { try { const editedMonitor = await req.db.editMonitor(req, res); + // Delete the old job(editedMonitor has the same ID as the old monitor) + await req.jobQueue.deleteJob(editedMonitor); + // Add the new job back to the queue + await req.jobQueue.addJob(editedMonitor._id, editedMonitor); return res.status(200).json({ success: true, msg: successMessages.MONITOR_EDIT, diff --git a/Server/db/MongoDB.js b/Server/db/MongoDB.js index 1dc26ce2c..1d16a14d3 100644 --- a/Server/db/MongoDB.js +++ b/Server/db/MongoDB.js @@ -42,7 +42,9 @@ const insertUser = async (req, res) => { } const newUser = new UserModel(userData); await newUser.save(); - return await UserModel.findOne({ _id: newUser._id }).select("-password"); // .select() doesn't work with create, need to save then find + return await UserModel.findOne({ _id: newUser._id }) + .select("-password") + .select("-profileImage"); // .select() doesn't work with create, need to save then find } catch (error) { if (error.code === DUPLICATE_KEY_CODE) { throw new Error(errorMessages.DB_USER_EXISTS); @@ -66,7 +68,9 @@ const getUserByEmail = async (req, res) => { try { // Need the password to be able to compare, removed .select() // We can strip the hash before returing the user - const user = await UserModel.findOne({ email: req.body.email }); + const user = await UserModel.findOne({ email: req.body.email }).select( + "-profileImage" + ); if (user) { return user; } else { @@ -102,7 +106,9 @@ const updateUser = async (req, res) => { candidateUserId, candidateUser, { new: true } // Returns updated user instead of pre-update user - ).select("-password"); + ) + .select("-password") + .select("-profileImage"); return updatedUser; } catch (error) { throw error; @@ -168,7 +174,9 @@ const resetPassword = async (req, res) => { // Fetch the user again without the password const userWithoutPassword = await UserModel.findOne({ email: recoveryToken.email, - }).select("-password"); + }) + .select("-password") + .select("-profileImage"); return userWithoutPassword; } else { throw new Error(errorMessages.DB_USER_NOT_FOUND);