diff --git a/client/src/Hooks/useBulkMonitors.js b/client/src/Hooks/useBulkMonitors.js index e5ee2eb25..3c4f3c00e 100644 --- a/client/src/Hooks/useBulkMonitors.js +++ b/client/src/Hooks/useBulkMonitors.js @@ -3,11 +3,9 @@ import { networkService } from "../main"; export const useBulkMonitors = () => { const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); const createBulkMonitors = async (file, user) => { setIsLoading(true); - setError(null); const formData = new FormData(); formData.append("csvFile", file); @@ -16,14 +14,14 @@ export const useBulkMonitors = () => { try { const response = await networkService.createBulkMonitors(formData); - return response.data; + return [true, response.data, null]; // [success, data, error] } catch (err) { - setError(err?.response?.data?.msg ?? err.message); - return null; + const errorMessage = err?.response?.data?.errors?.[0]?.message || err.message; + return [false, null, errorMessage]; } finally { setIsLoading(false); } }; - return [createBulkMonitors, isLoading, error]; + return [createBulkMonitors, isLoading]; }; diff --git a/client/src/Pages/Uptime/BulkImport/index.jsx b/client/src/Pages/Uptime/BulkImport/index.jsx index a1c38c0b6..4d2e716f3 100644 --- a/client/src/Pages/Uptime/BulkImport/index.jsx +++ b/client/src/Pages/Uptime/BulkImport/index.jsx @@ -26,20 +26,22 @@ const BulkImport = () => { { name: t("bulkImport.title"), path: `/uptime/bulk-import` }, ]; - const [createBulkMonitors, hookLoading, error] = useBulkMonitors(); + const [createBulkMonitors, hookLoading] = useBulkMonitors(); const handleSubmit = async () => { if (!selectedFile) { createToast({ body: t("bulkImport.noFileSelected") }); return; } - const success = await createBulkMonitors(selectedFile, user); + + const [success, data, error] = await createBulkMonitors(selectedFile, user); if (success) { + // You can use `data` here if needed createToast({ body: t("bulkImport.uploadSuccess") }); navigate("/uptime"); } else { - createToast({ body: error }); + createToast({ body: error || t("bulkImport.uploadFailed") }); } }; diff --git a/server/controllers/monitorController.js b/server/controllers/monitorController.js index 45ae8e05b..fc9482e59 100755 --- a/server/controllers/monitorController.js +++ b/server/controllers/monitorController.js @@ -256,37 +256,60 @@ class MonitorController { createBulkMonitors = async (req, res, next) => { try { const { parse } = pkg; - + if (!req.file) { - return res.status(400).json({ msg: "No file uploaded" }); + return res.status(400).json({ + errors: [ + { message: "No file uploaded" } + ] + }); } - + const { userId, teamId } = req.body; - + if (!userId || !teamId) { - return res.status(400).json({ msg: "Missing userId or teamId in form data" }); + return res.status(400).json({ + errors: [ + { message: "Missing userId or teamId in form data" } + ] + }); } - + // Get file buffer from memory and convert to string const fileData = req.file.buffer.toString("utf-8"); - + console.log("File data:", fileData); + + // Parse the CSV data parse(fileData, { header: true, skipEmptyLines: true, transform: (value, header) => { - if (value === "") return undefined; + if (value === "") return undefined; // Empty fields become undefined + + // Handle 'port' and 'interval' fields, check if they're valid numbers if (["port", "interval"].includes(header)) { const num = parseInt(value, 10); - return isNaN(num) ? undefined : num; + if (isNaN(num)) { + return res.status(400).json({ + errors: [ + { message: `${header} should be a valid number, got: ${value}` } + ] + }); // Return error immediately with this structure + } + return num; } - + return value; }, complete: async ({ data, errors }) => { if (errors.length > 0) { - return res.status(400).json({ msg: "Error parsing CSV", errors }); + return res.status(400).json({ + errors: [ + { message: "Error parsing CSV" } + ] + }); } - + const enrichedData = data.map((monitor) => ({ userId, teamId, @@ -295,20 +318,21 @@ class MonitorController { name: monitor.name || monitor.url, type: monitor.type || "http", })); - + try { + // Run Joi validation await createMonitorsBodyValidation.validateAsync(enrichedData); } catch (error) { return next(handleValidationError(error, SERVICE_NAME)); } - + try { const monitors = await this.db.createBulkMonitors(enrichedData); - + await Promise.all( monitors.map(async (monitor, index) => { const notifications = enrichedData[index].notifications; - + if (notifications?.length) { monitor.notifications = await Promise.all( notifications.map(async (notification) => { @@ -318,11 +342,11 @@ class MonitorController { ); await monitor.save(); } - + this.jobQueue.addJob(monitor._id, monitor); }) ); - + return res.success({ msg: this.stringService.bulkMonitorsCreate, data: monitors, @@ -335,7 +359,7 @@ class MonitorController { } catch (error) { return next(handleError(error, SERVICE_NAME, "createBulkMonitors")); } - }; + }; /** * Checks if the endpoint can be resolved * @async