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 && (
{