diff --git a/src/Components/FilterHeader/index.jsx b/src/Components/FilterHeader/index.jsx
new file mode 100644
index 000000000..73c7d69ac
--- /dev/null
+++ b/src/Components/FilterHeader/index.jsx
@@ -0,0 +1,81 @@
+import { Checkbox, FormControl, ListItemText, MenuItem, Select } from "@mui/material";
+import { useTheme } from "@emotion/react";
+import PropTypes from "prop-types";
+import AddCircleOutlineIcon from "@mui/icons-material/AddCircleOutline";
+
+/**
+ * A reusable filter header component that displays a dropdown menu with selectable options.
+ *
+ * @component
+ * @param {Object} props - The component props.
+ * @param {string} props.header - The header text to display when no options are selected.
+ * @param {Array} props.options - An array of options to display in the dropdown menu. Each option should have a `value` and `label`.
+ * @param {Array} [props.value] - The currently selected values.
+ * @param {Function} props.onChange - The callback function to handle changes in the selected values.
+ * @param {boolean} [props.multiple=true] - Whether multiple options can be selected.
+ * @returns {JSX.Element} The rendered FilterHeader component.
+ */
+
+const FilterHeader = ({ header, options, value, onChange, multiple = true }) => {
+ const theme = useTheme();
+
+ const controlledValue = value === undefined ? [] : value; // Ensure value is always treated as an array for controlled component purposes
+
+ return (
+
+
+
+ );
+};
+
+FilterHeader.propTypes = {
+ header: PropTypes.string.isRequired,
+ options: PropTypes.arrayOf(
+ PropTypes.shape({
+ value: PropTypes.string.isRequired,
+ label: PropTypes.string.isRequired,
+ })
+ ).isRequired,
+ value: PropTypes.arrayOf(PropTypes.string),
+ onChange: PropTypes.func.isRequired,
+ multiple: PropTypes.bool,
+};
+
+export default FilterHeader;
diff --git a/src/Pages/Uptime/Monitors/Components/Filter/index.jsx b/src/Pages/Uptime/Monitors/Components/Filter/index.jsx
new file mode 100644
index 000000000..7080b35f8
--- /dev/null
+++ b/src/Pages/Uptime/Monitors/Components/Filter/index.jsx
@@ -0,0 +1,151 @@
+import { useTheme } from "@emotion/react";
+import PropTypes from "prop-types";
+import FilterHeader from "../../../../../Components/FilterHeader";
+import { useMemo } from "react";
+import { Box, Button } from "@mui/material";
+import ClearIcon from "@mui/icons-material/Clear";
+import { useTranslation } from "react-i18next";
+
+/**
+ * Filter Component
+ *
+ * A high-level component that provides filtering options for type, status, and state.
+ * It allows users to select multiple options for each filter and reset the filters.
+ *
+ * @component
+ * @param {Object} props - The component props.
+ * @param {string[]} props.selectedTypes - An array of selected type values.
+ * @param {function} props.setSelectedTypes - A function to set the selected type values.
+ * @param {string[]} props.selectedStatus - An array of selected status values.
+ * @param {function} props.setSelectedStatus - A function to set the selected status values.
+ * @param {string[]} props.selectedState - An array of selected state values.
+ * @param {function} props.setSelectedState - A function to set the selected state values.
+ * @param {function} props.setToFilterStatus - A function to set the filter status based on selected status values.
+ * @param {function} props.setToFilterActive - A function to set the filter active state based on selected state values.
+ * @param {function} props.handleReset - A function to reset all filters.
+ *
+ * @returns {JSX.Element} The rendered Filter component.
+ */
+
+const typeOptions = [
+ { value: "http", label: "HTTP(S)" },
+ { value: "ping", label: "Ping" },
+ { value: "docker", label: "Docker" },
+ { value: "port", label: "Port" },
+];
+
+const statusOptions = [
+ { value: "Up", label: "Up" },
+ { value: "Down", label: "Down" },
+];
+
+const stateOptions = [
+ { value: "Active", label: "Active" },
+ { value: "Paused", label: "Paused" },
+];
+
+const Filter = ({
+ selectedTypes,
+ setSelectedTypes,
+ selectedStatus,
+ setSelectedStatus,
+ selectedState,
+ setSelectedState,
+ setToFilterStatus,
+ setToFilterActive,
+ handleReset,
+}) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+
+ const handleTypeChange = (event) => {
+ const selectedValues = event.target.value;
+ setSelectedTypes(selectedValues.length > 0 ? selectedValues : undefined);
+ };
+
+ const handleStatusChange = (event) => {
+ const selectedValues = event.target.value;
+ setSelectedStatus(selectedValues.length > 0 ? selectedValues : undefined);
+
+ if (selectedValues.length === 0 || selectedValues.length === 2) {
+ setToFilterStatus(undefined);
+ } else {
+ setToFilterStatus(selectedValues[0] === "Up" ? "true" : "false");
+ }
+ };
+
+ const handleStateChange = (event) => {
+ const selectedValues = event.target.value;
+ setSelectedState(selectedValues);
+
+ if (selectedValues.length === 0 || selectedValues.length === 2) {
+ setToFilterActive(undefined);
+ } else {
+ setToFilterActive(selectedValues[0] === "Active" ? "true" : "false");
+ }
+ };
+
+ const isFilterActive = useMemo(() => {
+ return (
+ (selectedTypes?.length ?? 0) > 0 ||
+ (selectedState?.length ?? 0) > 0 ||
+ (selectedStatus?.length ?? 0) > 0
+ );
+ }, [selectedState, selectedTypes, selectedStatus]);
+
+ return (
+
+
+
+
+ }
+ sx={{
+ visibility: isFilterActive ? "visible" : "hidden",
+ }}
+ >
+ {t("reset")}
+
+
+ );
+};
+
+Filter.propTypes = {
+ selectedTypes: PropTypes.arrayOf(PropTypes.string),
+ setSelectedTypes: PropTypes.func.isRequired,
+ selectedStatus: PropTypes.arrayOf(PropTypes.string),
+ setSelectedStatus: PropTypes.func.isRequired,
+ selectedState: PropTypes.arrayOf(PropTypes.string),
+ setSelectedState: PropTypes.func.isRequired,
+ setToFilterStatus: PropTypes.func.isRequired,
+ setToFilterActive: PropTypes.func.isRequired,
+ handleReset: PropTypes.func.isRequired,
+};
+
+export default Filter;
diff --git a/src/Pages/Uptime/Monitors/Components/SearchComponent/index.jsx b/src/Pages/Uptime/Monitors/Components/SearchComponent/index.jsx
index 749f921a8..0c6eec854 100644
--- a/src/Pages/Uptime/Monitors/Components/SearchComponent/index.jsx
+++ b/src/Pages/Uptime/Monitors/Components/SearchComponent/index.jsx
@@ -28,6 +28,7 @@ const SearchComponent = ({ monitors = [], onSearchChange, setIsSearching }) => {
width="25%"
minWidth={150}
ml="auto"
+ mt={2}
>
{
const [sort, setSort] = useState(undefined);
const [isSearching, setIsSearching] = useState(false);
const [monitorUpdateTrigger, setMonitorUpdateTrigger] = useState(false);
+ const [selectedTypes, setSelectedTypes] = useState(undefined);
+ const [selectedState, setSelectedState] = useState(undefined);
+ const [selectedStatus, setSelectedStatus] = useState(undefined);
+ const [toFilterStatus, setToFilterStatus] = useState(undefined);
+ const [toFilterActive, setToFilterActive] = useState(undefined);
// Utils
const theme = useTheme();
@@ -104,6 +110,25 @@ const UptimeMonitors = () => {
monitorUpdateTrigger,
});
+ const handleReset = () => {
+ setSelectedState(undefined);
+ setSelectedTypes(undefined);
+ setSelectedStatus(undefined);
+ setToFilterStatus(undefined);
+ setToFilterActive(undefined);
+ };
+
+ const filterLookup = new Map([
+ [toFilterStatus, "status"],
+ [toFilterActive, "isActive"]
+ ]);
+
+ const activeFilter = [...filterLookup].find(([key]) => key !== undefined);
+ const field = activeFilter?.[1] || sort?.field;
+ const filter = activeFilter?.[0] || search;
+
+ const effectiveTypes = selectedTypes?.length ? selectedTypes : TYPES;
+
const [
monitorsWithChecks,
monitorsWithChecksCount,
@@ -111,12 +136,12 @@ const UptimeMonitors = () => {
monitorsWithChecksNetworkError,
] = useFetchMonitorsWithChecks({
teamId,
- types: TYPES,
+ types: effectiveTypes,
limit: 25,
page: page,
rowsPerPage: rowsPerPage,
- filter: search,
- field: sort?.field,
+ filter: filter,
+ field: field,
order: sort?.order,
monitorUpdateTrigger,
});
@@ -179,6 +204,17 @@ const UptimeMonitors = () => {
monitorCount={monitorsSummary?.totalMonitors}
heading={"Uptime monitors"}
>
+
({
},
},
},
+ MuiListItemText: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ "& .MuiTypography-root": {
+ color: theme.palette.primary.contrastText,
+ },
+ }),
+ },
+ },
MuiMenuItem: {
styleOverrides: {
root: ({ theme }) => ({
@@ -462,6 +471,28 @@ const baseTheme = (palette) => ({
}),
},
},
+ MuiSelect: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ "& .MuiOutlinedInput-input": {
+ color: theme.palette.primary.contrastText,
+ },
+ "& .MuiOutlinedInput-notchedOutline": {
+ borderColor: theme.palette.primary.lowContrast,
+ borderRadius: theme.shape.borderRadius,
+ },
+ "& .MuiSelect-icon": {
+ color: theme.palette.primary.contrastTextSecondary, // Dropdown + color
+ },
+ "&:hover": {
+ backgroundColor: theme.palette.primary.main, // Background on hover
+ },
+ "&:hover .MuiOutlinedInput-notchedOutline": {
+ borderColor: theme.palette.primary.lowContrast,
+ },
+ }),
+ },
+ },
MuiButtonGroup: {
styleOverrides: {
root: ({ theme }) => ({
diff --git a/src/locales/gb.json b/src/locales/gb.json
index 5dfe9b4ca..b935f5b39 100644
--- a/src/locales/gb.json
+++ b/src/locales/gb.json
@@ -111,6 +111,7 @@
"configure": "Configure",
"networkError": "Network error",
"responseTime": "Response time:",
+ "reset": "Reset",
"ms": "ms",
"bar": "Bar",
"area": "Area",
@@ -314,6 +315,7 @@
"statusCode": "Status code",
"date&Time": "Date & Time",
"type": "Type",
+ "state": "State",
"statusPageName": "Status page name",
"publicURL": "Public URL",
"repeat": "Repeat",