## Summary Add support for monitoring Proxmox LXC containers through the Capture agent's /api/v1/metrics/proxmox endpoint. This feature is opt-in via a new setting in the infrastructure monitor configuration. ## Backend Changes - Add enableProxmoxContainers field to Monitor schema and types - Update networkService to conditionally fetch Proxmox metrics - Handle HTTP 207 multi-status responses and merge errors - Add containers field to Check schema for storing container data - Update validation schemas for the new setting ## Frontend Changes - Add ContainerStats component with charts and stat boxes - Add Containers tab to infrastructure monitor details (conditional) - Add enableProxmoxContainers switch in monitor create/edit form - Fix chart colors for disk I/O and network I/O to be distinct - Add dotColor2 prop to InfrastructureTooltip for multi-series charts - Add translation keys to all 16 locale files ## How It Works 1. User enables "Proxmox container monitoring" in monitor settings 2. Checkmate appends /proxmox to the metrics URL when fetching 3. Container data is displayed in a dedicated tab with: - CPU, memory, disk, swap, and uptime stat boxes - Interactive charts for CPU, memory, disk I/O, network I/O 4. Errors from Capture are merged and logged appropriately
131 lines
3.3 KiB
React
131 lines
3.3 KiB
React
import PropTypes from "prop-types";
|
|
import StatusBoxes from "@/Components/v1/StatusBoxes/index.jsx";
|
|
import StatBox from "@/Components/v1/StatBox/index.jsx";
|
|
import { Typography } from "@mui/material";
|
|
import { useTranslation } from "react-i18next";
|
|
import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx";
|
|
|
|
const formatUptime = (seconds) => {
|
|
if (!seconds || seconds === 0) return "0s";
|
|
|
|
const days = Math.floor(seconds / 86400);
|
|
const hours = Math.floor((seconds % 86400) / 3600);
|
|
const minutes = Math.floor((seconds % 3600) / 60);
|
|
|
|
if (days > 0) {
|
|
return `${days}d ${hours}h`;
|
|
} else if (hours > 0) {
|
|
return `${hours}h ${minutes}m`;
|
|
} else if (minutes > 0) {
|
|
return `${minutes}m`;
|
|
}
|
|
return `${Math.floor(seconds)}s`;
|
|
};
|
|
|
|
const formatPercentage = (value) => {
|
|
if (value === undefined || value === null) return "0%";
|
|
return `${(value * 100).toFixed(1)}%`;
|
|
};
|
|
|
|
// Map container status to Checkmate monitor status for theming
|
|
const containerStatusToMonitorStatus = (status) => {
|
|
if (status === "running") return "up";
|
|
if (status === "stopped") return "down";
|
|
return "pending";
|
|
};
|
|
|
|
const ContainerStatBoxes = ({ shouldRender, container }) => {
|
|
const { t } = useTranslation();
|
|
const { formatBytes } = useHardwareUtils();
|
|
|
|
if (!container) {
|
|
return <Typography>{t("noContainerSelected")}</Typography>;
|
|
}
|
|
|
|
const monitorStatus = containerStatusToMonitorStatus(container.status);
|
|
|
|
return (
|
|
<StatusBoxes
|
|
shouldRender={shouldRender}
|
|
flexWrap="wrap"
|
|
>
|
|
<StatBox
|
|
gradient={true}
|
|
status={monitorStatus}
|
|
heading={t("status")}
|
|
subHeading={container.status || "unknown"}
|
|
/>
|
|
<StatBox
|
|
heading={t("type")}
|
|
subHeading={
|
|
<Typography component="span">
|
|
{(container.type || "lxc").toUpperCase()}
|
|
</Typography>
|
|
}
|
|
/>
|
|
<StatBox
|
|
heading={t("uptime")}
|
|
subHeading={formatUptime(container.uptime)}
|
|
/>
|
|
<StatBox
|
|
heading={t("cpuUsage")}
|
|
subHeading={
|
|
<>
|
|
{formatPercentage(container.cpu_usage)}
|
|
<Typography component="span">
|
|
({container.cpu_cores || 0} {t("cores")})
|
|
</Typography>
|
|
</>
|
|
}
|
|
/>
|
|
<StatBox
|
|
heading={t("memoryUsage")}
|
|
subHeading={
|
|
<>
|
|
{formatBytes(container.memory_used)}
|
|
<Typography component="span"> / </Typography>
|
|
{formatBytes(container.memory_total)}
|
|
</>
|
|
}
|
|
/>
|
|
<StatBox
|
|
heading={t("diskUsage")}
|
|
subHeading={
|
|
<>
|
|
{formatBytes(container.disk_used)}
|
|
<Typography component="span"> / </Typography>
|
|
{formatBytes(container.disk_total)}
|
|
</>
|
|
}
|
|
/>
|
|
</StatusBoxes>
|
|
);
|
|
};
|
|
|
|
ContainerStatBoxes.propTypes = {
|
|
shouldRender: PropTypes.bool.isRequired,
|
|
container: PropTypes.shape({
|
|
vmid: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
name: PropTypes.string,
|
|
node: PropTypes.string,
|
|
status: PropTypes.string,
|
|
type: PropTypes.string,
|
|
uptime: PropTypes.number,
|
|
cpu_cores: PropTypes.number,
|
|
cpu_usage: PropTypes.number,
|
|
memory_used: PropTypes.number,
|
|
memory_total: PropTypes.number,
|
|
memory_usage: PropTypes.number,
|
|
swap_used: PropTypes.number,
|
|
swap_total: PropTypes.number,
|
|
disk_used: PropTypes.number,
|
|
disk_total: PropTypes.number,
|
|
disk_read: PropTypes.number,
|
|
disk_write: PropTypes.number,
|
|
network_in: PropTypes.number,
|
|
network_out: PropTypes.number,
|
|
}),
|
|
};
|
|
|
|
export default ContainerStatBoxes;
|