Feature release for uptime monitors.

This commit is contained in:
Owaise Imdad committed 2025-04-27 03:05:14 +05:30
1 parent 0001df9ad3
commit b2fd9b1e89
10 files changed
+260 -1

No files matched your search

+1
View File
@@ -38,6 +38,7 @@
"joi": "17.13.3",
"maplibre-gl": "5.3.1",
"mui-color-input": "^6.0.0",
"papaparse": "^5.5.2",
"react": "18.3.1",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
@@ -0,0 +1,6 @@
type,name,url,interval,port,expectedValue,jsonPath,matchMethod
http,HTTP Monitor,https://www.google.com,60000,,,,
http,API Monitor,http://reqres.in/api/users/2,120000,,Janet,data.first_name,equal
ping,Ping monitor,127.0.0.1,60000,,,,
docker,Docker monitor,bbdd20b4e3757d63e070bc16e04d3f770c4a8f6e62e813de380f23b141e2ca01,60000,,,,
port,Port monitor,127.0.0.1,60000,5000,,,
1 type name url interval port expectedValue jsonPath matchMethod
2 http HTTP Monitor https://www.google.com 60000
3 http API Monitor http://reqres.in/api/users/2 120000 Janet data.first_name equal
4 ping Ping monitor 127.0.0.1 60000
5 docker Docker monitor bbdd20b4e3757d63e070bc16e04d3f770c4a8f6e62e813de380f23b141e2ca01 60000
6 port Port monitor 127.0.0.1 60000 5000
@@ -2,21 +2,29 @@ import { Stack, Button } from "@mui/material";
import { useNavigate } from "react-router-dom";
import PropTypes from "prop-types";
import SkeletonLayout from "./skeleton";
import { useTranslation } from "react-i18next";
import { useTheme } from "@emotion/react";
const CreateMonitorHeader = ({
isAdmin,
label = "Create new",
shouldRender = true,
path,
bulkPath,
}) => {
const navigate = useNavigate();
const { t } = useTranslation();
const theme = useTheme();
if (!isAdmin) return null;
if (!shouldRender) return <SkeletonLayout />;
return (
<Stack
direction="row"
justifyContent="end"
alignItems="center"
gap={theme.spacing(6)}
>
<Button
variant="contained"
@@ -25,6 +33,17 @@ const CreateMonitorHeader = ({
>
{label}
</Button>
{bulkPath && (
<Button
variant="contained"
color="accent"
onClick={() => {
navigate(`${bulkPath}`);
}}
>
{t("bulkImport.title")}
</Button>
)}
</Stack>
);
};
@@ -36,4 +55,5 @@ CreateMonitorHeader.propTypes = {
shouldRender: PropTypes.bool,
path: PropTypes.string.isRequired,
label: PropTypes.string,
bulkPath: PropTypes.string,
};
@@ -0,0 +1,89 @@
import { useTheme } from "@emotion/react";
import { useState, useRef, useEffect } from "react";
import { Button, Typography } from "@mui/material";
import { parse } from "papaparse";
import { bulkMonitorsValidation } from "../../../Validation/validation";
import { useTranslation } from "react-i18next";
export function Upload({ onComplete }) {
const theme = useTheme();
const [file, setFile] = useState();
const [error, setError] = useState("");
const inputRef = useRef();
const { t } = useTranslation();
const handleSelectFile = () => {
inputRef.current.click();
};
const handleFileChange = (e) => {
setError("");
setFile(e.target.files[0]);
};
useEffect(() => {
if (!file) return;
parse(file, {
header: true,
skipEmptyLines: true,
transform: (value, header) => {
if (!value) {
return undefined;
}
if (header === "port" || header === "interval") {
return parseInt(value);
}
return value;
},
complete: ({ data, errors }) => {
if (errors.length > 0) {
setError(t("bulkImport.parsingFailed"));
return;
}
const { error } = bulkMonitorsValidation.validate(data);
if (error) {
setError(
error.details?.[0]?.message ||
error.message ||
t("bulkImport.validationFailed")
);
return;
}
onComplete(data);
},
});
}, [file]);
return (
<div>
<input
ref={inputRef}
type="file"
accept=".csv"
style={{ display: "none" }}
onChange={handleFileChange}
/>
<Typography
component="h2"
mb={theme.spacing(1.5)}
sx={{ wordBreak: "break-all" }}
>
{file?.name}
</Typography>
<Typography
component="div"
mb={theme.spacing(1.5)}
color={theme.palette.error.main}
>
{error}
</Typography>
<Button
variant="contained"
color="accent"
onClick={handleSelectFile}
>
{t("bulkImport.selectFile")}
</Button>
</div>
);
}
@@ -0,0 +1,116 @@
// React, Redux, Router
import { useTheme } from "@emotion/react";
import { useState } from "react";
// MUI
import { Box, Stack, Typography, Button, Link } from "@mui/material";
//Components
import { networkService } from "../../../main";
import { createToast } from "../../../Utils/toastUtils";
import Breadcrumbs from "../../../Components/Breadcrumbs";
import ConfigBox from "../../../Components/ConfigBox";
import { Upload } from "./Upload";
import { useSelector } from "react-redux";
import { useNavigate } from "react-router";
import { Trans, useTranslation } from "react-i18next";
const BulkImport = () => {
const theme = useTheme();
const [monitors, setMonitors] = useState([]);
const { user } = useSelector((state) => state.auth);
const navigate = useNavigate();
const { t } = useTranslation();
const crumbs = [
{ name: t("uptime"), path: "/uptime" },
{ name: t("bulkImport.title"), path: `/uptime/bulk-import` },
];
const [isLoading, setIsLoading] = useState(false);
const handleSubmit = async () => {
setIsLoading(true);
try {
const monitorsWithUser = monitors.map((monitor) => ({
...monitor,
description: monitor.name || monitor.url,
teamId: user.teamId,
userId: user._id,
}));
await networkService.createBulkMonitors({ monitors: monitorsWithUser });
createToast({ body: t("bulkImport.uploadSuccess") });
navigate("/uptime");
} catch (error) {
createToast({ body: error?.response?.data?.msg ?? error.message });
} finally {
setIsLoading(false);
}
};
return (
<Box className="bulk-import-monitor">
<Breadcrumbs list={crumbs} />
<Stack
component="form"
gap={theme.spacing(12)}
mt={theme.spacing(6)}
>
<Typography
component="h1"
variant="h1"
>
{t("bulkImport.title")}
</Typography>
<ConfigBox>
<Box>
<Typography component="h2">
{t("bulkImport.selectFileTips")}
</Typography>
<Typography component="p">
<Trans
i18nKey="bulkImport.selectFileDescription"
components={{
template: (
<Link
color="info"
download
href="bulk_import_monitors_template.csv"
/>
),
sample: (
<Link
color="info"
download
href="bulk_import_monitors_sample.csv"
/>
),
}}
/>
</Typography>
</Box>
<Stack gap={theme.spacing(12)}>
<Stack gap={theme.spacing(6)}>
<Upload onComplete={setMonitors} />
</Stack>
</Stack>
</ConfigBox>
<Stack
direction="row"
justifyContent="flex-end"
>
<Button
variant="contained"
color="accent"
onClick={handleSubmit}
disabled={!monitors?.length}
loading={isLoading}
>
{t("submit")}
</Button>
</Stack>
</Stack>
</Box>
);
};
export default BulkImport;
@@ -191,6 +191,7 @@ const UptimeMonitors = () => {
isAdmin={isAdmin}
shouldRender={!isLoading}
path="/uptime/create"
bulkPath="/uptime/bulk-import"
/>
<Greeting type="uptime" />
<StatusBoxes
+6
View File
@@ -56,6 +56,7 @@ import ProtectedRoute from "../Components/ProtectedRoute";
import ProtectedDistributedUptimeRoute from "../Components/ProtectedDistributedUptimeRoute";
import CreateNewMaintenanceWindow from "../Pages/Maintenance/CreateMaintenance";
import withAdminCheck from "../Components/HOC/withAdminCheck";
import BulkImport from "../Pages/Uptime/BulkImport";
const Routes = () => {
const AdminCheckedRegister = withAdminCheck(AuthRegister);
@@ -78,6 +79,11 @@ const Routes = () => {
element={<Uptime />}
/>
<Route
path="/uptime/bulk-import"
element={<BulkImport />}
/>
<Route
path="/uptime/create/:monitorId?"
element={<UptimeCreate />}
+8
View File
@@ -1086,6 +1086,14 @@ class NetworkService {
return this.axiosInstance.delete(`/status-page/${encodedUrl}`, {});
}
// ************************************
// Create bulk monitors
// ************************************
async createBulkMonitors({ monitors }) {
return this.axiosInstance.post(`/monitors/bulk`, monitors);
}
// ************************************
// Fetch monitors with summary by TeamID
// ************************************
+3
View File
@@ -173,6 +173,8 @@ const monitorValidation = joi.object({
matchMethod: joi.string(),
});
const bulkMonitorsValidation = joi.array().items(monitorValidation);
const imageValidation = joi.object({
type: joi.string().valid("image/jpeg", "image/png").messages({
"any.only": "Invalid file format.",
@@ -367,4 +369,5 @@ export {
infrastructureMonitorValidation,
statusPageValidation,
logoImageValidation,
bulkMonitorsValidation
};
+10 -1
View File
@@ -395,5 +395,14 @@
"DeleteAccount": "Delete account",
"DeleteDescriptionText": "Note that deleting your account will remove all data from the server. This is permanent and non-recoverable.",
"DeleteAccountWarning": "If you delete your account, you will no longer be able to sign in, and all of your data will be deleted. Deleting your account is permanent and non-recoverable action.",
"DeleteWarningTitle": "Really delete this account?"
"DeleteWarningTitle": "Really delete this account?",
"bulkImport": {
"title": "Bulk Import",
"selectFileTips": "Select CSV file to upload",
"selectFileDescription": "You can download our <template>template</template> or <sample>sample</sample>",
"selectFile": "Select File",
"parsingFailed": "Parsing failed",
"uploadSuccess": "Monitors created successfully!",
"validationFailed": "Validation failed"
}
}