From cf3305611f46646d2e232335d4af2530f6efb70c Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 31 Dec 2024 15:05:03 -0800 Subject: [PATCH 1/8] Add route and controller method for hardware details --- Server/controllers/monitorController.js | 13 +++++++++++++ Server/routes/monitorRoute.js | 4 ++++ 2 files changed, 17 insertions(+) diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index 613027f53..aa73de18d 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -104,6 +104,19 @@ class MonitorController { } }; + getHardwareDetailsById = async (req, res, next) => { + try { + const monitor = await this.db.getHardwareDetailsById(req); + return res.status(200).json({ + success: true, + msg: successMessages.MONITOR_GET_BY_ID, + data: monitor, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getHardwareDetailsById")); + } + }; + getMonitorCertificate = async (req, res, next, fetchMonitorCertificate) => { try { await getCertificateParamValidation.validateAsync(req.params); diff --git a/Server/routes/monitorRoute.js b/Server/routes/monitorRoute.js index c3257372b..9decd4339 100644 --- a/Server/routes/monitorRoute.js +++ b/Server/routes/monitorRoute.js @@ -13,6 +13,10 @@ class MonitorRoutes { this.router.get("/", this.monitorController.getAllMonitors); this.router.get("/uptime", this.monitorController.getAllMonitorsWithUptimeStats); this.router.get("/stats/:monitorId", this.monitorController.getMonitorStatsById); + this.router.get( + "/hardware/details/:monitorId", + this.monitorController.getHardwareDetailsById + ); this.router.get("/certificate/:monitorId", (req, res, next) => { this.monitorController.getMonitorCertificate( req, From 247bf44406df54085ea16aa50b98f77f8680f31e Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 31 Dec 2024 15:05:14 -0800 Subject: [PATCH 2/8] begin implementing aggregate query --- Server/db/mongo/modules/monitorModule.js | 25 ++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 61eab4e0e..c0e54d279 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -373,6 +373,30 @@ const getMonitorStatsById = async (req) => { } }; +const getHardwareDetailsById = async (req) => { + try { + const { monitorId } = req.params; + const monitor = await Monitor.findById(monitorId); + + const hardwareStats = await HardwareCheck.aggregate([ + { + $match: { + monitorId: monitor._id, + }, + }, + { + $facet: [], + }, + ]); + + return hardwareStats; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getHardwareDetailsById"; + throw error; + } +}; + /** * Get a monitor by ID * @async @@ -660,6 +684,7 @@ export { deleteMonitorsByUserId, editMonitor, addDemoMonitors, + getHardwareDetailsById, }; // Helper functions From 2d9b4eaab6c0c08a9553e3ebea7847036123aa15 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 31 Dec 2024 17:39:02 -0800 Subject: [PATCH 3/8] write hardware monitor query --- Server/db/mongo/modules/monitorModule.js | 208 ++++++++++++++++++++++- 1 file changed, 206 insertions(+), 2 deletions(-) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index c0e54d279..9bbdf8b88 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -376,19 +376,223 @@ const getMonitorStatsById = async (req) => { const getHardwareDetailsById = async (req) => { try { const { monitorId } = req.params; + const { dateRange } = req.query; const monitor = await Monitor.findById(monitorId); + const dates = getDateRange(dateRange); + console.log(dates); const hardwareStats = await HardwareCheck.aggregate([ { $match: { monitorId: monitor._id, + createdAt: { $gte: dates.start, $lte: dates.end }, }, }, { - $facet: [], + $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: { + $eq: ["$monitorId", monitor._id], + }, + }, + }, + { + $group: { + _id: { + $dateToString: { + format: "%Y-%m-%d-%H", + date: "$createdAt", + }, + }, + avgCpuUsage: { + $avg: "$cpu.usage_percent", + }, + avgMemoryUsage: { + $avg: "$memory.usage_percent", + }, + avgTemperature: { + $avg: "$cpu.temperature", + }, + disks: { + $push: "$disk", + }, + }, + }, + { + $project: { + _id: 1, + avgCpuUsage: 1, + avgMemoryUsage: 1, + avgTemperature: 1, + 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: "$checks", + }, }, ]); - return hardwareStats; } catch (error) { error.service = SERVICE_NAME; From a3518e4462aa51c6efd9193f4f1592acc0813e57 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 31 Dec 2024 20:00:02 -0800 Subject: [PATCH 4/8] add missing date range, add date range lookup --- Server/db/mongo/modules/monitorModule.js | 70 +++++++++++++++++++++--- 1 file changed, 61 insertions(+), 9 deletions(-) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 1d5ed5e33..94ebf9352 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -386,8 +386,12 @@ const getHardwareDetailsById = async (req) => { const { dateRange } = req.query; const monitor = await Monitor.findById(monitorId); const dates = getDateRange(dateRange); - console.log(dates); - + const formatLookup = { + day: "%Y-%m-%dT%H:00:00Z", + week: "%Y-%m-%dT%H:00:00Z", + month: "%Y-%m-%dT00:00:00Z", + }; + const dateString = formatLookup[dateRange]; const hardwareStats = await HardwareCheck.aggregate([ { $match: { @@ -451,7 +455,11 @@ const getHardwareDetailsById = async (req) => { { $match: { $expr: { - $eq: ["$monitorId", monitor._id], + $and: [ + { $eq: ["$monitorId", monitor._id] }, + { $gte: ["$createdAt", dates.start] }, + { $lte: ["$createdAt", dates.end] }, + ], }, }, }, @@ -459,7 +467,7 @@ const getHardwareDetailsById = async (req) => { $group: { _id: { $dateToString: { - format: "%Y-%m-%d-%H", + format: dateString, date: "$createdAt", }, }, @@ -469,8 +477,10 @@ const getHardwareDetailsById = async (req) => { avgMemoryUsage: { $avg: "$memory.usage_percent", }, - avgTemperature: { - $avg: "$cpu.temperature", + avgTemperatures: { + $push: { + $ifNull: ["$cpu.temperature", [0]], + }, }, disks: { $push: "$disk", @@ -482,7 +492,39 @@ const getHardwareDetailsById = async (req) => { _id: 1, avgCpuUsage: 1, avgMemoryUsage: 1, - avgTemperature: 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: { @@ -596,11 +638,21 @@ const getHardwareDetailsById = async (req) => { upChecks: { $arrayElemAt: ["$upChecks", 0], }, - checks: "$checks", + checks: { + $sortArray: { + input: "$checks", + sortBy: { _id: 1 }, + }, + }, }, }, ]); - return hardwareStats; + + const monitorStats = { + ...monitor.toObject(), + stats: hardwareStats[0], + }; + return monitorStats; } catch (error) { error.service = SERVICE_NAME; error.method = "getHardwareDetailsById"; From f176768b54dec5423ae14624efe7b28b42431f02 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 31 Dec 2024 20:00:22 -0800 Subject: [PATCH 5/8] Refactor FE to use new query --- .../Components/Charts/Utils/chartUtils.jsx | 3 +- .../Pages/Infrastructure/Details/index.jsx | 153 +++++++++++------- Client/src/Utils/NetworkService.js | 14 ++ 3 files changed, 113 insertions(+), 57 deletions(-) diff --git a/Client/src/Components/Charts/Utils/chartUtils.jsx b/Client/src/Components/Charts/Utils/chartUtils.jsx index 004f9a227..1d82f10b4 100644 --- a/Client/src/Components/Charts/Utils/chartUtils.jsx +++ b/Client/src/Components/Charts/Utils/chartUtils.jsx @@ -17,7 +17,6 @@ import { Box, Stack, Typography } from "@mui/material"; */ export const TzTick = ({ x, y, payload, index }) => { const theme = useTheme(); - const uiTimezone = useSelector((state) => state.ui.timezone); return ( {yIdx >= 0 ? `${yLabel} ${getFormattedPercentage(payload[0].payload[hardwareType][yIdx][metric])}` - : `${yLabel} ${getFormattedPercentage(payload[0].payload[hardwareType][metric])}`} + : `${yLabel} ${getFormattedPercentage(payload[0].payload[yKey])}`} diff --git a/Client/src/Pages/Infrastructure/Details/index.jsx b/Client/src/Pages/Infrastructure/Details/index.jsx index 66cebdea7..5c131deec 100644 --- a/Client/src/Pages/Infrastructure/Details/index.jsx +++ b/Client/src/Pages/Infrastructure/Details/index.jsx @@ -1,7 +1,7 @@ import { useParams } from "react-router-dom"; import { useEffect, useState } from "react"; import Breadcrumbs from "../../../Components/Breadcrumbs"; -import { Stack, Box, Typography } from "@mui/material"; +import { Button, ButtonGroup, Stack, Box, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import CustomGauge from "../../../Components/Charts/CustomGauge"; import AreaChart from "../../../Components/Charts/AreaChart"; @@ -185,9 +185,11 @@ GaugeBox.propTypes = { value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, heading: PropTypes.string.isRequired, metricOne: PropTypes.string.isRequired, - valueOne: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + valueOne: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.element]) + .isRequired, metricTwo: PropTypes.string.isRequired, - valueTwo: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + valueTwo: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.element]) + .isRequired, }; /** @@ -217,8 +219,9 @@ const InfrastructureDetails = () => { (chartContainerHeight - totalChartContainerPadding - totalTypographyPadding) * 0.95; // end height calculations - const buildStatBoxes = (checks, uptime) => { - let latestCheck = checks[0] ?? null; + const buildStatBoxes = (stats, uptime) => { + if (Object.keys(stats).length === 0) return []; + let latestCheck = stats.aggregateData.latestCheck ?? null; if (latestCheck === null) return []; // Extract values from latest check @@ -311,8 +314,10 @@ const InfrastructureDetails = () => { ]; }; - const buildGaugeBoxConfigs = (checks) => { - let latestCheck = checks[0] ?? null; + const buildGaugeBoxConfigs = (stats) => { + if (Object.keys(stats).length === 0) return []; + + let latestCheck = stats.aggregateData.latestCheck ?? null; if (latestCheck === null) return []; // Extract values from latest check @@ -359,27 +364,27 @@ const InfrastructureDetails = () => { if (checks === null) return { temps: [], tempKeys: [] }; for (const check of checks) { - if (check?.cpu?.temperature?.length > numCores) { - numCores = check.cpu.temperature.length; + if (check?.avgTemperature?.length > numCores) { + numCores = check.avgTemperature.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?.avgTemperature?.length === 0 || + check?.avgTemperature === undefined || + check?.avgTemperature === null ) { - check.cpu.temperature = Array(numCores).fill(0); + check.avgTemperature = Array(numCores).fill(0); } - const res = check?.cpu?.temperature?.reduce( + const res = check?.avgTemperature?.reduce( (acc, cur, idx) => { acc[`core${idx + 1}`] = cur; return acc; }, { - createdAt: check.createdAt, + _id: check._id, } ); return res; @@ -389,7 +394,7 @@ const InfrastructureDetails = () => { } return { - tempKeys: Object.keys(temps[0] || {}).filter((key) => key !== "createdAt"), + tempKeys: Object.keys(temps[0] || {}).filter((key) => key !== "_id"), temps, }; }; @@ -397,13 +402,12 @@ const InfrastructureDetails = () => { const buildAreaChartConfigs = (checks) => { let latestCheck = checks[0] ?? null; if (latestCheck === null) return []; - const reversedChecks = checks.toReversed(); - const tempData = buildTemps(reversedChecks); + const { temps, tempKeys } = buildTemps(checks); return [ { type: "memory", - data: reversedChecks, - dataKeys: ["memory.usage_percent"], + data: checks, + dataKeys: ["avgMemoryUsage"], heading: "Memory usage", strokeColor: theme.palette.primary.main, gradientStartColor: theme.palette.primary.main, @@ -414,15 +418,15 @@ const InfrastructureDetails = () => { toolTip: ( ), }, { type: "cpu", - data: reversedChecks, - dataKeys: ["cpu.usage_percent"], + data: checks, + dataKeys: ["avgCpuUsage"], heading: "CPU usage", strokeColor: theme.palette.success.main, gradientStartColor: theme.palette.success.main, @@ -433,15 +437,15 @@ const InfrastructureDetails = () => { toolTip: ( ), }, { type: "temperature", - data: tempData.temps, - dataKeys: tempData.tempKeys, + data: temps, + dataKeys: tempKeys, strokeColor: theme.palette.error.main, gradientStartColor: theme.palette.error.main, heading: "CPU Temperature", @@ -450,24 +454,22 @@ const InfrastructureDetails = () => { yDomain: [ 0, Math.max( - Math.max( - ...tempData.temps.flatMap((t) => tempData.tempKeys.map((k) => t[k])) - ) * 1.1, + Math.max(...temps.flatMap((t) => tempKeys.map((k) => t[k]))) * 1.1, 200 ), ], toolTip: ( ), }, - ...(latestCheck?.disk?.map((disk, idx) => ({ + ...(latestCheck?.disks?.map((disk, idx) => ({ type: "disk", - data: reversedChecks, + data: checks, diskIndex: idx, - dataKeys: [`disk[${idx}].usage_percent`], + dataKeys: [`disks[${idx}].usagePercent`], heading: `Disk${idx} usage`, strokeColor: theme.palette.warning.main, gradientStartColor: theme.palette.warning.main, @@ -478,7 +480,7 @@ const InfrastructureDetails = () => { toolTip: ( @@ -491,15 +493,12 @@ const InfrastructureDetails = () => { useEffect(() => { const fetchData = async () => { try { - const response = await networkService.getStatsByMonitorId({ + const response = await networkService.getHardwareDetailsByMonitorId({ authToken: authToken, monitorId: monitorId, - sortOrder: null, - limit: null, dateRange: dateRange, - numToDisplay: 50, - normalize: false, }); + response.data.data; setMonitor(response.data.data); } catch (error) { navigate("/not-found", { replace: true }); @@ -510,16 +509,16 @@ const InfrastructureDetails = () => { }, [authToken, monitorId, dateRange, navigate]); const statBoxConfigs = buildStatBoxes( - monitor?.checks ?? [], + monitor?.stats ?? {}, monitor?.uptimePercentage ?? "Unknown" ); - const gaugeBoxConfigs = buildGaugeBoxConfigs(monitor?.checks ?? []); - const areaChartConfigs = buildAreaChartConfigs(monitor?.checks ?? []); + const gaugeBoxConfigs = buildGaugeBoxConfigs(monitor?.stats ?? {}); + const areaChartConfigs = buildAreaChartConfigs(monitor?.stats?.checks ?? []); return ( - {monitor?.checks?.length > 0 ? ( + {monitor?.stats.checks?.length > 0 ? ( { {formatDurationSplit(monitor?.lastChecked).format} ago + { /> ))} + - {gaugeBoxConfigs.map((config) => ( - - ))} + {gaugeBoxConfigs.map((config) => { + return ( + + ); + })} + + + + Showing statistics for past{" "} + {dateRange === "day" + ? "24 hours" + : dateRange === "week" + ? "7 days" + : "30 days"} + . + + + + + + { height={areaChartHeight} data={config.data} dataKeys={config.dataKeys} - xKey="createdAt" + xKey="_id" yDomain={config.yDomain} customTooltip={config.toolTip} xTick={config.xTick} diff --git a/Client/src/Utils/NetworkService.js b/Client/src/Utils/NetworkService.js index 71cbe9670..148e61051 100644 --- a/Client/src/Utils/NetworkService.js +++ b/Client/src/Utils/NetworkService.js @@ -242,6 +242,20 @@ class NetworkService { ); } + async getHardwareDetailsByMonitorId(config) { + const params = new URLSearchParams(); + if (config.dateRange) params.append("dateRange", config.dateRange); + + return this.axiosInstance.get( + `/monitors/hardware/details/${config.monitorId}?${params.toString()}`, + { + headers: { + Authorization: `Bearer ${config.authToken}`, + }, + } + ); + } + /** * ************************************ * Updates a single monitor From 180585806b10c18efa7b291c698d648f93af69f5 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 31 Dec 2024 20:12:50 -0800 Subject: [PATCH 6/8] Add last checked --- Client/src/Pages/Infrastructure/Details/index.jsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Client/src/Pages/Infrastructure/Details/index.jsx b/Client/src/Pages/Infrastructure/Details/index.jsx index 5c131deec..10e070ee4 100644 --- a/Client/src/Pages/Infrastructure/Details/index.jsx +++ b/Client/src/Pages/Infrastructure/Details/index.jsx @@ -206,7 +206,7 @@ const InfrastructureDetails = () => { ]; const [monitor, setMonitor] = useState(null); const { authToken } = useSelector((state) => state.auth); - const [dateRange, setDateRange] = useState("all"); + const [dateRange, setDateRange] = useState("day"); const { statusColor, statusStyles, determineState } = useUtils(); // These calculations are needed because ResponsiveContainer // doesn't take padding of parent/siblings into account @@ -514,7 +514,8 @@ const InfrastructureDetails = () => { ); const gaugeBoxConfigs = buildGaugeBoxConfigs(monitor?.stats ?? {}); const areaChartConfigs = buildAreaChartConfigs(monitor?.stats?.checks ?? []); - + const lastChecked = + Date.now() - new Date(monitor?.stats?.aggregateData?.latestCheck?.createdAt); return ( @@ -544,8 +545,8 @@ const InfrastructureDetails = () => { Checking every {formatDurationRounded(monitor?.interval)} - Last checked {formatDurationSplit(monitor?.lastChecked).time}{" "} - {formatDurationSplit(monitor?.lastChecked).format} ago + Last checked {formatDurationSplit(lastChecked).time}{" "} + {formatDurationSplit(lastChecked).format} ago From c572096a50d7e4eb0e567a58eb47139fec2cf10f Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 1 Jan 2025 09:02:21 -0800 Subject: [PATCH 7/8] safe data access --- Client/src/Pages/Infrastructure/Details/index.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Client/src/Pages/Infrastructure/Details/index.jsx b/Client/src/Pages/Infrastructure/Details/index.jsx index 10e070ee4..29eddb9fc 100644 --- a/Client/src/Pages/Infrastructure/Details/index.jsx +++ b/Client/src/Pages/Infrastructure/Details/index.jsx @@ -221,7 +221,7 @@ const InfrastructureDetails = () => { const buildStatBoxes = (stats, uptime) => { if (Object.keys(stats).length === 0) return []; - let latestCheck = stats.aggregateData.latestCheck ?? null; + let latestCheck = stats?.aggregateData?.latestCheck ?? null; if (latestCheck === null) return []; // Extract values from latest check @@ -317,7 +317,7 @@ const InfrastructureDetails = () => { const buildGaugeBoxConfigs = (stats) => { if (Object.keys(stats).length === 0) return []; - let latestCheck = stats.aggregateData.latestCheck ?? null; + let latestCheck = stats?.aggregateData?.latestCheck ?? null; if (latestCheck === null) return []; // Extract values from latest check From 6563cedf88b6a103e26ea64d5a285daef0b00956 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 1 Jan 2025 09:08:07 -0800 Subject: [PATCH 8/8] add jsdoc and validation --- Server/controllers/monitorController.js | 18 ++++++++++++++++++ Server/validation/joi.js | 10 ++++++++++ 2 files changed, 28 insertions(+) diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index aa73de18d..e1e37aae0 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -12,6 +12,8 @@ import { getMonitorStatsByIdParamValidation, getMonitorStatsByIdQueryValidation, getCertificateParamValidation, + getHardwareDetailsByIdParamValidation, + getHardwareDetailsByIdQueryValidation, } from "../validation/joi.js"; import sslChecker from "ssl-checker"; import { successMessages } from "../utils/messages.js"; @@ -104,7 +106,23 @@ class MonitorController { } }; + /** + * Get hardware details for a specific monitor by ID + * @async + * @param {Express.Request} req - Express request object containing monitorId in params + * @param {Express.Response} res - Express response object + * @param {Express.NextFunction} next - Express next middleware function + * @returns {Promise} + * @throws {Error} - Throws error if monitor not found or other database errors + */ getHardwareDetailsById = async (req, res, next) => { + try { + await getHardwareDetailsByIdParamValidation.validateAsync(req.params); + await getHardwareDetailsByIdQueryValidation.validateAsync(req.query); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } try { const monitor = await this.db.getHardwareDetailsById(req); return res.status(200).json({ diff --git a/Server/validation/joi.js b/Server/validation/joi.js index 141394ac8..dbffef937 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -232,6 +232,14 @@ const getMonitorURLByQueryValidation = joi.object({ monitorURL: joi.string().uri().required(), }); +const getHardwareDetailsByIdParamValidation = joi.object({ + monitorId: joi.string().required(), +}); + +const getHardwareDetailsByIdQueryValidation = joi.object({ + dateRange: joi.string().valid("day", "week", "month"), +}); + //**************************************** // Alerts //**************************************** @@ -465,6 +473,8 @@ export { getMonitorsByTeamIdQueryValidation, getMonitorStatsByIdParamValidation, getMonitorStatsByIdQueryValidation, + getHardwareDetailsByIdParamValidation, + getHardwareDetailsByIdQueryValidation, getCertificateParamValidation, editMonitorBodyValidation, pauseMonitorParamValidation,