mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-01-21 17:19:42 -06:00
Merge pull request #1199 from bluewave-labs/feat/fe/infra-monitor-temp
feat/fe/infra monitor temp
This commit is contained in:
@@ -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
|
||||
* <CustomAreaChart
|
||||
* data={temperatureData}
|
||||
* dataKeys={["temperature"]}
|
||||
* xKey="date"
|
||||
* yKey="temperature"
|
||||
* gradient={true}
|
||||
* gradientStartColor="#ff6b6b"
|
||||
* gradientEndColor="#4ecdc4"
|
||||
* />
|
||||
*
|
||||
* @example
|
||||
* // Multi-series chart with custom tooltip
|
||||
* <CustomAreaChart
|
||||
* data={performanceData}
|
||||
* dataKeys={["cpu.usage", "memory.usage"]}
|
||||
* xKey="timestamp"
|
||||
* xTick={<CustomTimeTick />}
|
||||
* yTick={<PercentageTick />}
|
||||
* gradient={true}
|
||||
* customTooltip={({ active, payload, label }) => (
|
||||
* <CustomTooltip
|
||||
* label={label}
|
||||
* payload={payload}
|
||||
* active={active}
|
||||
* />
|
||||
* )}
|
||||
* />
|
||||
*/
|
||||
|
||||
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 (
|
||||
* <CustomAreaChart
|
||||
* data={data}
|
||||
* xKey="createdAt"
|
||||
* yKey="cpu.usage_percent"
|
||||
* xTick={<TzTick />}
|
||||
* yTick={<PercentTick />}
|
||||
* strokeColor="#8884d8"
|
||||
* fillColor="#8884d8"
|
||||
* gradient={true}
|
||||
* gradientStartColor="#8884d8"
|
||||
* gradientEndColor="#82ca9d"
|
||||
* customTooltip={({ active, payload, label }) => (
|
||||
* <InfrastructureTooltip
|
||||
* label={label?.toString() ?? ""}
|
||||
* yKey="cpu.usage_percent"
|
||||
* yLabel="CPU Usage"
|
||||
* active={active}
|
||||
* payload={payload}
|
||||
* />
|
||||
* )}
|
||||
* />
|
||||
* );
|
||||
* };
|
||||
*
|
||||
* 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 (
|
||||
<ResponsiveContainer
|
||||
width="100%"
|
||||
@@ -97,19 +139,15 @@ const CustomAreaChart = ({
|
||||
<AreaChart data={data}>
|
||||
<XAxis
|
||||
dataKey={xKey}
|
||||
{...(xDomain && { domain: xDomain })}
|
||||
{...(xTick && { tick: xTick })}
|
||||
/>
|
||||
<YAxis
|
||||
dataKey={yKey}
|
||||
{...(yDomain && { domain: yDomain })}
|
||||
{...(yTick && { tick: yTick })}
|
||||
/>
|
||||
{gradient === true &&
|
||||
createGradient({
|
||||
id: gradientId,
|
||||
startColor: gradientStartColor,
|
||||
endColor: gradientEndColor,
|
||||
direction: gradientDirection,
|
||||
})}
|
||||
|
||||
<CartesianGrid
|
||||
stroke={theme.palette.border.light}
|
||||
strokeWidth={1}
|
||||
@@ -117,12 +155,29 @@ const CustomAreaChart = ({
|
||||
fill="transparent"
|
||||
vertical={false}
|
||||
/>
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey={dataKey}
|
||||
stroke={strokeColor}
|
||||
fill={gradient === true ? `url(#${gradientId})` : fillColor}
|
||||
/>
|
||||
{dataKeys.map((dataKey, index) => {
|
||||
const gradientId = `gradient-${uniqueId}-${index}`;
|
||||
|
||||
return (
|
||||
<Fragment key={dataKey}>
|
||||
{gradient === true &&
|
||||
createGradient({
|
||||
id: gradientId,
|
||||
startColor: gradientStartColor || AREA_COLORS[index],
|
||||
endColor: gradientEndColor,
|
||||
direction: gradientDirection,
|
||||
})}
|
||||
<Area
|
||||
yKey={dataKey}
|
||||
key={dataKey}
|
||||
type="monotone"
|
||||
dataKey={dataKey}
|
||||
stroke={strokeColor || AREA_COLORS[index]}
|
||||
fill={gradient === true ? `url(#${gradientId})` : fillColor}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
{customTooltip ? (
|
||||
<Tooltip
|
||||
cursor={{ stroke: theme.palette.border.light }}
|
||||
@@ -139,18 +194,20 @@ const CustomAreaChart = ({
|
||||
|
||||
CustomAreaChart.propTypes = {
|
||||
data: PropTypes.array.isRequired,
|
||||
dataKey: PropTypes.string.isRequired,
|
||||
dataKeys: PropTypes.array.isRequired,
|
||||
xTick: PropTypes.object, // Recharts takes an instance of component, so we can't pass the component itself
|
||||
yTick: PropTypes.object, // Recharts takes an instance of component, so we can't pass the component itself
|
||||
xKey: PropTypes.string.isRequired,
|
||||
yKey: PropTypes.string.isRequired,
|
||||
xDomain: PropTypes.array,
|
||||
yKey: PropTypes.string,
|
||||
yDomain: PropTypes.array,
|
||||
fillColor: PropTypes.string,
|
||||
strokeColor: PropTypes.string,
|
||||
gradient: PropTypes.bool,
|
||||
gradientDirection: PropTypes.string,
|
||||
gradientStartColor: PropTypes.string,
|
||||
gradientEndColor: PropTypes.string,
|
||||
customTooltip: PropTypes.func,
|
||||
customTooltip: PropTypes.object,
|
||||
height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
};
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ export const PercentTick = ({ x, y, payload, index }) => {
|
||||
fontSize={11}
|
||||
fontWeight={400}
|
||||
>
|
||||
{`${payload?.value * 100}%`}
|
||||
{`${(payload?.value * 100).toFixed()}%`}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
@@ -165,7 +165,6 @@ export const InfrastructureTooltip = ({
|
||||
? `${yLabel} ${getFormattedPercentage(payload[0].payload[hardwareType][yIdx][metric])}`
|
||||
: `${yLabel} ${getFormattedPercentage(payload[0].payload[hardwareType][metric])}`}
|
||||
</Typography>
|
||||
<Typography component="span"></Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
{/* 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 (
|
||||
<Box
|
||||
className="area-tooltip"
|
||||
sx={{
|
||||
backgroundColor: theme.palette.background.main,
|
||||
border: 1,
|
||||
borderColor: theme.palette.border.dark,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
py: theme.spacing(2),
|
||||
px: theme.spacing(4),
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.text.tertiary,
|
||||
fontSize: 12,
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
{formatDateWithTz(label, "ddd, MMMM D, YYYY, h:mm A", uiTimezone)}
|
||||
</Typography>
|
||||
|
||||
<Stack direction="column">
|
||||
{keys.map((key) => {
|
||||
return (
|
||||
<Stack
|
||||
key={key}
|
||||
display="inline-flex"
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
ml={theme.spacing(3)}
|
||||
sx={{
|
||||
"& span": {
|
||||
color: theme.palette.text.tertiary,
|
||||
fontSize: 11,
|
||||
fontWeight: 500,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(2)}
|
||||
>
|
||||
<Box
|
||||
display="inline-block"
|
||||
width={theme.spacing(4)}
|
||||
height={theme.spacing(4)}
|
||||
backgroundColor={dotColor}
|
||||
sx={{ borderRadius: "50%" }}
|
||||
/>
|
||||
|
||||
<Typography
|
||||
component="span"
|
||||
sx={{ opacity: 0.8 }}
|
||||
>
|
||||
{`${formatCoreKey(key)}: ${payload[0].payload[key]} °C`}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Typography component="span"></Typography>
|
||||
</Stack>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
TemperatureTooltip.propTypes = {
|
||||
active: PropTypes.bool,
|
||||
keys: PropTypes.array,
|
||||
payload: PropTypes.array,
|
||||
label: PropTypes.oneOfType([
|
||||
PropTypes.instanceOf(Date),
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
]),
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
TzTick,
|
||||
PercentTick,
|
||||
InfrastructureTooltip,
|
||||
TemperatureTooltip,
|
||||
} from "../../../Components/Charts/Utils/chartUtils";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
@@ -204,6 +205,238 @@ 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 = 0;
|
||||
if (checks === null) return { temps: [], tempKeys: [] };
|
||||
|
||||
for (const check of checks) {
|
||||
if (check?.cpu?.temperature?.length > numCores) {
|
||||
numCores = check.cpu.temperature.length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (numCores === 0) return { temps: [], tempKeys: [] };
|
||||
|
||||
const temps = checks.map((check) => {
|
||||
if (check.cpu.temperature.length > numCores) {
|
||||
numCores = check.cpu.temperature.length;
|
||||
}
|
||||
|
||||
// If there's no data, set the temperature to 0
|
||||
if (check.cpu.temperature.length === 0) {
|
||||
check.cpu.temperature = Array(numCores).fill(0);
|
||||
}
|
||||
|
||||
return check.cpu.temperature.reduce(
|
||||
(acc, cur, idx) => {
|
||||
acc[`core${idx + 1}`] = cur;
|
||||
return acc;
|
||||
},
|
||||
{
|
||||
createdAt: check.createdAt,
|
||||
}
|
||||
);
|
||||
});
|
||||
// Slice to remove `createdAt` key
|
||||
return { tempKeys: Object.keys(temps[0]).slice(1), 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: <PercentTick />,
|
||||
xTick: <TzTick />,
|
||||
toolTip: (
|
||||
<InfrastructureTooltip
|
||||
dotColor={theme.palette.primary.main}
|
||||
yKey={"memory.usage_percent"}
|
||||
yLabel={"Memory Usage"}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
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: <PercentTick />,
|
||||
xTick: <TzTick />,
|
||||
toolTip: (
|
||||
<InfrastructureTooltip
|
||||
dotColor={theme.palette.success.main}
|
||||
yKey={"cpu.usage_percent"}
|
||||
yLabel={"CPU Usage"}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
type: "temperature",
|
||||
data: tempData.temps,
|
||||
dataKeys: tempData.tempKeys,
|
||||
strokeColor: theme.palette.error.main,
|
||||
gradientStartColor: theme.palette.error.main,
|
||||
heading: "CPU Temperature",
|
||||
yLabel: "Temperature",
|
||||
xTick: <TzTick />,
|
||||
yDomain: [
|
||||
Math.min(...tempData.temps.flatMap((t) => tempData.tempKeys.map((k) => t[k]))) *
|
||||
0.9,
|
||||
Math.max(...tempData.temps.flatMap((t) => tempData.tempKeys.map((k) => t[k]))) *
|
||||
1.1,
|
||||
],
|
||||
toolTip: (
|
||||
<TemperatureTooltip
|
||||
keys={tempData.tempKeys}
|
||||
dotColor={theme.palette.error.main}
|
||||
/>
|
||||
),
|
||||
},
|
||||
...(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: <PercentTick />,
|
||||
xTick: <TzTick />,
|
||||
toolTip: (
|
||||
<InfrastructureTooltip
|
||||
dotColor={theme.palette.warning.main}
|
||||
yKey={`disk.usage_percent`}
|
||||
yLabel={"Disc usage"}
|
||||
yIdx={idx}
|
||||
/>
|
||||
),
|
||||
})) || []),
|
||||
];
|
||||
};
|
||||
|
||||
// Fetch data
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
@@ -211,102 +444,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 (
|
||||
<Box>
|
||||
@@ -343,6 +498,7 @@ const InfrastructureDetails = () => {
|
||||
</Stack>
|
||||
<Stack
|
||||
direction="row"
|
||||
flexWrap="wrap"
|
||||
gap={theme.spacing(8)}
|
||||
>
|
||||
{statBoxConfigs.map((statBox) => (
|
||||
@@ -380,41 +536,36 @@ const InfrastructureDetails = () => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
{areaChartConfigs.map((config) => (
|
||||
<BaseBox key={`${config.type}-${config.diskIndex ?? ""}`}>
|
||||
<Typography
|
||||
component="h2"
|
||||
padding={theme.spacing(8)}
|
||||
>
|
||||
{config.heading}
|
||||
</Typography>
|
||||
<AreaChart
|
||||
height={areaChartHeight}
|
||||
data={monitor?.checks ?? []}
|
||||
dataKey={config.dataKey}
|
||||
xKey="createdAt"
|
||||
yKey={config.dataKey}
|
||||
customTooltip={({ active, payload, label }) => (
|
||||
<InfrastructureTooltip
|
||||
label={label}
|
||||
yKey={
|
||||
config.type === "disk" ? "disk.usage_percent" : config.dataKey
|
||||
}
|
||||
yLabel={config.yLabel}
|
||||
yIdx={config.diskIndex}
|
||||
active={active}
|
||||
payload={payload}
|
||||
/>
|
||||
)}
|
||||
xTick={<TzTick />}
|
||||
yTick={<PercentTick />}
|
||||
strokeColor={config.strokeColor}
|
||||
gradient={true}
|
||||
gradientStartColor={config.strokeColor}
|
||||
gradientEndColor="#ffffff"
|
||||
/>
|
||||
</BaseBox>
|
||||
))}
|
||||
{areaChartConfigs.map((config) => {
|
||||
if (config?.data?.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseBox key={`${config.type}-${config.diskIndex ?? ""}`}>
|
||||
<Typography
|
||||
component="h2"
|
||||
padding={theme.spacing(8)}
|
||||
>
|
||||
{config.heading}
|
||||
</Typography>
|
||||
<AreaChart
|
||||
height={areaChartHeight}
|
||||
data={config.data}
|
||||
dataKeys={config.dataKeys}
|
||||
xKey="createdAt"
|
||||
yDomain={config.yDomain}
|
||||
customTooltip={config.toolTip}
|
||||
xTick={config.xTick}
|
||||
yTick={config.yTick}
|
||||
strokeColor={config.strokeColor}
|
||||
gradient={true}
|
||||
gradientStartColor={config.gradientStartColor}
|
||||
gradientEndColor="#ffffff"
|
||||
/>
|
||||
</BaseBox>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
</Stack>
|
||||
) : (
|
||||
|
||||
@@ -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 };
|
||||
export { buildErrors, hasValidationErrors };
|
||||
|
||||
@@ -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.",
|
||||
// }),
|
||||
|
||||
@@ -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: () => [],
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user