mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-05-18 23:48:43 -05:00
Merge pull request #1632 from bluewave-labs/fix/fe/uptime-refactor
Fix/fe/uptime refactor
This commit is contained in:
@@ -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 (
|
||||
<Box
|
||||
component="span"
|
||||
color={theme.palette.tertiary.contrastText}
|
||||
border={2}
|
||||
borderColor={theme.palette.accent.main}
|
||||
backgroundColor={theme.palette.tertiary.main}
|
||||
sx={{
|
||||
padding: ".25em .75em",
|
||||
borderRadius: "50rem",
|
||||
fontSize: "12px",
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
{count}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
CircularCount.propTypes = {
|
||||
count: PropTypes.number,
|
||||
};
|
||||
|
||||
export default CircularCount;
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
@@ -625,7 +625,8 @@ const InfrastructureDetails = () => {
|
||||
</ButtonGroup>
|
||||
</Stack>
|
||||
<Stack
|
||||
direction="row"
|
||||
direction={"row"}
|
||||
// height={chartContainerHeight} // FE team HELP! Possibly no longer needed?
|
||||
gap={theme.spacing(8)} // FE team HELP!
|
||||
flexWrap="wrap" // //FE team HELP! Better way to do this?
|
||||
sx={{
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
/**
|
||||
|
||||
@@ -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";
|
||||
|
||||
+5
-5
@@ -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,
|
||||
+34
-35
@@ -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,42 @@ 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();
|
||||
return (
|
||||
<Box className="host">
|
||||
<Box
|
||||
display="inline-block"
|
||||
<Stack>
|
||||
<Stack
|
||||
direction="row"
|
||||
position="relative"
|
||||
sx={{
|
||||
fontWeight: 500,
|
||||
"&:before": {
|
||||
position: "absolute",
|
||||
content: `""`,
|
||||
width: "4px",
|
||||
height: "4px",
|
||||
borderRadius: "50%",
|
||||
backgroundColor: "gray",
|
||||
opacity: 0.8,
|
||||
right: "-10px",
|
||||
top: "42%",
|
||||
},
|
||||
}}
|
||||
alignItems="center"
|
||||
gap={theme.spacing(4)}
|
||||
>
|
||||
{title}
|
||||
</Box>
|
||||
{percentageColor && percentage && (
|
||||
<Typography
|
||||
component="span"
|
||||
sx={{
|
||||
color: percentageColor,
|
||||
fontWeight: 500,
|
||||
/* TODO point font weight to theme */
|
||||
ml: "15px",
|
||||
}}
|
||||
>
|
||||
{percentage}%
|
||||
</Typography>
|
||||
)}
|
||||
{!noTitle && <Box sx={{ opacity: 0.6 }}>{url}</Box>}
|
||||
</Box>
|
||||
{percentageColor && percentage && (
|
||||
<>
|
||||
<span
|
||||
style={{
|
||||
content: '""',
|
||||
width: "4px",
|
||||
height: "4px",
|
||||
borderRadius: "50%",
|
||||
backgroundColor: "gray",
|
||||
opacity: 0.8,
|
||||
}}
|
||||
/>
|
||||
<Typography
|
||||
component="span"
|
||||
sx={{
|
||||
color: percentageColor,
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
{percentage}%
|
||||
</Typography>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
<span style={{ opacity: 0.6 }}>{url}</span>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
<Box
|
||||
width="100%"
|
||||
height="100%"
|
||||
position="absolute"
|
||||
sx={{
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
opacity: 0.8,
|
||||
zIndex: 100,
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
height="100%"
|
||||
position="absolute"
|
||||
top="50%"
|
||||
left="50%"
|
||||
sx={{
|
||||
transform: "translateX(-50%)",
|
||||
zIndex: 101,
|
||||
}}
|
||||
>
|
||||
<CircularProgress
|
||||
sx={{
|
||||
color: theme.palette.accent.main,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
LoadingSpinner.propTypes = {
|
||||
shouldRender: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default LoadingSpinner;
|
||||
@@ -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 (
|
||||
<Box
|
||||
width="25%"
|
||||
minWidth={150}
|
||||
ml="auto"
|
||||
>
|
||||
<Search
|
||||
options={monitors}
|
||||
filteredBy="name"
|
||||
inputValue={localSearch}
|
||||
handleInputChange={handleSearch}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
SearchComponent.propTypes = {
|
||||
monitors: PropTypes.array,
|
||||
onSearchChange: PropTypes.func,
|
||||
setIsSearching: PropTypes.func,
|
||||
};
|
||||
|
||||
export default SearchComponent;
|
||||
@@ -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 <SkeletonLayout shouldRender={shouldRender} />;
|
||||
return (
|
||||
<Stack
|
||||
gap={theme.spacing(8)}
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<StatusBox
|
||||
title="up"
|
||||
value={monitorsSummary?.upMonitors ?? 0}
|
||||
/>
|
||||
<StatusBox
|
||||
title="down"
|
||||
value={monitorsSummary?.downMonitors ?? 0}
|
||||
/>
|
||||
<StatusBox
|
||||
title="paused"
|
||||
value={monitorsSummary?.pausedMonitors ?? 0}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
StatusBoxes.propTypes = {
|
||||
monitorsSummary: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default StatusBoxes;
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Skeleton, Stack } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
|
||||
const SkeletonLayout = () => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Stack
|
||||
gap={theme.spacing(12)}
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Skeleton
|
||||
variant="rounded"
|
||||
width="100%"
|
||||
height={100}
|
||||
/>
|
||||
<Skeleton
|
||||
variant="rounded"
|
||||
width="100%"
|
||||
height={100}
|
||||
/>
|
||||
<Skeleton
|
||||
variant="rounded"
|
||||
width="100%"
|
||||
height={100}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default SkeletonLayout;
|
||||
+41
-40
@@ -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"
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: "-10%",
|
||||
left: "5%",
|
||||
pointerEvents: "none",
|
||||
"& svg g g:last-of-type path": {
|
||||
stroke: theme.palette.primary.contrastTextTertiary,
|
||||
},
|
||||
}}
|
||||
position="absolute"
|
||||
top="-10%"
|
||||
left="5%"
|
||||
>
|
||||
<Background />
|
||||
</Box>
|
||||
<Box
|
||||
textTransform="uppercase"
|
||||
fontSize={15}
|
||||
letterSpacing={0.5}
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
mb={theme.spacing(8)}
|
||||
>
|
||||
{title}
|
||||
</Box>
|
||||
{icon}
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="flex-start"
|
||||
fontSize={36}
|
||||
fontWeight={600}
|
||||
color={color}
|
||||
gap="2px"
|
||||
>
|
||||
{value}
|
||||
|
||||
<Typography
|
||||
component="span"
|
||||
fontSize={20}
|
||||
fontWeight={300}
|
||||
color={theme.palette.primary.contrastTextSecondary}
|
||||
sx={{ opacity: 0.3 }}
|
||||
<Stack direction="column">
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
mb={theme.spacing(8)}
|
||||
>
|
||||
#
|
||||
</Typography>
|
||||
<Typography
|
||||
variant={"h2"}
|
||||
textTransform="uppercase"
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
{icon}
|
||||
</Stack>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="flex-start"
|
||||
fontSize={36}
|
||||
fontWeight={600}
|
||||
color={color}
|
||||
gap="2px"
|
||||
>
|
||||
{value}
|
||||
|
||||
<Typography
|
||||
fontSize={20}
|
||||
fontWeight={300}
|
||||
color={theme.palette.primary.contrastTextSecondary}
|
||||
sx={{
|
||||
opacity: 0.3,
|
||||
}}
|
||||
>
|
||||
#
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
+76
-139
@@ -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 (
|
||||
<Box
|
||||
width="25%"
|
||||
minWidth={150}
|
||||
ml="auto"
|
||||
>
|
||||
<Search
|
||||
options={monitors}
|
||||
filteredBy="name"
|
||||
inputValue={localSearch}
|
||||
handleInputChange={handleSearch}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
);
|
||||
SearchComponent.displayName = "SearchComponent";
|
||||
SearchComponent.propTypes = {
|
||||
monitors: PropTypes.array,
|
||||
debouncedSearch: PropTypes.string,
|
||||
onSearchChange: PropTypes.func,
|
||||
setIsSearching: PropTypes.func,
|
||||
if (!shouldRender) return null;
|
||||
return (
|
||||
<Box position="relative">
|
||||
<LoadingSpinner shouldRender={isSearching} />
|
||||
<DataTable
|
||||
headers={headers}
|
||||
data={filteredMonitors}
|
||||
config={{
|
||||
rowSX: {
|
||||
cursor: "pointer",
|
||||
"&:hover td": {
|
||||
backgroundColor: theme.palette.tertiary.main,
|
||||
transition: "background-color .3s ease",
|
||||
},
|
||||
},
|
||||
onRowClick: (row) => {
|
||||
navigate(`/uptime/${row.id}`);
|
||||
},
|
||||
emptyView: "No monitors found",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -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,6 @@ const UptimeDataTable = ({
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Box
|
||||
flex={1}
|
||||
@@ -225,87 +219,35 @@ const UptimeDataTable = ({
|
||||
gap={theme.spacing(2)}
|
||||
>
|
||||
<Heading component="h2">Uptime monitors</Heading>
|
||||
{/* TODO Same as the one in Infrastructure. Create component */}
|
||||
<Box
|
||||
component="span"
|
||||
color={theme.palette.tertiary.contrastText}
|
||||
border={2}
|
||||
borderColor={theme.palette.accent.main}
|
||||
backgroundColor={theme.palette.tertiary.main}
|
||||
sx={{
|
||||
padding: ".25em .75em",
|
||||
borderRadius: "10000px",
|
||||
fontSize: "12px",
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
{monitorCount}
|
||||
</Box>
|
||||
|
||||
<CircularCount count={monitorCount} />
|
||||
<SearchComponent
|
||||
monitorsAreLoading={monitorsAreLoading}
|
||||
monitors={monitors}
|
||||
debouncedSearch={debouncedSearch}
|
||||
onSearchChange={setSearch}
|
||||
setIsSearching={setIsSearching}
|
||||
/>
|
||||
</Stack>
|
||||
<Box position="relative">
|
||||
{(isSearching || isLoading) && (
|
||||
<>
|
||||
<Box
|
||||
width="100%"
|
||||
height="100%"
|
||||
position="absolute"
|
||||
sx={{
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
opacity: 0.8,
|
||||
zIndex: 100,
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
height="100%"
|
||||
position="absolute"
|
||||
top="50%"
|
||||
left="50%"
|
||||
sx={{
|
||||
transform: "translateX(-50%)",
|
||||
zIndex: 101,
|
||||
}}
|
||||
>
|
||||
<CircularProgress
|
||||
sx={{
|
||||
color: theme.palette.accent.main,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
<DataTable
|
||||
headers={headers}
|
||||
data={filteredMonitors}
|
||||
config={{
|
||||
rowSX: {
|
||||
cursor: "pointer",
|
||||
"&:hover td": {
|
||||
backgroundColor: theme.palette.tertiary.main,
|
||||
transition: "background-color .3s ease",
|
||||
},
|
||||
},
|
||||
onRowClick: (row) => {
|
||||
navigate(`/uptime/${row.id}`);
|
||||
},
|
||||
emptyView: "No monitors found",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<MonitorDataTable
|
||||
shouldRender={!monitorsAreLoading}
|
||||
isSearching={isSearching}
|
||||
headers={headers}
|
||||
filteredMonitors={filteredMonitors}
|
||||
/>
|
||||
<UptimeDataTableSkeleton shouldRender={monitorsAreLoading} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
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 +257,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;
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Skeleton } from "@mui/material";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const UptimeDataTableSkeleton = ({ shouldRender }) => {
|
||||
if (!shouldRender) return null;
|
||||
|
||||
return (
|
||||
<Skeleton
|
||||
variant="rounded"
|
||||
width="100%"
|
||||
height="100%"
|
||||
flex={1}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
UptimeDataTableSkeleton.propTypes = {
|
||||
shouldRender: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default UptimeDataTableSkeleton;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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: () => <Skeleton />,
|
||||
},
|
||||
{
|
||||
id: "status",
|
||||
content: "Status",
|
||||
render: () => <Skeleton />,
|
||||
},
|
||||
{
|
||||
id: "responseTime",
|
||||
content: "Response Time",
|
||||
render: () => <Skeleton />,
|
||||
},
|
||||
{
|
||||
id: "type",
|
||||
content: "Type",
|
||||
render: () => <Skeleton />,
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
content: "Actions",
|
||||
render: () => <Skeleton />,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
headers={headers}
|
||||
data={ROWS_ARRAY}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export { TableSkeleton };
|
||||
@@ -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 (
|
||||
<Stack
|
||||
alignItems="center"
|
||||
backgroundColor={theme.palette.primary.main}
|
||||
p={theme.spacing(30)}
|
||||
pt={theme.spacing(25)}
|
||||
gap={theme.spacing(2)}
|
||||
border={1}
|
||||
borderRadius={theme.shape.borderRadius}
|
||||
borderColor={theme.palette.primary.lowContrast}
|
||||
color={theme.palette.primary.contrastTextSecondary}
|
||||
>
|
||||
<Box pb={theme.spacing(20)}>
|
||||
{mode === "light" ? <PlaceholderLight /> : <PlaceholderDark />}
|
||||
</Box>
|
||||
<Typography
|
||||
component="h2"
|
||||
variant="h2"
|
||||
fontWeight={500}
|
||||
>
|
||||
No monitors found to display
|
||||
</Typography>
|
||||
<Typography variant="body1">
|
||||
It looks like you don’t have any monitors set up yet.
|
||||
</Typography>
|
||||
{isAdmin && (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="accent"
|
||||
onClick={() => {
|
||||
navigate("/uptime/create");
|
||||
}}
|
||||
sx={{ mt: theme.spacing(12) }}
|
||||
>
|
||||
Create your first monitor
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
Fallback.propTypes = {
|
||||
isAdmin: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default Fallback;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,126 +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 (
|
||||
<Box alignSelf="flex-end">
|
||||
<Button
|
||||
variant="contained"
|
||||
color="accent"
|
||||
onClick={() => {
|
||||
navigate("/uptime/create");
|
||||
}}
|
||||
>
|
||||
Create new
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
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
|
||||
: 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);
|
||||
};
|
||||
@@ -138,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 (
|
||||
<Stack
|
||||
className="monitors"
|
||||
gap={theme.spacing(8)}
|
||||
>
|
||||
<Box>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="end"
|
||||
alignItems="center"
|
||||
mt={theme.spacing(5)}
|
||||
gap={theme.spacing(6)}
|
||||
>
|
||||
{canAddMonitor && (
|
||||
<>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="accent"
|
||||
onClick={() => {
|
||||
navigate("/uptime/create");
|
||||
}}
|
||||
sx={{
|
||||
fontWeight: 500,
|
||||
whiteSpace: "nowrap",
|
||||
/* TODO IMPORTANT this should be applied to all buttons */
|
||||
"&:focus-visible": {
|
||||
outline: `2px solid ${theme.palette.primary.contrastText}`,
|
||||
outlineOffset: 4,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Create new
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
<Greeting type="uptime" />
|
||||
</Box>
|
||||
{
|
||||
<>
|
||||
{!isLoading && !hasMonitors && <Fallback isAdmin={isAdmin} />}
|
||||
{isLoading ? (
|
||||
<SkeletonLayout />
|
||||
) : (
|
||||
hasMonitors && (
|
||||
<>
|
||||
<Stack
|
||||
gap={theme.spacing(8)}
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<StatusBox
|
||||
title="up"
|
||||
value={monitorsSummary?.upMonitors ?? 0}
|
||||
/>
|
||||
<StatusBox
|
||||
title="down"
|
||||
value={monitorsSummary?.downMonitors ?? 0}
|
||||
/>
|
||||
<StatusBox
|
||||
title="paused"
|
||||
value={monitorsSummary?.pausedMonitors ?? 0}
|
||||
/>
|
||||
</Stack>
|
||||
<UptimeDataTable
|
||||
isAdmin={isAdmin}
|
||||
isLoading={isLoading}
|
||||
filteredMonitors={filteredMonitors}
|
||||
monitors={monitors}
|
||||
monitorCount={totalMonitors}
|
||||
sort={sort}
|
||||
setSort={setSort}
|
||||
debouncedSearch={debouncedFilter}
|
||||
setSearch={setSearch}
|
||||
isSearching={isSearching}
|
||||
setIsSearching={setIsSearching}
|
||||
setIsLoading={setIsLoading}
|
||||
triggerUpdate={triggerUpdate}
|
||||
/>
|
||||
<Pagination
|
||||
itemCount={totalMonitors}
|
||||
paginationLabel="monitors"
|
||||
page={page}
|
||||
rowsPerPage={rowsPerPage}
|
||||
handleChangePage={handleChangePage}
|
||||
handleChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
}
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<CreateMonitorButton shouldRender={true} />
|
||||
<Greeting type="uptime" />
|
||||
<StatusBoxes
|
||||
monitorsSummary={monitorsSummary}
|
||||
shouldRender={!monitorsAreLoading}
|
||||
/>
|
||||
<UptimeDataTable
|
||||
isAdmin={isAdmin}
|
||||
isLoading={isLoading}
|
||||
setIsLoading={setIsLoading}
|
||||
filteredMonitors={filteredMonitors}
|
||||
monitors={monitors}
|
||||
monitorCount={totalMonitors}
|
||||
sort={sort}
|
||||
setSort={setSort}
|
||||
setSearch={setSearch}
|
||||
isSearching={isSearching}
|
||||
setIsSearching={setIsSearching}
|
||||
monitorsAreLoading={monitorsAreLoading}
|
||||
triggerUpdate={triggerUpdate}
|
||||
/>
|
||||
<Pagination
|
||||
itemCount={totalMonitors}
|
||||
paginationLabel="monitors"
|
||||
page={page}
|
||||
rowsPerPage={rowsPerPage}
|
||||
handleChangePage={handleChangePage}
|
||||
handleChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,7 +8,6 @@ class DistributedUptimeController {
|
||||
|
||||
async resultsCallback(req, res, next) {
|
||||
try {
|
||||
console.log(req.body);
|
||||
res.status(200).json({ message: "OK" });
|
||||
} catch (error) {
|
||||
throw handleError(error, SERVICE_NAME, "resultsCallback");
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -497,7 +497,6 @@ const getMonitorById = async (monitorId) => {
|
||||
|
||||
const getMonitorsByTeamId = async (req) => {
|
||||
let { limit, type, page, rowsPerPage, filter, field, order } = req.query;
|
||||
|
||||
limit = parseInt(limit);
|
||||
page = parseInt(page);
|
||||
rowsPerPage = parseInt(rowsPerPage);
|
||||
@@ -512,7 +511,6 @@ const getMonitorsByTeamId = async (req) => {
|
||||
}
|
||||
|
||||
const skip = page && rowsPerPage ? page * rowsPerPage : 0;
|
||||
|
||||
const sort = { [field]: order === "asc" ? 1 : -1 };
|
||||
const results = await Monitor.aggregate([
|
||||
{ $match: matchStage },
|
||||
@@ -839,3 +837,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 }
|
||||
|
||||
Reference in New Issue
Block a user