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",