From 1322033d41847af1df7f1dcddd150f4d3d435a4f Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 20 May 2026 12:18:17 -0700 Subject: [PATCH] add tags to infra monitors --- .../Monitors/Components/MonitorsTable.tsx | 38 +++++++++++++++- .../Pages/Infrastructure/Monitors/index.tsx | 44 +++++++++++++++++-- 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable.tsx b/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable.tsx index e6458ea34..ba8399522 100644 --- a/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable.tsx +++ b/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable.tsx @@ -1,7 +1,14 @@ import Box from "@mui/material/Box"; import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import { Table, Pagination, StatusLabel, Gauge } from "@/Components/design-elements"; +import { + Table, + Pagination, + StatusLabel, + Gauge, + ColoredLabel, +} from "@/Components/design-elements"; +import { SPACING } from "@/Utils/Theme/constants"; import type { Header } from "@/Components/design-elements/Table"; import { ActionsMenu, type ActionMenuItem } from "@/Components/actions-menu"; import { ArrowUp, ArrowDown } from "lucide-react"; @@ -13,10 +20,12 @@ import { useNavigate } from "react-router-dom"; import { usePost } from "@/Hooks/UseApi"; import type { Monitor } from "@/Types/Monitor"; +import type { Tag } from "@/Types/Tag"; import { Checkbox } from "@/Components/inputs"; interface InfraMonitorsTableProps { monitors: Monitor[]; + tags: Tag[]; refetch: () => void; setSelectedMonitor: (monitor: Monitor | null) => void; sortField: string; @@ -34,6 +43,7 @@ interface InfraMonitorsTableProps { export const InfraMonitorsTable = ({ monitors, + tags, refetch, setSelectedMonitor, sortField, @@ -267,6 +277,32 @@ export const InfraMonitorsTable = ({ return ; }, }, + { + 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((t) => t.id === tagId); + if (!fullTag) return null; + return ( + + ); + })} + + ); + }, + }, { id: "actions", diff --git a/client/src/Pages/Infrastructure/Monitors/index.tsx b/client/src/Pages/Infrastructure/Monitors/index.tsx index 63031f41e..aa41d2031 100644 --- a/client/src/Pages/Infrastructure/Monitors/index.tsx +++ b/client/src/Pages/Infrastructure/Monitors/index.tsx @@ -1,6 +1,7 @@ import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; import useMediaQuery from "@mui/material/useMediaQuery"; -import { MonitorBasePageWithStates } from "@/Components/design-elements"; +import { MonitorBasePageWithStates, ColoredLabel } from "@/Components/design-elements"; import { HeaderCreate } from "@/Components/common"; import { ControlsFilter, @@ -13,7 +14,9 @@ import { Play, Pause } from "lucide-react"; import { useGet, useDelete } from "@/Hooks/UseApi"; import { useIsAdmin } from "@/Hooks/useIsAdmin"; import type { Monitor, MonitorsWithChecksResponse } from "@/Types/Monitor"; +import type { Tag } from "@/Types/Tag"; import { useTheme } from "@mui/material"; +import { SPACING } from "@/Utils/Theme/constants"; import { useState, useCallback, useMemo } from "react"; import { useTranslation } from "react-i18next"; import { useSelector, useDispatch } from "react-redux"; @@ -38,12 +41,15 @@ const InfrastructureMonitors = () => { // Local state const [selectedStatus, setSelectedStatus] = useState(""); const [selectedState, setSelectedState] = useState(""); + const [selectedTags, setSelectedTags] = useState([]); const [search, setSearch] = useState(""); const [page, setPage] = useState(0); const [sortField, setSortField] = useState(""); const [sortOrder, setSortOrder] = useState<"asc" | "desc">("asc"); const [selectedMonitor, setSelectedMonitor] = useState(null); + const { data: tags } = useGet("/tags/team"); + const isDialogOpen = Boolean(selectedMonitor); const debouncedSearch = useDebounce(search, 300); @@ -51,6 +57,7 @@ const InfrastructureMonitors = () => { const handleClearFilters = useCallback(() => { setSelectedStatus(""); setSelectedState(""); + setSelectedTags([]); setSearch(""); }, []); @@ -81,6 +88,7 @@ const InfrastructureMonitors = () => { const monitorsWithChecksUrl = useMemo(() => { const params = new URLSearchParams(); params.append("type", "hardware"); + selectedTags.forEach((tagId) => params.append("tags", tagId)); params.append("limit", "1"); if (page !== undefined) params.append("page", String(page)); if (rowsPerPage) params.append("rowsPerPage", String(rowsPerPage)); @@ -88,7 +96,7 @@ const InfrastructureMonitors = () => { if (field) params.append("field", field); if (sortOrder) params.append("order", sortOrder); return `/monitors/team/with-checks?${params.toString()}`; - }, [page, rowsPerPage, filter, field, sortOrder]); + }, [page, rowsPerPage, filter, field, sortOrder, selectedTags]); const { data: monitorsWithChecksData, @@ -114,7 +122,9 @@ const InfrastructureMonitors = () => { } = useBulkMonitorActions(refetch, page); // Check if any filters are active - const hasActiveFilters = Boolean(selectedStatus || selectedState || search); + const hasActiveFilters = Boolean( + selectedStatus || selectedState || selectedTags.length > 0 || search + ); // Show empty state only when there are truly no monitors (not just filtered out) const effectiveTotalCount = @@ -165,6 +175,9 @@ const InfrastructureMonitors = () => { setSelectedStatus={setSelectedStatus} selectedState={selectedState} setSelectedState={setSelectedState} + tagOptions={tags ?? []} + selectedTags={selectedTags} + setSelectedTags={setSelectedTags} onClearFilters={handleClearFilters} /> { /> + {selectedTags.length > 0 && ( + + + {t("pages.uptime.filters.activeTags")} + + {selectedTags.map((tagId) => { + const tag = tags?.find((t) => t.id === tagId); + if (!tag) return null; + return ( + + ); + })} + + )} + {!isLoading && ( {