diff --git a/Client/src/Components/CircularCount/index.jsx b/Client/src/Components/CircularCount/index.jsx new file mode 100644 index 000000000..5c09ad162 --- /dev/null +++ b/Client/src/Components/CircularCount/index.jsx @@ -0,0 +1,29 @@ +import { Box } from "@mui/material"; +import PropTypes from "prop-types"; +import { useTheme } from "@emotion/react"; +const CircularCount = ({ count }) => { + const theme = useTheme(); + return ( + + {count} + + ); +}; + +CircularCount.propTypes = { + count: PropTypes.number, +}; + +export default CircularCount; diff --git a/Client/src/Components/StatBox/index.jsx b/Client/src/Components/StatBox/index.jsx index 953d378ec..b476a1c33 100644 --- a/Client/src/Components/StatBox/index.jsx +++ b/Client/src/Components/StatBox/index.jsx @@ -1,7 +1,7 @@ import { Box, Typography } from "@mui/material"; import { useTheme } from "@mui/material/styles"; import PropTypes from "prop-types"; -import useUtils from "../../Pages/Uptime/utils"; +import useUtils from "../../Pages/Uptime/Home/Hooks/useUtils"; /** * StatBox Component diff --git a/Client/src/Pages/Infrastructure/Details/index.jsx b/Client/src/Pages/Infrastructure/Details/index.jsx index acd1bafb1..5da921e30 100644 --- a/Client/src/Pages/Infrastructure/Details/index.jsx +++ b/Client/src/Pages/Infrastructure/Details/index.jsx @@ -8,7 +8,7 @@ import AreaChart from "../../../Components/Charts/AreaChart"; import { useSelector } from "react-redux"; import { networkService } from "../../../main"; import PulseDot from "../../../Components/Animated/PulseDot"; -import useUtils from "../../Uptime/utils"; +import useUtils from "../../Uptime/Home/Hooks/useUtils"; import { useNavigate } from "react-router-dom"; import Empty from "./empty"; import { logger } from "../../../Utils/Logger"; diff --git a/Client/src/Pages/Infrastructure/index.jsx b/Client/src/Pages/Infrastructure/index.jsx index fbfae6a3c..51a254300 100644 --- a/Client/src/Pages/Infrastructure/index.jsx +++ b/Client/src/Pages/Infrastructure/index.jsx @@ -2,7 +2,7 @@ import { useEffect, useState, useCallback } from "react"; import { useNavigate } from "react-router-dom"; import { /* useDispatch, */ useSelector } from "react-redux"; import { useTheme } from "@emotion/react"; -import useUtils from "../Uptime/utils.jsx"; +import useUtils from "../Uptime/Home/Hooks/useUtils.jsx"; import { jwtDecode } from "jwt-decode"; import SkeletonLayout from "./skeleton"; import Fallback from "../../Components/Fallback"; @@ -17,7 +17,7 @@ import Pagination from "../../Components/Table/TablePagination/index.jsx"; // import { getInfrastructureMonitorsByTeamId } from "../../Features/InfrastructureMonitors/infrastructureMonitorsSlice"; import { networkService } from "../../Utils/NetworkService.js"; import CustomGauge from "../../Components/Charts/CustomGauge/index.jsx"; -import Host from "../Uptime/Home/host.jsx"; +import Host from "../Uptime/Home/Components/Host"; import { useIsAdmin } from "../../Hooks/useIsAdmin.js"; import { InfrastructureMenu } from "./components/Menu"; diff --git a/Client/src/Pages/PageSpeed/Configure/index.jsx b/Client/src/Pages/PageSpeed/Configure/index.jsx index f7829ef35..eee6dfcc9 100644 --- a/Client/src/Pages/PageSpeed/Configure/index.jsx +++ b/Client/src/Pages/PageSpeed/Configure/index.jsx @@ -23,7 +23,7 @@ import PulseDot from "../../../Components/Animated/PulseDot"; import LoadingButton from "@mui/lab/LoadingButton"; import PlayCircleOutlineRoundedIcon from "@mui/icons-material/PlayCircleOutlineRounded"; import SkeletonLayout from "./skeleton"; -import useUtils from "../../Uptime/utils"; +import useUtils from "../../Uptime/Home/Hooks/useUtils"; import "./index.css"; import Dialog from "../../../Components/Dialog"; diff --git a/Client/src/Pages/PageSpeed/Details/index.jsx b/Client/src/Pages/PageSpeed/Details/index.jsx index c5badbab8..9192faaa9 100644 --- a/Client/src/Pages/PageSpeed/Details/index.jsx +++ b/Client/src/Pages/PageSpeed/Details/index.jsx @@ -19,7 +19,7 @@ import PulseDot from "../../../Components/Animated/PulseDot"; import PagespeedDetailsAreaChart from "./Charts/AreaChart"; import Checkbox from "../../../Components/Inputs/Checkbox"; import PieChart from "./Charts/PieChart"; -import useUtils from "../../Uptime/utils"; +import useUtils from "../../Uptime/Home/Hooks/useUtils"; import "./index.css"; import { useIsAdmin } from "../../../Hooks/useIsAdmin"; import StatBox from "../../../Components/StatBox"; diff --git a/Client/src/Pages/PageSpeed/card.jsx b/Client/src/Pages/PageSpeed/card.jsx index f5b0d7110..f7f6c4249 100644 --- a/Client/src/Pages/PageSpeed/card.jsx +++ b/Client/src/Pages/PageSpeed/card.jsx @@ -7,7 +7,7 @@ import { useTheme } from "@emotion/react"; import { Area, AreaChart, CartesianGrid, ResponsiveContainer, Tooltip } from "recharts"; import { useSelector } from "react-redux"; import { formatDateWithTz, formatDurationSplit } from "../../Utils/timeUtils"; -import useUtils from "../Uptime/utils"; +import useUtils from "../Uptime/Home/Hooks/useUtils"; import { useState } from "react"; import IconBox from "../../Components/IconBox"; /** diff --git a/Client/src/Pages/Uptime/Details/index.jsx b/Client/src/Pages/Uptime/Details/index.jsx index d5be671d3..be589c1ba 100644 --- a/Client/src/Pages/Uptime/Details/index.jsx +++ b/Client/src/Pages/Uptime/Details/index.jsx @@ -19,7 +19,7 @@ import PulseDot from "../../../Components/Animated/PulseDot"; import { ChartBox } from "./styled"; import SkeletonLayout from "./skeleton"; import "./index.css"; -import useUtils from "../utils"; +import useUtils from "../Home/Hooks/useUtils"; import { formatDateWithTz, formatDurationSplit } from "../../../Utils/timeUtils"; import { useIsAdmin } from "../../../Hooks/useIsAdmin"; import IconBox from "../../../Components/IconBox"; diff --git a/Client/src/Pages/Uptime/Home/actionsMenu.jsx b/Client/src/Pages/Uptime/Home/Components/ActionsMenu/index.jsx similarity index 94% rename from Client/src/Pages/Uptime/Home/actionsMenu.jsx rename to Client/src/Pages/Uptime/Home/Components/ActionsMenu/index.jsx index 493c5f25e..f0561d6e6 100644 --- a/Client/src/Pages/Uptime/Home/actionsMenu.jsx +++ b/Client/src/Pages/Uptime/Home/Components/ActionsMenu/index.jsx @@ -2,16 +2,16 @@ import { useState } from "react"; import { useSelector, useDispatch } from "react-redux"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; -import { createToast } from "../../../Utils/toastUtils"; -import { logger } from "../../../Utils/Logger"; +import { createToast } from "../../../../../Utils/toastUtils"; +import { logger } from "../../../../../Utils/Logger"; import { IconButton, Menu, MenuItem } from "@mui/material"; import { deleteUptimeMonitor, pauseUptimeMonitor, -} from "../../../Features/UptimeMonitors/uptimeMonitorsSlice"; -import Settings from "../../../assets/icons/settings-bold.svg?react"; +} from "../../../../../Features/UptimeMonitors/uptimeMonitorsSlice"; +import Settings from "../../../../../assets/icons/settings-bold.svg?react"; import PropTypes from "prop-types"; -import Dialog from "../../../Components/Dialog"; +import Dialog from "../../../../../Components/Dialog"; const ActionsMenu = ({ monitor, diff --git a/Client/src/Pages/Uptime/Home/host.jsx b/Client/src/Pages/Uptime/Home/Components/Host/index.jsx similarity index 52% rename from Client/src/Pages/Uptime/Home/host.jsx rename to Client/src/Pages/Uptime/Home/Components/Host/index.jsx index ec78875f4..9238f9889 100644 --- a/Client/src/Pages/Uptime/Home/host.jsx +++ b/Client/src/Pages/Uptime/Home/Components/Host/index.jsx @@ -1,5 +1,6 @@ -import { Box, Typography } from "@mui/material"; +import { Stack, Box, Typography } from "@mui/material"; import PropTypes from "prop-types"; +import { useTheme } from "@emotion/react"; /** * Host component. * This subcomponent receives a params object and displays the host details. @@ -13,44 +14,43 @@ import PropTypes from "prop-types"; * @returns {React.ElementType} Returns a div element with the host details. */ const Host = ({ url, title, percentageColor, percentage }) => { - const noTitle = title === undefined || title === url; + const theme = useTheme(); + console.log(url, title); return ( - - + {title} - - {percentageColor && percentage && ( - - {percentage}% - - )} - {!noTitle && {url}} - + {percentageColor && percentage && ( + <> + + + {percentage}% + + + )} + + {url} + ); }; diff --git a/Client/src/Pages/Uptime/Home/Components/LoadingSpinner/index.jsx b/Client/src/Pages/Uptime/Home/Components/LoadingSpinner/index.jsx new file mode 100644 index 000000000..a649473af --- /dev/null +++ b/Client/src/Pages/Uptime/Home/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/Uptime/Home/Components/SearchComponent/index.jsx b/Client/src/Pages/Uptime/Home/Components/SearchComponent/index.jsx new file mode 100644 index 000000000..d93e6672b --- /dev/null +++ b/Client/src/Pages/Uptime/Home/Components/SearchComponent/index.jsx @@ -0,0 +1,43 @@ +import { useState } from "react"; +import Search from "../../../../../Components/Inputs/Search"; +import { Box } from "@mui/material"; +import useDebounce from "../../Hooks/useDebounce"; +import { useEffect } from "react"; +import PropTypes from "prop-types"; + +const SearchComponent = ({ monitors, onSearchChange, setIsSearching }) => { + const [localSearch, setLocalSearch] = useState(""); + const debouncedSearch = useDebounce(localSearch, 500); + useEffect(() => { + 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/Uptime/Home/skeleton.jsx b/Client/src/Pages/Uptime/Home/Components/Skeleton/index.jsx similarity index 100% rename from Client/src/Pages/Uptime/Home/skeleton.jsx rename to Client/src/Pages/Uptime/Home/Components/Skeleton/index.jsx diff --git a/Client/src/Pages/Uptime/Home/Components/StatusBoxes/index.jsx b/Client/src/Pages/Uptime/Home/Components/StatusBoxes/index.jsx new file mode 100644 index 000000000..ac9eca47f --- /dev/null +++ b/Client/src/Pages/Uptime/Home/Components/StatusBoxes/index.jsx @@ -0,0 +1,36 @@ +import PropTypes from "prop-types"; +import { Stack } from "@mui/material"; +import StatusBox from "./statusBox"; +import { useTheme } from "@emotion/react"; +import SkeletonLayout from "./skeleton"; + +const StatusBoxes = ({ shouldRender, monitorsSummary }) => { + const theme = useTheme(); + if (!shouldRender) return ; + return ( + + + + + + ); +}; + +StatusBoxes.propTypes = { + monitorsSummary: PropTypes.object.isRequired, +}; + +export default StatusBoxes; diff --git a/Client/src/Pages/Uptime/Home/Components/StatusBoxes/skeleton.jsx b/Client/src/Pages/Uptime/Home/Components/StatusBoxes/skeleton.jsx new file mode 100644 index 000000000..df6e08ccd --- /dev/null +++ b/Client/src/Pages/Uptime/Home/Components/StatusBoxes/skeleton.jsx @@ -0,0 +1,31 @@ +import { Skeleton, Stack } from "@mui/material"; +import { useTheme } from "@emotion/react"; + +const SkeletonLayout = () => { + const theme = useTheme(); + return ( + + + + + + ); +}; + +export default SkeletonLayout; diff --git a/Client/src/Pages/Uptime/Home/StatusBox.jsx b/Client/src/Pages/Uptime/Home/Components/StatusBoxes/statusBox.jsx similarity index 60% rename from Client/src/Pages/Uptime/Home/StatusBox.jsx rename to Client/src/Pages/Uptime/Home/Components/StatusBoxes/statusBox.jsx index d04ebdf5f..b157857ea 100644 --- a/Client/src/Pages/Uptime/Home/StatusBox.jsx +++ b/Client/src/Pages/Uptime/Home/Components/StatusBoxes/statusBox.jsx @@ -1,9 +1,9 @@ import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; import { Box, Stack, Typography } from "@mui/material"; -import Arrow from "../../../assets/icons/top-right-arrow.svg?react"; -import Background from "../../../assets/Images/background-grid.svg?react"; -import ClockSnooze from "../../../assets/icons/clock-snooze.svg?react"; +import Arrow from "../../../../../assets/icons/top-right-arrow.svg?react"; +import Background from "../../../../../assets/Images/background-grid.svg?react"; +import ClockSnooze from "../../../../../assets/icons/clock-snooze.svg?react"; const StatusBox = ({ title, value }) => { const theme = useTheme(); @@ -52,47 +52,48 @@ const StatusBox = ({ title, value }) => { overflow="hidden" > - - {title} - - {icon} - - {value} - - + + + {title} + + {icon} + + - # - + {value} + + + # + + ); diff --git a/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx b/Client/src/Pages/Uptime/Home/Components/UptimeDataTable/index.jsx similarity index 61% rename from Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx rename to Client/src/Pages/Uptime/Home/Components/UptimeDataTable/index.jsx index 6961c6d5c..eaf08046d 100644 --- a/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx +++ b/Client/src/Pages/Uptime/Home/Components/UptimeDataTable/index.jsx @@ -1,57 +1,51 @@ // Components import { Box, Stack, CircularProgress } from "@mui/material"; -import Search from "../../../../Components/Inputs/Search"; -import { Heading } from "../../../../Components/Heading"; -import DataTable from "../../../../Components/Table"; +import { Heading } from "../../../../../Components/Heading"; +import DataTable from "../../../../../Components/Table"; import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded"; import ArrowUpwardRoundedIcon from "@mui/icons-material/ArrowUpwardRounded"; -import Host from "../host"; -import { StatusLabel } from "../../../../Components/Label"; -import BarChart from "../../../../Components/Charts/BarChart"; -import ActionsMenu from "../actionsMenu"; - +import Host from "../Host"; +import { StatusLabel } from "../../../../../Components/Label"; +import BarChart from "../../../../../Components/Charts/BarChart"; +import ActionsMenu from "../ActionsMenu"; +import { useState } from "react"; +import SearchComponent from "../SearchComponent"; +import CircularCount from "../../../../../Components/CircularCount"; +import LoadingSpinner from "../LoadingSpinner"; +import UptimeDataTableSkeleton from "./skeleton"; // Utils import { useTheme } from "@emotion/react"; -import useUtils from "../../utils"; -import { useState, memo, useCallback } from "react"; +import useUtils from "../../Hooks/useUtils"; import { useNavigate } from "react-router-dom"; -import "../index.css"; import PropTypes from "prop-types"; -const SearchComponent = memo( - ({ monitors, debouncedSearch, onSearchChange, setIsSearching }) => { - const [localSearch, setLocalSearch] = useState(debouncedSearch); - const handleSearch = useCallback( - (value) => { - setIsSearching(true); - setLocalSearch(value); - onSearchChange(value); - }, - [onSearchChange, setIsSearching] - ); +const MonitorDataTable = ({ shouldRender, isSearching, headers, filteredMonitors }) => { + const theme = useTheme(); + const navigate = useNavigate(); - return ( - - - - ); - } -); -SearchComponent.displayName = "SearchComponent"; -SearchComponent.propTypes = { - monitors: PropTypes.array, - debouncedSearch: PropTypes.string, - onSearchChange: PropTypes.func, - setIsSearching: PropTypes.func, + if (!shouldRender) return null; + return ( + + + { + navigate(`/uptime/${row.id}`); + }, + emptyView: "No monitors found", + }} + /> + + ); }; /** @@ -79,31 +73,32 @@ SearchComponent.propTypes = { * @param {string} props.search - Current search query * @param {Function} props.setSearch - Callback to update search query * @param {boolean} props.isSearching - Whether a search is in progress - * @param {Function} props.setIsSearching - Callback to update search state * @param {Function} props.setIsLoading - Callback to update loading state * @param {Function} props.triggerUpdate - Callback to trigger a data refresh * @returns {JSX.Element} Rendered component */ -const UptimeDataTable = ({ - isAdmin, - isLoading, - monitors, - filteredMonitors, - monitorCount, - sort, - setSort, - debouncedSearch, - setSearch, - isSearching, - setIsSearching, - setIsLoading, - triggerUpdate, -}) => { - const { determineState } = useUtils(); +const UptimeDataTable = (props) => { + // Utils + const { + isAdmin, + setIsLoading, + monitors, + filteredMonitors, + monitorCount, + sort, + setSort, + setSearch, + triggerUpdate, + monitorsAreLoading, + } = props; + const { determineState } = useUtils(); const theme = useTheme(); const navigate = useNavigate(); + // Local state + const [isSearching, setIsSearching] = useState(false); + // Handlers const handleSort = (field) => { let order = ""; if (sort.field !== field) { @@ -212,7 +207,7 @@ const UptimeDataTable = ({ ), }, ]; - + console.log("rendering"); return ( Uptime monitors - {/* TODO Same as the one in Infrastructure. Create component */} - - {monitorCount} - - + - - {(isSearching || isLoading) && ( - <> - - - - - - )} - { - navigate(`/uptime/${row.id}`); - }, - emptyView: "No monitors found", - }} - /> - + + + ); }; -const MemoizedUptimeDataTable = memo(UptimeDataTable); -export default MemoizedUptimeDataTable; - UptimeDataTable.propTypes = { + isSearching: PropTypes.bool, + setIsSearching: PropTypes.func, + setSort: PropTypes.func, + setSearch: PropTypes.func, + setIsLoading: PropTypes.func, + triggerUpdate: PropTypes.func, + debouncedSearch: PropTypes.string, + onSearchChange: PropTypes.func, isAdmin: PropTypes.bool, isLoading: PropTypes.bool, monitors: PropTypes.array, @@ -315,11 +258,6 @@ UptimeDataTable.propTypes = { field: PropTypes.string, order: PropTypes.oneOf(["asc", "desc"]), }), - setSort: PropTypes.func, - debouncedSearch: PropTypes.string, - setSearch: PropTypes.func, - isSearching: PropTypes.bool, - setIsSearching: PropTypes.func, - setIsLoading: PropTypes.func, - triggerUpdate: PropTypes.func, }; + +export default UptimeDataTable; diff --git a/Client/src/Pages/Uptime/Home/Components/UptimeDataTable/skeleton.jsx b/Client/src/Pages/Uptime/Home/Components/UptimeDataTable/skeleton.jsx new file mode 100644 index 000000000..303eb48c8 --- /dev/null +++ b/Client/src/Pages/Uptime/Home/Components/UptimeDataTable/skeleton.jsx @@ -0,0 +1,21 @@ +import { Skeleton } from "@mui/material"; +import PropTypes from "prop-types"; + +const UptimeDataTableSkeleton = ({ shouldRender }) => { + if (!shouldRender) return null; + + return ( + + ); +}; + +UptimeDataTableSkeleton.propTypes = { + shouldRender: PropTypes.bool.isRequired, +}; + +export default UptimeDataTableSkeleton; diff --git a/Client/src/Pages/Uptime/Home/Hooks/useDebounce.jsx b/Client/src/Pages/Uptime/Home/Hooks/useDebounce.jsx new file mode 100644 index 000000000..11d639f14 --- /dev/null +++ b/Client/src/Pages/Uptime/Home/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/Uptime/Home/Hooks/useMonitorFetch.jsx b/Client/src/Pages/Uptime/Home/Hooks/useMonitorFetch.jsx new file mode 100644 index 000000000..d4b7e5ba7 --- /dev/null +++ b/Client/src/Pages/Uptime/Home/Hooks/useMonitorFetch.jsx @@ -0,0 +1,98 @@ +import { useEffect, useState } from "react"; +import { networkService } from "../../../../main"; +import { createToast } from "../../../../Utils/toastUtils"; +import { useTheme } from "@emotion/react"; +const getMonitorWithPercentage = (monitor, theme) => { + let uptimePercentage = ""; + let percentageColor = ""; + + if (monitor.uptimePercentage !== undefined) { + uptimePercentage = + monitor.uptimePercentage === 0 ? "0" : (monitor.uptimePercentage * 100).toFixed(2); + + percentageColor = + monitor.uptimePercentage < 0.25 + ? theme.palette.error.main + : monitor.uptimePercentage < 0.5 + ? theme.palette.warning.main + : monitor.uptimePercentage < 0.75 + ? theme.palette.success.main + : theme.palette.success.main; + } + + return { + id: monitor._id, + name: monitor.name, + url: monitor.url, + title: monitor.name, + percentage: uptimePercentage, + percentageColor, + monitor: monitor, + }; +}; + +export const useMonitorFetch = ({ + authToken, + teamId, + limit, + page, + rowsPerPage, + filter, + field, + order, + triggerUpdate, +}) => { + const [monitorsAreLoading, setMonitorsAreLoading] = useState(false); + const [monitors, setMonitors] = useState([]); + const [filteredMonitors, setFilteredMonitors] = useState([]); + const [monitorsSummary, setMonitorsSummary] = useState({}); + + const theme = useTheme(); + + useEffect(() => { + const fetchMonitors = async () => { + try { + setMonitorsAreLoading(true); + const res = await networkService.getMonitorsByTeamId({ + authToken, + teamId, + limit, + types: ["http", "ping", "docker", "port"], + page, + rowsPerPage, + filter, + field, + order, + }); + const { monitors, filteredMonitors, summary } = res.data.data; + const mappedMonitors = filteredMonitors.map((monitor) => + getMonitorWithPercentage(monitor, theme) + ); + setMonitors(monitors); + setFilteredMonitors(mappedMonitors); + setMonitorsSummary(summary); + } catch (error) { + createToast({ + body: error.message, + }); + } finally { + setMonitorsAreLoading(false); + } + }; + fetchMonitors(); + }, [ + authToken, + teamId, + limit, + field, + filter, + order, + page, + rowsPerPage, + theme, + triggerUpdate, + ]); + return { monitors, filteredMonitors, monitorsSummary, monitorsAreLoading }; +}; + +export default useMonitorFetch; diff --git a/Client/src/Pages/Uptime/utils.jsx b/Client/src/Pages/Uptime/Home/Hooks/useUtils.jsx similarity index 100% rename from Client/src/Pages/Uptime/utils.jsx rename to Client/src/Pages/Uptime/Home/Hooks/useUtils.jsx diff --git a/Client/src/Pages/Uptime/Home/UptimeDataTable/Skeleton/index.jsx b/Client/src/Pages/Uptime/Home/UptimeDataTable/Skeleton/index.jsx deleted file mode 100644 index c17f6d74b..000000000 --- a/Client/src/Pages/Uptime/Home/UptimeDataTable/Skeleton/index.jsx +++ /dev/null @@ -1,47 +0,0 @@ -import { Skeleton } from "@mui/material"; -import DataTable from "../../../../../Components/Table"; -const ROWS_NUMBER = 7; -const ROWS_ARRAY = Array.from({ length: ROWS_NUMBER }, (_, i) => i); - -const TableSkeleton = () => { - /* TODO Skeleton does not follow light and dark theme */ - - const headers = [ - { - id: "name", - - content: "Host", - - render: () => , - }, - { - id: "status", - content: "Status", - render: () => , - }, - { - id: "responseTime", - content: "Response Time", - render: () => , - }, - { - id: "type", - content: "Type", - render: () => , - }, - { - id: "actions", - content: "Actions", - render: () => , - }, - ]; - - return ( - - ); -}; - -export { TableSkeleton }; diff --git a/Client/src/Pages/Uptime/Home/fallback.jsx b/Client/src/Pages/Uptime/Home/fallback.jsx deleted file mode 100644 index 2606ca95f..000000000 --- a/Client/src/Pages/Uptime/Home/fallback.jsx +++ /dev/null @@ -1,59 +0,0 @@ -import { Box, Button, Stack, Typography } from "@mui/material"; -import { useTheme } from "@emotion/react"; -import { useNavigate } from "react-router-dom"; -import { useSelector } from "react-redux"; -import PlaceholderLight from "../../../assets/Images/data_placeholder.svg?react"; -import PlaceholderDark from "../../../assets/Images/data_placeholder_dark.svg?react"; -import PropTypes from "prop-types"; - -const Fallback = ({ isAdmin }) => { - const theme = useTheme(); - const navigate = useNavigate(); - const mode = useSelector((state) => state.ui.mode); - - return ( - - - {mode === "light" ? : } - - - No monitors found to display - - - It looks like you don’t have any monitors set up yet. - - {isAdmin && ( - - )} - - ); -}; - -Fallback.propTypes = { - isAdmin: PropTypes.bool, -}; - -export default Fallback; diff --git a/Client/src/Pages/Uptime/Home/index.css b/Client/src/Pages/Uptime/Home/index.css deleted file mode 100644 index adb1d8674..000000000 --- a/Client/src/Pages/Uptime/Home/index.css +++ /dev/null @@ -1,6 +0,0 @@ -/* TODO take these from here and declare using emotion. Plus, the values should live in the theme */ -.monitors .MuiStack-root > button:not(.MuiIconButton-root) { - font-size: var(--env-var-font-size-medium); - height: var(--env-var-height-2); - min-width: fit-content; -} diff --git a/Client/src/Pages/Uptime/Home/index.jsx b/Client/src/Pages/Uptime/Home/index.jsx index 446702ebb..c300118e0 100644 --- a/Client/src/Pages/Uptime/Home/index.jsx +++ b/Client/src/Pages/Uptime/Home/index.jsx @@ -1,128 +1,70 @@ // Components -import { Box, Stack, Button } from "@mui/material"; +import Breadcrumbs from "../../../Components/Breadcrumbs"; import Greeting from "../../../Utils/greeting"; -import SkeletonLayout from "./skeleton"; -import Fallback from "./fallback"; -import StatusBox from "./StatusBox"; -import UptimeDataTable from "./UptimeDataTable"; +import StatusBoxes from "./Components/StatusBoxes"; +import UptimeDataTable from "./Components/UptimeDataTable"; import Pagination from "../../../Components/Table/TablePagination"; +// MUI Components +import { Stack, Box, Button } from "@mui/material"; + // Utils -import { useTheme } from "@emotion/react"; -import { useEffect, useState, useCallback, useMemo } from "react"; -import { setRowsPerPage } from "../../../Features/UI/uiSlice"; +import { useState, useCallback } from "react"; import { useIsAdmin } from "../../../Hooks/useIsAdmin"; -import { useSelector, useDispatch } from "react-redux"; +import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; -import { createToast } from "../../../Utils/toastUtils"; -import Breadcrumbs from "../../../Components/Breadcrumbs"; -import useDebounce from "../../../Utils/debounce"; -import { networkService } from "../../../main"; +import useMonitorFetch from "./Hooks/useMonitorFetch"; +import { useSelector, useDispatch } from "react-redux"; +import { setRowsPerPage } from "../../../Features/UI/uiSlice"; +import PropTypes from "prop-types"; const BREADCRUMBS = [{ name: `Uptime`, path: "/uptime" }]; +const CreateMonitorButton = ({ shouldRender }) => { + // Utils + const navigate = useNavigate(); + if (shouldRender === false) { + return; + } + + return ( + + + + ); +}; + +CreateMonitorButton.propTypes = { + shouldRender: PropTypes.bool, +}; + const UptimeMonitors = () => { // Redux state + const { authToken, user } = useSelector((state) => state.auth); const rowsPerPage = useSelector((state) => state.ui.monitors.rowsPerPage); + // Local state - const [sort, setSort] = useState({}); const [search, setSearch] = useState(""); const [page, setPage] = useState(0); + const [sort, setSort] = useState({}); const [isSearching, setIsSearching] = useState(false); const [isLoading, setIsLoading] = useState(false); const [monitorUpdateTrigger, setMonitorUpdateTrigger] = useState(false); - const [monitors, setMonitors] = useState([]); - const [filteredMonitors, setFilteredMonitors] = useState([]); - const [monitorsSummary, setMonitorsSummary] = useState({}); // Utils - const debouncedFilter = useDebounce(search, 500); - const dispatch = useDispatch(); const theme = useTheme(); - const navigate = useNavigate(); const isAdmin = useIsAdmin(); + const dispatch = useDispatch(); - const authState = useSelector((state) => state.auth); - - const fetchParams = useMemo( - () => ({ - authToken: authState.authToken, - teamId: authState.user.teamId, - sort: { field: sort.field, order: sort.order }, - filter: debouncedFilter, - page, - rowsPerPage, - }), - [authState.authToken, authState.user.teamId, sort, debouncedFilter, page, rowsPerPage] - ); - - const getMonitorWithPercentage = useCallback((monitor, theme) => { - let uptimePercentage = ""; - let percentageColor = ""; - - if (monitor.uptimePercentage !== undefined) { - uptimePercentage = - monitor.uptimePercentage === 0 - ? "0" - : (monitor.uptimePercentage * 100).toFixed(2); - - percentageColor = - monitor.uptimePercentage < 0.25 - ? theme.palette.error.main - : monitor.uptimePercentage < 0.5 - ? theme.palette.warning.main - : monitor.uptimePercentage < 0.75 - ? theme.palette.success.main - : theme.palette.success.main; - } - - return { - id: monitor._id, - name: monitor.name, - url: monitor.url, - title: monitor.name, - percentage: uptimePercentage, - percentageColor, - monitor: monitor, - }; - }, []); - - const fetchMonitors = useCallback(async () => { - try { - setIsLoading(true); - const config = fetchParams; - const res = await networkService.getMonitorsByTeamId({ - authToken: config.authToken, - teamId: config.teamId, - limit: 25, - types: ["http", "ping", "docker", "port"], - page: config.page, - rowsPerPage: config.rowsPerPage, - filter: config.filter, - field: config.sort.field, - order: config.sort.order, - }); - const { monitors, filteredMonitors, summary } = res.data.data; - const mappedMonitors = filteredMonitors.map((monitor) => - getMonitorWithPercentage(monitor, theme) - ); - setMonitors(monitors); - setFilteredMonitors(mappedMonitors); - setMonitorsSummary(summary); - } catch (error) { - createToast({ - body: error.message, - }); - } finally { - setIsLoading(false); - setIsSearching(false); - } - }, [fetchParams, getMonitorWithPercentage, theme]); - - useEffect(() => { - fetchMonitors(); - }, [fetchMonitors, monitorUpdateTrigger]); - + // Handlers const handleChangePage = (event, newPage) => { setPage(newPage); }; @@ -140,103 +82,58 @@ const UptimeMonitors = () => { const triggerUpdate = useCallback(() => { setMonitorUpdateTrigger((prev) => !prev); }, []); + + const teamId = user.teamId; + + const { monitorsAreLoading, monitors, filteredMonitors, monitorsSummary } = + useMonitorFetch({ + authToken, + teamId, + limit: 25, + page, + rowsPerPage: rowsPerPage, + filter: search, + field: sort.field, + order: sort.order, + triggerUpdate: monitorUpdateTrigger, + }); const totalMonitors = monitorsSummary?.totalMonitors ?? 0; - const hasMonitors = totalMonitors > 0; - const canAddMonitor = isAdmin && hasMonitors; return ( - - - - {canAddMonitor && ( - <> - - - )} - - - - { - <> - {!isLoading && !hasMonitors && } - {isLoading ? ( - - ) : ( - hasMonitors && ( - <> - - - - - - - - - ) - )} - - } + + + + + + ); }; diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index 86c0fb840..ccd8ec26f 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -436,7 +436,7 @@ class MonitorController { monitor.isActive = !monitor.isActive; monitor.status = undefined; monitor.save(); - return res.ssuccess({ + return res.success({ msg: monitor.isActive ? successMessages.MONITOR_RESUME : successMessages.MONITOR_PAUSE, diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 4c60e0f22..cad9bd68f 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -497,7 +497,7 @@ const getMonitorById = async (monitorId) => { const getMonitorsByTeamId = async (req) => { let { limit, type, page, rowsPerPage, filter, field, order } = req.query; - + console.log("req.query", req.query); limit = parseInt(limit); page = parseInt(page); rowsPerPage = parseInt(rowsPerPage); @@ -512,8 +512,16 @@ const getMonitorsByTeamId = async (req) => { } const skip = page && rowsPerPage ? page * rowsPerPage : 0; - const sort = { [field]: order === "asc" ? 1 : -1 }; + + console.log("limit", limit); + console.log("page", page); + console.log("rowsPerPage", rowsPerPage); + console.log("filter", filter); + console.log("field", field); + console.log("order", order); + console.log("skip", skip); + console.log("sort", sort); const results = await Monitor.aggregate([ { $match: matchStage }, { @@ -675,6 +683,7 @@ const getMonitorsByTeamId = async (req) => { ]); let { monitors, filteredMonitors, summary } = results[0]; + console.log("filteredMonitors", filteredMonitors); filteredMonitors = filteredMonitors.map((monitor) => { if (!monitor.checks) { return monitor; @@ -839,3 +848,22 @@ export { groupChecksByTime, calculateGroupStats, }; + +// limit 25 +// page 1 +// rowsPerPage 25 +// filter undefined +// field name +// order asc +// skip 25 +// sort { name: 1 } +// filteredMonitors [] + +// limit 25 +// page NaN +// rowsPerPage 25 +// filter undefined +// field name +// order asc +// skip 0 +// sort { name: 1 }