From e9e509ab0458d01637c91b423c3345a296ba21cb Mon Sep 17 00:00:00 2001 From: Owaise Imdad Date: Tue, 17 Jun 2025 00:11:33 +0530 Subject: [PATCH] Addition of export. --- .../Components/MonitorCreateHeader/index.jsx | 33 ++++++++++++------ client/src/Hooks/monitorHooks.js | 34 +++++++++++++++++++ client/src/Utils/NetworkService.js | 7 ++++ client/src/locales/en.json | 7 +++- server/controllers/monitorController.js | 27 +++++++++++++++ server/routes/monitorRoute.js | 5 +++ 6 files changed, 102 insertions(+), 11 deletions(-) diff --git a/client/src/Components/MonitorCreateHeader/index.jsx b/client/src/Components/MonitorCreateHeader/index.jsx index b630cb259..7a4d75226 100644 --- a/client/src/Components/MonitorCreateHeader/index.jsx +++ b/client/src/Components/MonitorCreateHeader/index.jsx @@ -3,11 +3,13 @@ import { useNavigate } from "react-router-dom"; import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; import { useTheme } from "@emotion/react"; +import { useExportMonitors } from "../../Hooks/monitorHooks"; const CreateMonitorHeader = ({ isAdmin, label, isLoading = true, path, bulkPath }) => { const navigate = useNavigate(); const { t } = useTranslation(); const theme = useTheme(); + const [exportMonitors, isExporting] = useExportMonitors() // Use the provided label or fall back to the translated default @@ -29,16 +31,27 @@ const CreateMonitorHeader = ({ isAdmin, label, isLoading = true, path, bulkPath {label || t("createNew")} {bulkPath && ( - + <> + + + + )} ); diff --git a/client/src/Hooks/monitorHooks.js b/client/src/Hooks/monitorHooks.js index 7e46438e0..b3e4a9478 100644 --- a/client/src/Hooks/monitorHooks.js +++ b/client/src/Hooks/monitorHooks.js @@ -453,6 +453,39 @@ const useCreateBulkMonitors = () => { return [createBulkMonitors, isLoading]; }; +const useExportMonitors = () => { + const [isLoading, setIsLoading] = useState(false); + const { t } = useTranslation(); + + const exportMonitors = async () => { + setIsLoading(true); + try { + const blob = await networkService.exportMonitors(); + + // Create a download link + const url = window.URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.setAttribute('download', 'monitors.csv'); + document.body.appendChild(link); + link.click(); + link.remove(); + window.URL.revokeObjectURL(url); + + createToast({ body: t('export.success') }); + return [true, null]; + } catch (err) { + const errorMessage = err?.response?.data?.msg || err.message; + createToast({ body: errorMessage || t('export.failed') }); + return [false, errorMessage]; + } finally { + setIsLoading(false); + } + }; + + return [exportMonitors, isLoading]; +}; + export { useFetchMonitorsWithSummary, useFetchMonitorsWithChecks, @@ -469,4 +502,5 @@ export { useDeleteAllMonitors, useDeleteMonitorStats, useCreateBulkMonitors, + useExportMonitors, }; diff --git a/client/src/Utils/NetworkService.js b/client/src/Utils/NetworkService.js index eee795268..6451f93e8 100644 --- a/client/src/Utils/NetworkService.js +++ b/client/src/Utils/NetworkService.js @@ -1022,6 +1022,13 @@ class NetworkService { const { id, notification } = config; return this.axiosInstance.put(`/notifications/${id}`, notification); } + + async exportMonitors() { + const response = await this.axiosInstance.get("/monitors/export", { + responseType: "blob", + }); + return response.data; + } } export default NetworkService; diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 783cdb179..d71cf3212 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -739,5 +739,10 @@ "ping": "Enter the IP address or hostname to ping (e.g., 192.168.1.100 or example.com) and add a clear display name that appears on the dashboard.", "docker": "Enter the Docker ID of your container. Docker IDs must be the full 64 char Docker ID. You can run docker inspect to get the full container ID.", "port": "Enter the URL or IP of the server, the port number and a clear display name that appears on the dashboard." - } + }, + "export": { + "title": "Export Monitors", + "success": "Monitors exported successfully!", + "failed": "Failed to export monitors" + } } diff --git a/server/controllers/monitorController.js b/server/controllers/monitorController.js index 6c6136fb5..5d71f9cb6 100755 --- a/server/controllers/monitorController.js +++ b/server/controllers/monitorController.js @@ -702,6 +702,33 @@ class MonitorController { next(handleError(error, SERVICE_NAME, "seedDb")); } }; + + exportMonitorsToCSV = async (req, res, next) => { + try { + const { teamId } = req.user; + + const monitors = await this.db.getMonitorsByTeamId({ teamId }); + const csvData = monitors?.filteredMonitors?.map((monitor) => ({ + name: monitor.name, + description: monitor.description, + type: monitor.type, + url: monitor.url, + interval: monitor.interval, + port: monitor.port, + ignoreTlsErrors: monitor.ignoreTlsErrors, + isActive: monitor.isActive, + })); + + const csv = pkg.unparse(csvData); + + res.setHeader("Content-Type", "text/csv"); + res.setHeader("Content-Disposition", "attachment; filename=monitors.csv"); + + res.send(csv); + } catch (error) { + next(handleError(error, SERVICE_NAME, "exportMonitorsToCSV")); + } + }; } export default MonitorController; diff --git a/server/routes/monitorRoute.js b/server/routes/monitorRoute.js index 0dabab407..2c6ae870e 100755 --- a/server/routes/monitorRoute.js +++ b/server/routes/monitorRoute.js @@ -17,6 +17,11 @@ class MonitorRoutes { initRoutes() { this.router.get("/", this.monitorController.getAllMonitors); this.router.get("/uptime", this.monitorController.getAllMonitorsWithUptimeStats); + this.router.get( + "/export", + isAllowed(["admin", "superadmin"]), + this.monitorController.exportMonitorsToCSV + ); this.router.get("/stats/:monitorId", this.monitorController.getMonitorStatsById); this.router.get( "/hardware/details/:monitorId",