Check the monitor URL resolution before adding it

This commit is contained in:
om-3004 committed 2024-10-15 17:20:54 +05:30
1 parent 4a1b290416
commit ebf6a0ceb7
5 files changed
+161 -50

No files matched your search

@@ -31,6 +31,30 @@ export const createUptimeMonitor = createAsyncThunk(
}
);
export const checkEndpointResolution = createAsyncThunk(
"monitors/checkEnpoint",
async(data, thunkApi) => {
try{
const { authToken, monitorURL } = data;
const res = await networkService.checkEndpointResolution({
authToken: authToken,
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) => {
@@ -271,6 +295,24 @@ const uptimeMonitorsSlice = createSlice({
: "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) => {
@@ -3,6 +3,7 @@ import { Box, Button, ButtonGroup, Stack, Typography } from "@mui/material";
import { useSelector, useDispatch } from "react-redux";
import { monitorValidation } from "../../../Validation/validation";
import { createUptimeMonitor } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice";
import { checkEndpointResolution } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice"
import { useNavigate, useParams } from "react-router-dom";
import { useTheme } from "@emotion/react";
import { createToast } from "../../../Utils/toastUtils";
@@ -124,58 +125,55 @@ const CreateMonitor = () => {
const handleCreateMonitor = async (event) => {
event.preventDefault();
//obj to submit
let form = {
url:
//preprending protocol for url
monitor.type === "http"
? `http${https ? "s" : ""}://` + monitor.url
: monitor.url,
name: monitor.name === "" ? monitor.url : monitor.name,
type: monitor.type,
interval: monitor.interval * MS_PER_MINUTE,
};
const formattedUrl = monitor.type === "http"
? `http${https ? "s" : ""}://` + monitor.url
: monitor.url;
const { error } = monitorValidation.validate(form, {
abortEarly: false,
});
try {
// Check if the URL resolves by sending a request
const response = await fetch(formattedUrl, { method: 'GET', mode: 'no-cors' });
if (!response) {
throw new Error();
}
//obj to submit
let form = {
url:formattedUrl,
name: monitor.name === "" ? monitor.url : monitor.name,
type: monitor.type,
interval: monitor.interval * MS_PER_MINUTE,
};
const { error } = monitorValidation.validate(form, {
abortEarly: false,
if (error) {
const newErrors = {};
error.details.forEach((err) => {
newErrors[err.path[0]] = err.message;
});
if (error) {
const newErrors = {};
error.details.forEach((err) => {
newErrors[err.path[0]] = err.message;
});
setErrors(newErrors);
createToast({ body: "Error validation data." });
} else {
form = {
...form,
description: form.name,
teamId: user.teamId,
userId: user._id,
notifications: monitor.notifications,
};
const action = await dispatch(
createUptimeMonitor({ authToken, monitor: form })
);
if (action.meta.requestStatus === "fulfilled") {
createToast({ body: "Monitor created successfully!" });
navigate("/monitors");
} else {
createToast({ body: "Failed to create monitor." });
}
setErrors(newErrors);
createToast({ body: "Error validation data." });
} else {
const checkEndpointAction = await dispatch(
checkEndpointResolution({ authToken, 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(
createUptimeMonitor({ authToken, monitor: form })
);
if (action.meta.requestStatus === "fulfilled") {
createToast({ body: "Monitor created successfully!" });
navigate("/monitors");
} else {
createToast({ body: "Failed to create monitor." });
}
} catch (error) {
createToast({ body: "The endpoint you entered doesn't resolve. Check the URL again." });
setErrors({ url: "The entered URL is not reachable." });
}
};
@@ -387,7 +385,7 @@ const CreateMonitor = () => {
onClick={handleCreateMonitor}
disabled={Object.keys(errors).length !== 0 && true}
>
{monitor.type === "http" ? "Create Monitor" : "Check Endpoint"}
Create Monitor
</Button>
</Stack>
</Stack>
@@ -395,4 +393,4 @@ const CreateMonitor = () => {
);
};
export default CreateMonitor;
export default CreateMonitor;
+26
View File
@@ -89,6 +89,32 @@ class NetworkService {
});
}
/**
*
* ************************************
* Check the endpoint resolution
* ************************************
*
* @async
* @param {Object} config - The configuration object.
* @param {string} config.authToken - The authorization token to be used in the request header.
* @param {Object} config.monitorURL - The monitor url to be sent in the request body.
* @returns {Promise<AxiosResponse>} The response from the axios POST request.
*/
async checkEndpointResolution(config) {
const { authToken, monitorURL } = config;
const params = new URLSearchParams();
if (monitorURL) params.append("monitorURL", monitorURL);
return this.axiosInstance.get(`/monitors/check-endpoint/url?${params.toString()}`, {
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
}
})
}
/**
*
* ************************************
+39
View File
@@ -20,6 +20,7 @@ const jwt = require("jsonwebtoken");
const { getTokenFromHeaders } = require("../utils/utils");
const logger = require("../utils/logger");
const { handleError, handleValidationError } = require("./controllerUtils");
const dns = require('dns');
/**
* Returns all monitors
@@ -262,6 +263,43 @@ const createMonitor = async (req, res, next) => {
}
};
/**
* Checks if the endpoint can be resolved and adds it to the job queue.
* @async
* @param {Object} req - The Express request object.
* @property {Object} req.query - The query parameters of the request.
* @param {Object} res - The Express response object.
* @param {function} next - The next middleware function.
* @returns {Object} The response object with a success status, a message, and the resolution result.
* @throws {Error} If there is an error during the process, especially if there is a validation error (422).
*/
const checkEndpointResolution = async (req, res, next) => {
try {
let { monitorURL } = req.query;
// Remove the protocol (http:// or https://) if present
monitorURL = monitorURL.replace(/^https?:\/\//, '');
// Remove the trailing slash if it exists
monitorURL = monitorURL.endsWith('/') ? monitorURL.slice(0, -1) : monitorURL;
dns.setServers(['8.8.8.8', '1.1.1.1']);// Google, Cloudfare
dns.resolve(monitorURL, (error) => {
console.log("Inside .resolve");
if (error) {
next(handleError(error, SERVICE_NAME, "checkEndpointResolution"));
return ;
}
return res.status(200).json({
success: true,
msg: `URL resolved successfully`,
});
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "checkEndpointResolution"));
}
}
/**
* Deletes a monitor by its ID and also deletes associated checks, alerts, and notifications.
* @async
@@ -475,6 +513,7 @@ module.exports = {
getMonitorsAndSummaryByTeamId,
getMonitorsByTeamId,
createMonitor,
checkEndpointResolution,
deleteMonitor,
deleteAllMonitors,
editMonitor,
+6
View File
@@ -18,6 +18,12 @@ router.post(
monitorController.createMonitor
);
router.get(
"/check-endpoint/url",
isAllowed(["admin", "superadmin"]),
monitorController.checkEndpointResolution
)
router.delete(
"/:monitorId",
isAllowed(["admin", "superadmin"]),