Merge pull request #1498 from bluewave-labs/feat/be/hardware-details
feat: be/hardware details
This commit is contained in:
7 files changed
+436
-64
No files matched your search
@@ -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 (
|
||||
<Text
|
||||
@@ -163,7 +162,7 @@ export const InfrastructureTooltip = ({
|
||||
>
|
||||
{yIdx >= 0
|
||||
? `${yLabel} ${getFormattedPercentage(payload[0].payload[hardwareType][yIdx][metric])}`
|
||||
: `${yLabel} ${getFormattedPercentage(payload[0].payload[hardwareType][metric])}`}
|
||||
: `${yLabel} ${getFormattedPercentage(payload[0].payload[yKey])}`}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -204,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
|
||||
@@ -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: (
|
||||
<InfrastructureTooltip
|
||||
dotColor={theme.palette.primary.main}
|
||||
yKey={"memory.usage_percent"}
|
||||
yKey={"avgMemoryUsage"}
|
||||
yLabel={"Memory usage"}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
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: (
|
||||
<InfrastructureTooltip
|
||||
dotColor={theme.palette.success.main}
|
||||
yKey={"cpu.usage_percent"}
|
||||
yKey={"avgCpuUsage"}
|
||||
yLabel={"CPU usage"}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
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: (
|
||||
<TemperatureTooltip
|
||||
keys={tempData.tempKeys}
|
||||
keys={tempKeys}
|
||||
dotColor={theme.palette.error.main}
|
||||
/>
|
||||
),
|
||||
},
|
||||
...(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: (
|
||||
<InfrastructureTooltip
|
||||
dotColor={theme.palette.warning.main}
|
||||
yKey={`disk.usage_percent`}
|
||||
yKey={`disks.usagePercent`}
|
||||
yLabel={"Disc usage"}
|
||||
yIdx={idx}
|
||||
/>
|
||||
@@ -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,17 @@ 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 ?? []);
|
||||
const lastChecked =
|
||||
Date.now() - new Date(monitor?.stats?.aggregateData?.latestCheck?.createdAt);
|
||||
return (
|
||||
<Box>
|
||||
<Breadcrumbs list={navList} />
|
||||
{monitor?.checks?.length > 0 ? (
|
||||
{monitor?.stats.checks?.length > 0 ? (
|
||||
<Stack
|
||||
direction="column"
|
||||
gap={theme.spacing(10)}
|
||||
@@ -545,10 +545,11 @@ const InfrastructureDetails = () => {
|
||||
Checking every {formatDurationRounded(monitor?.interval)}
|
||||
</Typography>
|
||||
<Typography alignSelf="end">
|
||||
Last checked {formatDurationSplit(monitor?.lastChecked).time}{" "}
|
||||
{formatDurationSplit(monitor?.lastChecked).format} ago
|
||||
Last checked {formatDurationSplit(lastChecked).time}{" "}
|
||||
{formatDurationSplit(lastChecked).format} ago
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
<Stack
|
||||
direction="row"
|
||||
flexWrap="wrap"
|
||||
@@ -561,21 +562,64 @@ const InfrastructureDetails = () => {
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
|
||||
<Stack
|
||||
direction="row"
|
||||
gap={theme.spacing(8)}
|
||||
>
|
||||
{gaugeBoxConfigs.map((config) => (
|
||||
<GaugeBox
|
||||
key={`${config.type}-${config.diskIndex ?? ""}`}
|
||||
value={config.value}
|
||||
heading={config.heading}
|
||||
metricOne={config.metricOne}
|
||||
valueOne={config.valueOne}
|
||||
metricTwo={config.metricTwo}
|
||||
valueTwo={config.valueTwo}
|
||||
/>
|
||||
))}
|
||||
{gaugeBoxConfigs.map((config) => {
|
||||
return (
|
||||
<GaugeBox
|
||||
key={`${config.type}-${config.diskIndex ?? ""}`}
|
||||
value={config.value}
|
||||
heading={config.heading}
|
||||
metricOne={config.metricOne}
|
||||
valueOne={config.valueOne}
|
||||
metricTwo={config.metricTwo}
|
||||
valueTwo={config.valueTwo}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="flex-end"
|
||||
gap={theme.spacing(8)}
|
||||
mb={theme.spacing(8)}
|
||||
>
|
||||
<Typography variant="body2">
|
||||
Showing statistics for past{" "}
|
||||
{dateRange === "day"
|
||||
? "24 hours"
|
||||
: dateRange === "week"
|
||||
? "7 days"
|
||||
: "30 days"}
|
||||
.
|
||||
</Typography>
|
||||
<ButtonGroup sx={{ height: 32 }}>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(dateRange === "day").toString()}
|
||||
onClick={() => setDateRange("day")}
|
||||
>
|
||||
Day
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(dateRange === "week").toString()}
|
||||
onClick={() => setDateRange("week")}
|
||||
>
|
||||
Week
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(dateRange === "month").toString()}
|
||||
onClick={() => setDateRange("month")}
|
||||
>
|
||||
Month
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Stack>
|
||||
<Stack
|
||||
direction={"row"}
|
||||
@@ -602,7 +646,7 @@ const InfrastructureDetails = () => {
|
||||
height={areaChartHeight}
|
||||
data={config.data}
|
||||
dataKeys={config.dataKeys}
|
||||
xKey="createdAt"
|
||||
xKey="_id"
|
||||
yDomain={config.yDomain}
|
||||
customTooltip={config.toolTip}
|
||||
xTick={config.xTick}
|
||||
|
||||
@@ -242,6 +242,12 @@ 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()}`,
|
||||
async getUptimeDetailsById(config) {
|
||||
const params = new URLSearchParams();
|
||||
if (config.dateRange) params.append("dateRange", config.dateRange);
|
||||
|
||||
@@ -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";
|
||||
@@ -117,6 +119,35 @@ 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<Express.Response>}
|
||||
* @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({
|
||||
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);
|
||||
|
||||
@@ -703,6 +703,286 @@ 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);
|
||||
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: {
|
||||
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 monitorStats = {
|
||||
...monitor.toObject(),
|
||||
stats: hardwareStats[0],
|
||||
};
|
||||
return monitorStats;
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "getHardwareDetailsById";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a monitor by ID
|
||||
* @async
|
||||
@@ -991,6 +1271,7 @@ export {
|
||||
deleteMonitorsByUserId,
|
||||
editMonitor,
|
||||
addDemoMonitors,
|
||||
getHardwareDetailsById,
|
||||
};
|
||||
|
||||
// Helper functions
|
||||
|
||||
@@ -13,13 +13,14 @@ class MonitorRoutes {
|
||||
this.router.get("/", this.monitorController.getAllMonitors);
|
||||
this.router.get("/uptime", this.monitorController.getAllMonitorsWithUptimeStats);
|
||||
this.router.get("/stats/:monitorId", this.monitorController.getMonitorStatsById);
|
||||
|
||||
// New details routes
|
||||
this.router.get(
|
||||
"/hardware/details/:monitorId",
|
||||
this.monitorController.getHardwareDetailsById
|
||||
);
|
||||
this.router.get(
|
||||
"/uptime/details/:monitorId",
|
||||
this.monitorController.getUptimeDetailsById
|
||||
);
|
||||
|
||||
this.router.get("/certificate/:monitorId", (req, res, next) => {
|
||||
this.monitorController.getMonitorCertificate(
|
||||
req,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user