diff --git a/client/src/Components/monitors/MonitorListPage.tsx b/client/src/Components/monitors/MonitorListPage.tsx
new file mode 100644
index 000000000..2cadb2b81
--- /dev/null
+++ b/client/src/Components/monitors/MonitorListPage.tsx
@@ -0,0 +1,155 @@
+import { HeaderCreate } from "@/Components/common";
+import { ColoredLabel, MonitorBasePageWithStates } from "@/Components/design-elements";
+import { BulkActionsBar, ControlsFilter } from "@/Components/monitors";
+import { HeaderMonitorsSummary } from "@/Components/monitors/HeaderMonitorsSummary";
+import { useIsAdmin } from "@/Hooks/useIsAdmin";
+import type { MonitorListController } from "@/Hooks/useMonitorListController";
+import { useMediaQuery, useTheme } from "@mui/material";
+import { SPACING } from "@/Utils/Theme/constants";
+
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import { TextField, Button, Dialog } from "@/Components/inputs";
+import type { ReactNode } from "react";
+import { useTranslation } from "react-i18next";
+import { Pause, Play } from "lucide-react";
+
+interface MonitorListPageProps {
+ headerKey: string;
+ page: string;
+ actionLink: string;
+ controller: MonitorListController;
+ bulkActions?: boolean;
+ showTypeFilter?: boolean;
+ summaryProps?: { showBreached?: boolean };
+ priorityFallback?: ReactNode;
+ extraLoading?: boolean;
+ extraError?: unknown;
+ children: ReactNode;
+}
+
+// Base monitor list page, all monitors pages are nearly identical, but with different tables.
+
+export const MonitorListPage = ({
+ headerKey,
+ page,
+ actionLink,
+ controller: c,
+ bulkActions,
+ showTypeFilter,
+ summaryProps,
+ priorityFallback,
+ extraLoading,
+ extraError,
+ children,
+}: MonitorListPageProps) => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+ const isSmall = useMediaQuery(theme.breakpoints.down("md"));
+ const isAdmin = useIsAdmin();
+ const isLoading = c.isLoading || Boolean(extraLoading);
+
+ return (
+
+
+
+
+
+ {
+ c.setSearch(event.target.value);
+ }}
+ />
+
+ {c.selectedTags.length > 0 && (
+
+
+ {t("pages.uptime.filters.activeTags")}
+
+ {c.selectedTags.map((tagId) => {
+ const tag = c.tags?.find((t) => t.id === tagId);
+ if (!tag) return null;
+ return (
+
+ );
+ })}
+
+ )}
+ {bulkActions && !isLoading && (
+
+ }
+ onClick={c.handleBulkResume}
+ >
+ {t("common.buttons.resume")}
+
+ }
+ onClick={c.handleBulkPause}
+ >
+ {t("common.buttons.pause")}
+
+
+ )}
+ {children}
+
+
+ );
+};
diff --git a/client/src/Components/monitors/index.tsx b/client/src/Components/monitors/index.tsx
index 46532706f..cf87eb074 100644
--- a/client/src/Components/monitors/index.tsx
+++ b/client/src/Components/monitors/index.tsx
@@ -15,3 +15,4 @@ export * from "./charts/HistogramPageSpeedDetailsTooltip";
export * from "./charts/HistogramInfrastructure";
export * from "./HeaderMonitorsSummary";
export * from "./BulkActionsBar";
+export * from "./MonitorListPage";
diff --git a/client/src/Hooks/useMonitorListController.ts b/client/src/Hooks/useMonitorListController.ts
index 63cc4ee91..cd6852594 100644
--- a/client/src/Hooks/useMonitorListController.ts
+++ b/client/src/Hooks/useMonitorListController.ts
@@ -205,3 +205,5 @@ export const useMonitorListController = (config: MonitorListConfig) => {
...bulk,
};
};
+
+export type MonitorListController = ReturnType;