This commit is contained in:
Alex Holliday committed 2025-12-30 19:23:48 +00:00
1 parent 4f4863db75
commit 445b594cab
5 files changed
+234 -7

No files matched your search

@@ -0,0 +1,104 @@
import Stack from "@mui/material/Stack";
import Grid from "@mui/material/Grid";
import Typography from "@mui/material/Typography";
import { useTranslation } from "react-i18next";
import { BaseBox } from "@/components/design-elements";
type ExposedPort = { port: string; protocol: string };
export type DockerContainerSummary = {
container_id: string;
container_name: string;
base_image?: string;
exposed_ports?: ExposedPort[];
status?: string;
health?: { healthy?: boolean; source?: string; message?: string };
};
const shortId = (id?: string) => (id ? id.slice(0, 12) : "-");
const portsText = (ports?: ExposedPort[]) =>
(ports || [])
.map((p) => `${p.port}${p.protocol ? "/" + p.protocol : ""}`)
.join(", ");
export const CardDockerContainer = ({
container,
}: {
container: DockerContainerSummary;
}) => {
const { t } = useTranslation();
return (
<BaseBox p={4} width={{ xs: "100%", md: 420 }}>
<Stack spacing={6} sx={{ width: "100%" }}>
<Typography variant="h6" color="textPrimary">
{t("monitors.docker.details.dockerContainerCard.title", {
name: container.container_name || "-",
})}
</Typography>
<Grid container spacing={2}>
<Grid size={4}>
<Typography variant="body2" color="text.secondary">
{t("monitors.docker.details.dockerContainerCard.fields.status")}
</Typography>
</Grid>
<Grid size={8}>
<Typography variant="body2">{container.status || "-"}</Typography>
</Grid>
<Grid size={4}>
<Typography variant="body2" color="text.secondary">
{t("monitors.docker.details.dockerContainerCard.fields.health")}
</Typography>
</Grid>
<Grid size={8}>
<Typography variant="body2">
{container.health?.healthy === true
? t(
"monitors.docker.details.dockerContainerCard.values.healthy"
)
: container.health?.healthy === false
? t(
"monitors.docker.details.dockerContainerCard.values.unhealthy"
)
: "-"}
</Typography>
</Grid>
<Grid size={4}>
<Typography variant="body2" color="text.secondary">
{t("monitors.docker.details.dockerContainerCard.fields.id")}
</Typography>
</Grid>
<Grid size={8}>
<Typography variant="body2">
{shortId(container.container_id)}
</Typography>
</Grid>
<Grid size={4}>
<Typography variant="body2" color="text.secondary">
{t("monitors.docker.details.dockerContainerCard.fields.image")}
</Typography>
</Grid>
<Grid size={8}>
<Typography variant="body2">
{container.base_image || "-"}
</Typography>
</Grid>
<Grid size={4}>
<Typography variant="body2" color="text.secondary">
{t("monitors.docker.details.dockerContainerCard.fields.ports")}
</Typography>
</Grid>
<Grid size={8}>
<Typography variant="body2">
{portsText(container.exposed_ports)}
</Typography>
</Grid>
</Grid>
</Stack>
</BaseBox>
);
};
@@ -0,0 +1,77 @@
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import { BaseChart } from "@/components/monitors/Chart";
import {
ResponsiveContainer,
AreaChart,
Area,
XAxis,
YAxis,
CartesianGrid,
} from "recharts";
import { XTick } from "@/components/monitors/ChartResponseTime";
import { useTheme } from "@mui/material/styles";
export const HistogramDockerPercentage = ({
data,
dataKey,
range,
title,
emptyText,
}: {
data: any[];
dataKey: string;
range: string;
title: string;
emptyText?: string;
}) => {
const theme = useTheme();
const hasData = Array.isArray(data) && data.length > 0;
return (
<BaseChart title={title} icon={null}>
{!hasData ? (
<Stack height={"100%"} alignItems={"center"} justifyContent={"center"}>
<Typography variant="h2">{emptyText || "No data"}</Typography>
</Stack>
) : (
<ResponsiveContainer width="100%" height={155}>
<AreaChart data={data}>
<CartesianGrid
stroke={theme.palette.divider}
strokeWidth={1}
strokeOpacity={1}
fill="transparent"
vertical={false}
/>
<defs>
<linearGradient id="area" x1="0" y1="0" x2="0" y2="1">
<stop
offset="0%"
stopColor={theme.palette.primary.main}
stopOpacity={0.8}
/>
<stop
offset="100%"
stopColor={theme.palette.primary.light}
stopOpacity={0}
/>
</linearGradient>
</defs>
<XAxis
dataKey={"_id"}
tick={(props) => <XTick {...props} range={range} />}
/>
<YAxis />
<Area
dataKey={dataKey}
stroke={theme.palette.primary.main}
fill="url(#area)"
type="monotone"
/>
</AreaChart>
</ResponsiveContainer>
)}
</BaseChart>
);
};
+2 -1
View File
@@ -5,5 +5,6 @@ export { HeaderControls } from "./HeaderControls";
export { ChartResponseTime } from "./ChartResponseTime";
export { HistogramPageSpeed } from "./HistogramPageSpeed";
export { HistogramInfrastructure } from "./HistogramInfrastructure";
export { CardDockerContainer } from "./CardDockerContainer";
export { HeaderFilter } from "./HeaderFilter";
export { HistogramDockerPercentage } from "./HistogramDockerPercentage";
+25
View File
@@ -149,6 +149,7 @@
}
}
},
"common": {
"appName": "Checkmate",
"breadcrumbs": {
@@ -530,6 +531,30 @@
"headers": {
"runningContainers": "Running containers"
}
},
"details": {
"dockerContainerCard": {
"title": "Container: {{name}}",
"fields": {
"status": "Status",
"health": "Health",
"id": "ID",
"image": "Image",
"ports": "Ports"
},
"values": {
"healthy": "Healthy",
"unhealthy": "Unhealthy"
}
},
"healthyChart": {
"title": "Healthy container %",
"empty": "No data"
},
"runningChart": {
"title": "Running container %",
"empty": "No data"
}
}
},
"infrastructure": {
+26 -6
View File
@@ -1,7 +1,11 @@
import { BasePage, StatBox } from "@/components/design-elements";
import { HeaderControls, HeaderRange } from "@/components/monitors";
import {
HeaderControls,
HeaderRange,
HistogramDockerPercentage,
} from "@/components/monitors";
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import { CardDockerContainer } from "@/components/monitors";
import { useTranslation } from "react-i18next";
import prettyMilliseconds from "pretty-ms";
@@ -35,6 +39,8 @@ const DockerDetailsPage = () => {
const stats = response?.data?.stats;
const checks = (response?.data?.checks || []) as any;
const latestCheck = checks[0];
const snapshot = latestCheck?.runningContainersSnapshot || [];
console.log(checks);
const palette = getStatusPalette(monitor?.status || "initializing");
const streakDuration = stats?.currentStreakStartedAt
@@ -76,11 +82,25 @@ const DockerDetailsPage = () => {
/>
</Stack>
<HeaderRange loading={loading} range={range} setRange={setRange} />
<Stack gap={theme.spacing(6)}>
<Box component="pre" sx={{ whiteSpace: "pre-wrap" }}>
{JSON.stringify(latestCheck, null, 2)}
</Box>
<Stack gap={theme.spacing(6)} direction={{ xs: "column", md: "row" }}>
{snapshot.map((c: any) => (
<CardDockerContainer key={c.container_id} container={c} />
))}
</Stack>
<HistogramDockerPercentage
data={checks}
dataKey="healthyPercent"
range={range}
title={t("monitors.docker.details.healthyChart.title")}
emptyText={t("monitors.docker.details.healthyChart.empty")}
/>
<HistogramDockerPercentage
data={checks}
dataKey="runningPercent"
range={range}
title={t("monitors.docker.details.runningChart.title")}
emptyText={t("monitors.docker.details.runningChart.empty")}
/>
</BasePage>
);
};