diff --git a/client/src/Pages/Infrastructure/Monitors/Components/LoadingSpinner/index.jsx b/client/src/Pages/Infrastructure/Monitors/Components/LoadingSpinner/index.jsx
new file mode 100644
index 000000000..a649473af
--- /dev/null
+++ b/client/src/Pages/Infrastructure/Monitors/Components/LoadingSpinner/index.jsx
@@ -0,0 +1,46 @@
+import { CircularProgress, Box } from "@mui/material";
+import { useTheme } from "@emotion/react";
+import PropTypes from "prop-types";
+const LoadingSpinner = ({ shouldRender }) => {
+ const theme = useTheme();
+ if (shouldRender === false) {
+ return;
+ }
+
+ return (
+ <>
+
+
+
+
+ >
+ );
+};
+
+LoadingSpinner.propTypes = {
+ shouldRender: PropTypes.bool,
+};
+
+export default LoadingSpinner;
diff --git a/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx b/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx
index 0ec435c60..02c32cc8c 100644
--- a/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx
+++ b/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx
@@ -1,9 +1,11 @@
// Components
+import { Box } from "@mui/material";
import DataTable from "../../../../../Components/Table";
import Host from "../../../../../Components/Host";
import { StatusLabel } from "../../../../../Components/Label";
import { Stack } from "@mui/material";
import { InfrastructureMenu } from "../MonitorsTableMenu";
+import LoadingSpinner from "../LoadingSpinner";
// Assets
import CPUChipIcon from "../../../../../assets/icons/cpu-chip.svg?react";
import CustomGauge from "../../../../../Components/Charts/CustomGauge";
@@ -15,7 +17,7 @@ import { useNavigate } from "react-router-dom";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
-const MonitorsTable = ({ shouldRender, monitors, isAdmin, handleActionMenuDelete }) => {
+const MonitorsTable = ({ shouldRender, monitors, isAdmin, handleActionMenuDelete, isSearching }) => {
// Utils
const theme = useTheme();
const { t } = useTranslation();
@@ -119,23 +121,26 @@ const MonitorsTable = ({ shouldRender, monitors, isAdmin, handleActionMenuDelete
});
return (
-
+
+ openDetails(row.id),
- emptyView: "No monitors found",
- }}
- />
+ onRowClick: (row) => openDetails(row.id),
+ emptyView: "No monitors found",
+ }}
+ />
+
);
};
@@ -144,6 +149,7 @@ MonitorsTable.propTypes = {
monitors: PropTypes.array,
isAdmin: PropTypes.bool,
handleActionMenuDelete: PropTypes.func,
+ isSearching: PropTypes.bool,
};
export default MonitorsTable;
diff --git a/client/src/Pages/Infrastructure/Monitors/Components/SearchComponent/index.jsx b/client/src/Pages/Infrastructure/Monitors/Components/SearchComponent/index.jsx
new file mode 100644
index 000000000..0c6eec854
--- /dev/null
+++ b/client/src/Pages/Infrastructure/Monitors/Components/SearchComponent/index.jsx
@@ -0,0 +1,49 @@
+import { useState } from "react";
+import Search from "../../../../../Components/Inputs/Search";
+import { Box } from "@mui/material";
+import useDebounce from "../../Hooks/useDebounce";
+import { useEffect, useRef } from "react";
+import PropTypes from "prop-types";
+
+const SearchComponent = ({ monitors = [], onSearchChange, setIsSearching }) => {
+ const isFirstRender = useRef(true);
+ const [localSearch, setLocalSearch] = useState("");
+ const debouncedSearch = useDebounce(localSearch, 500);
+ useEffect(() => {
+ if (isFirstRender.current === true) {
+ isFirstRender.current = false;
+ return;
+ }
+ onSearchChange(debouncedSearch);
+ setIsSearching(false);
+ }, [debouncedSearch, onSearchChange, setIsSearching]);
+
+ const handleSearch = (value) => {
+ setLocalSearch(value);
+ setIsSearching(true);
+ };
+
+ return (
+
+
+
+ );
+};
+
+SearchComponent.propTypes = {
+ monitors: PropTypes.array,
+ onSearchChange: PropTypes.func,
+ setIsSearching: PropTypes.func,
+};
+
+export default SearchComponent;
diff --git a/client/src/Pages/Infrastructure/Monitors/Hooks/useDebounce.jsx b/client/src/Pages/Infrastructure/Monitors/Hooks/useDebounce.jsx
new file mode 100644
index 000000000..fad002e48
--- /dev/null
+++ b/client/src/Pages/Infrastructure/Monitors/Hooks/useDebounce.jsx
@@ -0,0 +1,18 @@
+import { useState, useEffect } from "react";
+
+const useDebounce = (value, delay) => {
+ const [debouncedValue, setDebouncedValue] = useState(value);
+
+ useEffect(() => {
+ const handler = setTimeout(() => {
+ setDebouncedValue(value);
+ }, delay);
+
+ return () => {
+ clearTimeout(handler);
+ };
+ }, [value, delay]);
+ return debouncedValue;
+};
+
+export default useDebounce;
diff --git a/client/src/Pages/Infrastructure/Monitors/Hooks/useMonitorFetch.jsx b/client/src/Pages/Infrastructure/Monitors/Hooks/useMonitorFetch.jsx
index 883205c77..2ef725ffd 100644
--- a/client/src/Pages/Infrastructure/Monitors/Hooks/useMonitorFetch.jsx
+++ b/client/src/Pages/Infrastructure/Monitors/Hooks/useMonitorFetch.jsx
@@ -3,7 +3,7 @@ import { useSelector } from "react-redux";
import { networkService } from "../../../../main";
import { createToast } from "../../../../Utils/toastUtils";
-const useMonitorFetch = ({ page, field, filter, rowsPerPage, updateTrigger }) => {
+const useMonitorFetch = ({ page, field, filter, rowsPerPage, updateTrigger, search }) => {
// Redux state
const { user } = useSelector((state) => state.auth);
@@ -24,6 +24,7 @@ const useMonitorFetch = ({ page, field, filter, rowsPerPage, updateTrigger }) =>
types: ["hardware"],
page: page,
rowsPerPage: rowsPerPage,
+ search: search
});
setMonitors(response?.data?.data?.filteredMonitors ?? []);
setSummary(response?.data?.data?.summary ?? {});
@@ -38,7 +39,7 @@ const useMonitorFetch = ({ page, field, filter, rowsPerPage, updateTrigger }) =>
};
fetchMonitors();
- }, [page, field, filter, rowsPerPage, user.teamId, updateTrigger]);
+ }, [page, field, filter, rowsPerPage, user.teamId, updateTrigger, search]);
return { monitors, summary, isLoading, networkError };
};
diff --git a/client/src/Pages/Infrastructure/Monitors/index.jsx b/client/src/Pages/Infrastructure/Monitors/index.jsx
index bfba843c0..7af0be373 100644
--- a/client/src/Pages/Infrastructure/Monitors/index.jsx
+++ b/client/src/Pages/Infrastructure/Monitors/index.jsx
@@ -8,6 +8,7 @@ import Pagination from "../../..//Components/Table/TablePagination";
import GenericFallback from "../../../Components/GenericFallback";
import Fallback from "../../../Components/Fallback";
import Filter from "./Components/Filters";
+import SearchComponent from "./Components/SearchComponent";
// Utils
import { useTheme } from "@emotion/react";
import { useMonitorFetch } from "./Hooks/useMonitorFetch";
@@ -24,6 +25,8 @@ const InfrastructureMonitors = () => {
const [updateTrigger, setUpdateTrigger] = useState(false);
const [selectedStatus, setSelectedStatus] = useState(undefined);
const [toFilterStatus, setToFilterStatus] = useState(undefined);
+ const [search, setSearch] = useState(undefined);
+ const [isSearching, setIsSearching] = useState(false);
// Utils
const theme = useTheme();
@@ -56,6 +59,7 @@ const InfrastructureMonitors = () => {
filter: toFilterStatus,
rowsPerPage,
updateTrigger,
+ search
});
if (networkError === true) {
@@ -108,12 +112,19 @@ const InfrastructureMonitors = () => {
setToFilterStatus={setToFilterStatus}
handleReset={handleReset}
/>
+
+
} The response from the axios GET request.
*/
async getMonitorsByTeamId(config) {
- const { teamId, limit, types, page, rowsPerPage, filter, field, order } = config;
+ const { teamId, limit, types, page, rowsPerPage, filter, field, order, search } = config;
const params = new URLSearchParams();
if (limit) params.append("limit", limit);
@@ -200,6 +201,7 @@ class NetworkService {
if (filter) params.append("filter", filter);
if (field) params.append("field", field);
if (order) params.append("order", order);
+ if (search) params.append("search", search);
return this.axiosInstance.get(`/monitors/team/${teamId}?${params.toString()}`, {
headers: {
diff --git a/server/db/mongo/modules/monitorModule.js b/server/db/mongo/modules/monitorModule.js
index 3d9adab39..0e90c9c7f 100755
--- a/server/db/mongo/modules/monitorModule.js
+++ b/server/db/mongo/modules/monitorModule.js
@@ -510,7 +510,7 @@ const getMonitorById = async (monitorId) => {
};
const getMonitorsByTeamId = async (req) => {
- let { limit, type, page, rowsPerPage, filter, field, order } = req.query;
+ let { limit, type, page, rowsPerPage, filter, field, order, search } = req.query;
limit = parseInt(limit);
page = parseInt(page);
rowsPerPage = parseInt(rowsPerPage);
@@ -523,6 +523,9 @@ const getMonitorsByTeamId = async (req) => {
if (type !== undefined) {
matchStage.type = Array.isArray(type) ? { $in: type } : type;
}
+ if (search) {
+ matchStage.name = { $regex: search, $options: "i" };
+ }
const summaryResult = await Monitor.aggregate(
buildMonitorSummaryByTeamIdPipeline({ matchStage })
diff --git a/server/validation/joi.js b/server/validation/joi.js
index 271cd2d21..40acc35c2 100755
--- a/server/validation/joi.js
+++ b/server/validation/joi.js
@@ -147,6 +147,7 @@ const getMonitorsByTeamIdQueryValidation = joi.object({
filter: joi.string(),
field: joi.string(),
order: joi.string().valid("asc", "desc"),
+ search: joi.string()
});
const getMonitorStatsByIdParamValidation = joi.object({