diff --git a/client/src/components/layouts/sidebar/Menu.tsx b/client/src/components/layouts/sidebar/Menu.tsx
index 71e81b226..cbf1e79e6 100644
--- a/client/src/components/layouts/sidebar/Menu.tsx
+++ b/client/src/components/layouts/sidebar/Menu.tsx
@@ -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: ,
},
+ {
+ name: t("menu.docker"),
+ path: "docker",
+ icon: ,
+ },
{
name: t("menu.notifications"),
path: "notification-channels",
diff --git a/client/src/locales/en.json b/client/src/locales/en.json
index 65cd8b76f..cf7e86cfb 100644
--- a/client/src/locales/en.json
+++ b/client/src/locales/en.json
@@ -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!",
diff --git a/client/src/pages/docker/DockerMonitors.tsx b/client/src/pages/docker/DockerMonitors.tsx
new file mode 100644
index 000000000..b8156097e
--- /dev/null
+++ b/client/src/pages/docker/DockerMonitors.tsx
@@ -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(null);
+ const isDialogOpen = Boolean(selectedMonitor);
+ const [sortField] = useState("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
+ >(
+ 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 (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default DockerMonitorsPage;
diff --git a/client/src/pages/docker/DockerMonitorsTable.tsx b/client/src/pages/docker/DockerMonitorsTable.tsx
new file mode 100644
index 000000000..6bf580f32
--- /dev/null
+++ b/client/src/pages/docker/DockerMonitorsTable.tsx
@@ -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 | null,
+ newPage: number
+ ) => setPage(newPage);
+
+ const handleRowsPerPageChange = (
+ e: React.ChangeEvent
+ ) => {
+ setPage(0);
+ setRowsPerPage(Number(e.target.value));
+ };
+
+ const headers: Header[] = [
+ {
+ id: "name",
+ content: (
+ {t("common.table.headers.name")}
+ ),
+ render: (row) => row.name,
+ },
+ {
+ id: "status",
+ content: (
+
+ {t("common.table.headers.status")}
+
+ ),
+ render: (row) => ,
+ },
+ ];
+
+ return (
+ <>
+
+
+ >
+ );
+};
diff --git a/client/src/pages/errors/NotFound.tsx b/client/src/pages/errors/NotFound.tsx
index cc9ad286d..ebd3e9181 100644
--- a/client/src/pages/errors/NotFound.tsx
+++ b/client/src/pages/errors/NotFound.tsx
@@ -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")}
diff --git a/client/src/routes/Router.tsx b/client/src/routes/Router.tsx
index 9037e1498..5a52804a9 100644
--- a/client/src/routes/Router.tsx
+++ b/client/src/routes/Router.tsx
@@ -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 = () => {
/>
} />
} />
+ } />
} />
} />