implement monitor filter

This commit is contained in:
Alex Holliday committed 2025-11-18 21:43:35 +00:00
1 parent a40277be4a
commit adbb4e7817
11 files changed
+276 -78

No files matched your search

@@ -1,6 +1,6 @@
import MuiBreadcrumbs from "@mui/material/Breadcrumbs";
import Typography from "@mui/material/Typography";
import { Link, useLocation } from "react-router";
import { Link, useLocation } from "react-router-dom";
import { useTheme } from "@mui/material/styles";
import { ChevronRight, Home } from "lucide-react";
+5 -1
View File
@@ -10,7 +10,11 @@ interface SelectInputProps<T> extends Omit<SelectProps<T>, "label"> {
required?: boolean;
}
export const SelectInput = <T,>({ fieldLabel, required, ...props }: SelectInputProps<T>) => {
export const SelectInput = <T,>({
fieldLabel,
required,
...props
}: SelectInputProps<T>) => {
const theme = useTheme();
const select = (
@@ -0,0 +1,95 @@
import Stack from "@mui/material/Stack";
import MenuItem from "@mui/material/MenuItem";
import Typography from "@mui/material/Typography";
import { Select, Checkbox } from "@/components/inputs";
import { useTheme } from "@mui/material/styles";
import type { SelectChangeEvent } from "@mui/material/Select";
import {
UptimeMonitorTypes,
MonitorStatuses,
type UptimeMonitorType,
type MonitorStatus,
} from "@/types/monitor";
const formatValue = (value: string[], fallback: string) => {
if (value.length === 0) {
return fallback;
}
const capitalized = value.map(
(item) => item.charAt(0).toUpperCase() + item.slice(1)
);
return capitalized.join(" | ");
};
type HeaderFilterProps = {
selectedTypes: UptimeMonitorType[];
selectedStatuses: MonitorStatus[];
onTypesChange: (types: UptimeMonitorType[]) => void;
onStatusesChange: (statuses: MonitorStatus[]) => void;
};
const coerceValue = <T extends string>(value: string | string[]): T[] => {
if (typeof value === "string") {
return value.split(",") as T[];
}
return value as T[];
};
export const HeaderFilter = ({
selectedTypes,
selectedStatuses,
onTypesChange,
onStatusesChange,
}: HeaderFilterProps) => {
const theme = useTheme();
const handleTypeChange = (event: SelectChangeEvent<UptimeMonitorType[]>) => {
onTypesChange(coerceValue<UptimeMonitorType>(event.target.value));
};
const handleStatusChange = (event: SelectChangeEvent<MonitorStatus[]>) => {
onStatusesChange(coerceValue<MonitorStatus>(event.target.value));
};
return (
<Stack spacing={theme.spacing(4)} direction={{ xs: "column", sm: "row" }}>
<Select
fieldLabel="Monitor type"
multiple
value={selectedTypes}
onChange={handleTypeChange}
renderValue={(selected) => (
<Typography>{formatValue(selected, "All Monitor Types")}</Typography>
)}
>
{UptimeMonitorTypes.map((type) => (
<MenuItem key={type} value={type}>
<Checkbox checked={selectedTypes.includes(type)} />
<Typography textTransform={"capitalize"}>{type}</Typography>
</MenuItem>
))}
</Select>
<Select
fieldLabel="Monitor status"
multiple
value={selectedStatuses}
onChange={handleStatusChange}
renderValue={(selected) => (
<Typography>
{formatValue(selected, "All Monitor Statuses")}
</Typography>
)}
>
{MonitorStatuses.map((status) => (
<MenuItem key={status} value={status}>
<Checkbox checked={selectedStatuses.includes(status)} />
<Typography textTransform={"capitalize"}>{status}</Typography>
</MenuItem>
))}
</Select>
</Stack>
);
};
export default HeaderFilter;
+2
View File
@@ -7,3 +7,5 @@ export { ChartResponseTime } from "./ChartResponseTime";
export { HistogramResponseTime } from "./HistogramResponseTime";
export { HistogramPageSpeed } from "./HistogramPageSpeed";
export { HistogramInfrastructure } from "./HistogramInfrastructure";
export { HeaderFilter } from "./HeaderFilter";
+60 -4
View File
@@ -5,7 +5,7 @@ import {
PausedStatusBox,
InfoBox,
} from "@/components/design-elements";
import { HeaderCreate } from "@/components/monitors";
import { HeaderCreate, HeaderFilter } from "@/components/monitors";
import Stack from "@mui/material/Stack";
import { Dialog } from "@/components/inputs";
import { MonitorTable } from "@/pages/uptime/MonitorTable";
@@ -14,7 +14,14 @@ import { useTranslation } from "react-i18next";
import { useTheme } from "@mui/material/styles";
import { useGet } from "@/hooks/UseApi";
import type { ApiResponse } from "@/hooks/UseApi";
import type { IMonitor, IMonitorWithStats } from "@/types/monitor";
import {
type IMonitor,
type IMonitorWithStats,
type MonitorStatus,
type UptimeMonitorType,
UptimeMonitorTypes,
MonitorStatuses,
} from "@/types/monitor";
import useMediaQuery from "@mui/material/useMediaQuery";
import { useState } from "react";
import { useDelete } from "@/hooks/UseApi";
@@ -29,12 +36,45 @@ const UptimeMonitors = () => {
const isDialogOpen = Boolean(selectedMonitor);
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(10);
const [selectedTypes, setSelectedTypes] = useState<UptimeMonitorType[]>([
"http",
"https",
"ping",
]);
const [selectedStatuses, setSelectedStatuses] = useState<MonitorStatus[]>([
"up",
"down",
"paused",
"initializing",
]);
const { deleteFn } = useDelete();
const typeFilter =
selectedTypes.length > 0 ? selectedTypes : UptimeMonitorTypes;
const typeQuery = typeFilter
.map((type: UptimeMonitorType) => `type=${encodeURIComponent(type)}`)
.join("&");
const statusFilter =
selectedStatuses.length > 0 ? selectedStatuses : MonitorStatuses;
const statusQuery = statusFilter
.map((status) => `status=${encodeURIComponent(status)}`)
.join("&");
const requestParams = [
"embedChecks=true",
typeQuery,
statusQuery,
`page=${page}`,
`rowsPerPage=${rowsPerPage}`,
].filter(Boolean);
const monitorRequestUrl = `/monitors?${requestParams.join("&")}`;
const { response, loading, isValidating, error, refetch } = useGet<
ApiResponse<IMonitorWithStats>
>(
`/monitors?embedChecks=true&type=http&type=https&type=ping&page=${page}&rowsPerPage=${rowsPerPage}`,
monitorRequestUrl,
{},
{
refreshInterval: GLOBAL_REFRESH,
@@ -62,11 +102,21 @@ const UptimeMonitors = () => {
setSelectedMonitor(null);
};
const hasActiveFilters =
selectedTypes.length > 0 || selectedStatuses.length > 0;
const showFallback = monitors.length === 0 && !hasActiveFilters;
const monitorItems = showFallback
? []
: monitors.length > 0
? monitors
: [{}];
return (
<MonitorBasePageWithStates
loading={isValidating}
error={error}
items={monitors}
items={monitorItems}
page="uptime"
actionLink="/uptime/create"
>
@@ -80,6 +130,12 @@ const UptimeMonitors = () => {
<DownStatusBox n={downCount} />
<PausedStatusBox n={pausedCount} />
</Stack>
<HeaderFilter
selectedTypes={selectedTypes}
selectedStatuses={selectedStatuses}
onTypesChange={setSelectedTypes}
onStatusesChange={setSelectedStatuses}
/>
<MonitorTable
monitors={monitors}
refetch={refetch}
+20 -1
View File
@@ -1,7 +1,26 @@
import type { GroupedCheck, ICheck } from "@/types/check";
import type { IMonitorStats } from "./monitorStats";
export type MonitorStatus = "up" | "down" | "initializing";
export const MonitorTypes = [
"http",
"https",
"ping",
"infrastructure",
"pagespeed",
] as const;
export type MonitorType = (typeof MonitorTypes)[number];
export const UptimeMonitorTypes = ["http", "https", "ping"] as const;
export type UptimeMonitorType = (typeof UptimeMonitorTypes)[number];
export const MonitorStatuses = [
"up",
"down",
"paused",
"initializing",
] as const;
export type MonitorStatus = (typeof MonitorStatuses)[number];
export interface IMonitor {
checks: ICheck[];