diff --git a/client/src/Pages/PageSpeed/Monitors/Components/PageSpeedMonitorsTable.tsx b/client/src/Pages/PageSpeed/Monitors/Components/PageSpeedMonitorsTable.tsx
index 9e9c6e89b..9d1789264 100644
--- a/client/src/Pages/PageSpeed/Monitors/Components/PageSpeedMonitorsTable.tsx
+++ b/client/src/Pages/PageSpeed/Monitors/Components/PageSpeedMonitorsTable.tsx
@@ -6,8 +6,9 @@ import {
Pagination,
StatusLabel,
StrategyBadge,
+ ColoredLabel,
} from "@/Components/design-elements";
-import { LAYOUT } from "@/Utils/Theme/constants";
+import { LAYOUT, SPACING } from "@/Utils/Theme/constants";
import { HistogramPageSpeed } from "@/Components/monitors";
import { ArrowDown, ArrowUp } from "lucide-react";
import type { Header } from "@/Components/design-elements/Table";
@@ -20,10 +21,12 @@ import { useNavigate } from "react-router-dom";
import { usePatch } from "@/Hooks/UseApi";
import type { Monitor, MonitorWithChecks } from "@/Types/Monitor";
+import type { Tag } from "@/Types/Tag";
import type { ActionMenuItem } from "@/Components/actions-menu";
export const PageSpeedMonitorsTable = ({
monitors,
+ tags,
refetch,
setSelectedMonitor,
sortField,
@@ -37,6 +40,7 @@ export const PageSpeedMonitorsTable = ({
setRowsPerPage,
}: {
monitors: MonitorWithChecks[];
+ tags: Tag[];
refetch: Function;
setSelectedMonitor: Function;
sortField: string;
@@ -233,6 +237,32 @@ export const PageSpeedMonitorsTable = ({
);
},
},
+ {
+ id: "tags",
+ content: t("common.table.headers.tags"),
+ render: (row) => {
+ if (row.tags.length === 0) return "-";
+ return (
+
+ {row.tags.map((tagId) => {
+ const fullTag = tags.find((tag) => tag.id === tagId);
+ if (!fullTag) return null;
+ return (
+
+ );
+ })}
+
+ );
+ },
+ },
{
id: "actions",
diff --git a/client/src/Pages/PageSpeed/Monitors/index.tsx b/client/src/Pages/PageSpeed/Monitors/index.tsx
index 4cb28b51e..b9d0b49b1 100644
--- a/client/src/Pages/PageSpeed/Monitors/index.tsx
+++ b/client/src/Pages/PageSpeed/Monitors/index.tsx
@@ -1,22 +1,32 @@
import {
MonitorBasePageWithStates,
PageSpeedKeyPriorityFallback,
+ ColoredLabel,
} from "@/Components/design-elements";
-import { Dialog } from "@/Components/inputs";
+import { Dialog, TextField } from "@/Components/inputs";
import { HeaderCreate } from "@/Components/common";
import { PageSpeedMonitorsTable } from "@/Pages/PageSpeed/Monitors/Components/PageSpeedMonitorsTable";
import type { Monitor } from "@/Types/Monitor";
+import type { Tag } from "@/Types/Tag";
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import useMediaQuery from "@mui/material/useMediaQuery";
+import { useTheme } from "@mui/material";
+import { SPACING } from "@/Utils/Theme/constants";
import { useTranslation } from "react-i18next";
-import { useMemo, useState } from "react";
+import { useCallback, useMemo, useState } from "react";
import { useIsAdmin } from "@/Hooks/useIsAdmin";
import { useGet, useDelete } from "@/Hooks/UseApi";
+import useDebounce from "@/Hooks/useDebounce";
import type { MonitorsWithChecksResponse } from "@/Types/Monitor";
import type { AppSettingsResponse } from "@/Types/Settings";
-import { HeaderMonitorsSummary } from "@/Components/monitors";
+import { ControlsFilter, HeaderMonitorsSummary } from "@/Components/monitors";
const PageSpeedMonitorsPage = () => {
const { t } = useTranslation();
+ const theme = useTheme();
+ const isSmall = useMediaQuery(theme.breakpoints.down("md"));
const isAdmin = useIsAdmin();
const { deleteFn, loading: isDeleting } = useDelete();
@@ -33,17 +43,50 @@ const PageSpeedMonitorsPage = () => {
const [sortOrder, setSortOrder] = useState<"asc" | "desc">("asc");
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(10);
+ const [selectedTags, setSelectedTags] = useState([]);
+ const [selectedStatus, setSelectedStatus] = useState("");
+ const [selectedState, setSelectedState] = useState("");
+ const [search, setSearch] = useState("");
+
+ const debouncedSearch = useDebounce(search, 300);
+
+ // Status: pass "up"/"down" directly to the API
+ // State: "active" -> true, "paused" -> false
+ const toFilterStatus = useMemo(() => {
+ if (selectedStatus === "up") return "up";
+ if (selectedStatus === "down") return "down";
+ return undefined;
+ }, [selectedStatus]);
+
+ const toFilterActive = useMemo(() => {
+ if (selectedState === "active") return "true";
+ if (selectedState === "paused") return "false";
+ return undefined;
+ }, [selectedState]);
+
+ // Priority: status > isActive > search > sort
+ const filterLookup = new Map([
+ [toFilterStatus, "status"],
+ [toFilterActive, "isActive"],
+ ]);
+ const activeFilter = [...filterLookup].find(([key]) => key !== undefined);
+ const field = activeFilter?.[1] || (debouncedSearch ? "name" : sortField || undefined);
+ const filter = activeFilter?.[0] || debouncedSearch;
+
+ const { data: tags } = useGet("/tags/team");
const monitorsWithChecksUrl = useMemo(() => {
const params = new URLSearchParams();
params.append("type", "pagespeed");
+ selectedTags.forEach((tagId) => params.append("tags", tagId));
params.append("limit", "25");
if (page !== undefined) params.append("page", String(page));
if (rowsPerPage) params.append("rowsPerPage", String(rowsPerPage));
- if (sortField) params.append("field", sortField);
+ if (filter) params.append("filter", filter);
+ if (field) params.append("field", field);
if (sortOrder) params.append("order", sortOrder);
return `/monitors/team/with-checks?${params.toString()}`;
- }, [page, rowsPerPage, sortField, sortOrder]);
+ }, [page, rowsPerPage, sortOrder, selectedTags, filter, field]);
const {
data: monitorsData,
@@ -75,12 +118,28 @@ const PageSpeedMonitorsPage = () => {
setSelectedMonitor(null);
};
+ const handleClearFilters = useCallback(() => {
+ setSelectedStatus("");
+ setSelectedState("");
+ setSelectedTags([]);
+ setSearch("");
+ }, []);
+
+ const hasActiveFilters = Boolean(
+ selectedStatus || selectedState || selectedTags.length > 0 || search
+ );
+
+ const effectiveTotalCount =
+ hasActiveFilters && (summary?.totalMonitors ?? 0) === 0
+ ? 1
+ : (summary?.totalMonitors ?? 0);
+
return (
: undefined}
@@ -91,8 +150,59 @@ const PageSpeedMonitorsPage = () => {
isAdmin={isAdmin}
/>
+
+
+
+ {
+ setSearch(event.target.value);
+ }}
+ />
+
+
+ {selectedTags.length > 0 && (
+
+
+ {t("pages.uptime.filters.activeTags")}
+
+ {selectedTags.map((tagId) => {
+ const tag = tags?.find((t) => t.id === tagId);
+ if (!tag) return null;
+ return (
+
+ );
+ })}
+
+ )}
+