diff --git a/ui/src/pages/Alerts/SilenceRuleMenu.tsx b/ui/src/pages/Alerts/SilenceRuleMenu.tsx new file mode 100644 index 00000000..d4530835 --- /dev/null +++ b/ui/src/pages/Alerts/SilenceRuleMenu.tsx @@ -0,0 +1,71 @@ +import { BellOff } from 'lucide-react'; +import { useNavigate } from 'react-router'; +import { Button, Dropdown, useToast } from '@cameleer/design-system'; +import type { DropdownItem } from '@cameleer/design-system'; +import { useCreateSilence } from '../../api/queries/alertSilences'; + +interface Props { + ruleId: string; + ruleTitle?: string; + onDone?: () => void; + variant?: 'row' | 'bulk'; +} + +const PRESETS: Array<{ label: string; hours: number }> = [ + { label: '1 hour', hours: 1 }, + { label: '8 hours', hours: 8 }, + { label: '24 hours', hours: 24 }, +]; + +export function SilenceRuleMenu({ ruleId, ruleTitle, onDone, variant = 'row' }: Props) { + const navigate = useNavigate(); + const { toast } = useToast(); + const createSilence = useCreateSilence(); + + const handlePreset = (hours: number) => async () => { + const now = new Date(); + const reason = ruleTitle + ? `Silenced from inbox (${ruleTitle})` + : 'Silenced from inbox'; + try { + await createSilence.mutateAsync({ + matcher: { ruleId }, + reason, + startsAt: now.toISOString(), + endsAt: new Date(now.getTime() + hours * 3_600_000).toISOString(), + }); + toast({ title: `Silenced for ${hours}h`, variant: 'success' }); + onDone?.(); + } catch (e) { + toast({ title: 'Silence failed', description: String(e), variant: 'error' }); + } + }; + + const handleCustom = () => { + navigate(`/alerts/silences?ruleId=${encodeURIComponent(ruleId)}`); + }; + + const items: DropdownItem[] = [ + ...PRESETS.map(({ label, hours }) => ({ + label, + disabled: createSilence.isPending, + onClick: handlePreset(hours), + })), + { divider: true, label: '' }, + { label: 'Custom…', onClick: handleCustom }, + ]; + + const buttonLabel = variant === 'bulk' ? 'Silence rules' : 'Silence rule…'; + + return ( + + + {buttonLabel} + + } + items={items} + /> + ); +}