Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions client/src/Components/inputs/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const DialogInput = ({
loading = false,
cancelText,
confirmText,
confirmDisabled = false,
children,
maxWidth,
fullWidth = false,
Expand All @@ -35,6 +36,7 @@ export const DialogInput = ({
loading?: boolean;
cancelText?: string;
confirmText?: string;
confirmDisabled?: boolean;
children?: ReactNode;
maxWidth?: DialogProps["maxWidth"];
fullWidth?: boolean;
Expand Down Expand Up @@ -104,6 +106,7 @@ export const DialogInput = ({
variant="contained"
color={confirmColor}
onClick={onConfirm}
disabled={confirmDisabled}
>
{confirmText ?? t("common.buttons.confirm")}
</Button>
Expand Down
41 changes: 27 additions & 14 deletions client/src/Components/monitors/BulkActionsBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,32 +17,39 @@ interface BulkActionsBarProps {
selectedCount: number;
onCancel: () => void;
children?: React.ReactNode;
hidden?: boolean;
}

export const BulkActionsBar = ({
selectedCount,
onCancel,
children,
hidden = false,
}: BulkActionsBarProps) => {
const { t } = useTranslation();
const theme = useTheme();
const isOpen = selectedCount > 0;
const isOpen = selectedCount > 0 && !hidden;

const isSmall = useMediaQuery(theme.breakpoints.down("md"));
const { width, collapsedWidth, transition } = useSidebar();

const sidebarOffset = isSmall ? collapsedWidth : width;

return (
<Box
display="flex"
sx={{
position: "fixed",
bottom: theme.spacing(LAYOUT.XS),
left: isSmall ? collapsedWidth : width,
right: 0,
display: "flex",
left: sidebarOffset,
right: sidebarOffset,
justifyContent: "center",
pointerEvents: "none",
zIndex: theme.zIndex.snackbar,
transition: transition.replace("width", "left"),
transition:
transition.replace("width", "left") +
", " +
transition.replace("width", "right"),
}}
>
<Slide
Expand All @@ -51,18 +58,22 @@ export const BulkActionsBar = ({
mountOnEnter
unmountOnExit
>
<Paper
<Box
component={Paper as any}
elevation={6}
px={LAYOUT.MD}
py={LAYOUT.XS}
borderRadius={theme.shape.borderRadius}
bgcolor={theme.palette.background.paper}
border={`1px solid ${theme.palette.divider}`}
display="flex"
gap={LAYOUT.XS}
maxWidth={isSmall ? `calc(100vw - ${theme.spacing(LAYOUT.XS)})` : "none"}
sx={{
pointerEvents: "auto",
px: LAYOUT.MD,
py: LAYOUT.XS,
borderRadius: theme.shape.borderRadius,
backgroundColor: theme.palette.background.paper,
border: `1px solid ${theme.palette.divider}`,
display: "flex",
flexWrap: "wrap",
justifyContent: "center",
alignItems: "center",
gap: LAYOUT.XS,
boxShadow: theme.shadows[8],
}}
>
Expand Down Expand Up @@ -91,11 +102,13 @@ export const BulkActionsBar = ({
<Stack
direction="row"
alignItems="center"
justifyContent="center"
gap={LAYOUT.XXS}
flexWrap="wrap"
>
{children}
</Stack>
</Paper>
</Box>
</Slide>
</Box>
);
Expand Down
153 changes: 153 additions & 0 deletions client/src/Components/monitors/BulkEditNotificationsModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import React, { useState, useEffect, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { useTheme } from "@mui/material/styles";
import {
Dialog,
Autocomplete,
TextField,
RadioWithDescription,
} from "@/Components/inputs";
import { SPACING, LAYOUT } from "@/Utils/Theme/constants";
import Stack from "@mui/material/Stack";
import RadioGroup from "@mui/material/RadioGroup";
import Typography from "@mui/material/Typography";
import Alert from "@mui/material/Alert";
import { useGet, usePatch } from "@/Hooks/UseApi";
import type { Notification } from "@/Types/Notification";

interface BulkEditNotificationsModalProps {
open: boolean;
onClose: () => void;
selectedMonitors: string[];
onComplete: (success: boolean) => void;
}

export const BulkEditNotificationsModal: React.FC<BulkEditNotificationsModalProps> = ({
open,
onClose,
selectedMonitors,
onComplete,
}) => {
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, error } = 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,
});

onComplete(!!res);
};

// Prevent submitting empty arrays unless the action is 'set'
const isMissingSelection = action !== "set" && notificationIds.length === 0;

// Map notifications to the { id, name } structure expected by the Autocomplete component
const options = useMemo(() => {
return (notifications ?? []).map((n) => ({
id: n.id,
name: `${n.notificationName} (${n.type.toUpperCase()})`,
}));
}, [notifications]);

const selectedOptions = useMemo(() => {
return options.filter((opt) => notificationIds.includes(opt.id));
}, [options, notificationIds]);

return (
<Dialog
open={open}
title={t("pages.common.monitors.bulkEdit.title")}
onCancel={onClose}
onConfirm={handleConfirm}
confirmDisabled={isMissingSelection}
loading={isPatching}
maxWidth="sm"
fullWidth
>
<Stack
spacing={theme.spacing(LAYOUT.XS)}
mt={theme.spacing(SPACING.LG)}
>
{error && <Alert severity="error">{error}</Alert>}
<Typography variant="body1">
{t("pages.common.monitors.bulkEdit.selectedText", {
count: selectedMonitors.length,
})}
</Typography>

<RadioGroup
value={action}
onChange={(e) => {
const val = e.target.value;
if (val === "add" || val === "remove" || val === "set") {
setAction(val);
}
}}
>
<RadioWithDescription
value="add"
label={t("pages.common.monitors.bulkEdit.actionAdd")}
description={t("pages.common.monitors.bulkEdit.actionAddDesc")}
/>
<RadioWithDescription
value="remove"
label={t("pages.common.monitors.bulkEdit.actionRemove")}
description={t("pages.common.monitors.bulkEdit.actionRemoveDesc")}
/>
<RadioWithDescription
value="set"
label={t("pages.common.monitors.bulkEdit.actionSet")}
description={t("pages.common.monitors.bulkEdit.actionSetDesc")}
/>
</RadioGroup>

<Autocomplete
multiple
value={selectedOptions}
options={options}
getOptionLabel={(option) => option.name}
isOptionEqualToValue={(option, val) => option.id === val.id}
onChange={(_, newValue) => {
if (Array.isArray(newValue)) {
setNotificationIds(newValue.map((v) => v.id));
} else {
setNotificationIds([]);
}
}}
fieldLabel={t("pages.common.monitors.bulkEdit.selectLabel")}
renderInput={(params) => (
<TextField
{...params}
placeholder={t("pages.common.monitors.bulkEdit.selectPlaceholder")}
/>
)}
/>
</Stack>
</Dialog>
);
};
6 changes: 6 additions & 0 deletions client/src/Components/monitors/MonitorListPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ interface MonitorListPageProps {
actionLink: string;
controller: MonitorListController;
bulkActions?: boolean;
bulkActionsExtra?: ReactNode;
bulkActionsHidden?: boolean;
showTypeFilter?: boolean;
summaryProps?: { showBreached?: boolean };
priorityFallback?: ReactNode;
Expand All @@ -37,6 +39,8 @@ export const MonitorListPage = ({
actionLink,
controller: c,
bulkActions,
bulkActionsExtra,
bulkActionsHidden,
showTypeFilter,
summaryProps,
priorityFallback,
Expand Down Expand Up @@ -125,6 +129,7 @@ export const MonitorListPage = ({
<BulkActionsBar
selectedCount={c.selectedRows.length}
onCancel={c.handleCancelSelection}
hidden={bulkActionsHidden}
>
<Button
size="small"
Expand All @@ -140,6 +145,7 @@ export const MonitorListPage = ({
>
{t("common.buttons.pause")}
</Button>
{bulkActionsExtra}
</BulkActionsBar>
)}
{children}
Expand Down
1 change: 1 addition & 0 deletions client/src/Components/monitors/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ export * from "./charts/HistogramPageSpeedDetailsTooltip";
export * from "./charts/HistogramInfrastructure";
export * from "./HeaderMonitorsSummary";
export * from "./BulkActionsBar";
export * from "./BulkEditNotificationsModal";
export * from "./MonitorListPage";
Loading
Loading