diff --git a/client/src/components/monitors/HeaderFilter.tsx b/client/src/components/monitors/HeaderFilter.tsx index b1b1fcf80..45a9a9401 100644 --- a/client/src/components/monitors/HeaderFilter.tsx +++ b/client/src/components/monitors/HeaderFilter.tsx @@ -1,8 +1,10 @@ import Stack from "@mui/material/Stack"; +import Box from "@mui/material/Box"; import MenuItem from "@mui/material/MenuItem"; import Typography from "@mui/material/Typography"; -import { Select, Checkbox } from "@/components/inputs"; +import { Select, Checkbox, TextInput } from "@/components/inputs"; +import { useEffect, useState } from "react"; import { useTheme } from "@mui/material/styles"; import type { SelectChangeEvent } from "@mui/material/Select"; import { @@ -17,6 +19,8 @@ type HeaderFilterProps = { selectedStatuses: MonitorStatus[]; onTypesChange: (types: UptimeMonitorType[]) => void; onStatusesChange: (statuses: MonitorStatus[]) => void; + searchString: string; + onSearchStringChange?: (value: string) => void; }; const coerceValue = (value: string | string[]): T[] => { @@ -31,8 +35,24 @@ export const HeaderFilter = ({ selectedStatuses, onTypesChange, onStatusesChange, + searchString, + onSearchStringChange, }: HeaderFilterProps) => { const theme = useTheme(); + const [localSearch, setLocalSearch] = useState(searchString); + + useEffect(() => { + setLocalSearch(searchString); + }, [searchString]); + + useEffect(() => { + const handle = setTimeout(() => { + if (localSearch !== searchString) { + onSearchStringChange?.(localSearch); + } + }, 300); + return () => clearTimeout(handle); + }, [localSearch, onSearchStringChange, searchString]); const handleTypeChange = (event: SelectChangeEvent) => { onTypesChange(coerceValue(event.target.value)); @@ -76,6 +96,14 @@ export const HeaderFilter = ({ ))} + + { + setLocalSearch(event.target.value); + }} + /> ); }; diff --git a/client/src/pages/uptime/UptimeMonitors.tsx b/client/src/pages/uptime/UptimeMonitors.tsx index 7a26f866b..709e479e1 100644 --- a/client/src/pages/uptime/UptimeMonitors.tsx +++ b/client/src/pages/uptime/UptimeMonitors.tsx @@ -41,6 +41,7 @@ const UptimeMonitors = () => { const [rowsPerPage, setRowsPerPage] = useState(10); const [selectedTypes, setSelectedTypes] = useState([]); const [selectedStatuses, setSelectedStatuses] = useState([]); + const [searchString, setSearchString] = useState(""); const { deleteFn, loading: isDeleting } = useDelete(); @@ -60,6 +61,7 @@ const UptimeMonitors = () => { "embedChecks=true", typeQuery, statusQuery, + `search=${searchString}`, `sortField=${sortField}`, `sortOrder=${sortOrder}`, `page=${page}`, @@ -99,7 +101,9 @@ const UptimeMonitors = () => { }; const hasActiveFilters = - selectedTypes.length > 0 || selectedStatuses.length > 0; + selectedTypes.length > 0 || + selectedStatuses.length > 0 || + searchString.trim() !== ""; const showFallback = monitors.length === 0 && !hasActiveFilters; const monitorItems = showFallback @@ -134,6 +138,8 @@ const UptimeMonitors = () => { selectedStatuses={selectedStatuses} onTypesChange={setSelectedTypes} onStatusesChange={setSelectedStatuses} + searchString={searchString} + onSearchStringChange={setSearchString} /> Promise; getAllEmbedChecks: ( teamId: string, + search: string, sortField: string, sortOrder: string, page: number, @@ -104,6 +105,7 @@ class MonitorService implements IMonitorService { getAllEmbedChecks = async ( teamId: string, + search: string, sortField: string, sortOrder: string, page: number, @@ -117,6 +119,14 @@ class MonitorService implements IMonitorService { teamId: teamObjectId, }; + if (search && search.trim() !== "") { + const term = search.trim(); + (matchConditions as any).$or = [ + { name: { $regex: term, $options: "i" } }, + { url: { $regex: term, $options: "i" } }, + ]; + } + if (Array.isArray(type) && type.length > 0) { matchConditions.type = { $in: type }; } diff --git a/server/src/validation/index.ts b/server/src/validation/index.ts index 8069a01a5..a4bd753a9 100644 --- a/server/src/validation/index.ts +++ b/server/src/validation/index.ts @@ -181,6 +181,7 @@ export const monitorAllEmbedChecksQuerySchema = z.object({ if (!val) return []; return Array.isArray(val) ? val : [val]; }), + search: z.string().optional(), sortField: z.string().optional(), sortOrder: z.string().optional(), page: z