Addressed all the comments in pr.

This commit is contained in:
Owaise Imdad
2025-06-18 01:07:22 +05:30
parent 11212c7371
commit e71e2ef42c
7 changed files with 147 additions and 34 deletions
@@ -0,0 +1,116 @@
import * as React from "react";
import Button from "@mui/material/Button";
import ButtonGroup from "@mui/material/ButtonGroup";
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
import ClickAwayListener from "@mui/material/ClickAwayListener";
import Grow from "@mui/material/Grow";
import Paper from "@mui/material/Paper";
import Popper from "@mui/material/Popper";
import MenuItem from "@mui/material/MenuItem";
import MenuList from "@mui/material/MenuList";
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { createToast } from "../../Utils/toastUtils";
import { useExportMonitors } from "../../Hooks/monitorHooks";
const options = ["Import Monitors", "Export Monitors"];
const MonitorActions = ({ isLoading }) => {
const [open, setOpen] = React.useState(false);
const anchorRef = React.useRef(null);
const [selectedIndex, setSelectedIndex] = React.useState(0);
const navigate = useNavigate();
const { t } = useTranslation();
const [exportMonitors, isExporting] = useExportMonitors();
const handleClick = async () => {
if (selectedIndex === 0) {
// Import
navigate("/uptime/bulk-import");
} else {
// Export
const [success, error] = await exportMonitors();
if (!success) {
createToast({ body: error || t("export.failed") });
}
}
};
const handleMenuItemClick = (event, index) => {
setSelectedIndex(index);
setOpen(false);
};
const handleToggle = () => {
setOpen((prevOpen) => !prevOpen);
};
const handleClose = (event) => {
if (anchorRef.current && anchorRef.current.contains(event.target)) {
return;
}
setOpen(false);
};
return (
<React.Fragment>
<ButtonGroup
variant="contained"
color="accent"
ref={anchorRef}
aria-label="Monitor actions"
disabled={isLoading || isExporting}
>
<Button onClick={handleClick}>{options[selectedIndex]}</Button>
<Button
size="small"
aria-controls={open ? "split-button-menu" : undefined}
aria-expanded={open ? "true" : undefined}
aria-label="select monitor action"
aria-haspopup="menu"
onClick={handleToggle}
>
<ArrowDropDownIcon />
</Button>
</ButtonGroup>
<Popper
sx={{ zIndex: 1 }}
open={open}
anchorEl={anchorRef.current}
role={undefined}
transition
disablePortal
>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{
transformOrigin: placement === "bottom" ? "center top" : "center bottom",
}}
>
<Paper>
<ClickAwayListener onClickAway={handleClose}>
<MenuList
id="split-button-menu"
autoFocusItem
>
{options.map((option, index) => (
<MenuItem
key={option}
selected={index === selectedIndex}
onClick={(event) => handleMenuItemClick(event, index)}
>
{option}
</MenuItem>
))}
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</React.Fragment>
);
};
export default MonitorActions;
@@ -3,13 +3,12 @@ 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";
import MonitorActions from "../MonitorActions";
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
@@ -30,29 +29,7 @@ const CreateMonitorHeader = ({ isAdmin, label, isLoading = true, path, bulkPath
>
{label || t("createNew")}
</Button>
{bulkPath && (
<>
<Button
loading={isLoading}
variant="contained"
color="accent"
onClick={() => {
navigate(`${bulkPath}`);
}}
>
{t("bulkImport.title")}
</Button>
<Button
loading={isExporting}
variant="contained"
color="accent"
onClick={exportMonitors}
>
{t("export.title")}
</Button>
</>
)}
{bulkPath && <MonitorActions isLoading={isLoading} />}
</Stack>
);
};
+2 -2
View File
@@ -460,10 +460,10 @@ const useExportMonitors = () => {
const exportMonitors = async () => {
setIsLoading(true);
try {
const blob = await networkService.exportMonitors();
const response = await networkService.exportMonitors();
// Create a download link
const url = window.URL.createObjectURL(blob);
const url = window.URL.createObjectURL(response);
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", "monitors.csv");
+1 -1
View File
@@ -1027,7 +1027,7 @@ class NetworkService {
const response = await this.axiosInstance.get("/monitors/export", {
responseType: "blob",
});
return response.data;
return response;
}
}
+5
View File
@@ -744,5 +744,10 @@
"title": "Export Monitors",
"success": "Monitors exported successfully!",
"failed": "Failed to export monitors"
},
"monitorActions": {
"title": "Export/Import",
"import": "Import Monitors",
"export": "Export Monitors"
}
}
+15 -5
View File
@@ -685,7 +685,7 @@ class MonitorController {
explain,
});
return res.success({
msg: "OK", // TODO
msg: "OK",
data: result,
});
} catch (error) {
@@ -708,6 +708,12 @@ class MonitorController {
const { teamId } = req.user;
const monitors = await this.db.getMonitorsByTeamId({ teamId });
if (!monitors || monitors.length === 0) {
return res.success({
msg: this.stringService.noMonitorsFound,
data: null,
});
}
const csvData = monitors?.filteredMonitors?.map((monitor) => ({
name: monitor.name,
description: monitor.description,
@@ -721,10 +727,14 @@ class MonitorController {
const csv = pkg.unparse(csvData);
res.setHeader("Content-Type", "text/csv");
res.setHeader("Content-Disposition", "attachment; filename=monitors.csv");
res.send(csv);
return res.success({
msg: this.stringService.monitorsExported,
data: csv,
headers: {
"Content-Type": "text/csv",
"Content-Disposition": "attachment; filename=monitors.csv",
},
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "exportMonitorsToCSV"));
}
+6 -1
View File
@@ -16,7 +16,12 @@ const responseHandler = (req, res, next) => {
* @param {*} [options.data=null] - Response data payload
* @returns {Object} Express response object
*/
res.success = ({ status = 200, msg = "OK", data = null }) => {
res.success = ({ status = 200, msg = "OK", data = null, headers = {} }) => {
// Set custom headers if provided
Object.entries(headers).forEach(([key, value]) => {
res.set(key, value);
});
return res.status(status).json({
success: true,
msg: msg,