feat: added bulk edit notification UI to uptime monitors
This commit is contained in:
1 parent
eb29a725c2
commit
ff0ef928a6
5 files changed
+185
-1
No files matched your search
@@ -0,0 +1,145 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { Dialog, Select, RadioWithDescription } from "@/Components/inputs";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import RadioGroup from "@mui/material/RadioGroup";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { useGet, usePatch } from "@/Hooks/UseApi";
|
||||
import type { Notification } from "@/Types/Notification";
|
||||
|
||||
interface BulkEditNotificationsModalProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
selectedMonitors: string[];
|
||||
onSuccess: () => void;
|
||||
}
|
||||
|
||||
export const BulkEditNotificationsModal: React.FC<BulkEditNotificationsModalProps> = ({
|
||||
open,
|
||||
onClose,
|
||||
selectedMonitors,
|
||||
onSuccess,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
|
||||
const [action, setAction] = useState<"add" | "remove" | "set">("add");
|
||||
const [notificationIds, setNotificationIds] = useState<string[]>([]);
|
||||
|
||||
// Fetch available notifications
|
||||
const { data: notifications } = useGet<Notification[]>(
|
||||
"/notifications/team",
|
||||
undefined,
|
||||
{
|
||||
keepPreviousData: true,
|
||||
}
|
||||
);
|
||||
|
||||
const { patch, loading: isPatching } = usePatch();
|
||||
|
||||
// Reset state when modal opens
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setAction("add");
|
||||
setNotificationIds([]);
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
const handleConfirm = async () => {
|
||||
const res = await patch("/monitors/notifications", {
|
||||
monitorIds: selectedMonitors,
|
||||
notificationIds,
|
||||
action,
|
||||
});
|
||||
|
||||
if (res) {
|
||||
onSuccess();
|
||||
}
|
||||
};
|
||||
|
||||
// Prevent submitting empty arrays unless the action is 'set' (as per PR feedback!)
|
||||
const isMissingSelection = action !== "set" && notificationIds.length === 0;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
title={t("pages.common.monitors.bulkEdit.title", {
|
||||
defaultValue: "Edit Notifications",
|
||||
})}
|
||||
onCancel={onClose}
|
||||
onConfirm={isMissingSelection ? undefined : handleConfirm}
|
||||
loading={isPatching}
|
||||
maxWidth="sm"
|
||||
fullWidth
|
||||
>
|
||||
<Stack
|
||||
spacing={theme.spacing(4)}
|
||||
mt={theme.spacing(2)}
|
||||
>
|
||||
<Typography variant="body1">
|
||||
{t("pages.common.monitors.bulkEdit.selectedText", {
|
||||
count: selectedMonitors.length,
|
||||
defaultValue: `You are applying changes to {{count}} monitor(s).`,
|
||||
})}
|
||||
</Typography>
|
||||
|
||||
<RadioGroup
|
||||
value={action}
|
||||
onChange={(e) => setAction(e.target.value as "add" | "remove" | "set")}
|
||||
>
|
||||
<RadioWithDescription
|
||||
value="add"
|
||||
label={t("pages.common.monitors.bulkEdit.actionAdd", { defaultValue: "Add" })}
|
||||
description={t("pages.common.monitors.bulkEdit.actionAddDesc", {
|
||||
defaultValue:
|
||||
"Adds the selected notifications to these monitors without removing existing ones.",
|
||||
})}
|
||||
/>
|
||||
<RadioWithDescription
|
||||
value="remove"
|
||||
label={t("pages.common.monitors.bulkEdit.actionRemove", {
|
||||
defaultValue: "Remove",
|
||||
})}
|
||||
description={t("pages.common.monitors.bulkEdit.actionRemoveDesc", {
|
||||
defaultValue:
|
||||
"Removes exclusively the selected notifications from these monitors.",
|
||||
})}
|
||||
/>
|
||||
<RadioWithDescription
|
||||
value="set"
|
||||
label={t("pages.common.monitors.bulkEdit.actionSet", {
|
||||
defaultValue: "Replace all",
|
||||
})}
|
||||
description={t("pages.common.monitors.bulkEdit.actionSetDesc", {
|
||||
defaultValue:
|
||||
"Replaces all existing notifications. Leave empty to clear all notifications.",
|
||||
})}
|
||||
/>
|
||||
</RadioGroup>
|
||||
|
||||
<Select
|
||||
multiple
|
||||
value={notificationIds}
|
||||
onChange={(e) => setNotificationIds(e.target.value as string[])}
|
||||
fieldLabel={t("pages.common.monitors.bulkEdit.selectLabel", {
|
||||
defaultValue: "Notification Channels",
|
||||
})}
|
||||
placeholder={t("pages.common.monitors.bulkEdit.selectPlaceholder", {
|
||||
defaultValue: "Select channels...",
|
||||
})}
|
||||
>
|
||||
{notifications?.map((n) => (
|
||||
<MenuItem
|
||||
key={n.id}
|
||||
value={n.id}
|
||||
>
|
||||
{n.name} ({n.type.toUpperCase()})
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</Stack>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -15,3 +15,4 @@ export * from "./charts/HistogramPageSpeedDetailsTooltip";
|
||||
export * from "./charts/HistogramInfrastructure";
|
||||
export * from "./HeaderMonitorsSummary";
|
||||
export * from "./BulkActionsBar";
|
||||
export * from "./BulkEditNotificationsModal";
|
||||
@@ -44,6 +44,9 @@ export const MonitorTable = ({
|
||||
monitors,
|
||||
refetch,
|
||||
setSelectedMonitor,
|
||||
selectedMonitors,
|
||||
onSelectMonitor,
|
||||
onSelectAll,
|
||||
sortField,
|
||||
setSortField,
|
||||
sortOrder,
|
||||
|
||||
@@ -9,6 +9,7 @@ import Stack from "@mui/material/Stack";
|
||||
import { MonitorTable } from "@/Pages/Uptime/Monitors/Components/UptimeMonitorsTable";
|
||||
import { HeaderCreate } from "@/Components/common";
|
||||
import { Play, Pause } from "lucide-react";
|
||||
import { BulkEditNotificationsModal } from "@/Components/monitors";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import useMediaQuery from "@mui/material/useMediaQuery";
|
||||
@@ -43,7 +44,7 @@ const UptimeMonitorsPage = () => {
|
||||
const [sortField, setSortField] = useState<string>("");
|
||||
const [sortOrder, setSortOrder] = useState<"asc" | "desc">("asc");
|
||||
const [selectedMonitor, setSelectedMonitor] = useState<Monitor | null>(null);
|
||||
|
||||
const [isBulkEditModalOpen, setIsBulkEditModalOpen] = useState(false);
|
||||
const isDialogOpen = Boolean(selectedMonitor);
|
||||
|
||||
const debouncedSearch = useDebounce<string>(search, 300);
|
||||
@@ -136,6 +137,12 @@ const UptimeMonitorsPage = () => {
|
||||
refetch();
|
||||
};
|
||||
|
||||
const handleBulkEditSuccess = () => {
|
||||
setIsBulkEditModalOpen(false);
|
||||
setSelectedRows([]);
|
||||
refetch();
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setSelectedMonitor(null);
|
||||
};
|
||||
@@ -213,6 +220,14 @@ const UptimeMonitorsPage = () => {
|
||||
>
|
||||
{t("common.buttons.pause")}
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => setIsBulkEditModalOpen(true)}
|
||||
>
|
||||
{t("pages.common.monitors.bulkEdit.editButton", {
|
||||
defaultValue: "Edit Notifications",
|
||||
})}
|
||||
</Button>
|
||||
</BulkActionsBar>
|
||||
)}
|
||||
|
||||
@@ -248,6 +263,12 @@ const UptimeMonitorsPage = () => {
|
||||
onCancel={handleCancel}
|
||||
loading={isDeleting}
|
||||
/>
|
||||
<BulkEditNotificationsModal
|
||||
open={isBulkEditModalOpen}
|
||||
onClose={() => setIsBulkEditModalOpen(false)}
|
||||
selectedMonitors={selectedRows}
|
||||
onSuccess={handleBulkEditSuccess}
|
||||
/>
|
||||
</MonitorBasePageWithStates>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"common": {
|
||||
"selected": "selected",
|
||||
"auth": {
|
||||
"roles": {
|
||||
"admin": "Admin",
|
||||
@@ -425,6 +426,19 @@
|
||||
},
|
||||
"common": {
|
||||
"monitors": {
|
||||
"bulkEdit": {
|
||||
"editButton": "Edit Notifications",
|
||||
"title": "Edit Notifications",
|
||||
"selectedText": "You are applying changes to {{count}} monitor(s).",
|
||||
"actionAdd": "Add",
|
||||
"actionAddDesc": "Adds the selected notifications to these monitors without removing existing ones.",
|
||||
"actionRemove": "Remove",
|
||||
"actionRemoveDesc": "Removes exclusively the selected notifications from these monitors.",
|
||||
"actionSet": "Replace all",
|
||||
"actionSetDesc": "Replaces all existing notifications. Leave empty to clear all notifications.",
|
||||
"selectLabel": "Notification Channels",
|
||||
"selectPlaceholder": "Select channels..."
|
||||
},
|
||||
"actions": {
|
||||
"configure": "Configure",
|
||||
"delete": "Delete",
|
||||
|
||||
Reference in new issue
Block a user