Merge pull request #2284 from bluewave-labs/feat/unified-details-header
feat: unified details header
This commit is contained in:
8 files changed
+217
-58
No files matched your search
@@ -0,0 +1,94 @@
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Status from "./status";
|
||||
import Skeleton from "./skeleton";
|
||||
import Button from "@mui/material/Button";
|
||||
import SettingsOutlinedIcon from "@mui/icons-material/SettingsOutlined";
|
||||
import PauseOutlinedIcon from "@mui/icons-material/PauseOutlined";
|
||||
import PlayArrowOutlinedIcon from "@mui/icons-material/PlayArrowOutlined";
|
||||
|
||||
// Utils
|
||||
import PropTypes from "prop-types";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { usePauseMonitor } from "../../Hooks/useMonitorControls";
|
||||
|
||||
/**
|
||||
* MonitorDetailsControlHeader component displays the control header for monitor details.
|
||||
* It includes status display, pause/resume button, and a configure button for admins.
|
||||
*
|
||||
* @component
|
||||
* @param {Object} props - Component props
|
||||
* @param {string} props.path - The base path for navigation
|
||||
* @param {boolean} [props.isLoading=false] - Flag indicating if the data is loading
|
||||
* @param {boolean} [props.isAdmin=false] - Flag indicating if the user is an admin
|
||||
* @param {Object} props.monitor - The monitor object containing details
|
||||
* @param {Function} props.triggerUpdate - Function to trigger an update
|
||||
* @returns {JSX.Element} The rendered component
|
||||
*/
|
||||
const MonitorDetailsControlHeader = ({
|
||||
path,
|
||||
isLoading = false,
|
||||
isAdmin = false,
|
||||
monitor,
|
||||
triggerUpdate,
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
const [pauseMonitor, isPausing, error] = usePauseMonitor({
|
||||
monitorId: monitor?._id,
|
||||
triggerUpdate,
|
||||
});
|
||||
|
||||
if (isLoading) {
|
||||
return <Skeleton />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Status monitor={monitor} />
|
||||
|
||||
<Stack
|
||||
direction="row"
|
||||
gap={theme.spacing(2)}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
loading={isPausing}
|
||||
startIcon={
|
||||
monitor?.isActive ? <PauseOutlinedIcon /> : <PlayArrowOutlinedIcon />
|
||||
}
|
||||
onClick={() => {
|
||||
pauseMonitor();
|
||||
}}
|
||||
>
|
||||
{monitor?.isActive ? "Pause" : "Resume"}
|
||||
</Button>
|
||||
|
||||
{isAdmin && (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
startIcon={<SettingsOutlinedIcon />}
|
||||
onClick={() => navigate(`/${path}/configure/${monitor._id}`)}
|
||||
>
|
||||
Configure
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
MonitorDetailsControlHeader.propTypes = {
|
||||
path: PropTypes.string,
|
||||
isLoading: PropTypes.bool,
|
||||
isAdmin: PropTypes.bool,
|
||||
monitor: PropTypes.object,
|
||||
triggerUpdate: PropTypes.func,
|
||||
};
|
||||
|
||||
export default MonitorDetailsControlHeader;
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Stack, Skeleton } from "@mui/material";
|
||||
|
||||
const SkeletonLayout = () => {
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Skeleton
|
||||
height={40}
|
||||
variant="rounded"
|
||||
width="15%"
|
||||
/>
|
||||
<Skeleton
|
||||
height={40}
|
||||
variant="rounded"
|
||||
width="15%"
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default SkeletonLayout;
|
||||
@@ -0,0 +1,56 @@
|
||||
// Components
|
||||
import Stack from "@mui/material/Stack";
|
||||
import PulseDot from "../../Components/Animated/PulseDot";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Dot from "../../Components/Dot";
|
||||
// Utils
|
||||
import { formatDurationRounded } from "../../Utils/timeUtils";
|
||||
import PropTypes from "prop-types";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import useUtils from "../../Pages/Uptime/Monitors/Hooks/useUtils";
|
||||
|
||||
/**
|
||||
* Status component displays the status information of a monitor.
|
||||
* It includes the monitor's name, URL, and check interval.
|
||||
*
|
||||
* @component
|
||||
* @param {Object} props - Component props
|
||||
* @param {Object} props.monitor - The monitor object containing details
|
||||
* @param {string} props.monitor.name - The name of the monitor
|
||||
* @param {string} props.monitor.url - The URL of the monitor
|
||||
* @param {number} props.monitor.interval - The interval at which the monitor checks
|
||||
* @returns {JSX.Element} The rendered component
|
||||
*/
|
||||
const Status = ({ monitor }) => {
|
||||
const theme = useTheme();
|
||||
const { statusColor, determineState } = useUtils();
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Typography variant="h1">{monitor?.name}</Typography>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems={"center"}
|
||||
gap={theme.spacing(4)}
|
||||
>
|
||||
<PulseDot color={statusColor[determineState(monitor)]} />
|
||||
<Typography
|
||||
variant="h2"
|
||||
style={{ fontFamily: "monospace", fontWeight: "bolder" }}
|
||||
>
|
||||
{monitor?.url?.replace(/^https?:\/\//, "") || "..."}
|
||||
</Typography>
|
||||
<Dot />
|
||||
<Typography>
|
||||
Checking every {formatDurationRounded(monitor?.interval)}.
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
Status.propTypes = {
|
||||
monitor: PropTypes.object,
|
||||
};
|
||||
|
||||
export default Status;
|
||||
@@ -3,13 +3,12 @@ import { networkService } from "../main";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { createToast } from "../Utils/toastUtils";
|
||||
|
||||
export const useFetchUptimeMonitorDetails = ({ monitorId, dateRange }) => {
|
||||
export const useFetchUptimeMonitorDetails = ({ monitorId, dateRange, trigger }) => {
|
||||
const [networkError, setNetworkError] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [monitor, setMonitor] = useState(undefined);
|
||||
const [monitorStats, setMonitorStats] = useState(undefined);
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchMonitors = async () => {
|
||||
try {
|
||||
@@ -29,7 +28,7 @@ export const useFetchUptimeMonitorDetails = ({ monitorId, dateRange }) => {
|
||||
}
|
||||
};
|
||||
fetchMonitors();
|
||||
}, [dateRange, monitorId, navigate]);
|
||||
}, [dateRange, monitorId, navigate, trigger]);
|
||||
return [monitor, monitorStats, isLoading, networkError];
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { useState } from "react";
|
||||
import { networkService } from "../main";
|
||||
import { createToast } from "../Utils/toastUtils";
|
||||
|
||||
const usePauseMonitor = ({ monitorId, triggerUpdate }) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState(undefined);
|
||||
const pauseMonitor = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const res = await networkService.pauseMonitorById({ monitorId });
|
||||
createToast({
|
||||
body: res.data.data.isActive
|
||||
? "Monitor resumed successfully"
|
||||
: "Monitor paused successfully",
|
||||
});
|
||||
triggerUpdate();
|
||||
} catch (error) {
|
||||
setError(error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return [pauseMonitor, isLoading, error];
|
||||
};
|
||||
|
||||
export { usePauseMonitor };
|
||||
@@ -158,23 +158,6 @@ const Configure = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handlePause = async () => {
|
||||
try {
|
||||
const action = await dispatch(pauseUptimeMonitor({ monitorId }));
|
||||
if (pauseUptimeMonitor.fulfilled.match(action)) {
|
||||
const monitor = action.payload.data;
|
||||
setMonitor(monitor);
|
||||
const state = action?.payload?.data.isActive === false ? "paused" : "resumed";
|
||||
createToast({ body: `Monitor ${state} successfully.` });
|
||||
} else if (pauseUptimeMonitor.rejected.match(action)) {
|
||||
throw new Error(action.error.message);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("Error pausing monitor: " + monitorId);
|
||||
createToast({ body: "Failed to pause monitor" });
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async (event) => {
|
||||
event.preventDefault();
|
||||
const action = await dispatch(updateUptimeMonitor({ monitor: monitor }));
|
||||
@@ -329,38 +312,6 @@ const Configure = () => {
|
||||
ml: "auto",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
loading={isLoading}
|
||||
sx={{
|
||||
pl: theme.spacing(4),
|
||||
pr: theme.spacing(6),
|
||||
mr: theme.spacing(6),
|
||||
"& svg": {
|
||||
mr: theme.spacing(2),
|
||||
width: 22,
|
||||
height: 22,
|
||||
"& path": {
|
||||
stroke: theme.palette.primary.contrastTextTertiary,
|
||||
strokeWidth: 1.7,
|
||||
},
|
||||
},
|
||||
}}
|
||||
onClick={handlePause}
|
||||
>
|
||||
{monitor?.isActive ? (
|
||||
<>
|
||||
<PauseIcon />
|
||||
{t("pause")}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ResumeIcon />
|
||||
{t("resume")}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
loading={isLoading}
|
||||
variant="contained"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Components
|
||||
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
||||
import MonitorDetailsControlHeader from "../../../Components/MonitorDetailsControlHeader";
|
||||
import MonitorStatusHeader from "../../../Components/MonitorStatusHeader";
|
||||
import MonitorTimeFrameHeader from "../../../Components/MonitorTimeFrameHeader";
|
||||
import ChartBoxes from "./Components/ChartBoxes";
|
||||
@@ -36,9 +37,9 @@ const UptimeDetails = () => {
|
||||
|
||||
// Local state
|
||||
const [dateRange, setDateRange] = useState("recent");
|
||||
|
||||
const [page, setPage] = useState(0);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(5);
|
||||
const [trigger, setTrigger] = useState(false);
|
||||
|
||||
// Utils
|
||||
const dateFormat =
|
||||
@@ -52,6 +53,7 @@ const UptimeDetails = () => {
|
||||
useFetchUptimeMonitorDetails({
|
||||
monitorId,
|
||||
dateRange,
|
||||
trigger,
|
||||
});
|
||||
|
||||
const monitor = monitorData?.monitor;
|
||||
@@ -73,6 +75,10 @@ const UptimeDetails = () => {
|
||||
});
|
||||
|
||||
// Handlers
|
||||
const triggerUpdate = () => {
|
||||
setTrigger(!trigger);
|
||||
};
|
||||
|
||||
const handlePageChange = (_, newPage) => {
|
||||
setPage(newPage);
|
||||
};
|
||||
@@ -101,11 +107,12 @@ const UptimeDetails = () => {
|
||||
return (
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<MonitorStatusHeader
|
||||
<MonitorDetailsControlHeader
|
||||
path={"uptime"}
|
||||
isAdmin={isAdmin}
|
||||
shouldRender={!monitorIsLoading}
|
||||
isLoading={monitorIsLoading}
|
||||
monitor={monitor}
|
||||
triggerUpdate={triggerUpdate}
|
||||
/>
|
||||
<GenericFallback>
|
||||
<Typography>{t("distributedUptimeDetailsNoMonitorHistory")}</Typography>
|
||||
@@ -117,11 +124,12 @@ const UptimeDetails = () => {
|
||||
return (
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<MonitorStatusHeader
|
||||
<MonitorDetailsControlHeader
|
||||
path={"uptime"}
|
||||
isAdmin={isAdmin}
|
||||
isLoading={monitorIsLoading}
|
||||
monitor={monitor}
|
||||
triggerUpdate={triggerUpdate}
|
||||
/>
|
||||
<UptimeStatusBoxes
|
||||
isLoading={monitorIsLoading}
|
||||
|
||||
@@ -595,8 +595,8 @@ class MonitorController {
|
||||
{ new: true }
|
||||
);
|
||||
monitor.isActive === true
|
||||
? await this.jobQueue.deleteJob(monitor)
|
||||
: await this.jobQueue.addJob(monitor._id, monitor);
|
||||
? await this.jobQueue.addJob(monitor._id, monitor)
|
||||
: await this.jobQueue.deleteJob(monitor);
|
||||
|
||||
return res.success({
|
||||
msg: monitor.isActive
|
||||
|
||||
Reference in new issue
Block a user