diff --git a/Client/index.html b/Client/index.html index 8186ba31c..2d13bd25d 100644 --- a/Client/index.html +++ b/Client/index.html @@ -1,23 +1,16 @@ - - - - - BlueWave Uptime - - -
- - - + + + + + Checkmate + + + +
+ + + + \ No newline at end of file diff --git a/Client/package-lock.json b/Client/package-lock.json index bcd5567ef..80aa209c1 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.1.8", - "@mui/lab": "6.0.0-beta.16", - "@mui/material": "6.1.8", + "@mui/icons-material": "6.1.9", + "@mui/lab": "6.0.0-beta.17", + "@mui/material": "6.1.9", "@mui/x-charts": "^7.5.1", "@mui/x-data-grid": "7.22.3", "@mui/x-date-pickers": "7.22.3", @@ -1078,15 +1078,15 @@ "license": "MIT" }, "node_modules/@mui/base": { - "version": "5.0.0-beta.62", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.62.tgz", - "integrity": "sha512-TzJLCNlrMkSU4bTCdTT+TVUiGx4sjZLhH673UV6YN+rNNP8wJpkWfRSvjDB5HcbH2T0lUamnz643ZnV+8IiMjw==", + "version": "5.0.0-beta.63", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.63.tgz", + "integrity": "sha512-W6aIqKP9X8VUX0KhSnYWo2+5C7MnKV1IhYVd517L/apvfkVq5KaTdlnxSBVwnaWt46whayVgQ/9KXwUVCXp6+w==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", "@floating-ui/react-dom": "^2.1.1", "@mui/types": "^7.2.19", - "@mui/utils": "^6.1.8", + "@mui/utils": "^6.1.9", "@popperjs/core": "^2.11.8", "clsx": "^2.1.1", "prop-types": "^15.8.1" @@ -1110,9 +1110,9 @@ } }, "node_modules/@mui/core-downloads-tracker": { - "version": "6.1.8", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.8.tgz", - "integrity": "sha512-TGAvzwUg9hybDacwfIGFjI2bXYXrIqky+vMfaeay8rvT56/PNAlvIDUJ54kpT5KRc9AWAihOvtDI7/LJOThOmQ==", + "version": "6.1.9", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.9.tgz", + "integrity": "sha512-TWqj7b1w5cmSz4H/uf+y2AHxAH4ldPR7D2bz0XVyn60GCAo/zRbRPx7cF8gTs/i7CiYeHzV6dtat0VpMwOtolw==", "license": "MIT", "funding": { "type": "opencollective", @@ -1120,9 +1120,9 @@ } }, "node_modules/@mui/icons-material": { - "version": "6.1.8", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.1.8.tgz", - "integrity": "sha512-6frsXcf1TcJKWevWwRup6V4L8lzI33cbHcAjT83YLgKw0vYRZKY0kjMI9fhrJZdRWXgFFgKKvEv3GjoxbqFF7A==", + "version": "6.1.9", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.1.9.tgz", + "integrity": "sha512-AzlhIT51rdjkZ/EcUV2dbhNkNSUHIqCnNoUxodpiTw8buyAUBd+qnxg5OBSuPpun/ZEdSSB8Q7Uyh6zqjiMsEQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0" @@ -1135,7 +1135,7 @@ "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "@mui/material": "^6.1.8", + "@mui/material": "^6.1.9", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, @@ -1146,16 +1146,16 @@ } }, "node_modules/@mui/lab": { - "version": "6.0.0-beta.16", - "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-6.0.0-beta.16.tgz", - "integrity": "sha512-YFeKREMMCiUhp4dGXd6Y/7N3BLepys9bM6xi4aF0WTZOvfl1ksDXPzuXPGiiiIuMgQFJeyN5iUnS1iPu3wH+kQ==", + "version": "6.0.0-beta.17", + "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-6.0.0-beta.17.tgz", + "integrity": "sha512-Ls1pIuYi5D9wq9mUwncky6CWokd6CCqQDCxXbm0TP0e7ksU5DcCPUZXBmTWQgbkldLu14aUXbJHyts63L0rycQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/base": "5.0.0-beta.62", - "@mui/system": "^6.1.8", + "@mui/base": "5.0.0-beta.63", + "@mui/system": "^6.1.9", "@mui/types": "^7.2.19", - "@mui/utils": "^6.1.8", + "@mui/utils": "^6.1.9", "clsx": "^2.1.1", "prop-types": "^15.8.1" }, @@ -1169,8 +1169,8 @@ "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@mui/material": "^6.1.8", - "@mui/material-pigment-css": "^6.1.8", + "@mui/material": "^6.1.9", + "@mui/material-pigment-css": "^6.1.9", "@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" @@ -1191,16 +1191,16 @@ } }, "node_modules/@mui/material": { - "version": "6.1.8", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.8.tgz", - "integrity": "sha512-QZdQFnXct+7NXIzHgT3qt+sQiO7HYGZU2vymP9Xl9tUMXEOA/S1mZMMb7+WGZrk5TzNlU/kP/85K0da5V1jXoQ==", + "version": "6.1.9", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.9.tgz", + "integrity": "sha512-NwqIN0bdsgzSbZd5JFcC+2ez0XW/XNs8uiV2PDHrqQ4qf/FEasFJG1z6g8JbCN0YlTrHZekVb17X0Fv0qcYJfQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/core-downloads-tracker": "^6.1.8", - "@mui/system": "^6.1.8", + "@mui/core-downloads-tracker": "^6.1.9", + "@mui/system": "^6.1.9", "@mui/types": "^7.2.19", - "@mui/utils": "^6.1.8", + "@mui/utils": "^6.1.9", "@popperjs/core": "^2.11.8", "@types/react-transition-group": "^4.4.11", "clsx": "^2.1.1", @@ -1219,7 +1219,7 @@ "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@mui/material-pigment-css": "^6.1.8", + "@mui/material-pigment-css": "^6.1.9", "@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" @@ -1240,13 +1240,13 @@ } }, "node_modules/@mui/private-theming": { - "version": "6.1.8", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.8.tgz", - "integrity": "sha512-TuKl7msynCNCVvhX3c0ef1sF0Qb3VHcPs8XOGB/8bdOGBr/ynmIG1yTMjZeiFQXk8yN9fzK/FDEKMFxILNn3wg==", + "version": "6.1.9", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.9.tgz", + "integrity": "sha512-7aum/O1RquBYhfwL/7egDyl9GqJgPM6hoJDFFBbhF6Sgv9yI9v4w3ArKUkuVvR0CtVj4NXRVMKEioh1bjUzvuA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/utils": "^6.1.8", + "@mui/utils": "^6.1.9", "prop-types": "^15.8.1" }, "engines": { @@ -1267,14 +1267,14 @@ } }, "node_modules/@mui/styled-engine": { - "version": "6.1.8", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.8.tgz", - "integrity": "sha512-ZvEoT0U2nPLSLI+B4by4cVjaZnPT2f20f4JUPkyHdwLv65ZzuoHiTlwyhqX1Ch63p8bcJzKTHQVGisEoMK6PGA==", + "version": "6.1.9", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.9.tgz", + "integrity": "sha512-xynSLlJRxHLzSfQaiDjkaTx8LiFb9ByVa7aOdwFnTxGWFMY1F+mkXwAUY4jDDE+MAxkWxlzzQE0wOohnsxhdQg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@emotion/cache": "^11.13.1", - "@emotion/serialize": "^1.3.2", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", "@emotion/sheet": "^1.4.0", "csstype": "^3.1.3", "prop-types": "^15.8.1" @@ -1301,16 +1301,16 @@ } }, "node_modules/@mui/system": { - "version": "6.1.8", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.8.tgz", - "integrity": "sha512-i1kLfQoWxzFpXTBQIuPoA3xKnAnP3en4I2T8xIolovSolGQX5k8vGjw1JaydQS40td++cFsgCdEU458HDNTGUA==", + "version": "6.1.9", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.9.tgz", + "integrity": "sha512-8x+RucnNp21gfFYsklCaZf0COXbv3+v0lrVuXONxvPEkESi2rwLlOi8UPJfcz6LxZOAX3v3oQ7qw18vnpgueRg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/private-theming": "^6.1.8", - "@mui/styled-engine": "^6.1.8", + "@mui/private-theming": "^6.1.9", + "@mui/styled-engine": "^6.1.9", "@mui/types": "^7.2.19", - "@mui/utils": "^6.1.8", + "@mui/utils": "^6.1.9", "clsx": "^2.1.1", "csstype": "^3.1.3", "prop-types": "^15.8.1" @@ -1355,9 +1355,9 @@ } }, "node_modules/@mui/utils": { - "version": "6.1.8", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.8.tgz", - "integrity": "sha512-O2DWb1kz8hiANVcR7Z4gOB3SvPPsSQGUmStpyBDzde6dJIfBzgV9PbEQOBZd3EBsd1pB+Uv1z5LAJAbymmawrA==", + "version": "6.1.9", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.9.tgz", + "integrity": "sha512-N7uzBp7p2or+xanXn3aH2OTINC6F/Ru/U8h6amhRZEev8bJhKN86rIDIoxZZ902tj+09LXtH83iLxFMjMHyqNA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", @@ -2409,15 +2409,15 @@ "license": "ISC" }, "node_modules/@vitejs/plugin-react": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.3.tgz", - "integrity": "sha512-NooDe9GpHGqNns1i8XDERg0Vsg5SSYRhRxxyTGogUdkdNt47jal+fbuYi+Yfq6pzRCKXyoPcWisfxE6RIM3GKA==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz", + "integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.25.2", - "@babel/plugin-transform-react-jsx-self": "^7.24.7", - "@babel/plugin-transform-react-jsx-source": "^7.24.7", + "@babel/core": "^7.26.0", + "@babel/plugin-transform-react-jsx-self": "^7.25.9", + "@babel/plugin-transform-react-jsx-source": "^7.25.9", "@types/babel__core": "^7.20.5", "react-refresh": "^0.14.2" }, @@ -2425,7 +2425,7 @@ "node": "^14.18.0 || >=16.0.0" }, "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0" + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" } }, "node_modules/acorn": { @@ -5251,9 +5251,9 @@ } }, "node_modules/prettier": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.0.tgz", - "integrity": "sha512-/OXNZcLyWkfo13ofOW5M7SLh+k5pnIs07owXK2teFpnfaOEcycnSy7HQxldaVX1ZP/7Q8oO1eDuQJNwbomQq5Q==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.1.tgz", + "integrity": "sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg==", "dev": true, "license": "MIT", "bin": { diff --git a/Client/package.json b/Client/package.json index c2ea33996..795dd8d87 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.1.8", - "@mui/lab": "6.0.0-beta.16", - "@mui/material": "6.1.8", + "@mui/icons-material": "6.1.9", + "@mui/lab": "6.0.0-beta.17", + "@mui/material": "6.1.9", "@mui/x-charts": "^7.5.1", "@mui/x-data-grid": "7.22.3", "@mui/x-date-pickers": "7.22.3", diff --git a/Client/public/bluewave_favicon.ico b/Client/public/bluewave_favicon.ico deleted file mode 100644 index 487946975..000000000 Binary files a/Client/public/bluewave_favicon.ico and /dev/null differ diff --git a/Client/public/checkmate_favicon.svg b/Client/public/checkmate_favicon.svg new file mode 100644 index 000000000..63ffd1dec --- /dev/null +++ b/Client/public/checkmate_favicon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Client/src/Components/Charts/AreaChart/index.jsx b/Client/src/Components/Charts/AreaChart/index.jsx index d05dbb14a..5e239996b 100644 --- a/Client/src/Components/Charts/AreaChart/index.jsx +++ b/Client/src/Components/Charts/AreaChart/index.jsx @@ -1,3 +1,57 @@ +/** + * CustomAreaChart component for rendering an area chart with optional gradient and custom ticks. + * + * @param {Object} props - The properties object. + * @param {Array} props.data - The data array for the chart. + * @param {Array} props.dataKeys - An array of data keys to be plotted as separate areas. + * @param {string} props.xKey - The key for the x-axis data. + * @param {string} [props.yKey] - The key for the y-axis data (optional). + * @param {Object} [props.xTick] - Custom tick component for the x-axis. + * @param {Object} [props.yTick] - Custom tick component for the y-axis. + * @param {string} [props.strokeColor] - The base stroke color for the areas. + * If not provided, uses a predefined color palette. + * @param {string} [props.fillColor] - The base fill color for the areas. + * @param {boolean} [props.gradient=false] - Whether to apply a gradient fill to the areas. + * @param {string} [props.gradientDirection="vertical"] - The direction of the gradient. + * @param {string} [props.gradientStartColor] - The start color of the gradient. + * Defaults to the area's stroke color if not provided. + * @param {string} [props.gradientEndColor] - The end color of the gradient. + * @param {Object} [props.customTooltip] - Custom tooltip component for the chart. + * @param {string|number} [props.height="100%"] - Height of the chart container. + * + * @returns {JSX.Element} The rendered area chart component. + * + * @example + * // Single series chart + * + * + * @example + * // Multi-series chart with custom tooltip + * } + * yTick={} + * gradient={true} + * customTooltip={({ active, payload, label }) => ( + * + * )} + * /> + */ + import { AreaChart, Area, @@ -11,69 +65,15 @@ import { createGradient } from "../Utils/gradientUtils"; import PropTypes from "prop-types"; import { useTheme } from "@mui/material"; import { useId } from "react"; -/** - * CustomAreaChart component for rendering an area chart with optional gradient and custom ticks. - * - * @param {Object} props - The properties object. - * @param {Array} props.data - The data array for the chart. - * @param {string} props.xKey - The key for the x-axis data. - * @param {string} props.yKey - The key for the y-axis data. - * @param {Object} [props.xTick] - Custom tick component for the x-axis. - * @param {Object} [props.yTick] - Custom tick component for the y-axis. - * @param {string} [props.strokeColor] - The stroke color for the area. - * @param {string} [props.fillColor] - The fill color for the area. - * @param {boolean} [props.gradient=false] - Whether to apply a gradient fill. - * @param {string} [props.gradientDirection="vertical"] - The direction of the gradient. - * @param {string} [props.gradientStartColor] - The start color of the gradient. - * @param {string} [props.gradientEndColor] - The end color of the gradient. - * @param {Object} [props.customTooltip] - Custom tooltip component. - * @returns {JSX.Element} The rendered area chart component. - * - * @example - * // Example usage of CustomAreaChart - * import React from 'react'; - * import CustomAreaChart from './CustomAreaChart'; - * import { TzTick, PercentTick, InfrastructureTooltip } from './chartUtils'; - * - * const data = [ - * { createdAt: '2023-01-01T00:00:00Z', cpu: { usage_percent: 0.5 } }, - * { createdAt: '2023-01-01T01:00:00Z', cpu: { usage_percent: 0.6 } }, - * // more data points... - * ]; - * - * const MyChartComponent = () => { - * return ( - * } - * yTick={} - * strokeColor="#8884d8" - * fillColor="#8884d8" - * gradient={true} - * gradientStartColor="#8884d8" - * gradientEndColor="#82ca9d" - * customTooltip={({ active, payload, label }) => ( - * - * )} - * /> - * ); - * }; - * - * export default MyChartComponent; - */ +import { Fragment } from "react"; + const CustomAreaChart = ({ data, - dataKey, + dataKeys, xKey, + xDomain, yKey, + yDomain, xTick, yTick, strokeColor, @@ -87,7 +87,49 @@ const CustomAreaChart = ({ }) => { const theme = useTheme(); const uniqueId = useId(); - const gradientId = `gradient-${uniqueId}`; + + const AREA_COLORS = [ + // Blues + "#3182bd", // Deep blue + "#6baed6", // Medium blue + "#9ecae1", // Light blue + + // Greens + "#74c476", // Soft green + "#a1d99b", // Light green + "#c7e9c0", // Pale green + + // Oranges + "#fdae6b", // Warm orange + "#fdd0a2", // Light orange + "#feedde", // Pale orange + + // Purples + "#9467bd", // Lavender + "#a55194", // Deep magenta + "#c994c7", // Soft magenta + + // Reds + "#ff9896", // Soft red + "#de2d26", // Deep red + "#fc9272", // Medium red + + // Cyans/Teals + "#17becf", // Cyan + "#7fcdbb", // Teal + "#a1dab4", // Light teal + + // Yellows + "#fec44f", // Mustard + "#fee391", // Light yellow + "#ffffd4", // Pale yellow + + // Additional colors + "#e377c2", // Soft pink + "#bcbd22", // Olive + "#2ca02c", // Vibrant green + ]; + return ( - {gradient === true && - createGradient({ - id: gradientId, - startColor: gradientStartColor, - endColor: gradientEndColor, - direction: gradientDirection, - })} + - + {dataKeys.map((dataKey, index) => { + const gradientId = `gradient-${uniqueId}-${index}`; + + return ( + + {gradient === true && + createGradient({ + id: gradientId, + startColor: gradientStartColor || AREA_COLORS[index], + endColor: gradientEndColor, + direction: gradientDirection, + })} + + + ); + })} {customTooltip ? ( { fontSize={11} fontWeight={400} > - {`${payload?.value * 100}%`} + {`${(payload?.value * 100).toFixed()}%`} ); }; @@ -165,7 +165,6 @@ export const InfrastructureTooltip = ({ ? `${yLabel} ${getFormattedPercentage(payload[0].payload[hardwareType][yIdx][metric])}` : `${yLabel} ${getFormattedPercentage(payload[0].payload[hardwareType][metric])}`} - {/* Display original value */} @@ -188,3 +187,91 @@ InfrastructureTooltip.propTypes = { yLabel: PropTypes.string, dotColor: PropTypes.string, }; + +export const TemperatureTooltip = ({ active, payload, label, keys, dotColor }) => { + const uiTimezone = useSelector((state) => state.ui.timezone); + const theme = useTheme(); + const formatCoreKey = (key) => { + return key.replace(/^core(\d+)$/, "Core $1"); + }; + if (active && payload && payload.length) { + return ( + + + {formatDateWithTz(label, "ddd, MMMM D, YYYY, h:mm A", uiTimezone)} + + + + {keys.map((key) => { + return ( + + + + + + {`${formatCoreKey(key)}: ${payload[0].payload[key]} °C`} + + + + + ); + })} + + + ); + } + return null; +}; + +TemperatureTooltip.propTypes = { + active: PropTypes.bool, + keys: PropTypes.array, + payload: PropTypes.array, + label: PropTypes.oneOfType([ + PropTypes.instanceOf(Date), + PropTypes.string, + PropTypes.number, + ]), +}; diff --git a/Client/src/Pages/Infrastructure/CreateMonitor/index.jsx b/Client/src/Pages/Infrastructure/CreateMonitor/index.jsx index abbd2e6f6..61070ba7d 100644 --- a/Client/src/Pages/Infrastructure/CreateMonitor/index.jsx +++ b/Client/src/Pages/Infrastructure/CreateMonitor/index.jsx @@ -33,12 +33,14 @@ const CreateInfrastructureMonitor = () => { usage_memory: "", disk: false, usage_disk: "", + temperature: false, + usage_temperature: "", secret: "", }); const MS_PER_MINUTE = 60000; const THRESHOLD_FIELD_PREFIX = "usage_"; - const HARDWARE_MONITOR_TYPES = ["cpu", "memory", "disk"]; + const HARDWARE_MONITOR_TYPES = ["cpu", "memory", "disk", "temperature"]; const { user, authToken } = useSelector((state) => state.auth); const monitorState = useSelector((state) => state.infrastructureMonitor); const dispatch = useDispatch(); @@ -75,18 +77,18 @@ const CreateInfrastructureMonitor = () => { }); }; - const handleBlur = (event, appenedID) => { + const handleBlur = (event, appendID) => { event.preventDefault(); const { value, id } = event.target; if (id?.startsWith("notify-email-")) return; const { error } = infrastructureMonitorValidation.validate( - { [id ?? appenedID]: value }, + { [id ?? appendID]: value }, { abortEarly: false, } ); setErrors((prev) => { - return buildErrors(prev, id ?? appenedID, error); + return buildErrors(prev, id ?? appendID, error); }); }; diff --git a/Client/src/Pages/Infrastructure/Details/index.jsx b/Client/src/Pages/Infrastructure/Details/index.jsx index 9fbaae81a..06fb1296a 100644 --- a/Client/src/Pages/Infrastructure/Details/index.jsx +++ b/Client/src/Pages/Infrastructure/Details/index.jsx @@ -17,6 +17,7 @@ import { TzTick, PercentTick, InfrastructureTooltip, + TemperatureTooltip, } from "../../../Components/Charts/Utils/chartUtils"; import PropTypes from "prop-types"; @@ -204,6 +205,245 @@ const InfrastructureDetails = () => { (chartContainerHeight - totalChartContainerPadding - totalTypographyPadding) * 0.95; // end height calculations + const buildStatBoxes = (checks) => { + let latestCheck = checks[0] ?? null; + if (latestCheck === null) return []; + + // Extract values from latest check + const physicalCores = latestCheck?.cpu?.physical_core ?? 0; + const logicalCores = latestCheck?.cpu?.logical_core ?? 0; + const cpuFrequency = latestCheck?.cpu?.frequency ?? 0; + const cpuTemperature = + latestCheck?.cpu?.temperature?.length > 0 + ? latestCheck.cpu.temperature.reduce((acc, curr) => acc + curr, 0) / + latestCheck.cpu.temperature.length + : 0; + const memoryTotalBytes = latestCheck?.memory?.total_bytes ?? 0; + const diskTotalBytes = latestCheck?.disk[0]?.total_bytes ?? 0; + const os = latestCheck?.host?.os ?? null; + const platform = latestCheck?.host?.platform ?? null; + const osPlatform = os === null && platform === null ? null : `${os} ${platform}`; + return [ + { + id: 0, + heading: "CPU (Physical)", + subHeading: `${physicalCores} cores`, + }, + { + id: 1, + heading: "CPU (Logical)", + subHeading: `${logicalCores} cores`, + }, + { + id: 2, + heading: "CPU Frequency", + subHeading: `${(cpuFrequency / 1000).toFixed(2)} Ghz`, + }, + { + id: 3, + heading: "Average CPU Temperature", + subHeading: `${cpuTemperature.toFixed(2)} C`, + }, + { + id: 4, + heading: "Memory", + subHeading: formatBytes(memoryTotalBytes), + }, + { + id: 5, + heading: "Disk", + subHeading: formatBytes(diskTotalBytes), + }, + { id: 6, heading: "Uptime", subHeading: "100%" }, + { + id: 7, + heading: "Status", + subHeading: monitor?.status === true ? "Active" : "Inactive", + }, + { + id: 8, + heading: "OS", + subHeading: osPlatform, + }, + ]; + }; + + const buildGaugeBoxConfigs = (checks) => { + let latestCheck = checks[0] ?? null; + if (latestCheck === null) return []; + + // Extract values from latest check + const memoryUsagePercent = latestCheck?.memory?.usage_percent ?? 0; + const memoryUsedBytes = latestCheck?.memory?.used_bytes ?? 0; + const memoryTotalBytes = latestCheck?.memory?.total_bytes ?? 0; + const cpuUsagePercent = latestCheck?.cpu?.usage_percent ?? 0; + const cpuPhysicalCores = latestCheck?.cpu?.physical_core ?? 0; + const cpuFrequency = latestCheck?.cpu?.frequency ?? 0; + return [ + { + type: "memory", + value: decimalToPercentage(memoryUsagePercent), + heading: "Memory Usage", + metricOne: "Used", + valueOne: formatBytes(memoryUsedBytes), + metricTwo: "Total", + valueTwo: formatBytes(memoryTotalBytes), + }, + { + type: "cpu", + value: decimalToPercentage(cpuUsagePercent), + heading: "CPU Usage", + metricOne: "Cores", + valueOne: cpuPhysicalCores ?? 0, + metricTwo: "Frequency", + valueTwo: `${(cpuFrequency / 1000).toFixed(2)} Ghz`, + }, + ...(latestCheck?.disk ?? []).map((disk, idx) => ({ + type: "disk", + diskIndex: idx, + value: decimalToPercentage(disk.usage_percent), + heading: `Disk${idx} usage`, + metricOne: "Used", + valueOne: formatBytes(disk.total_bytes - disk.free_bytes), + metricTwo: "Total", + valueTwo: formatBytes(disk.total_bytes), + })), + ]; + }; + + const buildTemps = (checks) => { + let numCores = 1; + if (checks === null) return { temps: [], tempKeys: [] }; + + for (const check of checks) { + if (check?.cpu?.temperature?.length > numCores) { + numCores = check.cpu.temperature.length; + break; + } + } + const temps = checks.map((check) => { + // If there's no data, set the temperature to 0 + if ( + check?.cpu?.temperature?.length === 0 || + check?.cpu?.temperature === undefined || + check?.cpu?.temperature === null + ) { + check.cpu.temperature = Array(numCores).fill(0); + } + const res = check?.cpu?.temperature?.reduce( + (acc, cur, idx) => { + acc[`core${idx + 1}`] = cur; + return acc; + }, + { + createdAt: check.createdAt, + } + ); + return res; + }); + if (temps.length === 0 || !temps[0]) { + return { temps: [], tempKeys: [] }; + } + + return { + tempKeys: Object.keys(temps[0] || {}).filter((key) => key !== "createdAt"), + temps, + }; + }; + + const buildAreaChartConfigs = (checks) => { + let latestCheck = checks[0] ?? null; + if (latestCheck === null) return []; + const reversedChecks = checks.toReversed(); + const tempData = buildTemps(reversedChecks); + return [ + { + type: "memory", + data: reversedChecks, + dataKeys: ["memory.usage_percent"], + heading: "Memory usage", + strokeColor: theme.palette.primary.main, + gradientStartColor: theme.palette.primary.main, + yLabel: "Memory Usage", + yDomain: [0, 1], + yTick: , + xTick: , + toolTip: ( + + ), + }, + { + type: "cpu", + data: reversedChecks, + dataKeys: ["cpu.usage_percent"], + heading: "CPU usage", + strokeColor: theme.palette.success.main, + gradientStartColor: theme.palette.success.main, + yLabel: "CPU Usage", + yDomain: [0, 1], + yTick: , + xTick: , + toolTip: ( + + ), + }, + { + type: "temperature", + data: tempData.temps, + dataKeys: tempData.tempKeys, + strokeColor: theme.palette.error.main, + gradientStartColor: theme.palette.error.main, + heading: "CPU Temperature", + yLabel: "Temperature", + xTick: , + yDomain: [ + 0, + Math.max( + Math.max( + ...tempData.temps.flatMap((t) => tempData.tempKeys.map((k) => t[k])) + ) * 1.1, + 200 + ), + ], + toolTip: ( + + ), + }, + ...(latestCheck?.disk?.map((disk, idx) => ({ + type: "disk", + data: reversedChecks, + diskIndex: idx, + dataKeys: [`disk[${idx}].usage_percent`], + heading: `Disk${idx} usage`, + strokeColor: theme.palette.warning.main, + gradientStartColor: theme.palette.warning.main, + yLabel: "Disk Usage", + yDomain: [0, 1], + yTick: , + xTick: , + toolTip: ( + + ), + })) || []), + ]; + }; + // Fetch data useEffect(() => { const fetchData = async () => { @@ -211,102 +451,24 @@ const InfrastructureDetails = () => { const response = await networkService.getStatsByMonitorId({ authToken: authToken, monitorId: monitorId, - sortOrder: "asc", + sortOrder: null, limit: null, dateRange: dateRange, numToDisplay: 50, normalize: false, }); - setMonitor(response.data.data); } catch (error) { navigate("/not-found", { replace: true }); - logger.error(error); + logger.error(error); } }; fetchData(); - }, [authToken, monitorId, dateRange]); + }, [authToken, monitorId, dateRange, navigate]); - - const statBoxConfigs = [ - { - id: 0, - heading: "CPU", - subHeading: `${monitor?.checks[0]?.cpu?.physical_core ?? 0} cores`, - }, - { - id: 1, - heading: "Memory", - subHeading: formatBytes(monitor?.checks[0]?.memory?.total_bytes), - }, - { - id: 2, - heading: "Disk", - subHeading: formatBytes(monitor?.checks[0]?.disk[0]?.total_bytes), - }, - { id: 3, heading: "Uptime", subHeading: "100%" }, - { - id: 4, - heading: "Status", - subHeading: monitor?.status === true ? "Active" : "Inactive", - }, - ]; - - const gaugeBoxConfigs = [ - { - type: "memory", - value: decimalToPercentage(monitor?.checks[0]?.memory?.usage_percent), - heading: "Memory Usage", - metricOne: "Used", - valueOne: formatBytes(monitor?.checks[0]?.memory?.used_bytes), - metricTwo: "Total", - valueTwo: formatBytes(monitor?.checks[0]?.memory?.total_bytes), - }, - { - type: "cpu", - value: decimalToPercentage(monitor?.checks[0]?.cpu?.usage_percent), - heading: "CPU Usage", - metricOne: "Cores", - valueOne: monitor?.checks[0]?.cpu?.physical_core ?? 0, - metricTwo: "Frequency", - valueTwo: `${(monitor?.checks[0]?.cpu?.frequency ?? 0 / 1000).toFixed(2)} Ghz`, - }, - ...(monitor?.checks?.[0]?.disk ?? []).map((disk, idx) => ({ - type: "disk", - diskIndex: idx, - value: decimalToPercentage(disk.usage_percent), - heading: `Disk${idx} usage`, - metricOne: "Used", - valueOne: formatBytes(disk.total_bytes - disk.free_bytes), - metricTwo: "Total", - valueTwo: formatBytes(disk.total_bytes), - })), - ]; - - const areaChartConfigs = [ - { - type: "memory", - dataKey: "memory.usage_percent", - heading: "Memory usage", - strokeColor: theme.palette.primary.main, - yLabel: "Memory Usage", - }, - { - type: "cpu", - dataKey: "cpu.usage_percent", - heading: "CPU usage", - strokeColor: theme.palette.success.main, - yLabel: "CPU Usage", - }, - ...(monitor?.checks?.[0]?.disk?.map((disk, idx) => ({ - type: "disk", - diskIndex: idx, - dataKey: `disk[${idx}].usage_percent`, - heading: `Disk${idx} usage`, - strokeColor: theme.palette.warning.main, - yLabel: "Disk Usage", - })) || []), - ]; + const statBoxConfigs = buildStatBoxes(monitor?.checks ?? []); + const gaugeBoxConfigs = buildGaugeBoxConfigs(monitor?.checks ?? []); + const areaChartConfigs = buildAreaChartConfigs(monitor?.checks ?? []); return ( @@ -343,6 +505,7 @@ const InfrastructureDetails = () => { {statBoxConfigs.map((statBox) => ( @@ -380,41 +543,32 @@ const InfrastructureDetails = () => { }, }} > - {areaChartConfigs.map((config) => ( - - - {config.heading} - - ( - - )} - xTick={} - yTick={} - strokeColor={config.strokeColor} - gradient={true} - gradientStartColor={config.strokeColor} - gradientEndColor="#ffffff" - /> - - ))} + {areaChartConfigs.map((config) => { + return ( + + + {config.heading} + + + + ); + })} ) : ( diff --git a/Client/src/Validation/error.js b/Client/src/Validation/error.js index a941e2509..0d1359ad5 100644 --- a/Client/src/Validation/error.js +++ b/Client/src/Validation/error.js @@ -37,8 +37,13 @@ const hasValidationErrors = (form, validation, setErrors) => { if (!form.disk || form.usage_disk) { newErrors["usage_disk"] = null; } + if (!form.temperature || form.usage_temperature) { + newErrors["usage_temperature"] = null; + } }); - if (Object.values(newErrors).some(v=> v)) { + + console.log("newErrors", newErrors); + if (Object.values(newErrors).some((v) => v)) { setErrors(newErrors); return true; } else { @@ -48,4 +53,4 @@ const hasValidationErrors = (form, validation, setErrors) => { } return false; }; -export { buildErrors, hasValidationErrors }; \ No newline at end of file +export { buildErrors, hasValidationErrors }; diff --git a/Client/src/Validation/validation.js b/Client/src/Validation/validation.js index 7dfbeb246..332ea6aea 100644 --- a/Client/src/Validation/validation.js +++ b/Client/src/Validation/validation.js @@ -190,15 +190,16 @@ const infrastructureMonitorValidation = joi.object({ cpu: joi.boolean(), memory: joi.boolean(), disk: joi.boolean(), + temperature: joi.boolean(), usage_memory: joi.number().messages({ "number.base": THRESHOLD_COMMON_BASE_MSG, }), usage_disk: joi.number().messages({ "number.base": THRESHOLD_COMMON_BASE_MSG, }), - // usage_temperature: joi.number().messages({ - // "number.base": "Temperature must be a number.", - // }), + usage_temperature: joi.number().messages({ + "number.base": "Temperature must be a number.", + }), // usage_system: joi.number().messages({ // "number.base": "System load must be a number.", // }), diff --git a/Server/db/models/HardwareCheck.js b/Server/db/models/HardwareCheck.js index 527f226b6..ba6fbdf2d 100644 --- a/Server/db/models/HardwareCheck.js +++ b/Server/db/models/HardwareCheck.js @@ -4,7 +4,7 @@ const cpuSchema = mongoose.Schema({ physical_core: { type: Number, default: 0 }, logical_core: { type: Number, default: 0 }, frequency: { type: Number, default: 0 }, - temperature: { type: Number, default: 0 }, + temperature: { type: [Number], default: [] }, free_percent: { type: Number, default: 0 }, usage_percent: { type: Number, default: 0 }, }); @@ -54,6 +54,7 @@ const HardwareCheckSchema = mongoose.Schema( type: hostSchema, default: () => ({}), }, + errors: { type: [errorSchema], default: () => [], diff --git a/Server/db/models/Notification.js b/Server/db/models/Notification.js index 868324cc2..8767880c1 100644 --- a/Server/db/models/Notification.js +++ b/Server/db/models/Notification.js @@ -38,9 +38,42 @@ const NotificationSchema = mongoose.Schema( return this.alertThreshold; }, }, + tempAlertThreshold: { + type: Number, + default: function () { + return this.alertThreshold; + }, + }, }, { timestamps: true, } ); + +NotificationSchema.pre("save", function (next) { + if (!this.cpuAlertThreshold || this.isModified("alertThreshold")) { + this.cpuAlertThreshold = this.alertThreshold; + } + if (!this.memoryAlertThreshold || this.isModified("alertThreshold")) { + this.memoryAlertThreshold = this.alertThreshold; + } + if (!this.diskAlertThreshold || this.isModified("alertThreshold")) { + this.diskAlertThreshold = this.alertThreshold; + } + if (!this.tempAlertThreshold || this.isModified("alertThreshold")) { + this.tempAlertThreshold = this.alertThreshold; + } + next(); +}); + +NotificationSchema.pre("findOneAndUpdate", function (next) { + const update = this.getUpdate(); + if (update.alertThreshold) { + update.cpuAlertThreshold = update.alertThreshold; + update.memoryAlertThreshold = update.alertThreshold; + update.diskAlertThreshold = update.alertThreshold; + update.tempAlertThreshold = update.alertThreshold; + } + next(); +}); export default mongoose.model("Notification", NotificationSchema); diff --git a/Server/package-lock.json b/Server/package-lock.json index 9fdd415c4..34613e0c9 100644 --- a/Server/package-lock.json +++ b/Server/package-lock.json @@ -4524,9 +4524,9 @@ } }, "node_modules/mongoose": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.8.2.tgz", - "integrity": "sha512-jCTSqDANfRzk909v4YoZQi7jlGRB2MTvgG+spVBc/BA4tOs1oWJr//V6yYujqNq9UybpOtsSfBqxI0dSOEFJHQ==", + "version": "8.8.3", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.8.3.tgz", + "integrity": "sha512-/I4n/DcXqXyIiLRfAmUIiTjj3vXfeISke8dt4U4Y8Wfm074Wa6sXnQrXN49NFOFf2mM1kUdOXryoBvkuCnr+Qw==", "license": "MIT", "dependencies": { "bson": "^6.7.0", @@ -5671,9 +5671,9 @@ } }, "node_modules/prettier": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", - "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.1.tgz", + "integrity": "sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg==", "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" diff --git a/Server/validation/joi.js b/Server/validation/joi.js index 10b798753..e22aac1a6 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -205,6 +205,7 @@ const createMonitorBodyValidation = joi.object({ usage_cpu: joi.number(), usage_memory: joi.number(), usage_disk: joi.number(), + usage_temperature: joi.number(), }), notifications: joi.array().items(joi.object()), secret: joi.string(),