docker monitors skeleotn

This commit is contained in:
Alex Holliday committed 2025-12-29 20:22:28 +00:00
1 parent 378a40ce84
commit a4d818e0c5
6 files changed
+219 -1

No files matched your search

@@ -3,6 +3,7 @@ import {
Monitor,
Gauge,
Server,
Boxes,
AlertTriangle,
LayoutList,
ListChecks,
@@ -36,6 +37,11 @@ export const getMenu = (t: Function, user: IUser) => {
path: "infrastructure",
icon: <Server size={20} strokeWidth={1.5} />,
},
{
name: t("menu.docker"),
path: "docker",
icon: <Boxes size={20} strokeWidth={1.5} />,
},
{
name: t("menu.notifications"),
path: "notification-channels",
+14
View File
@@ -184,6 +184,7 @@
"incidents": "Incidents",
"limitReached": "Your plan's limit has been reached",
"no": "No",
"home": "Return home",
"pause": "Pause",
"range": {
"all": "All",
@@ -447,6 +448,7 @@
"checks": "Checks",
"diagnostics": "Diagnostics",
"discussions": "Discussions",
"docker": "Docker",
"docs": "Documentation",
"incidents": "Incidents",
"infrastructure": "Infrastructure",
@@ -513,6 +515,18 @@
"up": "up"
}
},
"docker": {
"fallback": {
"actionButton": "Create a monitor!",
"checks": [
"Discover running Docker containers and track their lifecycle",
"Determine container health based on runtime state or health checks",
"Track exposed ports and network availability",
"Provide visibility into what is running on each Docker host"
],
"title": "A Docker monitor is used to:"
}
},
"infrastructure": {
"fallback": {
"actionButton": "Create a monitor!",
+128
View File
@@ -0,0 +1,128 @@
import {
MonitorBasePageWithStates,
UpStatusBox,
DownStatusBox,
PausedStatusBox,
InfoBox,
} from "@/components/design-elements";
import { HeaderCreate } from "@/components/common";
import Stack from "@mui/material/Stack";
import { Dialog } from "@/components/inputs";
import { DockerMonitorsTable } from "@/pages/docker/DockerMonitorsTable";
import { useTranslation } from "react-i18next";
import { useTheme } from "@mui/material/styles";
import { useGet } from "@/hooks/UseApi";
import type { ApiResponse } from "@/types/api";
import type { IMonitor, IMonitorWithStats } from "@/types/monitor";
import useMediaQuery from "@mui/material/useMediaQuery";
import { useState } from "react";
import { useDelete } from "@/hooks/UseApi";
import { InitializingStatusBox } from "@/components/design-elements/StatusBox";
import { config } from "@/config/index";
import { useLimitReached } from "@/hooks/UsePlanEntitlements";
const GLOBAL_REFRESH = config.GLOBAL_REFRESH;
const DockerMonitorsPage = () => {
const theme = useTheme();
const { t } = useTranslation();
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
const [selectedMonitor, setSelectedMonitor] = useState<IMonitor | null>(null);
const isDialogOpen = Boolean(selectedMonitor);
const [sortField] = useState<string>("name");
const [sortOrder] = useState<"asc" | "desc">("desc");
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(10);
const { deleteFn, loading: isDeleting } = useDelete();
const requestParams = [
"embedChecks=true",
"type=docker",
`sortField=${sortField}`,
`sortOrder=${sortOrder}`,
`page=${page}`,
`rowsPerPage=${rowsPerPage}`,
];
const monitorRequestUrl = `/monitors?${requestParams.join("&")}`;
const { response, loading, isValidating, error, refetch } = useGet<
ApiResponse<IMonitorWithStats>
>(
monitorRequestUrl,
{},
{
refreshInterval: GLOBAL_REFRESH,
keepPreviousData: true,
dedupingInterval: 0,
},
{
useTeamIdAsKey: true,
}
);
const monitors: IMonitor[] = response?.data?.monitors ?? ([] as IMonitor[]);
const count = response?.data?.count || 0;
const upCount = response?.data?.upCount || 0;
const downCount = response?.data?.downCount || 0;
const pausedCount = response?.data?.pausedCount || 0;
const monitorLimitReached = useLimitReached("monitorsMax", count);
const handleConfirm = async () => {
await deleteFn(`/monitors/${selectedMonitor?._id}`);
setSelectedMonitor(null);
refetch();
};
const handleCancel = () => {
setSelectedMonitor(null);
};
return (
<MonitorBasePageWithStates
loading={isValidating}
error={error}
items={monitors}
monitorLimitReached={monitorLimitReached}
page="docker"
actionLink="/docker/create"
>
<InfoBox
title={t("monitors.infrastructure.infoBox.title")}
description={t("monitors.infrastructure.infoBox.description")}
/>
<HeaderCreate
isLoading={loading}
path="/docker/create"
entitlement="monitorsMax"
entitlementCount={count}
/>
<Stack direction={isSmall ? "column" : "row"} gap={theme.spacing(8)}>
<UpStatusBox n={upCount} />
<DownStatusBox n={downCount} />
<PausedStatusBox n={pausedCount} />
<InitializingStatusBox
n={count - (upCount + downCount + pausedCount)}
/>
</Stack>
<DockerMonitorsTable
monitors={monitors}
count={count}
page={page}
setPage={setPage}
rowsPerPage={rowsPerPage}
setRowsPerPage={setRowsPerPage}
/>
<Dialog
open={isDialogOpen}
title={t("common.dialog.delete.title")}
content={t("common.dialog.delete.description")}
onConfirm={handleConfirm}
onCancel={handleCancel}
loading={isDeleting}
/>
</MonitorBasePageWithStates>
);
};
export default DockerMonitorsPage;
@@ -0,0 +1,68 @@
import Typography from "@mui/material/Typography";
import { Table, Pagination, StatusLabel } from "@/components/design-elements";
import type { Header } from "@/components/design-elements/Table";
import type { IMonitor } from "@/types/monitor";
import { useTranslation } from "react-i18next";
export const DockerMonitorsTable = ({
monitors,
count,
page,
setPage,
rowsPerPage,
setRowsPerPage,
}: {
monitors: IMonitor[];
count: number;
page: number;
setPage: (page: number) => void;
rowsPerPage: number;
setRowsPerPage: (rowsPerPage: number) => void;
}) => {
const { t } = useTranslation();
const handlePageChange = (
_e: React.MouseEvent<HTMLButtonElement> | null,
newPage: number
) => setPage(newPage);
const handleRowsPerPageChange = (
e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>
) => {
setPage(0);
setRowsPerPage(Number(e.target.value));
};
const headers: Header<IMonitor>[] = [
{
id: "name",
content: (
<Typography component="div">{t("common.table.headers.name")}</Typography>
),
render: (row) => row.name,
},
{
id: "status",
content: (
<Typography component="div">
{t("common.table.headers.status")}
</Typography>
),
render: (row) => <StatusLabel status={row.status} />,
},
];
return (
<>
<Table headers={headers} data={monitors} />
<Pagination
component="div"
count={count}
page={page}
rowsPerPage={rowsPerPage}
onPageChange={handlePageChange}
onRowsPerPageChange={handleRowsPerPageChange}
/>
</>
);
};
+1 -1
View File
@@ -31,7 +31,7 @@ const NotFound = ({ title = DefaultValue.title, desc = DefaultValue.desc }) => {
sx={{ mt: theme.spacing(10) }}
onClick={() => navigate("/")}
>
{t("notFoundButton")}
{t("common.buttons.home")}
</Button>
</Stack>
</Stack>
+2
View File
@@ -31,6 +31,7 @@ import InfraMonitorsPage from "@/pages/infrastructure/InfraMonitors";
import InfraDetailsPage from "@/pages/infrastructure/InfraDetails";
import InfraConfigurePage from "@/pages/infrastructure/InfraConfig";
import InfraCreatePage from "@/pages/infrastructure/InfraCreate";
import DockerMonitorsPage from "@/pages/docker/DockerMonitors";
import MaintenancePage from "@/pages/maintenance/Maintenance";
import MaintenanceConfigPage from "@/pages/maintenance/MaintenanceConfig";
@@ -137,6 +138,7 @@ const Router = () => {
/>
<Route path="infrastructure/:id" element={<InfraDetailsPage />} />
<Route path="infrastructure" element={<InfraMonitorsPage />} />
<Route path="docker" element={<DockerMonitorsPage />} />
<Route path="maintenance" element={<MaintenancePage />} />
<Route path="maintenance/create" element={<MaintenanceCreatePage />} />