Merge branch 'develop' into feat/search-infra-moniters

This commit is contained in:
Amol Vyas
2025-06-15 03:39:10 +05:30
committed by GitHub
95 changed files with 1313 additions and 2750 deletions

View File

@@ -10,6 +10,7 @@ import { logger } from "./Utils/Logger"; // Import the logger
import { networkService } from "./main";
import { Routes } from "./Routes";
import WalletProvider from "./Components/WalletProvider";
import AppLayout from "./Components/Layouts/AppLayout";
function App() {
const mode = useSelector((state) => state.ui.mode);
@@ -27,17 +28,10 @@ function App() {
<ThemeProvider theme={mode === "light" ? lightTheme : darkTheme}>
<WalletProvider>
<CssBaseline />
<GlobalStyles
styles={({ palette }) => {
return {
body: {
backgroundImage: `radial-gradient(circle, ${palette.gradient.color1}, ${palette.gradient.color2}, ${palette.gradient.color3}, ${palette.gradient.color4}, ${palette.gradient.color5})`,
color: palette.primary.contrastText,
},
};
}}
/>
<Routes />
<AppLayout>
<Routes />
</AppLayout>
<ToastContainer />
</WalletProvider>
</ThemeProvider>

View File

@@ -1,17 +1,18 @@
// Components
import IconButton from "@mui/material/IconButton";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import Settings from "../../assets/icons/settings-bold.svg?react";
import Dialog from "../../Components/Dialog";
// Utils
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 { IconButton, Menu, MenuItem } from "@mui/material";
import {
deleteUptimeMonitor,
pauseUptimeMonitor,
} from "../../Features/UptimeMonitors/uptimeMonitorsSlice";
import Settings from "../../assets/icons/settings-bold.svg?react";
import PropTypes from "prop-types";
import Dialog from "../../Components/Dialog";
import { usePauseMonitor, useDeleteMonitor } from "../../Hooks/monitorHooks";
const ActionsMenu = ({
monitor,
@@ -23,38 +24,27 @@ const ActionsMenu = ({
const [anchorEl, setAnchorEl] = useState(null);
const [actions, setActions] = useState({});
const [isOpen, setIsOpen] = useState(false);
const dispatch = useDispatch();
const theme = useTheme();
const { isLoading } = useSelector((state) => state.uptimeMonitors);
const [pauseMonitor, isPausing, error] = usePauseMonitor();
const [deleteMonitor, isDeleting] = useDeleteMonitor();
const handleRemove = async (event) => {
event.preventDefault();
event.stopPropagation();
let monitor = { _id: actions.id };
const action = await dispatch(deleteUptimeMonitor({ monitor }));
if (action.meta.requestStatus === "fulfilled") {
setIsOpen(false); // close modal
updateRowCallback();
createToast({ body: "Monitor deleted successfully." });
} else {
createToast({ body: "Failed to delete monitor." });
}
await deleteMonitor({ monitor });
updateRowCallback();
};
const handlePause = async () => {
try {
setIsLoading(true);
const action = await dispatch(pauseUptimeMonitor({ monitorId: monitor._id }));
if (pauseUptimeMonitor.fulfilled.match(action)) {
const state = action?.payload?.data.isActive === false ? "resumed" : "paused";
createToast({ body: `Monitor ${state} successfully.` });
pauseCallback();
} else {
throw new Error(action?.error?.message ?? "Failed to pause monitor.");
}
await pauseMonitor({ monitorId: monitor._id });
pauseCallback();
} catch (error) {
logger.error("Error pausing monitor:", monitor._id, error);
createToast({ body: "Failed to pause monitor." });
} finally {
setIsLoading(false);
}
};
@@ -210,7 +200,7 @@ const ActionsMenu = ({
e.stopPropagation();
handleRemove(e);
}}
isLoading={isLoading}
isLoading={isDeleting}
modelTitle="modal-delete-monitor"
modelDescription="delete-monitor-confirmation"
/>

View File

@@ -57,7 +57,7 @@ Dialog.propTypes = {
onCancel: PropTypes.func.isRequired,
confirmationButtonLabel: PropTypes.string.isRequired,
onConfirm: PropTypes.func.isRequired,
isLoading: PropTypes.bool.isRequired,
isLoading: PropTypes.bool,
};
export default Dialog;

View File

@@ -11,7 +11,7 @@ import { useSelector } from "react-redux";
import Alert from "../Alert";
import { useTranslation } from "react-i18next";
import "./index.css";
import { useFetchSettings } from "../../Hooks/useFetchSettings";
import { useFetchSettings } from "../../Hooks/settingsHooks";
import { useState } from "react";
/**
* Fallback component to display a fallback UI with a title, a list of checks, and a navigation button.

View File

@@ -105,7 +105,7 @@ const Checkbox = ({
};
Checkbox.propTypes = {
id: PropTypes.string.isRequired,
id: PropTypes.string,
name: PropTypes.string,
label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired,
size: PropTypes.oneOf(["small", "medium", "large"]),

View File

@@ -102,27 +102,7 @@ const Search = ({
...(endAdornment && { endAdornment: endAdornment }),
},
}}
sx={{
"& fieldset": {
borderColor: theme.palette.primary.lowContrast,
borderRadius: theme.shape.borderRadius,
},
"& .MuiOutlinedInput-root:hover:not(:has(input:focus)):not(:has(textarea:focus)) fieldset":
{
borderColor: theme.palette.primary.lowContrast,
},
"& .MuiOutlinedInput-root": {
paddingY: 0,
},
"& .MuiAutocomplete-tag": {
// CAIO_REVIEW
color: theme.palette.primary.contrastText,
backgroundColor: theme.palette.primary.lowContrast,
},
"& .MuiChip-deleteIcon": {
color: theme.palette.primary.contrastText, // CAIO_REVIEW
},
}}
sx={{}}
/>
{error && (
<Typography

View File

@@ -0,0 +1,30 @@
import Box from "@mui/material/Box";
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import BackgroundSVG from "../../../assets/Images/background.svg";
const AppLayout = ({ children }) => {
const theme = useTheme();
return (
<Box
sx={{
minHeight: "100vh",
backgroundColor: theme.palette.primaryBackground.main,
backgroundImage: `url("${BackgroundSVG}")`,
backgroundSize: "100% 100%",
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
color: theme.palette.primary.contrastText,
}}
>
{children}
</Box>
);
};
AppLayout.propTypes = {
children: PropTypes.node,
};
export default AppLayout;

View File

@@ -11,10 +11,9 @@ import EmailIcon from "@mui/icons-material/Email";
import PropTypes from "prop-types";
import { useNavigate } from "react-router-dom";
import { useTheme } from "@mui/material/styles";
import { usePauseMonitor } from "../../Hooks/useMonitorControls";
import { usePauseMonitor } from "../../Hooks/monitorHooks";
import { useSendTestEmail } from "../../Hooks/useSendTestEmail";
import { useTranslation } from "react-i18next";
/**
* MonitorDetailsControlHeader component displays the control header for monitor details.
* It includes status display, pause/resume button, and a configure button for admins.
@@ -38,10 +37,7 @@ const MonitorDetailsControlHeader = ({
const navigate = useNavigate();
const theme = useTheme();
const { t } = useTranslation();
const [pauseMonitor, isPausing, error] = usePauseMonitor({
monitorId: monitor?._id,
triggerUpdate,
});
const [pauseMonitor, isPausing, error] = usePauseMonitor();
const [isSending, emailError, sendTestEmail] = useSendTestEmail();
@@ -88,7 +84,10 @@ const MonitorDetailsControlHeader = ({
monitor?.isActive ? <PauseOutlinedIcon /> : <PlayArrowOutlinedIcon />
}
onClick={() => {
pauseMonitor();
pauseMonitor({
monitorId: monitor?._id,
triggerUpdate,
});
}}
>
{monitor?.isActive ? "Pause" : "Resume"}

View File

@@ -7,8 +7,7 @@ import Dot from "../../Components/Dot";
import { formatDurationRounded } from "../../Utils/timeUtils";
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import useUtils from "../../Pages/Uptime/Monitors/Hooks/useUtils";
import { useMonitorUtils } from "../../Hooks/useMonitorUtils";
/**
* Status component displays the status information of a monitor.
* It includes the monitor's name, URL, and check interval.
@@ -23,7 +22,7 @@ import useUtils from "../../Pages/Uptime/Monitors/Hooks/useUtils";
*/
const Status = ({ monitor }) => {
const theme = useTheme();
const { statusColor, determineState } = useUtils();
const { statusColor, determineState } = useMonitorUtils();
return (
<Stack>

View File

@@ -2,7 +2,7 @@ import { Stack, Typography } from "@mui/material";
import PulseDot from "../Animated/PulseDot";
import Dot from "../Dot";
import { useTheme } from "@emotion/react";
import useUtils from "../../Pages/Uptime/Monitors/Hooks/useUtils";
import { useMonitorUtils } from "../../Hooks/useMonitorUtils";
import { formatDurationRounded } from "../../Utils/timeUtils";
import ConfigButton from "./ConfigButton";
import SkeletonLayout from "./skeleton";
@@ -12,7 +12,7 @@ import { useTranslation } from "react-i18next";
const MonitorStatusHeader = ({ path, isLoading = false, isAdmin, monitor }) => {
const theme = useTheme();
const { t } = useTranslation();
const { statusColor, determineState } = useUtils();
const { statusColor, determineState } = useMonitorUtils();
if (isLoading) {
return <SkeletonLayout />;
}

View File

@@ -49,7 +49,6 @@ import { useDispatch, useSelector } from "react-redux";
import { useTranslation } from "react-i18next";
import { clearAuthState } from "../../Features/Auth/authSlice";
import { toggleSidebar } from "../../Features/UI/uiSlice";
import { clearUptimeMonitorState } from "../../Features/UptimeMonitors/uptimeMonitorsSlice";
const getMenu = (t) => [
{ name: t("menu.uptime"), path: "uptime", icon: <Monitors /> },
@@ -197,7 +196,6 @@ function Sidebar() {
const logout = async () => {
// Clear auth state
dispatch(clearAuthState());
dispatch(clearUptimeMonitorState());
navigate("/login");
};
@@ -231,7 +229,6 @@ function Sidebar() {
borderRight: `1px solid ${theme.palette.primary.lowContrast}`,
borderColor: theme.palette.primary.lowContrast,
borderRadius: 0,
backgroundColor: theme.palette.primary.main,
"& :is(p, span, .MuiListSubheader-root)": {
/*
Text color for unselected menu items and menu headings

View File

@@ -33,7 +33,6 @@ const StarPrompt = ({ repoUrl = "https://github.com/bluewave-labs/checkmate" })
borderBottom: `1px solid ${theme.palette.primary.lowContrast}`,
borderRadius: 0,
gap: theme.spacing(1.5),
backgroundColor: theme.palette.primary.main,
}}
>
<Stack

View File

@@ -2,7 +2,7 @@ import { Stack, Typography } from "@mui/material";
import Image from "../Image";
import { useTheme } from "@mui/material/styles";
import PropTypes from "prop-types";
import useUtils from "../../Pages/Uptime/Monitors/Hooks/useUtils";
import { useMonitorUtils } from "../../Hooks/useMonitorUtils";
/**
* StatBox Component
@@ -41,7 +41,7 @@ const StatBox = ({
sx,
}) => {
const theme = useTheme();
const { statusToTheme } = useUtils();
const { statusToTheme } = useMonitorUtils();
const themeColor = statusToTheme[status];
const statusBoxStyles = gradient
@@ -136,8 +136,8 @@ const StatBox = ({
};
StatBox.propTypes = {
heading: PropTypes.string.isRequired,
subHeading: PropTypes.node.isRequired,
heading: PropTypes.string,
subHeading: PropTypes.node,
gradient: PropTypes.bool,
status: PropTypes.string,
sx: PropTypes.object,

View File

@@ -8,7 +8,6 @@ import ImageUpload from "../../Inputs/ImageUpload";
import { newOrChangedCredentials } from "../../../Validation/validation";
import { useDispatch, useSelector } from "react-redux";
import { clearAuthState, deleteUser, update } from "../../../Features/Auth/authSlice";
import { clearUptimeMonitorState } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice";
import { createToast } from "../../../Utils/toastUtils";
import { logger } from "../../../Utils/Logger";
import { GenericDialog } from "../../Dialog/genericDialog";
@@ -162,7 +161,6 @@ const ProfilePanel = () => {
const action = await dispatch(deleteUser());
if (action.payload.success) {
dispatch(clearAuthState());
dispatch(clearUptimeMonitorState());
} else {
if (action.payload) {
// dispatch errors

View File

@@ -1,389 +0,0 @@
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import { networkService } from "../../main";
const initialState = {
isLoading: false,
monitorsSummary: [],
success: null,
msg: null,
};
export const createInfrastructureMonitor = createAsyncThunk(
"infrastructureMonitors/createMonitor",
async (data, thunkApi) => {
try {
const { monitor } = data;
const res = await networkService.createMonitor({ monitor: monitor });
return res.data;
} catch (error) {
if (error.response && error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
const payload = {
status: false,
msg: error.message ? error.message : "Unknown error",
};
return thunkApi.rejectWithValue(payload);
}
}
);
export const checkInfrastructureEndpointResolution = createAsyncThunk(
"infrastructureMonitors/CheckEndpoint",
async (data, thunkApi) => {
try {
const { monitorURL } = data;
const res = await networkService.checkEndpointResolution({
monitorURL: monitorURL,
});
return res.data;
} catch (error) {
if (error.response && error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
const payload = {
status: false,
msg: error.message ? error.message : "Unknown error",
};
return thunkApi.rejectWithValue(payload);
}
}
);
export const getInfrastructureMonitorById = createAsyncThunk(
"infrastructureMonitors/getMonitorById",
async (data, thunkApi) => {
try {
const { monitorId } = data;
const res = await networkService.getMonitorById({ monitorId: monitorId });
return res.data;
} catch (error) {
if (error.response && error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
const payload = {
status: false,
msg: error.message ? error.message : "Unknown error",
};
return thunkApi.rejectWithValue(payload);
}
}
);
export const getInfrastructureMonitorsByTeamId = createAsyncThunk(
"infrastructureMonitors/getMonitorsByTeamId",
async (_, thunkApi) => {
const user = thunkApi.getState().auth.user;
try {
const res = await networkService.getMonitorsAndSummaryByTeamId({
teamId: user.teamId,
types: ["hardware"],
limit: 1,
rowsPerPage: 0,
});
return res.data;
} catch (error) {
if (error.response && error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
const payload = {
status: false,
msg: error.message ? error.message : "Unknown error",
};
return thunkApi.rejectWithValue(payload);
}
}
);
export const updateInfrastructureMonitor = createAsyncThunk(
"infrastructureMonitors/updateMonitor",
async ({ monitorId, monitor }, thunkApi) => {
try {
const updatedFields = {
name: monitor.name,
description: monitor.description,
interval: monitor.interval,
notifications: monitor.notifications,
thresholds: monitor.thresholds,
secret: monitor.secret,
};
const res = await networkService.updateMonitor({
monitorId,
monitor,
updatedFields,
});
return res.data;
} catch (error) {
if (error.response && error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
const payload = {
status: false,
msg: error.message ? error.message : "Unknown error",
};
return thunkApi.rejectWithValue(payload);
}
}
);
export const deleteInfrastructureMonitor = createAsyncThunk(
"infrastructureMonitors/deleteMonitor",
async (data, thunkApi) => {
try {
const { monitor } = data;
const res = await networkService.deleteMonitorById({ monitorId: monitor._id });
return res.data;
} catch (error) {
if (error.response && error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
const payload = {
status: false,
msg: error.message ? error.message : "Unknown error",
};
return thunkApi.rejectWithValue(payload);
}
}
);
export const pauseInfrastructureMonitor = createAsyncThunk(
"infrastructureMonitors/pauseMonitor",
async (data, thunkApi) => {
try {
const { monitorId } = data;
const res = await networkService.pauseMonitorById({ monitorId: monitorId });
return res.data;
} catch (error) {
if (error.response && error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
const payload = {
status: false,
msg: error.message ? error.message : "Unknown error",
};
return thunkApi.rejectWithValue(payload);
}
}
);
export const deleteInfrastructureMonitorChecksByTeamId = createAsyncThunk(
"infrastructureMonitors/deleteChecksByTeamId",
async (data, thunkApi) => {
try {
const { teamId } = data;
const res = await networkService.deleteChecksByTeamId({ teamId: teamId });
return res.data;
} catch (error) {
if (error.response && error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
const payload = {
status: false,
msg: error.message ? error.message : "Unknown error",
};
return thunkApi.rejectWithValue(payload);
}
}
);
export const deleteAllInfrastructureMonitors = createAsyncThunk(
"infrastructureMonitors/deleteAllMonitors",
async (data, thunkApi) => {
try {
const res = await networkService.deleteAllMonitors();
return res.data;
} catch (error) {
if (error.response && error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
const payload = {
status: false,
msg: error.message ? error.message : "Unknown error",
};
return thunkApi.rejectWithValue(payload);
}
}
);
const infrastructureMonitorsSlice = createSlice({
name: "infrastructureMonitors",
initialState,
reducers: {
clearInfrastructureMonitorState: (state) => {
state.isLoading = false;
state.monitorsSummary = [];
state.success = null;
state.msg = null;
},
},
extraReducers: (builder) => {
builder
// *****************************************************
// Monitors by teamId
// *****************************************************
.addCase(getInfrastructureMonitorsByTeamId.pending, (state) => {
state.isLoading = true;
})
.addCase(getInfrastructureMonitorsByTeamId.fulfilled, (state, action) => {
state.isLoading = false;
state.success = action.payload.msg;
state.monitorsSummary = action.payload.data;
})
.addCase(getInfrastructureMonitorsByTeamId.rejected, (state, action) => {
state.isLoading = false;
state.success = false;
state.msg = action.payload
? action.payload.msg
: "Getting infrastructure monitors failed";
})
// *****************************************************
// Create Monitor
// *****************************************************
.addCase(createInfrastructureMonitor.pending, (state) => {
state.isLoading = true;
})
.addCase(createInfrastructureMonitor.fulfilled, (state, action) => {
state.isLoading = false;
state.success = action.payload.success;
state.msg = action.payload.msg;
})
.addCase(createInfrastructureMonitor.rejected, (state, action) => {
state.isLoading = false;
state.success = false;
state.msg = action.payload
? action.payload.msg
: "Failed to create infrastructure monitor";
})
// *****************************************************
// Resolve Endpoint
// *****************************************************
.addCase(checkInfrastructureEndpointResolution.pending, (state) => {
state.isLoading = true;
})
.addCase(checkInfrastructureEndpointResolution.fulfilled, (state, action) => {
state.isLoading = false;
state.success = action.payload.success;
state.msg = action.payload.msg;
})
.addCase(checkInfrastructureEndpointResolution.rejected, (state, action) => {
state.isLoading = false;
state.success = false;
state.msg = action.payload
? action.payload.msg
: "Failed to check endpoint resolution";
})
// *****************************************************
// Get Monitor By Id
// *****************************************************
.addCase(getInfrastructureMonitorById.pending, (state) => {
state.isLoading = true;
})
.addCase(getInfrastructureMonitorById.fulfilled, (state, action) => {
state.isLoading = false;
state.success = action.payload.success;
state.msg = action.payload.msg;
})
.addCase(getInfrastructureMonitorById.rejected, (state, action) => {
state.isLoading = false;
state.success = false;
state.msg = action.payload
? action.payload.msg
: "Failed to get infrastructure monitor";
})
// *****************************************************
// update Monitor
// *****************************************************
.addCase(updateInfrastructureMonitor.pending, (state) => {
state.isLoading = true;
})
.addCase(updateInfrastructureMonitor.fulfilled, (state, action) => {
state.isLoading = false;
state.success = action.payload.success;
state.msg = action.payload.msg;
})
.addCase(updateInfrastructureMonitor.rejected, (state, action) => {
state.isLoading = false;
state.success = false;
state.msg = action.payload
? action.payload.msg
: "Failed to update infrastructure monitor";
})
// *****************************************************
// Delete Monitor
// *****************************************************
.addCase(deleteInfrastructureMonitor.pending, (state) => {
state.isLoading = true;
})
.addCase(deleteInfrastructureMonitor.fulfilled, (state, action) => {
state.isLoading = false;
state.success = action.payload.success;
state.msg = action.payload.msg;
})
.addCase(deleteInfrastructureMonitor.rejected, (state, action) => {
state.isLoading = false;
state.success = false;
state.msg = action.payload
? action.payload.msg
: "Failed to delete infrastructure monitor";
})
// *****************************************************
// Delete Monitor checks by Team ID
// *****************************************************
.addCase(deleteInfrastructureMonitorChecksByTeamId.pending, (state) => {
state.isLoading = true;
})
.addCase(deleteInfrastructureMonitorChecksByTeamId.fulfilled, (state, action) => {
state.isLoading = false;
state.success = action.payload.success;
state.msg = action.payload.msg;
})
.addCase(deleteInfrastructureMonitorChecksByTeamId.rejected, (state, action) => {
state.isLoading = false;
state.success = false;
state.msg = action.payload
? action.payload.msg
: "Failed to delete monitor checks";
})
// *****************************************************
// Pause Monitor
// *****************************************************
.addCase(pauseInfrastructureMonitor.pending, (state) => {
state.isLoading = true;
})
.addCase(pauseInfrastructureMonitor.fulfilled, (state, action) => {
state.isLoading = false;
state.success = action.payload.success;
state.msg = action.payload.msg;
})
.addCase(pauseInfrastructureMonitor.rejected, (state, action) => {
state.isLoading = false;
state.success = false;
state.msg = action.payload
? action.payload.msg
: "Failed to pause infrastructure monitor";
})
// *****************************************************
// Delete all Monitors
// *****************************************************
.addCase(deleteAllInfrastructureMonitors.pending, (state) => {
state.isLoading = true;
})
.addCase(deleteAllInfrastructureMonitors.fulfilled, (state, action) => {
state.isLoading = false;
state.success = action.payload.success;
state.msg = action.payload.msg;
})
.addCase(deleteAllInfrastructureMonitors.rejected, (state, action) => {
state.isLoading = false;
state.success = false;
state.msg = action.payload ? action.payload.msg : "Failed to delete all monitors";
});
},
});
export const { setInfrastructureMonitors, clearInfrastructureMonitorState } =
infrastructureMonitorsSlice.actions;
export default infrastructureMonitorsSlice.reducer;

View File

@@ -1,309 +0,0 @@
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import { networkService } from "../../main";
const initialState = {
isLoading: false,
monitorsSummary: [],
success: null,
msg: null,
};
export const createPageSpeed = createAsyncThunk(
"pageSpeedMonitors/createPageSpeed",
async (data, thunkApi) => {
try {
const { monitor } = data;
const res = await networkService.createMonitor({ monitor: monitor });
return res.data;
} catch (error) {
if (error.response && error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
const payload = {
status: false,
msg: error.message ? error.message : "Unknown error",
};
return thunkApi.rejectWithValue(payload);
}
}
);
export const checkEndpointResolution = createAsyncThunk(
"monitors/checkEndpoint",
async (data, thunkApi) => {
try {
const { monitorURL } = data;
const res = await networkService.checkEndpointResolution({
monitorURL: monitorURL,
});
return res.data;
} catch (error) {
if (error.response && error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
const payload = {
status: false,
msg: error.message ? error.message : "Unknown error",
};
return thunkApi.rejectWithValue(payload);
}
}
);
export const getPagespeedMonitorById = createAsyncThunk(
"monitors/getMonitorById",
async (data, thunkApi) => {
try {
const { monitorId } = data;
const res = await networkService.getMonitorById({ monitorId: monitorId });
return res.data;
} catch (error) {
if (error.response && error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
const payload = {
status: false,
msg: error.message ? error.message : "Unknown error",
};
return thunkApi.rejectWithValue(payload);
}
}
);
export const getPageSpeedByTeamId = createAsyncThunk(
"pageSpeedMonitors/getPageSpeedByTeamId",
async (_, thunkApi) => {
const user = thunkApi.getState().auth.user;
try {
const res = await networkService.getMonitorsAndSummaryByTeamId({
teamId: user.teamId,
types: ["pagespeed"],
});
return res.data;
} catch (error) {
if (error.response && error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
const payload = {
status: false,
msg: error.message ? error.message : "Unknown error",
};
return thunkApi.rejectWithValue(payload);
}
}
);
export const updatePageSpeed = createAsyncThunk(
"pageSpeedMonitors/updatePageSpeed",
async (data, thunkApi) => {
try {
const { monitor } = data;
const updatedFields = {
name: monitor.name,
description: monitor.description,
interval: monitor.interval,
notifications: monitor.notifications,
};
const res = await networkService.updateMonitor({
monitorId: monitor._id,
updatedFields: updatedFields,
});
return res.data;
} catch (error) {
if (error.response && error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
const payload = {
status: false,
msg: error.message ? error.message : "Unknown error",
};
return thunkApi.rejectWithValue(payload);
}
}
);
export const deletePageSpeed = createAsyncThunk(
"pageSpeedMonitors/deletePageSpeed",
async (data, thunkApi) => {
try {
const { monitor } = data;
const res = await networkService.deleteMonitorById({ monitorId: monitor._id });
return res.data;
} catch (error) {
if (error.response && error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
const payload = {
status: false,
msg: error.message ? error.message : "Unknown error",
};
return thunkApi.rejectWithValue(payload);
}
}
);
export const pausePageSpeed = createAsyncThunk(
"pageSpeedMonitors/pausePageSpeed",
async (data, thunkApi) => {
try {
const { monitorId } = data;
const res = await networkService.pauseMonitorById({ monitorId: monitorId });
return res.data;
} catch (error) {
if (error.response && error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
const payload = {
status: false,
msg: error.message ? error.message : "Unknown error",
};
return thunkApi.rejectWithValue(payload);
}
}
);
const pageSpeedMonitorSlice = createSlice({
name: "pageSpeedMonitor",
initialState,
reducers: {
clearMonitorState: (state) => {
state.isLoading = false;
state.monitorsSummary = [];
state.success = null;
state.msg = null;
},
},
extraReducers: (builder) => {
builder
// *****************************************************
// Monitors by teamId
// *****************************************************
.addCase(getPageSpeedByTeamId.pending, (state) => {
state.isLoading = true;
})
.addCase(getPageSpeedByTeamId.fulfilled, (state, action) => {
state.isLoading = false;
state.success = action.payload.msg;
state.monitorsSummary = action.payload.data;
})
.addCase(getPageSpeedByTeamId.rejected, (state, action) => {
state.isLoading = false;
state.success = false;
state.msg = action.payload
? action.payload.msg
: "Getting page speed monitors failed";
})
// *****************************************************
.addCase(getPagespeedMonitorById.pending, (state) => {
state.isLoading = true;
})
.addCase(getPagespeedMonitorById.fulfilled, (state, action) => {
state.isLoading = false;
state.success = action.payload.success;
state.msg = action.payload.msg;
})
.addCase(getPagespeedMonitorById.rejected, (state, action) => {
state.isLoading = false;
state.success = false;
state.msg = action.payload
? action.payload.msg
: "Failed to get pagespeed monitor";
})
// *****************************************************
// Create Monitor
// *****************************************************
.addCase(createPageSpeed.pending, (state) => {
state.isLoading = true;
})
.addCase(createPageSpeed.fulfilled, (state, action) => {
state.isLoading = false;
state.success = action.payload.success;
state.msg = action.payload.msg;
})
.addCase(createPageSpeed.rejected, (state, action) => {
state.isLoading = false;
state.success = false;
state.msg = action.payload
? action.payload.msg
: "Failed to create page speed monitor";
})
// *****************************************************
// Resolve Endpoint
// *****************************************************
.addCase(checkEndpointResolution.pending, (state) => {
state.isLoading = true;
})
.addCase(checkEndpointResolution.fulfilled, (state, action) => {
state.isLoading = false;
state.success = action.payload.success;
state.msg = action.payload.msg;
})
.addCase(checkEndpointResolution.rejected, (state, action) => {
state.isLoading = false;
state.success = false;
state.msg = action.payload
? action.payload.msg
: "Failed to check endpoint resolution";
})
// *****************************************************
// Update Monitor
// *****************************************************
.addCase(updatePageSpeed.pending, (state) => {
state.isLoading = true;
})
.addCase(updatePageSpeed.fulfilled, (state, action) => {
state.isLoading = false;
state.success = action.payload.success;
state.msg = action.payload.msg;
})
.addCase(updatePageSpeed.rejected, (state, action) => {
state.isLoading = false;
state.success = false;
state.msg = action.payload
? action.payload.msg
: "Failed to update page speed monitor";
})
// *****************************************************
// Delete Monitor
// *****************************************************
.addCase(deletePageSpeed.pending, (state) => {
state.isLoading = true;
})
.addCase(deletePageSpeed.fulfilled, (state, action) => {
state.isLoading = false;
state.success = action.payload.success;
state.msg = action.payload.msg;
})
.addCase(deletePageSpeed.rejected, (state, action) => {
state.isLoading = false;
state.success = false;
state.msg = action.payload
? action.payload.msg
: "Failed to delete page speed monitor";
})
// *****************************************************
// Pause Monitor
// *****************************************************
.addCase(pausePageSpeed.pending, (state) => {
state.isLoading = true;
})
.addCase(pausePageSpeed.fulfilled, (state, action) => {
state.isLoading = false;
state.success = action.payload.success;
state.msg = action.payload.msg;
})
.addCase(pausePageSpeed.rejected, (state, action) => {
state.isLoading = false;
state.success = false;
state.msg = action.payload
? action.payload.msg
: "Failed to pause page speed monitor";
});
},
});
export const { setMonitors, clearMonitorState } = pageSpeedMonitorSlice.actions;
export default pageSpeedMonitorSlice.reducer;

View File

@@ -1,100 +0,0 @@
import { networkService } from "../../main";
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
const initialState = {
isLoading: false,
apiBaseUrl: "",
logLevel: "debug",
pagespeedApiKey: "",
};
export const getAppSettings = createAsyncThunk(
"settings/getSettings",
async (data, thunkApi) => {
try {
const res = await networkService.getAppSettings();
return res.data;
} catch (error) {
if (error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
const payload = {
status: false,
msg: error.message ? error.message : "Unknown error",
};
return thunkApi.rejectWithValue(payload);
}
}
);
export const updateAppSettings = createAsyncThunk(
"settings/updateSettings",
async ({ settings }, thunkApi) => {
try {
const parsedSettings = {
language: settings.language,
pagespeedApiKey: settings.pagespeedApiKey,
};
const res = await networkService.updateAppSettings({ settings: parsedSettings });
return res.data;
} catch (error) {
if (error.response && error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
const payload = {
status: false,
msg: error.message ? error.message : "Unknown error",
};
return thunkApi.rejectWithValue(payload);
}
}
);
const handleGetSettingsFulfilled = (state, action) => {
state.isLoading = false;
state.success = action.payload.success;
state.msg = action.payload.msg;
state.apiBaseUrl = action.payload.data.apiBaseUrl;
state.logLevel = action.payload.data.logLevel;
state.language = action.payload.data.language;
state.pagespeedApiKey = action.payload.data.pagespeedApiKey;
};
const handleGetSettingsRejected = (state, action) => {
state.isLoading = false;
state.success = false;
state.msg = action.payload ? action.payload.msg : "Failed to get settings.";
};
const handleUpdateSettingsFulfilled = (state, action) => {
state.isLoading = false;
state.success = action.payload.success;
state.msg = action.payload.msg;
state.apiBaseUrl = action.payload.data.apiBaseUrl;
state.logLevel = action.payload.data.logLevel;
};
const handleUpdateSettingsRejected = (state, action) => {
state.isLoading = false;
state.success = false;
state.msg = action.payload ? action.payload.msg : "Failed to update settings.";
};
const settingsSlice = createSlice({
name: "settings",
initialState,
extraReducers: (builder) => {
builder
.addCase(getAppSettings.pending, (state) => {
state.isLoading = true;
})
.addCase(getAppSettings.fulfilled, handleGetSettingsFulfilled)
.addCase(getAppSettings.rejected, handleGetSettingsRejected);
builder
.addCase(updateAppSettings.pending, (state) => {
state.isLoading = true;
})
.addCase(updateAppSettings.fulfilled, handleUpdateSettingsFulfilled)
.addCase(updateAppSettings.rejected, handleUpdateSettingsRejected);
},
});
export default settingsSlice.reducer;

View File

@@ -1,380 +0,0 @@
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import { networkService } from "../../main";
const initialState = {
isLoading: false,
monitorsSummary: [],
success: null,
msg: null,
};
export const createUptimeMonitor = createAsyncThunk(
"monitors/createMonitor",
async (data, thunkApi) => {
try {
const { monitor } = data;
const res = await networkService.createMonitor({ monitor: monitor });
return res.data;
} catch (error) {
if (error.response && error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
const payload = {
status: false,
msg: error.message ? error.message : "Unknown error",
};
return thunkApi.rejectWithValue(payload);
}
}
);
export const checkEndpointResolution = createAsyncThunk(
"monitors/checkEndpoint",
async (data, thunkApi) => {
try {
const { monitorURL } = data;
const res = await networkService.checkEndpointResolution({
monitorURL: monitorURL,
});
return res.data;
} catch (error) {
if (error.response && error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
const payload = {
status: false,
msg: error.message ? error.message : "Unknown error",
};
return thunkApi.rejectWithValue(payload);
}
}
);
export const getUptimeMonitorById = createAsyncThunk(
"monitors/getMonitorById",
async (data, thunkApi) => {
try {
const { monitorId } = data;
const res = await networkService.getMonitorById({ monitorId: monitorId });
return res.data;
} catch (error) {
if (error.response && error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
const payload = {
status: false,
msg: error.message ? error.message : "Unknown error",
};
return thunkApi.rejectWithValue(payload);
}
}
);
export const updateUptimeMonitor = createAsyncThunk(
"monitors/updateMonitor",
async (data, thunkApi) => {
try {
const { monitor } = data;
const updatedFields = {
name: monitor.name,
description: monitor.description,
interval: monitor.interval,
notifications: monitor.notifications,
matchMethod: monitor.matchMethod,
expectedValue: monitor.expectedValue,
ignoreTlsErrors: monitor.ignoreTlsErrors,
jsonPath: monitor.jsonPath,
...(monitor.type === "port" && { port: monitor.port }),
};
const res = await networkService.updateMonitor({
monitorId: monitor._id,
updatedFields,
});
return res.data;
} catch (error) {
if (error.response && error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
const payload = {
status: false,
msg: error.message ? error.message : "Unknown error",
};
return thunkApi.rejectWithValue(payload);
}
}
);
export const deleteUptimeMonitor = createAsyncThunk(
"monitors/deleteMonitor",
async (data, thunkApi) => {
try {
const { monitor } = data;
const res = await networkService.deleteMonitorById({ monitorId: monitor._id });
return res.data;
} catch (error) {
if (error.response && error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
const payload = {
status: false,
msg: error.message ? error.message : "Unknown error",
};
return thunkApi.rejectWithValue(payload);
}
}
);
export const pauseUptimeMonitor = createAsyncThunk(
"monitors/pauseMonitor",
async (data, thunkApi) => {
try {
const { monitorId } = data;
const res = await networkService.pauseMonitorById({ monitorId: monitorId });
return res.data;
} catch (error) {
if (error.response && error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
const payload = {
status: false,
msg: error.message ? error.message : "Unknown error",
};
return thunkApi.rejectWithValue(payload);
}
}
);
export const deleteMonitorChecksByTeamId = createAsyncThunk(
"monitors/deleteChecksByTeamId",
async (data, thunkApi) => {
try {
const { teamId } = data;
const res = await networkService.deleteChecksByTeamId({ teamId: teamId });
return res.data;
} catch (error) {
if (error.response && error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
const payload = {
status: false,
msg: error.message ? error.message : "Unknown error",
};
return thunkApi.rejectWithValue(payload);
}
}
);
export const addDemoMonitors = createAsyncThunk(
"monitors/addDemoMonitors",
async (data, thunkApi) => {
try {
const res = await networkService.addDemoMonitors();
return res.data;
} catch (error) {
if (error.response && error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
const payload = {
status: false,
msg: error.message ? error.message : "Unknown error",
};
return thunkApi.rejectWithValue(payload);
}
}
);
export const deleteAllMonitors = createAsyncThunk(
"monitors/deleteAllMonitors",
async (data, thunkApi) => {
try {
const res = await networkService.deleteAllMonitors();
return res.data;
} catch (error) {
if (error.response && error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
const payload = {
status: false,
msg: error.message ? error.message : "Unknown error",
};
return thunkApi.rejectWithValue(payload);
}
}
);
const uptimeMonitorsSlice = createSlice({
name: "uptimeMonitors",
initialState,
reducers: {
clearUptimeMonitorState: (state) => {
state.isLoading = false;
state.monitorsSummary = [];
state.success = null;
state.msg = null;
},
},
extraReducers: (builder) => {
builder
// *****************************************************
// Create Monitor
// *****************************************************
.addCase(createUptimeMonitor.pending, (state) => {
state.isLoading = true;
})
.addCase(createUptimeMonitor.fulfilled, (state, action) => {
state.isLoading = false;
state.success = action.payload.success;
state.msg = action.payload.msg;
})
.addCase(createUptimeMonitor.rejected, (state, action) => {
state.isLoading = false;
state.success = false;
state.msg = action.payload
? action.payload.msg
: "Failed to create uptime monitor";
})
// *****************************************************
// Resolve Endpoint
// *****************************************************
.addCase(checkEndpointResolution.pending, (state) => {
state.isLoading = true;
})
.addCase(checkEndpointResolution.fulfilled, (state, action) => {
state.isLoading = false;
state.success = action.payload.success;
state.msg = action.payload.msg;
})
.addCase(checkEndpointResolution.rejected, (state, action) => {
state.isLoading = false;
state.success = false;
state.msg = action.payload
? action.payload.msg
: "Failed to check endpoint resolution";
})
// *****************************************************
// Get Monitor By Id
// *****************************************************
.addCase(getUptimeMonitorById.pending, (state) => {
state.isLoading = true;
})
.addCase(getUptimeMonitorById.fulfilled, (state, action) => {
state.isLoading = false;
state.success = action.payload.success;
state.msg = action.payload.msg;
})
.addCase(getUptimeMonitorById.rejected, (state, action) => {
state.isLoading = false;
state.success = false;
state.msg = action.payload ? action.payload.msg : "Failed to get uptime monitor";
})
// *****************************************************
// update Monitor
// *****************************************************
.addCase(updateUptimeMonitor.pending, (state) => {
state.isLoading = true;
})
.addCase(updateUptimeMonitor.fulfilled, (state, action) => {
state.isLoading = false;
state.success = action.payload.success;
state.msg = action.payload.msg;
})
.addCase(updateUptimeMonitor.rejected, (state, action) => {
state.isLoading = false;
state.success = false;
state.msg = action.payload
? action.payload.msg
: "Failed to update uptime monitor";
})
// *****************************************************
// Delete Monitor
// *****************************************************
.addCase(deleteUptimeMonitor.pending, (state) => {
state.isLoading = true;
})
.addCase(deleteUptimeMonitor.fulfilled, (state, action) => {
state.isLoading = false;
state.success = action.payload.success;
state.msg = action.payload.msg;
})
.addCase(deleteUptimeMonitor.rejected, (state, action) => {
state.isLoading = false;
state.success = false;
state.msg = action.payload
? action.payload.msg
: "Failed to delete uptime monitor";
})
// *****************************************************
// Delete Monitor checks by Team ID
// *****************************************************
.addCase(deleteMonitorChecksByTeamId.pending, (state) => {
state.isLoading = true;
})
.addCase(deleteMonitorChecksByTeamId.fulfilled, (state, action) => {
state.isLoading = false;
state.success = action.payload.success;
state.msg = action.payload.msg;
})
.addCase(deleteMonitorChecksByTeamId.rejected, (state, action) => {
state.isLoading = false;
state.success = false;
state.msg = action.payload
? action.payload.msg
: "Failed to delete monitor checks";
})
// *****************************************************
// Pause Monitor
// *****************************************************
.addCase(pauseUptimeMonitor.pending, (state) => {
state.isLoading = true;
})
.addCase(pauseUptimeMonitor.fulfilled, (state, action) => {
state.isLoading = false;
state.success = action.payload.success;
state.msg = action.payload.msg;
})
.addCase(pauseUptimeMonitor.rejected, (state, action) => {
state.isLoading = false;
state.success = false;
state.msg = action.payload
? action.payload.msg
: "Failed to pause uptime monitor";
})
// *****************************************************
// Add Demo Monitors
// *****************************************************
.addCase(addDemoMonitors.pending, (state) => {
state.isLoading = true;
})
.addCase(addDemoMonitors.fulfilled, (state, action) => {
state.isLoading = false;
state.success = action.payload.success;
state.msg = action.payload.msg;
})
.addCase(addDemoMonitors.rejected, (state, action) => {
state.isLoading = false;
state.success = false;
state.msg = action.payload
? action.payload.msg
: "Failed to add demo uptime monitors";
})
// *****************************************************
// Delete all Monitors
// *****************************************************
.addCase(deleteAllMonitors.pending, (state) => {
state.isLoading = true;
})
.addCase(deleteAllMonitors.fulfilled, (state, action) => {
state.isLoading = false;
state.success = action.payload.success;
state.msg = action.payload.msg;
})
.addCase(deleteAllMonitors.rejected, (state, action) => {
state.isLoading = false;
state.success = false;
state.msg = action.payload ? action.payload.msg : "Failed to delete all monitors";
});
},
});
export const { setUptimeMonitors, clearUptimeMonitorState } = uptimeMonitorsSlice.actions;
export default uptimeMonitorsSlice.reducer;

View File

@@ -0,0 +1,85 @@
import { useState, useEffect } from "react";
import { networkService } from "../main";
import { createToast } from "../Utils/toastUtils";
const useFetchChecks = ({
teamId,
monitorId,
type,
status,
sortOrder,
limit,
dateRange,
filter,
page,
rowsPerPage,
}) => {
const [checks, setChecks] = useState(undefined);
const [checksCount, setChecksCount] = useState(undefined);
const [isLoading, setIsLoading] = useState(false);
const [networkError, setNetworkError] = useState(false);
useEffect(() => {
const fetchChecks = async () => {
if (!type && !teamId) {
return;
}
const method = monitorId
? networkService.getChecksByMonitor
: networkService.getChecksByTeam;
const config = monitorId
? {
monitorId,
type,
status,
sortOrder,
limit,
dateRange,
filter,
page,
rowsPerPage,
}
: {
status,
teamId,
sortOrder,
limit,
dateRange,
filter,
page,
rowsPerPage,
};
try {
setIsLoading(true);
const res = await method(config);
setChecks(res.data.data.checks);
setChecksCount(res.data.data.checksCount);
} catch (error) {
setNetworkError(true);
createToast({ body: error.message });
} finally {
setIsLoading(false);
}
};
fetchChecks();
}, [
monitorId,
teamId,
type,
status,
sortOrder,
limit,
dateRange,
filter,
page,
rowsPerPage,
]);
return [checks, checksCount, isLoading, networkError];
};
export { useFetchChecks };

View File

@@ -0,0 +1,471 @@
import { useEffect, useState } from "react";
import { networkService } from "../main";
import { createToast } from "../Utils/toastUtils";
import { useTheme } from "@emotion/react";
import { useMonitorUtils } from "./useMonitorUtils";
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
const useFetchMonitorsWithSummary = ({ types, monitorUpdateTrigger }) => {
const [isLoading, setIsLoading] = useState(false);
const [monitors, setMonitors] = useState(undefined);
const [monitorsSummary, setMonitorsSummary] = useState(undefined);
const [networkError, setNetworkError] = useState(false);
useEffect(() => {
const fetchMonitors = async () => {
try {
setIsLoading(true);
const res = await networkService.getMonitorsWithSummaryByTeamId({
types,
});
const { monitors, summary } = res?.data?.data ?? {};
setMonitors(monitors);
setMonitorsSummary(summary);
} catch (error) {
console.error(error);
setNetworkError(true);
createToast({
body: error.message,
});
} finally {
setIsLoading(false);
}
};
fetchMonitors();
}, [types, monitorUpdateTrigger]);
return [monitors, monitorsSummary, isLoading, networkError];
};
const useFetchMonitorsWithChecks = ({
types,
limit,
page,
rowsPerPage,
filter,
field,
order,
monitorUpdateTrigger,
}) => {
const [isLoading, setIsLoading] = useState(false);
const [count, setCount] = useState(undefined);
const [monitors, setMonitors] = useState(undefined);
const [networkError, setNetworkError] = useState(false);
const theme = useTheme();
const { getMonitorWithPercentage } = useMonitorUtils();
useEffect(() => {
const fetchMonitors = async () => {
try {
setIsLoading(true);
const res = await networkService.getMonitorsWithChecksByTeamId({
limit,
types,
page,
rowsPerPage,
filter,
field,
order,
});
const { count, monitors } = res?.data?.data ?? {};
const mappedMonitors = monitors.map((monitor) =>
getMonitorWithPercentage(monitor, theme)
);
setMonitors(mappedMonitors);
setCount(count?.monitorsCount ?? 0);
} catch (error) {
console.error(error);
setNetworkError(true);
createToast({
body: error.message,
});
} finally {
setIsLoading(false);
}
};
fetchMonitors();
}, [
field,
filter,
getMonitorWithPercentage,
limit,
order,
page,
rowsPerPage,
theme,
types,
monitorUpdateTrigger,
]);
return [monitors, count, isLoading, networkError];
};
const useFetchMonitorsByTeamId = ({
types,
limit,
page,
rowsPerPage,
filter,
field,
order,
checkOrder,
normalize,
status,
updateTrigger,
}) => {
const [isLoading, setIsLoading] = useState(false);
const [monitors, setMonitors] = useState(undefined);
const [summary, setSummary] = useState(undefined);
const [networkError, setNetworkError] = useState(false);
useEffect(() => {
const fetchMonitors = async () => {
try {
setIsLoading(true);
const res = await networkService.getMonitorsByTeamId({
limit,
types,
page,
rowsPerPage,
filter,
field,
order,
checkOrder,
status,
normalize,
});
if (res?.data?.data?.filteredMonitors) {
setMonitors(res.data.data.filteredMonitors);
setSummary(res.data.data.summary);
}
} catch (error) {
setNetworkError(true);
createToast({
body: error.message,
});
} finally {
setIsLoading(false);
}
};
fetchMonitors();
}, [
types,
limit,
page,
rowsPerPage,
filter,
field,
order,
updateTrigger,
checkOrder,
normalize,
status,
]);
return [monitors, summary, isLoading, networkError];
};
const useFetchStatsByMonitorId = ({
monitorId,
sortOrder,
limit,
dateRange,
numToDisplay,
normalize,
}) => {
const [monitor, setMonitor] = useState(undefined);
const [audits, setAudits] = useState(undefined);
const [isLoading, setIsLoading] = useState(true);
const [networkError, setNetworkError] = useState(false);
useEffect(() => {
const fetchMonitor = async () => {
try {
setIsLoading(true);
const res = await networkService.getStatsByMonitorId({
monitorId: monitorId,
sortOrder,
limit,
dateRange,
numToDisplay,
normalize,
});
setMonitor(res?.data?.data ?? undefined);
setAudits(res?.data?.data?.checks?.[0]?.audits ?? undefined);
} catch (error) {
setNetworkError(true);
createToast({ body: error.message });
} finally {
setIsLoading(false);
}
};
fetchMonitor();
}, [monitorId, dateRange, numToDisplay, normalize, sortOrder, limit]);
return [monitor, audits, isLoading, networkError];
};
const useFetchMonitorById = ({ monitorId, setMonitor, updateTrigger }) => {
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const fetchMonitor = async () => {
try {
setIsLoading(true);
const res = await networkService.getMonitorById({ monitorId: monitorId });
setMonitor(res.data.data);
} catch (error) {
createToast({ body: error.message });
} finally {
setIsLoading(false);
}
};
fetchMonitor();
}, [monitorId, setMonitor, updateTrigger]);
return [isLoading];
};
const useFetchHardwareMonitorById = ({ monitorId, dateRange }) => {
const [isLoading, setIsLoading] = useState(true);
const [networkError, setNetworkError] = useState(false);
const [monitor, setMonitor] = useState(undefined);
useEffect(() => {
const fetchMonitor = async () => {
try {
if (!monitorId) {
return { monitor: undefined, isLoading: false, networkError: undefined };
}
const response = await networkService.getHardwareDetailsByMonitorId({
monitorId: monitorId,
dateRange: dateRange,
});
setMonitor(response.data.data);
} catch (error) {
setNetworkError(true);
} finally {
setIsLoading(false);
}
};
fetchMonitor();
}, [monitorId, dateRange]);
return [monitor, isLoading, networkError];
};
const useFetchUptimeMonitorById = ({ monitorId, dateRange, trigger }) => {
const [networkError, setNetworkError] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [monitor, setMonitor] = useState(undefined);
const [monitorStats, setMonitorStats] = useState(undefined);
useEffect(() => {
const fetchMonitors = async () => {
try {
const res = await networkService.getUptimeDetailsById({
monitorId: monitorId,
dateRange: dateRange,
normalize: true,
});
const { monitorData, monitorStats } = res?.data?.data ?? {};
setMonitor(monitorData);
setMonitorStats(monitorStats);
} catch (error) {
setNetworkError(true);
createToast({ body: error.message });
} finally {
setIsLoading(false);
}
};
fetchMonitors();
}, [dateRange, monitorId, trigger]);
return [monitor, monitorStats, isLoading, networkError];
};
const useCreateMonitor = () => {
const [isLoading, setIsLoading] = useState(false);
const navigate = useNavigate();
const createMonitor = async ({ monitor, redirect }) => {
try {
setIsLoading(true);
await networkService.createMonitor({ monitor });
createToast({ body: "Monitor created successfully!" });
if (redirect) {
navigate(redirect);
}
} catch (error) {
createToast({ body: "Failed to create monitor." });
} finally {
setIsLoading(false);
}
};
return [createMonitor, isLoading];
};
const useDeleteMonitor = () => {
const [isLoading, setIsLoading] = useState(false);
const navigate = useNavigate();
const deleteMonitor = async ({ monitor, redirect }) => {
try {
setIsLoading(true);
await networkService.deleteMonitorById({ monitorId: monitor._id });
createToast({ body: "Monitor deleted successfully!" });
if (redirect) {
navigate(redirect);
}
} catch (error) {
createToast({ body: "Failed to delete monitor." });
} finally {
setIsLoading(false);
}
};
return [deleteMonitor, isLoading];
};
const useUpdateMonitor = () => {
const [isLoading, setIsLoading] = useState(false);
const navigate = useNavigate();
const updateMonitor = async ({ monitor, redirect }) => {
try {
setIsLoading(true);
const updatedFields = {
name: monitor.name,
description: monitor.description,
interval: monitor.interval,
notifications: monitor.notifications,
matchMethod: monitor.matchMethod,
expectedValue: monitor.expectedValue,
ignoreTlsErrors: monitor.ignoreTlsErrors,
jsonPath: monitor.jsonPath,
...(monitor.type === "port" && { port: monitor.port }),
...(monitor.type === "hardware" && {
thresholds: monitor.thresholds,
secret: monitor.secret,
}),
};
await networkService.updateMonitor({
monitorId: monitor._id,
updatedFields,
});
createToast({ body: "Monitor updated successfully!" });
if (redirect) {
navigate(redirect);
}
} catch (error) {
createToast({ body: "Failed to update monitor." });
} finally {
setIsLoading(false);
}
};
return [updateMonitor, isLoading];
};
const usePauseMonitor = () => {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(undefined);
const pauseMonitor = async ({ monitorId, triggerUpdate }) => {
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];
};
const useAddDemoMonitors = () => {
const [isLoading, setIsLoading] = useState(false);
const { t } = useTranslation();
const addDemoMonitors = async () => {
try {
setIsLoading(true);
await networkService.addDemoMonitors();
createToast({ body: t("settingsDemoMonitorsAdded") });
} catch (error) {
createToast({ body: t("settingsFailedToAddDemoMonitors") });
} finally {
setIsLoading(false);
}
};
return [addDemoMonitors, isLoading];
};
const useDeleteAllMonitors = () => {
const [isLoading, setIsLoading] = useState(false);
const { t } = useTranslation();
const deleteAllMonitors = async () => {
try {
setIsLoading(true);
await networkService.deleteAllMonitors();
createToast({ body: t("settingsMonitorsDeleted") });
} catch (error) {
createToast({ body: t("settingsFailedToDeleteMonitors") });
} finally {
setIsLoading(false);
}
};
return [deleteAllMonitors, isLoading];
};
const useDeleteMonitorStats = () => {
const { t } = useTranslation();
const [isLoading, setIsLoading] = useState(false);
const deleteMonitorStats = async () => {
setIsLoading(true);
try {
await networkService.deleteChecksByTeamId();
createToast({ body: t("settingsStatsCleared") });
} catch (error) {
createToast({ body: t("settingsFailedToClearStats") });
} finally {
setIsLoading(false);
}
};
return [deleteMonitorStats, isLoading];
};
const useCreateBulkMonitors = () => {
const [isLoading, setIsLoading] = useState(false);
const createBulkMonitors = async (file, user) => {
setIsLoading(true);
const formData = new FormData();
formData.append("csvFile", file);
try {
const response = await networkService.createBulkMonitors(formData);
return [true, response.data, null]; // [success, data, error]
} catch (err) {
const errorMessage = err?.response?.data?.msg || err.message;
return [false, null, errorMessage];
} finally {
setIsLoading(false);
}
};
return [createBulkMonitors, isLoading];
};
export {
useFetchMonitorsWithSummary,
useFetchMonitorsWithChecks,
useFetchMonitorsByTeamId,
useFetchStatsByMonitorId,
useFetchMonitorById,
useFetchUptimeMonitorById,
useFetchHardwareMonitorById,
useCreateMonitor,
useDeleteMonitor,
useUpdateMonitor,
usePauseMonitor,
useAddDemoMonitors,
useDeleteAllMonitors,
useDeleteMonitorStats,
useCreateBulkMonitors,
};

View File

@@ -33,12 +33,13 @@ const useSaveSettings = () => {
const saveSettings = async (settings) => {
setIsLoading(true);
try {
await networkService.updateAppSettings({ settings });
const settingsResponse = await networkService.updateAppSettings({ settings });
if (settings.checkTTL) {
await networkService.updateChecksTTL({
ttl: settings.checkTTL,
});
}
console.log({ settingsResponse });
createToast({ body: t("settingsSuccessSaved") });
} catch (error) {
createToast({ body: t("settingsFailedToSave") });

View File

@@ -1,27 +0,0 @@
import { useState } from "react";
import { networkService } from "../main";
export const useBulkMonitors = () => {
const [isLoading, setIsLoading] = useState(false);
const createBulkMonitors = async (file, user) => {
setIsLoading(true);
const formData = new FormData();
formData.append("csvFile", file);
formData.append("userId", user._id);
formData.append("teamId", user.teamId);
try {
const response = await networkService.createBulkMonitors(formData);
return [true, response.data, null]; // [success, data, error]
} catch (err) {
const errorMessage = err?.response?.data?.msg || err.message;
return [false, null, errorMessage];
} finally {
setIsLoading(false);
}
};
return [createBulkMonitors, isLoading];
};

View File

@@ -1,24 +0,0 @@
import { useState } from "react";
import { networkService } from "../main";
import { createToast } from "../Utils/toastUtils";
import { useTranslation } from "react-i18next";
const UseDeleteMonitorStats = () => {
const { t } = useTranslation();
const [isLoading, setIsLoading] = useState(false);
const deleteMonitorStats = async ({ teamId }) => {
setIsLoading(true);
try {
const res = await networkService.deleteChecksByTeamId({ teamId });
createToast({ body: t("settingsStatsCleared") });
} catch (error) {
createToast({ body: t("settingsFailedToClearStats") });
} finally {
setIsLoading(false);
}
};
return [deleteMonitorStats, isLoading];
};
export { UseDeleteMonitorStats };

View File

@@ -1,72 +0,0 @@
import { useEffect, useState } from "react";
import { networkService } from "../main";
import { createToast } from "../Utils/toastUtils";
import { useTheme } from "@emotion/react";
import { useMonitorUtils } from "./useMonitorUtils";
export const useFetchMonitorsWithChecks = ({
teamId,
types,
limit,
page,
rowsPerPage,
filter,
field,
order,
monitorUpdateTrigger,
}) => {
const [isLoading, setIsLoading] = useState(false);
const [count, setCount] = useState(undefined);
const [monitors, setMonitors] = useState(undefined);
const [networkError, setNetworkError] = useState(false);
const theme = useTheme();
const { getMonitorWithPercentage } = useMonitorUtils();
useEffect(() => {
const fetchMonitors = async () => {
try {
setIsLoading(true);
const res = await networkService.getMonitorsWithChecksByTeamId({
teamId,
limit,
types,
page,
rowsPerPage,
filter,
field,
order,
});
const { count, monitors } = res?.data?.data ?? {};
const mappedMonitors = monitors.map((monitor) =>
getMonitorWithPercentage(monitor, theme)
);
setMonitors(mappedMonitors);
setCount(count?.monitorsCount ?? 0);
} catch (error) {
console.error(error);
setNetworkError(true);
createToast({
body: error.message,
});
} finally {
setIsLoading(false);
}
};
fetchMonitors();
}, [
field,
filter,
getMonitorWithPercentage,
limit,
order,
page,
rowsPerPage,
teamId,
theme,
types,
monitorUpdateTrigger,
]);
return [monitors, count, isLoading, networkError];
};
export default useFetchMonitorsWithChecks;

View File

@@ -1,37 +0,0 @@
import { useEffect, useState } from "react";
import { networkService } from "../main";
import { createToast } from "../Utils/toastUtils";
export const useFetchMonitorsWithSummary = ({ teamId, types, monitorUpdateTrigger }) => {
const [isLoading, setIsLoading] = useState(false);
const [monitors, setMonitors] = useState(undefined);
const [monitorsSummary, setMonitorsSummary] = useState(undefined);
const [networkError, setNetworkError] = useState(false);
useEffect(() => {
const fetchMonitors = async () => {
try {
setIsLoading(true);
const res = await networkService.getMonitorsWithSummaryByTeamId({
teamId,
types,
});
const { monitors, summary } = res?.data?.data ?? {};
setMonitors(monitors);
setMonitorsSummary(summary);
} catch (error) {
console.error(error);
setNetworkError(true);
createToast({
body: error.message,
});
} finally {
setIsLoading(false);
}
};
fetchMonitors();
}, [teamId, types, monitorUpdateTrigger]);
return [monitors, monitorsSummary, isLoading, networkError];
};
export default useFetchMonitorsWithSummary;

View File

@@ -1,35 +0,0 @@
import { useState, useEffect } from "react";
import { useDispatch } from "react-redux";
import { getUptimeMonitorById } from "../Features/UptimeMonitors/uptimeMonitorsSlice";
import { useNavigate } from "react-router";
const useFetchUptimeMonitorById = (monitorId, updateTrigger) => {
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
const [monitor, setMonitor] = useState(null);
const navigate = useNavigate();
const dispatch = useDispatch();
useEffect(() => {
const fetchMonitor = async () => {
try {
setIsLoading(true);
const action = await dispatch(getUptimeMonitorById({ monitorId }));
if (getUptimeMonitorById.fulfilled.match(action)) {
const monitor = action.payload.data;
setMonitor(monitor);
} else if (getUptimeMonitorById.rejected.match(action)) {
throw new Error(action.error.message);
}
} catch (error) {
navigate("/not-found", { replace: true });
} finally {
setIsLoading(false);
}
};
fetchMonitor();
}, [monitorId, dispatch, navigate, updateTrigger]);
return [monitor, isLoading, error];
};
export { useFetchUptimeMonitorById };

View File

@@ -1,35 +0,0 @@
import { useEffect, useState } from "react";
import { networkService } from "../main";
import { useNavigate } from "react-router-dom";
import { createToast } from "../Utils/toastUtils";
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 {
const res = await networkService.getUptimeDetailsById({
monitorId: monitorId,
dateRange: dateRange,
normalize: true,
});
const { monitorData, monitorStats } = res?.data?.data ?? {};
setMonitor(monitorData);
setMonitorStats(monitorStats);
} catch (error) {
setNetworkError(true);
createToast({ body: error.message });
} finally {
setIsLoading(false);
}
};
fetchMonitors();
}, [dateRange, monitorId, navigate, trigger]);
return [monitor, monitorStats, isLoading, networkError];
};
export default useFetchUptimeMonitorDetails;

View File

@@ -1,28 +0,0 @@
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 };

View File

@@ -46,7 +46,27 @@ const useMonitorUtils = () => {
pending: theme.palette.warning.lowContrast,
};
return { getMonitorWithPercentage, determineState, statusColor };
const statusToTheme = {
up: "success",
down: "error",
paused: "warning",
pending: "secondary",
"cannot resolve": "tertiary",
};
const pagespeedStatusMsg = {
up: "Live (collecting data)",
down: "Inactive",
paused: "Paused",
};
return {
getMonitorWithPercentage,
determineState,
statusColor,
statusToTheme,
pagespeedStatusMsg,
};
};
export { useMonitorUtils };

View File

@@ -2,7 +2,6 @@ import { useState, useEffect, useCallback } from "react";
import { createToast } from "../Utils/toastUtils";
import { networkService } from "../main";
import { useNavigate } from "react-router-dom";
import { useSelector } from "react-redux";
import { useTranslation } from "react-i18next";
import { NOTIFICATION_TYPES } from "../Pages/Notifications/utils";
@@ -37,15 +36,12 @@ const useGetNotificationsByTeamId = (updateTrigger) => {
const [notifications, setNotifications] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
const { user } = useSelector((state) => state.auth);
const { t } = useTranslation();
const getNotifications = useCallback(async () => {
try {
setIsLoading(true);
const response = await networkService.getNotificationsByTeamId({
teamId: user.teamId,
});
const response = await networkService.getNotificationsByTeamId();
setNotifications(response?.data?.data ?? []);
} catch (error) {
setError(error);
@@ -55,7 +51,7 @@ const useGetNotificationsByTeamId = (updateTrigger) => {
} finally {
setIsLoading(false);
}
}, [user.teamId]);
}, [t]);
useEffect(() => {
getNotifications();

View File

@@ -5,7 +5,7 @@ import Button from "@mui/material/Button";
import TextInput from "../../../Components/Inputs/TextInput";
import { PasswordEndAdornment } from "../../../Components/Inputs/TextInput/Adornments";
import { loginCredentials } from "../../../Validation/validation";
import TextLink from "../components/TextLink";
import TextLink from "../../../Components/TextLink";
import Typography from "@mui/material/Typography";
// Utils

View File

@@ -3,7 +3,6 @@ import { useTheme } from "@emotion/react";
import { useNavigate } from "react-router";
import { useDispatch } from "react-redux";
import { clearAuthState } from "../../Features/Auth/authSlice";
import { clearUptimeMonitorState } from "../../Features/UptimeMonitors/uptimeMonitorsSlice";
import Background from "../../assets/Images/background-grid.svg?react";
import ConfirmIcon from "../../assets/icons/check-outlined.svg?react";
import Logo from "../../assets/icons/checkmate-icon.svg?react";
@@ -19,7 +18,6 @@ const NewPasswordConfirmed = () => {
const handleNavigate = () => {
dispatch(clearAuthState());
dispatch(clearUptimeMonitorState());
navigate("/login");
};

View File

@@ -10,10 +10,10 @@ import NetworkError from "../../../../Components/GenericFallback/NetworkError";
//Utils
import { formatDateWithTz } from "../../../../Utils/timeUtils";
import { useSelector } from "react-redux";
import { useState } from "react";
import useChecksFetch from "../../Hooks/useChecksFetch";
import { useState, useEffect } from "react";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
import { useFetchChecks } from "../../../../Hooks/checkHooks";
const IncidentTable = ({
shouldRender,
@@ -26,17 +26,25 @@ const IncidentTable = ({
const uiTimezone = useSelector((state) => state.ui.timezone);
//Local state
const [teamId, setTeamId] = useState(undefined);
const [monitorId, setMonitorId] = useState(undefined);
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(10);
const selectedMonitorDetails = monitors?.[selectedMonitor];
const selectedMonitorType = selectedMonitorDetails?.type;
const { isLoading, networkError, checks, checksCount } = useChecksFetch({
selectedMonitor,
selectedMonitorType,
filter,
const [checks, checksCount, isLoading, networkError] = useFetchChecks({
status: false,
monitorId,
teamId,
type: selectedMonitorType,
sortOrder: "desc",
limit: null,
dateRange,
page,
rowsPerPage,
filter: filter,
page: page,
rowsPerPage: rowsPerPage,
});
const { t } = useTranslation();
@@ -50,6 +58,16 @@ const IncidentTable = ({
setRowsPerPage(event.target.value);
};
useEffect(() => {
if (selectedMonitor === "0") {
setTeamId("placeholder"); // TODO this isn't needed any longer, fix hook
setMonitorId(undefined);
} else {
setMonitorId(selectedMonitor);
setTeamId(undefined);
}
}, [selectedMonitor]);
const headers = [
{
id: "monitorName",

View File

@@ -1,66 +0,0 @@
import { useState, useEffect } from "react";
import { networkService } from "../../../main";
import { createToast } from "../../../Utils/toastUtils";
import { useSelector } from "react-redux";
const useChecksFetch = ({
selectedMonitor,
selectedMonitorType,
filter,
dateRange,
page,
rowsPerPage,
}) => {
//Redux
const { user } = useSelector((state) => state.auth);
//Local
const [isLoading, setIsLoading] = useState(true);
const [networkError, setNetworkError] = useState(false);
const [checks, setChecks] = useState(undefined);
const [checksCount, setChecksCount] = useState(undefined);
useEffect(() => {
const fetchChecks = async () => {
try {
setIsLoading(true);
let res;
if (selectedMonitor === "0") {
res = await networkService.getChecksByTeam({
status: false,
teamId: user.teamId,
sortOrder: "desc",
limit: null,
dateRange,
filter: filter,
page: page,
rowsPerPage: rowsPerPage,
});
} else {
res = await networkService.getChecksByMonitor({
status: false,
monitorId: selectedMonitor,
type: selectedMonitorType,
sortOrder: "desc",
limit: null,
dateRange,
filter: filter,
page,
rowsPerPage,
});
}
setChecks(res.data.data.checks);
setChecksCount(res.data.data.checksCount);
} catch (error) {
setNetworkError(true);
createToast({ body: error.message });
} finally {
setIsLoading(false);
}
};
fetchChecks();
}, [user, dateRange, page, rowsPerPage, filter, selectedMonitor, selectedMonitorType]);
return { isLoading, networkError, checks, checksCount };
};
export default useChecksFetch;

View File

@@ -1,54 +0,0 @@
import { useState, useEffect } from "react";
import { networkService } from "../../../main";
import { createToast } from "../../../Utils/toastUtils";
const useMonitorsFetch = ({ teamId }) => {
//Local state
const [isLoading, setIsLoading] = useState(true);
const [networkError, setNetworkError] = useState(false);
const [monitors, setMonitors] = useState(undefined);
useEffect(() => {
const fetchMonitors = async () => {
try {
setIsLoading(true);
const res = await networkService.getMonitorsByTeamId({
teamId,
limit: null,
types: null,
status: null,
checkOrder: null,
normalize: null,
page: null,
rowsPerPage: null,
filter: null,
field: null,
order: null,
});
if (res?.data?.data?.filteredMonitors?.length > 0) {
const monitorLookup = res.data.data.filteredMonitors.reduce((acc, monitor) => {
acc[monitor._id] = {
_id: monitor._id,
name: monitor.name,
type: monitor.type,
};
return acc;
}, {});
setMonitors(monitorLookup);
}
} catch (error) {
setNetworkError(true);
createToast({
body: error.message,
});
} finally {
setIsLoading(false);
}
};
fetchMonitors();
}, [teamId]);
return { isLoading, monitors, networkError };
};
export { useMonitorsFetch };

View File

@@ -1,22 +1,20 @@
// Components
import { Stack } from "@mui/material";
import Breadcrumbs from "../../Components/Breadcrumbs";
import GenericFallback from "../../Components/GenericFallback";
import IncidentTable from "./Components/IncidentTable";
import OptionsHeader from "./Components/OptionsHeader";
//Utils
import { useTheme } from "@emotion/react";
import { useMonitorsFetch } from "./Hooks/useMonitorsFetch";
import { useSelector } from "react-redux";
import OptionsHeader from "./Components/OptionsHeader";
import { useState } from "react";
import IncidentTable from "./Components/IncidentTable";
import GenericFallback from "../../Components/GenericFallback";
import { useFetchMonitorsByTeamId } from "../../Hooks/monitorHooks";
import { useState, useEffect } from "react";
import NetworkError from "../../Components/GenericFallback/NetworkError";
import { useTranslation } from "react-i18next";
//Constants
//Constants
const Incidents = () => {
// Redux state
const { user } = useSelector((state) => state.auth);
const { t } = useTranslation();
const BREADCRUMBS = [
@@ -27,12 +25,23 @@ const Incidents = () => {
const [selectedMonitor, setSelectedMonitor] = useState("0");
const [filter, setFilter] = useState(undefined);
const [dateRange, setDateRange] = useState(undefined);
const [monitorLookup, setMonitorLookup] = useState(undefined);
//Utils
const theme = useTheme();
const [monitors, , isLoading, networkError] = useFetchMonitorsByTeamId({});
const { monitors, isLoading, networkError } = useMonitorsFetch({
teamId: user.teamId,
});
useEffect(() => {
const monitorLookup = monitors?.reduce((acc, monitor) => {
acc[monitor._id] = {
_id: monitor._id,
name: monitor.name,
type: monitor.type,
};
return acc;
}, {});
setMonitorLookup(monitorLookup);
}, [monitors]);
if (networkError) {
return (
@@ -47,7 +56,7 @@ const Incidents = () => {
<Breadcrumbs list={BREADCRUMBS} />
<OptionsHeader
shouldRender={!isLoading}
monitors={monitors}
monitors={monitorLookup}
selectedMonitor={selectedMonitor}
setSelectedMonitor={setSelectedMonitor}
filter={filter}
@@ -57,7 +66,7 @@ const Incidents = () => {
/>
<IncidentTable
shouldRender={!isLoading}
monitors={monitors}
monitors={monitorLookup ? monitorLookup : {}}
selectedMonitor={selectedMonitor}
filter={filter}
dateRange={dateRange}

View File

@@ -1,20 +1,17 @@
// React, Redux, Router
import { useTheme } from "@emotion/react";
import { useNavigate, useParams } from "react-router-dom";
import { useParams } from "react-router-dom";
import { useState, useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { useSelector } from "react-redux";
// Utility and Network
import { infrastructureMonitorValidation } from "../../../Validation/validation";
import {
createInfrastructureMonitor,
updateInfrastructureMonitor,
} from "../../../Features/InfrastructureMonitors/infrastructureMonitorsSlice";
import { useHardwareMonitorsFetch } from "../Details/Hooks/useHardwareMonitorsFetch";
import { useFetchHardwareMonitorById } from "../../../Hooks/monitorHooks";
import { capitalizeFirstLetter } from "../../../Utils/stringUtils";
import { useTranslation } from "react-i18next";
import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications";
import NotificationsConfig from "../../../Components/NotificationConfig";
import { useUpdateMonitor, useCreateMonitor } from "../../../Hooks/monitorHooks";
// MUI
import { Box, Stack, Typography, Button, ButtonGroup } from "@mui/material";
@@ -55,9 +52,6 @@ const getAlertError = (errors) => {
const CreateInfrastructureMonitor = () => {
const theme = useTheme();
const { user } = useSelector((state) => state.auth);
const monitorState = useSelector((state) => state.infrastructureMonitor);
const dispatch = useDispatch();
const navigate = useNavigate();
const { monitorId } = useParams();
const { t } = useTranslation();
@@ -65,9 +59,11 @@ const CreateInfrastructureMonitor = () => {
const isCreate = typeof monitorId === "undefined";
// Fetch monitor details if editing
const { monitor, isLoading, networkError } = useHardwareMonitorsFetch({ monitorId });
const [monitor, isLoading, networkError] = useFetchHardwareMonitorById({ monitorId });
const [notifications, notificationsAreLoading, notificationsError] =
useGetNotificationsByTeamId();
const [updateMonitor, isUpdating] = useUpdateMonitor();
const [createMonitor, isCreating] = useCreateMonitor();
// State
const [errors, setErrors] = useState({});
@@ -187,29 +183,18 @@ const CreateInfrastructureMonitor = () => {
};
form = {
...(isCreate ? {} : { _id: monitorId }),
...rest,
description: form.name,
teamId: user.teamId,
userId: user._id,
type: "hardware",
notifications: infrastructureMonitor.notifications,
thresholds,
};
// Handle create or update
const action = isCreate
? await dispatch(createInfrastructureMonitor({ monitor: form }))
: await dispatch(updateInfrastructureMonitor({ monitorId, monitor: form }));
if (action.meta.requestStatus === "fulfilled") {
createToast({
body: isCreate
? t("infrastructureMonitorCreated")
: t("infrastructureMonitorUpdated"),
});
navigate("/infrastructure");
} else {
createToast({ body: "Failed to save monitor." });
}
isCreate
? await createMonitor({ monitor: form, redirect: "/infrastructure" })
: await updateMonitor({ monitor: form, redirect: "/infrastructure" });
};
const onChange = (event) => {
@@ -448,7 +433,7 @@ const CreateInfrastructureMonitor = () => {
type="submit"
variant="contained"
color="accent"
loading={monitorState?.isLoading}
loading={isLoading || isUpdating || isCreating || notificationsAreLoading}
>
{t(isCreate ? "infrastructureCreateMonitor" : "infrastructureEditMonitor")}
</Button>

View File

@@ -4,14 +4,14 @@ import StatusBoxes from "../../../../../Components/StatusBoxes";
import StatBox from "../../../../../Components/StatBox";
//Utils
import useUtils from "../../../../../Pages/Uptime/Monitors/Hooks/useUtils";
import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils";
import { useHardwareUtils } from "../../Hooks/useHardwareUtils";
import { useTranslation } from "react-i18next";
const InfraStatBoxes = ({ shouldRender, monitor }) => {
// Utils
const { formatBytes } = useHardwareUtils();
const { determineState } = useUtils();
const { determineState } = useMonitorUtils();
const { t } = useTranslation();
const { stats } = monitor ?? {};

View File

@@ -1,38 +0,0 @@
import { useEffect, useState } from "react";
import { networkService } from "../../../../main";
const useHardwareMonitorsFetch = ({ monitorId, dateRange }) => {
// Abort early if creating monitor
const [isLoading, setIsLoading] = useState(true);
const [networkError, setNetworkError] = useState(false);
const [monitor, setMonitor] = useState(undefined);
useEffect(() => {
const fetchData = async () => {
try {
if (!monitorId) {
return { monitor: undefined, isLoading: false, networkError: undefined };
}
const response = await networkService.getHardwareDetailsByMonitorId({
monitorId: monitorId,
dateRange: dateRange,
});
setMonitor(response.data.data);
} catch (error) {
setNetworkError(true);
} finally {
setIsLoading(false);
}
};
fetchData();
}, [monitorId, dateRange]);
return {
isLoading,
networkError,
monitor,
};
};
export { useHardwareMonitorsFetch };

View File

@@ -11,7 +11,7 @@ import GenericFallback from "../../../Components/GenericFallback";
// Utils
import { useTheme } from "@emotion/react";
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
import { useHardwareMonitorsFetch } from "./Hooks/useHardwareMonitorsFetch";
import { useFetchHardwareMonitorById } from "../../../Hooks/monitorHooks";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useParams } from "react-router-dom";
@@ -33,7 +33,7 @@ const InfrastructureDetails = () => {
const { t } = useTranslation();
const isAdmin = useIsAdmin();
const { isLoading, networkError, monitor } = useHardwareMonitorsFetch({
const [monitor, isLoading, networkError] = useFetchHardwareMonitorById({
monitorId,
dateRange,
});

View File

@@ -12,7 +12,7 @@ import CustomGauge from "../../../../../Components/Charts/CustomGauge";
// Utils
import { useTheme } from "@emotion/react";
import useUtils from "../../../../Uptime/Monitors/Hooks/useUtils";
import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils";
import { useNavigate } from "react-router-dom";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
@@ -27,7 +27,7 @@ const MonitorsTable = ({
// Utils
const theme = useTheme();
const { t } = useTranslation();
const { determineState } = useUtils();
const { determineState } = useMonitorUtils();
const navigate = useNavigate();
// Handlers
@@ -94,6 +94,7 @@ const MonitorsTable = ({
monitor={row}
isAdmin={isAdmin}
updateCallback={handleActionMenuDelete}
isLoading={isLoading}
/>
),
},
@@ -151,7 +152,7 @@ const MonitorsTable = ({
};
MonitorsTable.propTypes = {
shouldRender: PropTypes.bool,
isLoading: PropTypes.bool,
monitors: PropTypes.array,
isAdmin: PropTypes.bool,
handleActionMenuDelete: PropTypes.func,

View File

@@ -1,7 +1,6 @@
/* TODO I basically copied and pasted this component from the actionsMenu. Check how we can make it reusable */
import { useRef, useState } from "react";
import { useSelector } from "react-redux";
import { useTheme } from "@emotion/react";
import { useNavigate } from "react-router-dom";
import { createToast } from "../../../../../Utils/toastUtils";
@@ -30,7 +29,6 @@ const InfrastructureMenu = ({ monitor, isAdmin, updateCallback }) => {
const [isOpen, setIsOpen] = useState(false);
const [isDialogOpen, setIsDialogOpen] = useState(false);
const theme = useTheme();
const { isLoading } = useSelector((state) => state.uptimeMonitors);
const openMenu = (e) => {
e.stopPropagation();
@@ -117,7 +115,6 @@ const InfrastructureMenu = ({ monitor, isAdmin, updateCallback }) => {
onCancel={cancelRemove}
confirmationButtonLabel="Delete"
onConfirm={handleRemove}
isLoading={isLoading}
modelTitle="modal-delete-monitor"
modelDescription="delete-monitor-confirmation"
/>

View File

@@ -1,46 +0,0 @@
import { useState, useEffect } from "react";
import { useSelector } from "react-redux";
import { networkService } from "../../../../main";
import { createToast } from "../../../../Utils/toastUtils";
const useMonitorFetch = ({ page, field, filter, rowsPerPage, updateTrigger }) => {
// Redux state
const { user } = useSelector((state) => state.auth);
// Local state
const [isLoading, setIsLoading] = useState(true);
const [networkError, setNetworkError] = useState(false);
const [monitors, setMonitors] = useState(undefined);
const [summary, setSummary] = useState(undefined);
useEffect(() => {
const fetchMonitors = async () => {
try {
const response = await networkService.getMonitorsByTeamId({
teamId: user.teamId,
limit: 1,
field: field,
filter: filter,
types: ["hardware"],
page: page,
rowsPerPage: rowsPerPage,
});
setMonitors(response?.data?.data?.filteredMonitors ?? []);
setSummary(response?.data?.data?.summary ?? {});
} catch (error) {
setNetworkError(true);
createToast({
body: error.message,
});
} finally {
setIsLoading(false);
}
};
fetchMonitors();
}, [page, field, filter, rowsPerPage, user.teamId, updateTrigger]);
return { monitors, summary, isLoading, networkError };
};
export { useMonitorFetch };

View File

@@ -11,15 +11,18 @@ import Filter from "./Components/Filters";
import SearchComponent from "../../Uptime/Monitors/Components/SearchComponent";
// Utils
import { useTheme } from "@emotion/react";
import { useMonitorFetch } from "./Hooks/useMonitorFetch";
import { useState } from "react";
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
import { useTranslation } from "react-i18next";
import { useFetchMonitorsByTeamId } from "../../../Hooks/monitorHooks";
// Constants
const TYPES = ["hardware"];
const BREADCRUMBS = [{ name: `infrastructure`, path: "/infrastructure" }];
const InfrastructureMonitors = () => {
// Redux state
// Local state
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(5);
const [updateTrigger, setUpdateTrigger] = useState(false);
@@ -53,7 +56,9 @@ const InfrastructureMonitors = () => {
const field = toFilterStatus !== undefined ? "status" : undefined;
const { monitors, summary, isLoading, networkError } = useMonitorFetch({
const [monitors, summary, isLoading, networkError] = useFetchMonitorsByTeamId({
limit: 1,
types: TYPES,
page,
field: field,
filter: toFilterStatus ?? search,
@@ -119,7 +124,7 @@ const InfrastructureMonitors = () => {
</Stack>
<MonitorsTable
shouldRender={!isLoading}
isLoading={isLoading}
monitors={monitors}
isAdmin={isAdmin}
handleActionMenuDelete={handleActionMenuDelete}

View File

@@ -135,7 +135,6 @@ const CreateMaintenance = () => {
setIsLoading(true);
try {
const response = await networkService.getMonitorsByTeamId({
teamId: user.teamId,
limit: null,
types: ["http", "ping", "pagespeed", "port"],
});

View File

@@ -48,8 +48,6 @@ const CreateNotifications = () => {
// local state
const [notification, setNotification] = useState({
userId: user._id,
teamId: user.teamId,
notificationName: "",
address: "",
type: NOTIFICATION_TYPES[0]._id,

View File

@@ -17,7 +17,6 @@ import {
useDeleteNotification,
} from "../../Hooks/useNotifications";
import { useTranslation } from "react-i18next";
import { useParams } from "react-router-dom";
const Notifications = () => {
const navigate = useNavigate();

View File

@@ -12,45 +12,43 @@ import NotificationsConfig from "../../../Components/NotificationConfig";
import Dialog from "../../../Components/Dialog";
// Utils
import { useEffect, useState } from "react";
import { useState } from "react";
import { useTheme } from "@emotion/react";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate, useParams } from "react-router";
import {
deletePageSpeed,
getPagespeedMonitorById,
getPageSpeedByTeamId,
updatePageSpeed,
pausePageSpeed,
} from "../../../Features/PageSpeedMonitor/pageSpeedMonitorSlice";
import { useParams } from "react-router";
import { monitorValidation } from "../../../Validation/validation";
import { createToast } from "../../../Utils/toastUtils";
import { useTranslation } from "react-i18next";
import useUtils from "../../Uptime/Monitors/Hooks/useUtils";
import { useMonitorUtils } from "../../../Hooks/useMonitorUtils";
import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications";
import {
useFetchMonitorById,
useDeleteMonitor,
useUpdateMonitor,
usePauseMonitor,
} from "../../../Hooks/monitorHooks";
const PageSpeedConfigure = () => {
// Redux state
const { isLoading } = useSelector((state) => state.pageSpeedMonitors);
// Local state
const [monitor, setMonitor] = useState({});
const [errors, setErrors] = useState({});
const [buttonLoading, setButtonLoading] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const [updateTrigger, setUpdateTrigger] = useState(false);
// Utils
const theme = useTheme();
const { t } = useTranslation();
const navigate = useNavigate();
const dispatch = useDispatch();
const MS_PER_MINUTE = 60000;
const { monitorId } = useParams();
const { statusColor, pagespeedStatusMsg, determineState } = useUtils();
const { statusColor, pagespeedStatusMsg, determineState } = useMonitorUtils();
const [notifications, notificationsAreLoading, notificationsError] =
useGetNotificationsByTeamId();
const [isLoading] = useFetchMonitorById({ monitorId, setMonitor, updateTrigger });
const [deleteMonitor, isDeleting] = useDeleteMonitor();
const [updateMonitor, isUpdating] = useUpdateMonitor();
const [pauseMonitor, isPausing] = usePauseMonitor();
const frequencies = [
{ _id: 3, name: "3 minutes" },
{ _id: 5, name: "5 minutes" },
@@ -61,24 +59,10 @@ const PageSpeedConfigure = () => {
{ _id: 10080, name: "1 week" },
];
useEffect(() => {
const fetchMonitor = async () => {
try {
const action = await dispatch(getPagespeedMonitorById({ monitorId }));
if (getPagespeedMonitorById.fulfilled.match(action)) {
const monitor = action.payload.data;
setMonitor(monitor);
} else if (getPagespeedMonitorById.rejected.match(action)) {
throw new Error(action.error.message);
}
} catch (error) {
logger.error("Error fetching monitor of id: " + monitorId);
navigate("/not-found", { replace: true });
}
};
fetchMonitor();
}, [dispatch, monitorId, navigate]);
// Handlers
const triggerUpdate = () => {
setUpdateTrigger(!updateTrigger);
};
const onChange = (event) => {
let { value, name } = event.target;
@@ -106,42 +90,17 @@ const PageSpeedConfigure = () => {
};
const handlePause = async () => {
try {
const action = await dispatch(pausePageSpeed({ monitorId }));
if (pausePageSpeed.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 (pausePageSpeed.rejected.match(action)) {
throw new Error(action.error.message);
}
} catch (error) {
createToast({ body: "Failed to pause monitor" });
}
await pauseMonitor({ monitorId, triggerUpdate });
};
const onSubmit = async (event) => {
event.preventDefault();
const action = await dispatch(updatePageSpeed({ monitor: monitor }));
if (action.meta.requestStatus === "fulfilled") {
createToast({ body: "Monitor updated successfully!" });
dispatch(getPageSpeedByTeamId());
} else {
createToast({ body: "Failed to update monitor." });
}
await updateMonitor({ monitor, redirect: "/pagespeed" });
};
const handleRemove = async (event) => {
event.preventDefault();
setButtonLoading(true);
const action = await dispatch(deletePageSpeed({ monitor }));
if (action.meta.requestStatus === "fulfilled") {
navigate("/pagespeed");
} else {
createToast({ body: "Failed to delete monitor." });
}
setButtonLoading(false);
await deleteMonitor({ monitor, redirect: "/pagespeed" });
};
return (
@@ -363,7 +322,7 @@ const PageSpeedConfigure = () => {
mt="auto"
>
<Button
loading={isLoading}
loading={isLoading || isDeleting || isUpdating || isPausing}
type="submit"
variant="contained"
color="accent"
@@ -383,7 +342,7 @@ const PageSpeedConfigure = () => {
onCancel={() => setIsOpen(false)}
confirmationButtonLabel={t("delete")}
onConfirm={handleRemove}
isLoading={buttonLoading}
isLoading={isLoading || isDeleting || isUpdating || isPausing}
/>
</Stack>
);

View File

@@ -1,31 +1,28 @@
// React, Redux, Router
import { useNavigate } from "react-router-dom";
import { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
// Utility and Network
import { monitorValidation } from "../../../Validation/validation";
import {
createPageSpeed,
checkEndpointResolution,
} from "../../../Features/PageSpeedMonitor/pageSpeedMonitorSlice";
import { parseDomainName } from "../../../Utils/monitorUtils";
import { useTranslation } from "react-i18next";
import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications";
// MUI
import { useTheme } from "@emotion/react";
import { Box, Stack, Typography, Button, ButtonGroup } from "@mui/material";
//Components
import Box from "@mui/material/Box";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import ButtonGroup from "@mui/material/ButtonGroup";
import Breadcrumbs from "../../../Components/Breadcrumbs";
import TextInput from "../../../Components/Inputs/TextInput";
import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments";
import ConfigBox from "../../../Components/ConfigBox";
import { createToast } from "../../../Utils/toastUtils";
import Radio from "../../../Components/Inputs/Radio";
import Select from "../../../Components/Inputs/Select";
import NotificationsConfig from "../../../Components/NotificationConfig";
// Utils
import { useState } from "react";
import { useSelector } from "react-redux";
import { monitorValidation } from "../../../Validation/validation";
import { parseDomainName } from "../../../Utils/monitorUtils";
import { useTranslation } from "react-i18next";
import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications";
import { useTheme } from "@emotion/react";
import { createToast } from "../../../Utils/toastUtils";
import { useCreateMonitor } from "../../../Hooks/monitorHooks";
const MS_PER_MINUTE = 60000;
const CRUMBS = [
@@ -56,13 +53,11 @@ const CreatePageSpeed = () => {
const [https, setHttps] = useState(true);
const [errors, setErrors] = useState({});
const { user } = useSelector((state) => state.auth);
const { isLoading } = useSelector((state) => state.pageSpeedMonitors);
const [notifications, notificationsAreLoading, error] = useGetNotificationsByTeamId();
// Setup
const dispatch = useDispatch();
const navigate = useNavigate();
const theme = useTheme();
const [createMonitor, isCreating] = useCreateMonitor();
// Handlers
const onSubmit = async (event) => {
@@ -88,33 +83,13 @@ const CreatePageSpeed = () => {
return;
}
const checkEndpointAction = await dispatch(
checkEndpointResolution({ monitorURL: form.url })
);
if (checkEndpointAction.meta.requestStatus === "rejected") {
createToast({
body: "The endpoint you entered doesn't resolve. Check the URL again.",
});
setErrors({ url: "The entered URL is not reachable." });
return;
}
form = {
...form,
description: form.name,
teamId: user.teamId,
userId: user._id,
notifications: monitor.notifications,
};
const action = await dispatch(createPageSpeed({ monitor: form }));
if (action.meta.requestStatus === "fulfilled") {
createToast({ body: "Monitor created successfully!" });
navigate("/pagespeed");
} else {
createToast({ body: "Failed to create monitor." });
}
await createMonitor({ monitor: form, redirect: "/pagespeed" });
};
const handleChange = (event) => {
@@ -325,7 +300,7 @@ const CreatePageSpeed = () => {
variant="contained"
color="accent"
disabled={!Object.values(errors).every((value) => value === undefined)}
loading={isLoading}
loading={isCreating}
>
{t("createMonitor")}
</Button>

View File

@@ -1,39 +0,0 @@
import { useEffect, useState } from "react";
import { networkService } from "../../../../main";
import { createToast } from "../../../../Utils/toastUtils";
import { useNavigate } from "react-router-dom";
const useMonitorFetch = ({ monitorId }) => {
const navigate = useNavigate();
const [monitor, setMonitor] = useState(undefined);
const [audits, setAudits] = useState(undefined);
const [isLoading, setIsLoading] = useState(true);
const [networkError, setNetworkError] = useState(false);
useEffect(() => {
const fetchMonitor = async () => {
try {
const res = await networkService.getStatsByMonitorId({
monitorId: monitorId,
sortOrder: "desc",
limit: 50,
dateRange: "day",
numToDisplay: null,
normalize: null,
});
setMonitor(res?.data?.data ?? undefined);
setAudits(res?.data?.data?.checks?.[0]?.audits ?? undefined);
} catch (error) {
setNetworkError(true);
createToast({ body: error.message });
} finally {
setIsLoading(false);
}
};
fetchMonitor();
}, [monitorId, navigate]);
return { monitor, audits, isLoading };
};
export { useMonitorFetch };

View File

@@ -11,8 +11,7 @@ import GenericFallback from "../../../Components/GenericFallback";
import { useTheme } from "@emotion/react";
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
import { useParams } from "react-router-dom";
import { useSelector } from "react-redux";
import { useMonitorFetch } from "./Hooks/useMonitorFetch";
import { useFetchStatsByMonitorId } from "../../../Hooks/monitorHooks";
import { useState } from "react";
import { useTranslation } from "react-i18next";
// Constants
@@ -28,8 +27,13 @@ const PageSpeedDetails = () => {
const isAdmin = useIsAdmin();
const { monitorId } = useParams();
const { monitor, audits, isLoading, networkError } = useMonitorFetch({
const [monitor, audits, isLoading, networkError] = useFetchStatsByMonitorId({
monitorId,
sortOrder: "desc",
limit: 50,
dateRange: "day",
numToDisplay: null,
normalize: null,
});
const [metrics, setMetrics] = useState({

View File

@@ -8,7 +8,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/Monitors/Hooks/useUtils";
import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import IconBox from "../../../../../Components/IconBox";
@@ -118,7 +118,7 @@ const processData = (data) => {
const PagespeedAreaChart = ({ data, status }) => {
const theme = useTheme();
const [isHovered, setIsHovered] = useState(false);
const { statusToTheme } = useUtils();
const { statusToTheme } = useMonitorUtils();
const themeColor = statusToTheme[status];
@@ -206,7 +206,7 @@ PagespeedAreaChart.propTypes = {
* @returns {JSX.Element} - The rendered card.
*/
const Card = ({ monitor }) => {
const { determineState, pagespeedStatusMsg } = useUtils();
const { determineState, pagespeedStatusMsg } = useMonitorUtils();
const theme = useTheme();
const { t } = useTranslation();
const navigate = useNavigate();
@@ -275,7 +275,7 @@ const Card = ({ monitor }) => {
sx={{ gridColumnStart: 1, gridColumnEnd: 4 }}
>
<PagespeedAreaChart
data={monitor.checks.slice().reverse()}
data={monitor?.checks?.slice().reverse()}
status={monitorState}
/>
</Box>

View File

@@ -1,44 +0,0 @@
import { useEffect, useState } from "react";
import { networkService } from "../../../../main";
import { createToast } from "../../../../Utils/toastUtils";
const useMonitorsFetch = ({ teamId }) => {
const [isLoading, setIsLoading] = useState(true);
const [monitors, setMonitors] = useState([]);
const [summary, setSummary] = useState({});
const [networkError, setNetworkError] = useState(false);
useEffect(() => {
const fetchMonitors = async () => {
try {
setIsLoading(true);
const res = await networkService.getMonitorsByTeamId({
teamId: teamId,
limit: 10,
types: ["pagespeed"],
page: null,
rowsPerPage: null,
filter: null,
field: null,
order: null,
});
if (res?.data?.data?.filteredMonitors) {
setMonitors(res.data.data.filteredMonitors);
setSummary(res.data.data.summary);
}
} catch (error) {
setNetworkError(true);
createToast({
body: error.message,
});
} finally {
setIsLoading(false);
}
};
fetchMonitors();
}, [teamId]);
return { isLoading, monitors, summary, networkError };
};
export default useMonitorsFetch;

View File

@@ -5,26 +5,29 @@ import CreateMonitorHeader from "../../../Components/MonitorCreateHeader";
import MonitorCountHeader from "../../../Components/MonitorCountHeader";
import MonitorGrid from "./Components/MonitorGrid";
import Fallback from "../../../Components/Fallback";
import GenericFallback from "../../../Components/GenericFallback";
// Utils
import { useTheme } from "@emotion/react";
import { useSelector } from "react-redux";
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
import useMonitorsFetch from "./Hooks/useMonitorsFetch";
import GenericFallback from "../../../Components/GenericFallback";
import { useTranslation } from "react-i18next";
import { useFetchMonitorsByTeamId } from "../../../Hooks/monitorHooks";
// Constants
const BREADCRUMBS = [{ name: `pagespeed`, path: "/pagespeed" }];
const TYPES = ["pagespeed"];
const PageSpeed = () => {
const theme = useTheme();
const { t } = useTranslation();
const isAdmin = useIsAdmin();
const { user } = useSelector((state) => state.auth);
const { pagespeedApiKey } = useSelector((state) => state.settings);
const { isLoading, monitors, summary, networkError } = useMonitorsFetch({
teamId: user.teamId,
const [monitors, monitorsSummary, isLoading, networkError] = useFetchMonitorsByTeamId({
limit: 10,
types: TYPES,
page: null,
rowsPerPage: null,
filter: null,
field: null,
order: null,
});
if (networkError === true) {
@@ -53,7 +56,7 @@ const PageSpeed = () => {
]}
link="/pagespeed/create"
isAdmin={isAdmin}
showPageSpeedWarning={isAdmin && !pagespeedApiKey}
// showPageSpeedWarning={isAdmin && !pagespeedApiKey}
/>
);
}
@@ -68,7 +71,7 @@ const PageSpeed = () => {
/>
<MonitorCountHeader
shouldRender={!isLoading}
monitorCount={summary?.totalMonitors}
monitorCount={monitorsSummary?.totalMonitors}
sx={{ mb: theme.spacing(8) }}
/>
<MonitorGrid

View File

@@ -11,22 +11,20 @@ import SettingsEmail from "./SettingsEmail";
import Button from "@mui/material/Button";
// Utils
import { settingsValidation } from "../../Validation/validation";
import { createToast } from "../../Utils/toastUtils";
import { useState } from "react";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
import { useSelector, useDispatch } from "react-redux";
import { setTimezone, setMode, setLanguage, setShowURL } from "../../Features/UI/uiSlice";
import SettingsStats from "./SettingsStats";
import {
deleteMonitorChecksByTeamId,
addDemoMonitors,
deleteAllMonitors,
} from "../../Features/UptimeMonitors/uptimeMonitorsSlice";
import { useFetchSettings, useSaveSettings } from "../../Hooks/useFetchSettings";
import { UseDeleteMonitorStats } from "../../Hooks/useDeleteMonitorStats";
import { useIsAdmin } from "../../Hooks/useIsAdmin";
import { useFetchSettings, useSaveSettings } from "../../Hooks/settingsHooks";
import { useIsAdmin } from "../../Hooks/useIsAdmin";
import {
useAddDemoMonitors,
useDeleteAllMonitors,
useDeleteMonitorStats,
} from "../../Hooks/monitorHooks";
// Constants
const BREADCRUMBS = [{ name: `Settings`, path: "/settings" }];
@@ -43,15 +41,17 @@ const Settings = () => {
const [isSettingsLoading, settingsError] = useFetchSettings({
setSettingsData,
});
const [addDemoMonitors, isAddingDemoMonitors] = useAddDemoMonitors();
const [isSaving, saveError, saveSettings] = useSaveSettings();
const [deleteMonitorStats, isDeletingMonitorStats] = UseDeleteMonitorStats();
const [deleteAllMonitors, isDeletingMonitors] = useDeleteAllMonitors();
const [deleteMonitorStats, isDeletingMonitorStats] = useDeleteMonitorStats();
// Setup
const isAdmin = useIsAdmin();
const theme = useTheme();
const HEADING_SX = { mt: theme.spacing(2), mb: theme.spacing(2) };
const { t, i18n } = useTranslation();
const { t } = useTranslation();
const dispatch = useDispatch();
// Handlers
@@ -96,35 +96,17 @@ const Settings = () => {
}
if (name === "deleteStats") {
await deleteMonitorStats({ teamId: user.teamId });
await deleteMonitorStats();
return;
}
if (name === "demo") {
try {
const action = await dispatch(addDemoMonitors());
if (addDemoMonitors.fulfilled.match(action)) {
createToast({ body: t("settingsDemoMonitorsAdded") });
} else {
createToast({ body: t("settingsFailedToAddDemoMonitors") });
}
} catch (error) {
createToast({ body: t("settingsFailedToAddDemoMonitors") });
}
await addDemoMonitors();
return;
}
if (name === "deleteMonitors") {
try {
const action = await dispatch(deleteAllMonitors());
if (deleteAllMonitors.fulfilled.match(action)) {
createToast({ body: t("settingsMonitorsDeleted") });
} else {
createToast({ body: t("settingsFailedToDeleteMonitors") });
}
} catch (error) {
createToast({ body: t("settingsFailedToDeleteMonitors") });
}
await deleteAllMonitors();
return;
}
@@ -185,7 +167,9 @@ const Settings = () => {
isAdmin={isAdmin}
HEADER_SX={HEADING_SX}
handleChange={handleChange}
isLoading={isSettingsLoading || isSaving || isDeletingMonitorStats}
isLoading={
isSettingsLoading || isSaving || isDeletingMonitorStats || isAddingDemoMonitors
}
/>
<SettingsEmail
isAdmin={isAdmin}
@@ -219,7 +203,9 @@ const Settings = () => {
}}
>
<Button
loading={isSaving || isDeletingMonitorStats || isSettingsLoading}
loading={
isSaving || isDeletingMonitorStats || isSettingsLoading || isDeletingMonitors
}
disabled={Object.keys(errors).length > 0}
variant="contained"
color="accent"

View File

@@ -1,17 +1,14 @@
import { useState } from "react";
import { networkService } from "../../../../main";
import { useSelector } from "react-redux";
import { createToast } from "../../../../Utils/toastUtils";
const useCreateStatusPage = (isCreate, url) => {
const { user } = useSelector((state) => state.auth);
const [isLoading, setIsLoading] = useState(false);
const [networkError, setNetworkError] = useState(false);
const createStatusPage = async ({ form }) => {
setIsLoading(true);
try {
await networkService.createStatusPage({ user, form, isCreate, url });
await networkService.createStatusPage({ form, isCreate, url });
return true;
} catch (error) {
setNetworkError(true);

View File

@@ -13,7 +13,6 @@ const useMonitorsFetch = () => {
const fetchMonitors = async () => {
try {
const response = await networkService.getMonitorsByTeamId({
teamId: user.teamId,
limit: null, // donot return any checks for the monitors
types: ["http", "ping", "port"], // status page is available for uptime, ping, and port monitors
});

View File

@@ -6,14 +6,14 @@ import { StatusLabel } from "../../../../../Components/Label";
//Utils
import { useTheme } from "@mui/material/styles";
import useUtils from "../../../../Uptime/Monitors/Hooks/useUtils";
import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils";
import PropTypes from "prop-types";
import { useSelector } from "react-redux";
const MonitorsList = ({ isLoading = false, shouldRender = true, monitors = [] }) => {
const theme = useTheme();
const { determineState } = useUtils();
const { determineState } = useMonitorUtils();
const { showURL } = useSelector((state) => state.ui);

View File

@@ -8,6 +8,7 @@ import StatusBar from "./Components/StatusBar";
import MonitorsList from "./Components/MonitorsList";
import Dialog from "../../../Components/Dialog";
import Breadcrumbs from "../../../Components/Breadcrumbs/index.jsx";
import TextLink from "../../../Components/TextLink";
// Utils
import { useStatusPageFetch } from "./Hooks/useStatusPageFetch";
@@ -30,6 +31,7 @@ const PublicStatus = () => {
const { t } = useTranslation();
const location = useLocation();
const navigate = useNavigate();
const isAdmin = useIsAdmin();
const [statusPage, monitors, isLoading, networkError, fetchStatusPage] =
useStatusPageFetch(false, url);
@@ -60,6 +62,26 @@ const PublicStatus = () => {
return <SkeletonLayout />;
}
if (monitors.length === 0) {
return (
<GenericFallback>
<Typography
variant="h1"
marginY={theme.spacing(4)}
color={theme.palette.primary.contrastTextTertiary}
>
{"Theres nothing here yet"}
</Typography>
{isAdmin && (
<TextLink
linkText={"Add a monitor to get started"}
href={`/status/uptime/configure/${url}`}
/>
)}
</GenericFallback>
);
}
// Error fetching data
if (networkError === true) {
return (

View File

@@ -13,9 +13,7 @@ const useStatusPagesFetch = () => {
useEffect(() => {
const fetchStatusPages = async () => {
try {
const res = await networkService.getStatusPagesByTeamId({
teamId: user.teamId,
});
const res = await networkService.getStatusPagesByTeamId();
setStatusPages(res?.data?.data);
} catch (error) {
setNetworkError(true);

View File

@@ -12,7 +12,7 @@ import UploadFile from "./Upload";
import { useSelector } from "react-redux";
import { useNavigate } from "react-router";
import { Trans, useTranslation } from "react-i18next";
import { useBulkMonitors } from "../../../Hooks/useBulkMonitors";
import { useCreateBulkMonitors } from "../../../Hooks/monitorHooks";
const BulkImport = () => {
const theme = useTheme();
@@ -26,7 +26,7 @@ const BulkImport = () => {
{ name: t("bulkImport.title"), path: `/uptime/bulk-import` },
];
const [createBulkMonitors, hookLoading] = useBulkMonitors();
const [createBulkMonitors, hookLoading] = useCreateBulkMonitors();
const handleSubmit = async () => {
if (!selectedFile) {

View File

@@ -1,37 +1,37 @@
import { useNavigate, useParams } from "react-router";
import { useTheme } from "@emotion/react";
import { useDispatch, useSelector } from "react-redux";
import { useState, useEffect } from "react";
import {
Box,
Stack,
Tooltip,
Typography,
Button,
FormControlLabel,
Switch,
} from "@mui/material";
import { monitorValidation } from "../../../Validation/validation";
import { createToast } from "../../../Utils/toastUtils";
import { useTranslation } from "react-i18next";
// Components
import Box from "@mui/material/Box";
import Stack from "@mui/material/Stack";
import Tooltip from "@mui/material/Tooltip";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import FormControlLabel from "@mui/material/FormControlLabel";
import Switch from "@mui/material/Switch";
import ConfigBox from "../../../Components/ConfigBox";
import {
updateUptimeMonitor,
deleteUptimeMonitor,
} from "../../../Features/UptimeMonitors/uptimeMonitorsSlice";
import Breadcrumbs from "../../../Components/Breadcrumbs";
import TextInput from "../../../Components/Inputs/TextInput";
import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments";
import Select from "../../../Components/Inputs/Select";
import Breadcrumbs from "../../../Components/Breadcrumbs";
import PulseDot from "../../../Components/Animated/PulseDot";
import "./index.css";
import Dialog from "../../../Components/Dialog";
import { usePauseMonitor } from "../../../Hooks/useMonitorControls";
import PulseDot from "../../../Components/Animated/PulseDot";
import Checkbox from "../../../Components/Inputs/Checkbox";
// Utils
import { useParams } from "react-router";
import { useTheme } from "@emotion/react";
import { useState } from "react";
import { monitorValidation } from "../../../Validation/validation";
import { createToast } from "../../../Utils/toastUtils";
import { useTranslation } from "react-i18next";
import PauseOutlinedIcon from "@mui/icons-material/PauseOutlined";
import PlayArrowOutlinedIcon from "@mui/icons-material/PlayArrowOutlined";
import { useMonitorUtils } from "../../../Hooks/useMonitorUtils";
import { useFetchUptimeMonitorById } from "../../../Hooks/useFetchUptimeMonitorById";
import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications";
import {
useDeleteMonitor,
useUpdateMonitor,
usePauseMonitor,
useFetchMonitorById,
} from "../../../Hooks/monitorHooks";
import NotificationsConfig from "../../../Components/NotificationConfig";
/**
@@ -76,18 +76,19 @@ const Configure = () => {
};
// Network
const [monitor, isLoading, error] = useFetchUptimeMonitorById(monitorId, updateTrigger);
const [notifications, notificationsAreLoading, notificationsError] =
useGetNotificationsByTeamId();
const [pauseMonitor, isPausing, pauseError] = usePauseMonitor({
monitorId: monitor?._id,
triggerUpdate,
const [pauseMonitor, isPausing, pauseError] = usePauseMonitor({});
const [deleteMonitor, isDeleting] = useDeleteMonitor();
const [updateMonitor, isUpdating] = useUpdateMonitor();
const [isLoading] = useFetchMonitorById({
monitorId,
setMonitor: setForm,
updateTrigger,
});
const MS_PER_MINUTE = 60000;
const navigate = useNavigate();
const theme = useTheme();
const dispatch = useDispatch();
const matchMethodOptions = [
{ _id: "equal", name: "Equal" },
@@ -111,7 +112,7 @@ const Configure = () => {
// Handlers
const handlePause = async () => {
const res = await pauseMonitor();
const res = await pauseMonitor({ monitorId: form?._id, triggerUpdate });
if (typeof res !== "undefined") {
triggerUpdate();
}
@@ -119,12 +120,7 @@ const Configure = () => {
const handleRemove = async (event) => {
event.preventDefault();
const action = await dispatch(deleteUptimeMonitor({ monitor }));
if (action.meta.requestStatus === "fulfilled") {
navigate("/uptime");
} else {
createToast({ body: "Failed to delete monitor." });
}
await deleteMonitor({ monitor: form, redirect: "/uptime" });
};
const onChange = (event) => {
@@ -134,6 +130,17 @@ const Configure = () => {
value = checked;
}
if (name === "useAdvancedMatching") {
setForm((prevForm) => {
return {
...prevForm,
matchMethod: "equal",
};
});
setUseAdvancedMatching(!useAdvancedMatching);
return;
}
if (name === "interval") {
value = value * MS_PER_MINUTE;
}
@@ -146,7 +153,6 @@ const Configure = () => {
setErrors((prev) => {
const updatedErrors = { ...prev };
if (validation.error) updatedErrors[name] = validation.error.details[0].message;
else delete updatedErrors[name];
return updatedErrors;
@@ -183,7 +189,7 @@ const Configure = () => {
if (validation.error) {
const newErrors = {};
error.details.forEach((err) => {
validation.error.details.forEach((err) => {
newErrors[err.path[0]] = err.message;
});
setErrors(newErrors);
@@ -192,27 +198,12 @@ const Configure = () => {
}
toSubmit.notifications = form.notifications;
const action = await dispatch(updateUptimeMonitor({ monitor: toSubmit }));
if (action.meta.requestStatus === "fulfilled") {
createToast({ body: "Monitor updated successfully!" });
} else {
createToast({ body: "Failed to update monitor." });
}
console.log(JSON.stringify(toSubmit, null, 2));
// await updateMonitor({ monitor: toSubmit, redirect: "/uptime" });
};
// Effects
useEffect(() => {
if (monitor?.matchMethod) {
setUseAdvancedMatching(true);
}
setForm({
...monitor,
});
}, [monitor, notifications]);
// Parse the URL
const parsedUrl = parseUrl(monitor?.url);
const parsedUrl = parseUrl(form?.url);
const protocol = parsedUrl?.protocol?.replace(":", "") || "";
const { determineState, statusColor } = useMonitorUtils();
@@ -434,7 +425,13 @@ const Configure = () => {
onChange={onChange}
items={frequencies}
/>
{form.type === "http" && (
<Checkbox
name="useAdvancedMatching"
label={t("advancedMatching")}
isChecked={useAdvancedMatching}
onChange={onChange}
/>
{form.type === "http" && useAdvancedMatching && (
<>
<Select
name="matchMethod"
@@ -502,6 +499,7 @@ const Configure = () => {
mt="auto"
>
<Button
disabled={isDeleting || isUpdating}
type="submit"
variant="contained"
color="accent"

View File

@@ -1,35 +1,30 @@
// React, Redux, Router
import { useTheme } from "@emotion/react";
import { useNavigate } from "react-router-dom";
import { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { useTranslation } from "react-i18next";
// Utility and Network
import { monitorValidation } from "../../../Validation/validation";
import { createUptimeMonitor } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice";
// MUI
import { Box, Stack, Typography, Button, ButtonGroup } from "@mui/material";
import Switch from "@mui/material/Switch";
import FormControlLabel from "@mui/material/FormControlLabel";
//Components
import Breadcrumbs from "../../../Components/Breadcrumbs";
import TextInput from "../../../Components/Inputs/TextInput";
import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments";
import { createToast } from "../../../Utils/toastUtils";
import Radio from "../../../Components/Inputs/Radio";
import Select from "../../../Components/Inputs/Select";
import ConfigBox from "../../../Components/ConfigBox";
import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications";
import NotificationsConfig from "../../../Components/NotificationConfig";
import Button from "@mui/material/Button";
import ButtonGroup from "@mui/material/ButtonGroup";
import Box from "@mui/material/Box";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import Switch from "@mui/material/Switch";
import FormControlLabel from "@mui/material/FormControlLabel";
import Checkbox from "../../../Components/Inputs/Checkbox";
// Utils
import { useTheme } from "@emotion/react";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { monitorValidation } from "../../../Validation/validation";
import { createToast } from "../../../Utils/toastUtils";
import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications";
import { useCreateMonitor } from "../../../Hooks/monitorHooks";
const CreateMonitor = () => {
// Redux state
const { user } = useSelector((state) => state.auth);
const { isLoading } = useSelector((state) => state.uptimeMonitors);
const dispatch = useDispatch();
// Local state
const [errors, setErrors] = useState({});
const [https, setHttps] = useState(true);
@@ -39,6 +34,8 @@ const CreateMonitor = () => {
name: "",
type: "http",
matchMethod: "equal",
expectedValue: "",
jsonPath: "",
notifications: [],
interval: 1,
ignoreTlsErrors: false,
@@ -47,8 +44,8 @@ const CreateMonitor = () => {
// Setup
const theme = useTheme();
const { t } = useTranslation();
const navigate = useNavigate();
const [notifications, notificationsAreLoading, error] = useGetNotificationsByTeamId();
const [createMonitor, isCreating] = useCreateMonitor();
const MS_PER_MINUTE = 60000;
const SELECT_VALUES = [
@@ -142,18 +139,10 @@ const CreateMonitor = () => {
form = {
...form,
description: monitor.name || monitor.url,
teamId: user.teamId,
userId: user._id,
notifications: monitor.notifications,
};
const action = await dispatch(createUptimeMonitor({ monitor: form }));
if (action.meta.requestStatus === "fulfilled") {
createToast({ body: "Monitor created successfully!" });
navigate("/uptime");
} else {
createToast({ body: "Failed to create monitor." });
}
await createMonitor({ monitor: form, redirect: "/uptime" });
};
const onChange = (event) => {
@@ -162,6 +151,12 @@ const CreateMonitor = () => {
if (name === "ignoreTlsErrors") {
newValue = checked;
}
if (name === "useAdvancedMatching") {
setUseAdvancedMatching(checked);
return;
}
const updatedMonitor = {
...monitor,
[name]: newValue,
@@ -399,7 +394,13 @@ const CreateMonitor = () => {
onChange={onChange}
items={SELECT_VALUES}
/>
{monitor.type === "http" && (
<Checkbox
name="useAdvancedMatching"
label={t("advancedMatching")}
isChecked={useAdvancedMatching}
onChange={onChange}
/>
{monitor.type === "http" && useAdvancedMatching && (
<>
<Select
name="matchMethod"
@@ -472,7 +473,7 @@ const CreateMonitor = () => {
variant="contained"
color="accent"
disabled={!Object.values(errors).every((value) => value === undefined)}
loading={isLoading}
loading={isCreating}
>
{t("createMonitor")}
</Button>

View File

@@ -5,7 +5,7 @@ import PropTypes from "prop-types";
import { getHumanReadableDuration } from "../../../../../Utils/timeUtils";
import { useTheme } from "@mui/material/styles";
import { Typography } from "@mui/material";
import useUtils from "../../../Monitors/Hooks/useUtils";
import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils";
const UptimeStatusBoxes = ({
isLoading = false,
@@ -14,7 +14,7 @@ const UptimeStatusBoxes = ({
certificateExpiry,
}) => {
const theme = useTheme();
const { determineState } = useUtils();
const { determineState } = useMonitorUtils();
// Determine time since last failure
const timeOfLastFailure = monitorStats?.timeOfLastFailure;

View File

@@ -1,50 +0,0 @@
import { useState } from "react";
import { useEffect } from "react";
import { networkService } from "../../../../main";
import { createToast } from "../../../../Utils/toastUtils";
export const useChecksFetch = ({
monitorId,
monitorType,
dateRange,
page,
rowsPerPage,
}) => {
const [checks, setChecks] = useState(undefined);
const [checksCount, setChecksCount] = useState(undefined);
const [isLoading, setIsLoading] = useState(false);
const [networkError, setNetworkError] = useState(false);
useEffect(() => {
if (!monitorType) {
return;
}
const fetchChecks = async () => {
try {
setIsLoading(true);
const res = await networkService.getChecksByMonitor({
monitorId: monitorId,
type: monitorType,
sortOrder: "desc",
limit: null,
dateRange: dateRange,
filter: null,
page: page,
rowsPerPage: rowsPerPage,
});
setChecks(res.data.data.checks);
setChecksCount(res.data.data.checksCount);
} catch (error) {
setNetworkError(true);
createToast({ body: error.message });
} finally {
setIsLoading(false);
}
};
fetchChecks();
}, [monitorId, monitorType, dateRange, page, rowsPerPage]);
return [checks, checksCount, isLoading, networkError];
};
export default useChecksFetch;

View File

@@ -1,33 +0,0 @@
import { useEffect, useState } from "react";
import { networkService } from "../../../../main";
import { useNavigate } from "react-router-dom";
import { createToast } from "../../../../Utils/toastUtils";
export const useMonitorFetch = ({ monitorId, dateRange }) => {
const [networkError, setNetworkError] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [monitor, setMonitor] = useState(undefined);
const navigate = useNavigate();
useEffect(() => {
const fetchMonitors = async () => {
try {
const res = await networkService.getUptimeDetailsById({
monitorId: monitorId,
dateRange: dateRange,
normalize: true,
});
setMonitor(res?.data?.data ?? {});
} catch (error) {
setNetworkError(true);
createToast({ body: error.message });
} finally {
setIsLoading(false);
}
};
fetchMonitors();
}, [dateRange, monitorId, navigate]);
return [monitor, isLoading, networkError];
};
export default useMonitorFetch;

View File

@@ -1,15 +1,14 @@
// 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";
import ResponseTimeChart from "./Components/Charts/ResponseTimeChart";
import ResponseTable from "./Components/ResponseTable";
import UptimeStatusBoxes from "./Components/UptimeStatusBoxes";
import GenericFallback from "../../../Components/GenericFallback";
// MUI Components
import { Stack, Typography } from "@mui/material";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
// Utils
import { useState } from "react";
@@ -17,9 +16,9 @@ import { useParams } from "react-router-dom";
import { useSelector } from "react-redux";
import { useTheme } from "@emotion/react";
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
import useFetchUptimeMonitorDetails from "../../../Hooks/useFetchUptimeMonitorDetails";
import { useFetchUptimeMonitorById } from "../../../Hooks/monitorHooks";
import useCertificateFetch from "./Hooks/useCertificateFetch";
import useChecksFetch from "./Hooks/useChecksFetch";
import { useFetchChecks } from "../../../Hooks/checkHooks";
import { useTranslation } from "react-i18next";
// Constants
@@ -50,7 +49,7 @@ const UptimeDetails = () => {
const { t } = useTranslation();
const [monitorData, monitorStats, monitorIsLoading, monitorNetworkError] =
useFetchUptimeMonitorDetails({
useFetchUptimeMonitorById({
monitorId,
dateRange,
trigger,
@@ -66,10 +65,14 @@ const UptimeDetails = () => {
});
const monitorType = monitor?.type;
const [checks, checksCount, checksAreLoading, checksNetworkError] = useChecksFetch({
const [checks, checksCount, checksAreLoading, checksNetworkError] = useFetchChecks({
monitorId,
monitorType,
type: monitorType,
sortOrder: "desc",
limit: null,
dateRange,
filter: null,
page,
rowsPerPage,
});

View File

@@ -13,7 +13,7 @@ import TableSkeleton from "../../../../../Components/Table/skeleton";
// Utils
import { useTheme } from "@emotion/react";
import useUtils from "../../Hooks/useUtils";
import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils";
import { useNavigate } from "react-router-dom";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
@@ -58,7 +58,7 @@ const UptimeDataTable = ({
}) => {
// Utils
const navigate = useNavigate();
const { determineState } = useUtils();
const { determineState } = useMonitorUtils();
const theme = useTheme();
const { t } = useTranslation();

View File

@@ -1,77 +0,0 @@
import { useEffect, useState } from "react";
import { networkService } from "../../../../main";
import { createToast } from "../../../../Utils/toastUtils";
import { useTheme } from "@emotion/react";
import { useMonitorUtils } from "../../../../Hooks/useMonitorUtils";
export const useMonitorFetch = ({
teamId,
limit,
page,
rowsPerPage,
filter,
field,
order,
triggerUpdate,
}) => {
const [monitorsAreLoading, setMonitorsAreLoading] = useState(false);
const [monitors, setMonitors] = useState(undefined);
const [filteredMonitors, setFilteredMonitors] = useState(undefined);
const [monitorsSummary, setMonitorsSummary] = useState(undefined);
const [networkError, setNetworkError] = useState(false);
const theme = useTheme();
const { getMonitorWithPercentage } = useMonitorUtils();
useEffect(() => {
const fetchMonitors = async () => {
try {
setMonitorsAreLoading(true);
const res = await networkService.getMonitorsByTeamId({
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) {
setNetworkError(true);
createToast({
body: error.message,
});
} finally {
setMonitorsAreLoading(false);
}
};
fetchMonitors();
}, [
teamId,
limit,
field,
filter,
order,
page,
rowsPerPage,
theme,
triggerUpdate,
getMonitorWithPercentage,
]);
return {
monitors,
filteredMonitors,
monitorsSummary,
monitorsAreLoading,
networkError,
};
};
export default useMonitorFetch;

View File

@@ -1,110 +0,0 @@
import { useTheme } from "@mui/material";
const useUtils = () => {
const determineState = (monitor) => {
if (typeof monitor === "undefined") return "pending";
if (monitor.isActive === false) return "paused";
if (monitor?.status === undefined) return "pending";
return monitor?.status == true ? "up" : "down";
};
/* TODO Refactor: from here on shouldn't live in a custom hook, but on theme, or constants */
const theme = useTheme();
const statusColor = {
up: theme.palette.success.lowContrast,
down: theme.palette.error.lowContrast,
paused: theme.palette.warning.lowContrast,
pending: theme.palette.warning.lowContrast,
};
const statusMsg = {
up: "Your site is up.",
down: "Your site is down.",
paused: "Pending...",
};
const pagespeedStatusMsg = {
up: "Live (collecting data)",
down: "Inactive",
paused: "Paused",
};
/*
TODO
This is used on
1) Details > Gradient card */
/* These are rediections. We should do something that maps up to success, down to error, and get the theme by that
See Client\src\Components\Label\index.jsx
*/
const statusToTheme = {
up: "success",
down: "error",
paused: "warning",
pending: "secondary",
"cannot resolve": "tertiary",
};
const getStatusStyles = (status) => {
const themeColor = statusToTheme[status];
return {
backgroundColor: theme.palette[themeColor].lowContrast,
background: `linear-gradient(340deg, ${theme.palette[themeColor].main} -60%, ${theme.palette[themeColor].lowContrast} 35%)`,
borderColor: theme.palette[themeColor].lowContrast,
"& h2": {
color: theme.palette[themeColor].contrastText,
textTransform: "uppercase",
},
"& p": {
color: theme.palette[themeColor].contrastText,
},
};
};
const statusStyles = {
up: {
backgroundColor: theme.palette.success.lowContrast,
background: `linear-gradient(340deg, ${theme.palette.tertiary.main} -60%, ${theme.palette.success.lowContrast} 35%)`, // CAIO_REVIEW
borderColor: theme.palette.success.contrastText,
// "& h2": { color: theme.palette.success.contrastText }, // CAIO_REVIEW
},
down: {
backgroundColor: theme.palette.error.lowContrast,
background: `linear-gradient(340deg, ${theme.palette.tertiary.main} -60%, ${theme.palette.error.lowContrast} 35%)`, // CAIO_REVIEW
borderColor: theme.palette.error.contrastText,
"& h2": { color: theme.palette.error.contrastText }, // CAIO_REVIEW
"& .MuiTypography-root": { color: theme.palette.error.contrastText }, // CAIO_REVIEW
},
paused: {
backgroundColor: theme.palette.warning.lowContrast,
background: `linear-gradient(340deg, ${theme.palette.tertiary.main} -60%, ${theme.palette.warning.lowContrast} 35%)`, // CAIO_REVIEW
borderColor: theme.palette.warning.contrastText,
"& h2": { color: theme.palette.warning.contrastText }, // CAIO_REVIEW
"& .MuiTypography-root": { color: theme.palette.warning.contrastText }, // CAIO_REVIEW
},
pending: {
backgroundColor: theme.palette.warning.lowContrast,
background: `linear-gradient(340deg, ${theme.palette.tertiary.main} -60%, ${theme.palette.warning.lowContrast} 35%)`, // CAIO_REVIEW
borderColor: theme.palette.warning.contrastText,
"& h2": { color: theme.palette.warning.contrastText }, // CAIO_REVIEW
},
};
/* These are rediections. We should do something that maps up to success, down to error, and get the theme by that
*/
return {
determineState,
statusColor,
statusMsg,
pagespeedStatusMsg,
statusStyles,
statusToTheme,
getStatusStyles,
};
};
export default useUtils;

View File

@@ -28,9 +28,12 @@ import { useNavigate } from "react-router-dom";
import { useSelector, useDispatch } from "react-redux";
import { setRowsPerPage } from "../../../Features/UI/uiSlice";
import PropTypes from "prop-types";
import useFetchMonitorsWithSummary from "../../../Hooks/useFetchMonitorsWithSummary";
import useFetchMonitorsWithChecks from "../../../Hooks/useFetchMonitorsWithChecks";
import {
useFetchMonitorsWithSummary,
useFetchMonitorsWithChecks,
} from "../../../Hooks/monitorHooks";
import { useTranslation } from "react-i18next";
const TYPES = ["http", "ping", "docker", "port"];
const CreateMonitorButton = ({ shouldRender }) => {
// Utils
@@ -61,7 +64,6 @@ CreateMonitorButton.propTypes = {
const UptimeMonitors = () => {
// Redux state
const { user } = useSelector((state) => state.auth);
const rowsPerPage = useSelector((state) => state.ui.monitors.rowsPerPage);
// Local state
@@ -78,7 +80,6 @@ const UptimeMonitors = () => {
// Utils
const theme = useTheme();
const navigate = useNavigate();
const isAdmin = useIsAdmin();
const dispatch = useDispatch();
const { t } = useTranslation();
@@ -104,11 +105,8 @@ const UptimeMonitors = () => {
setMonitorUpdateTrigger((prev) => !prev);
}, []);
const teamId = user.teamId;
const [monitors, monitorsSummary, monitorsWithSummaryIsLoading, networkError] =
useFetchMonitorsWithSummary({
teamId,
types: TYPES,
monitorUpdateTrigger,
});
@@ -138,7 +136,6 @@ const UptimeMonitors = () => {
monitorsWithChecksIsLoading,
monitorsWithChecksNetworkError,
] = useFetchMonitorsWithChecks({
teamId,
types: effectiveTypes,
limit: 25,
page: page,

View File

@@ -6,8 +6,7 @@ class Logger {
constructor() {
let logLevel = LOG_LEVEL;
this.unsubscribe = store.subscribe(() => {
const state = store.getState();
logLevel = state.settings.logLevel || "debug";
logLevel = "debug";
this.updateLogLevel(logLevel);
});
}

View File

@@ -3,7 +3,6 @@ import i18next from "i18next";
const BASE_URL = import.meta.env.VITE_APP_API_BASE_URL;
const FALLBACK_BASE_URL = "http://localhost:5000/api/v1";
import { clearAuthState } from "../Features/Auth/authSlice";
import { clearUptimeMonitorState } from "../Features/UptimeMonitors/uptimeMonitorsSlice";
class NetworkService {
constructor(store, dispatch, navigate) {
this.store = store;
@@ -56,7 +55,6 @@ class NetworkService {
if (error.response && error.response.status === 401) {
dispatch(clearAuthState());
dispatch(clearUptimeMonitorState());
navigate("/login");
} else if (error.request && !error.response) {
return Promise.reject(error);
@@ -137,36 +135,6 @@ class NetworkService {
});
}
/**
*
* ************************************
* Gets monitors and summary of stats by TeamID
* ************************************
*
* @async
* @param {Object} config - The configuration object.
* @param {string} config.teamId - Team ID
* @param {Array<string>} config.types - Array of monitor types
* @returns {Promise<AxiosResponse>} The response from the axios POST request.
*/
async getMonitorsSummaryByTeamId(config) {
const params = new URLSearchParams();
if (config.types) {
config.types.forEach((type) => {
params.append("type", type);
});
}
return this.axiosInstance.get(
`/monitors/team/summary/${config.teamId}?${params.toString()}`,
{
headers: {
"Content-Type": "application/json",
},
}
);
}
/**
* ************************************
* Get all uptime monitors for a Team
@@ -186,7 +154,7 @@ class NetworkService {
*/
async getMonitorsByTeamId(config) {
const { teamId, limit, types, page, rowsPerPage, filter, field, order } = config;
const { limit, types, page, rowsPerPage, filter, field, order } = config;
const params = new URLSearchParams();
if (limit) params.append("limit", limit);
@@ -201,7 +169,7 @@ class NetworkService {
if (field) params.append("field", field);
if (order) params.append("order", order);
return this.axiosInstance.get(`/monitors/team/${teamId}?${params.toString()}`, {
return this.axiosInstance.get(`/monitors/team?${params.toString()}`, {
headers: {
"Content-Type": "application/json",
},
@@ -304,7 +272,7 @@ class NetworkService {
* @returns {Promise<AxiosResponse>} The response from the axios DELETE request.
*/
async deleteChecksByTeamId(config) {
return this.axiosInstance.delete(`/checks/team/${config.teamId}`, {
return this.axiosInstance.delete(`/checks/team`, {
headers: {
"Content-Type": "application/json",
},
@@ -587,7 +555,7 @@ class NetworkService {
*
*/
async getChecksByMonitor(config) {
getChecksByMonitor = async (config) => {
const params = new URLSearchParams();
if (config.type) params.append("type", config.type);
if (config.sortOrder) params.append("sortOrder", config.sortOrder);
@@ -599,7 +567,7 @@ class NetworkService {
if (config.status !== undefined) params.append("status", config.status);
return this.axiosInstance.get(`/checks/${config.monitorId}?${params.toString()}`);
}
};
/**
* ************************************
@@ -618,7 +586,7 @@ class NetworkService {
* @returns {Promise<AxiosResponse>} The response from the axios GET request.
*
*/
async getChecksByTeam(config) {
getChecksByTeam = async (config) => {
const params = new URLSearchParams();
if (config.sortOrder) params.append("sortOrder", config.sortOrder);
if (config.limit) params.append("limit", config.limit);
@@ -627,8 +595,8 @@ class NetworkService {
if (config.page) params.append("page", config.page);
if (config.rowsPerPage) params.append("rowsPerPage", config.rowsPerPage);
if (config.status !== undefined) params.append("status", config.status);
return this.axiosInstance.get(`/checks/team/${config.teamId}?${params.toString()}`);
}
return this.axiosInstance.get(`/checks/team?${params.toString()}`);
};
/**
* ************************************
@@ -870,8 +838,7 @@ class NetworkService {
}
async getStatusPagesByTeamId(config) {
const { teamId } = config;
return this.axiosInstance.get(`/status-page/team/${teamId}`, {
return this.axiosInstance.get(`/status-page/team`, {
headers: {
"Content-Type": "application/json",
},
@@ -879,11 +846,9 @@ class NetworkService {
}
async createStatusPage(config) {
const { user, form, isCreate } = config;
const { form, isCreate } = config;
const fd = new FormData();
fd.append("teamId", user.teamId);
fd.append("userId", user._id);
fd.append("type", form.type);
form.isPublished !== undefined && fd.append("isPublished", form.isPublished);
form.companyName && fd.append("companyName", form.companyName);
@@ -951,7 +916,7 @@ class NetworkService {
// Fetch monitors with summary by TeamID
// ************************************
async getMonitorsWithSummaryByTeamId(config) {
const { teamId, types } = config;
const { types } = config;
const params = new URLSearchParams();
if (types) {
@@ -960,21 +925,18 @@ class NetworkService {
});
}
return this.axiosInstance.get(
`/monitors/summary/team/${teamId}?${params.toString()}`,
{
headers: {
"Content-Type": "application/json",
},
}
);
return this.axiosInstance.get(`/monitors/summary/team?${params.toString()}`, {
headers: {
"Content-Type": "application/json",
},
});
}
// ************************************
// Fetch monitors with checks by TeamID
// ************************************
async getMonitorsWithChecksByTeamId(config) {
const { teamId, limit, types, page, rowsPerPage, filter, field, order } = config;
const { limit, types, page, rowsPerPage, filter, field, order } = config;
const params = new URLSearchParams();
if (limit) params.append("limit", limit);
@@ -989,14 +951,11 @@ class NetworkService {
if (field) params.append("field", field);
if (order) params.append("order", order);
return this.axiosInstance.get(
`/monitors/team/${teamId}/with-checks?${params.toString()}`,
{
headers: {
"Content-Type": "application/json",
},
}
);
return this.axiosInstance.get(`/monitors/team/with-checks?${params.toString()}`, {
headers: {
"Content-Type": "application/json",
},
});
}
// ************************************
@@ -1034,8 +993,7 @@ class NetworkService {
}
async getNotificationsByTeamId(config) {
const { teamId } = config;
return this.axiosInstance.get(`/notifications/team/${teamId}`);
return this.axiosInstance.get(`/notifications/team`);
}
async deleteNotificationById(config) {

View File

@@ -85,6 +85,7 @@ const semanticColors = {
const newColors = {
offWhite: "#FEFEFE",
offBlack: "#131315",
gray10: "#F4F4FF",
gray100: "#F3F3F3",
gray200: "#EFEFEF",
gray500: "#A2A3A3",
@@ -175,6 +176,12 @@ const newSemanticColors = {
dark: newColors.blueGray600,
},
},
primaryBackground: {
main: {
light: newColors.gray10,
dark: "#000000",
},
},
secondary: {
main: {
light: newColors.gray200,

View File

@@ -380,6 +380,33 @@ const baseTheme = (palette) => ({
},
},
},
MuiAutocomplete: {
styleOverrides: {
root: ({ theme }) => ({
"& .MuiOutlinedInput-root": {
paddingTop: 0,
paddingBottom: 0,
},
"& fieldset": {
borderColor: theme.palette.primary.lowContrast,
borderRadius: theme.shape.borderRadius,
},
"& .MuiOutlinedInput-root:hover:not(:has(input:focus)):not(:has(textarea:focus)) fieldset":
{
borderColor: theme.palette.primary.lowContrast,
},
"& .MuiAutocomplete-tag": {
color: theme.palette.primary.contrastText,
backgroundColor: theme.palette.primary.lowContrast,
},
"& .MuiChip-deleteIcon": {
color: theme.palette.primary.contrastText, // CAIO_REVIEW
},
}),
},
},
MuiTab: {
styleOverrides: {
root: ({ theme }) => ({

View File

@@ -0,0 +1,49 @@
<svg width="1440" height="1025" viewBox="0 0 1440 1025" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_f_563_20635)">
<ellipse cx="971.521" cy="1024.07" rx="278.644" ry="273.684" fill="#7054FF" fill-opacity="0.2" />
</g>
<g filter="url(#filter1_f_563_20635)">
<ellipse cx="44.3184" cy="1161.28" rx="513.227" ry="529.699" fill="#7282FF" fill-opacity="0.12" />
</g>
<g filter="url(#filter2_f_563_20635)">
<ellipse cx="1484.89" cy="1016.24" rx="376.575" ry="404.555" fill="#4C7FFF" fill-opacity="0.2" />
</g>
<g filter="url(#filter3_f_563_20635)">
<ellipse cx="505.276" cy="-87.3996" rx="339.168" ry="238.417" fill="#7054FF" fill-opacity="0.2" />
</g>
<g filter="url(#filter4_f_563_20635)">
<ellipse cx="915.714" cy="-111.32" rx="358.17" ry="325.578" fill="#4C7FFF" fill-opacity="0.16" />
</g>
<defs>
<filter id="filter0_f_563_20635" x="212.877" y="270.389" width="1517.29" height="1507.37"
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="240" result="effect1_foregroundBlur_563_20635" />
</filter>
<filter id="filter1_f_563_20635" x="-948.908" y="151.583" width="1986.45" height="2019.4"
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="240" result="effect1_foregroundBlur_563_20635" />
</filter>
<filter id="filter2_f_563_20635" x="628.316" y="131.683" width="1713.15" height="1769.11"
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="240" result="effect1_foregroundBlur_563_20635" />
</filter>
<filter id="filter3_f_563_20635" x="-313.893" y="-805.816" width="1638.34" height="1436.83"
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="240" result="effect1_foregroundBlur_563_20635" />
</filter>
<filter id="filter4_f_563_20635" x="77.5449" y="-916.898" width="1676.34" height="1611.16"
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="240" result="effect1_foregroundBlur_563_20635" />
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -525,6 +525,7 @@
"notifyEmails": "Also notify via email to multiple addresses (coming soon)",
"seperateEmails": "You can separate multiple emails with a comma",
"checkFrequency": "Check frequency",
"advancedMatching": "Advanced matching",
"matchMethod": "Match Method",
"expectedValue": "Expected value",
"deleteDialogTitle": "Do you really want to delete this monitor?",

View File

@@ -1,11 +1,7 @@
import { configureStore, combineReducers } from "@reduxjs/toolkit";
import uptimeMonitorsReducer from "./Features/UptimeMonitors/uptimeMonitorsSlice";
import infrastructureMonitorsReducer from "./Features/InfrastructureMonitors/infrastructureMonitorsSlice";
import pageSpeedMonitorReducer from "./Features/PageSpeedMonitor/pageSpeedMonitorSlice";
import authReducer from "./Features/Auth/authSlice";
import uiReducer from "./Features/UI/uiSlice";
import settingsReducer from "./Features/Settings/settingsSlice";
import storage from "redux-persist/lib/storage";
import { persistReducer, persistStore, createTransform } from "redux-persist";
@@ -23,17 +19,13 @@ const authTransform = createTransform(
const persistConfig = {
key: "root",
storage,
whitelist: ["auth", "monitors", "pageSpeed", "ui", "settings"],
whitelist: ["auth", "ui"],
transforms: [authTransform],
};
const rootReducer = combineReducers({
uptimeMonitors: uptimeMonitorsReducer,
infrastructureMonitors: infrastructureMonitorsReducer,
auth: authReducer,
pageSpeedMonitors: pageSpeedMonitorReducer,
ui: uiReducer,
settings: settingsReducer,
});
const persistedReducer = persistReducer(persistConfig, rootReducer);