From 837fcbf92647f868f3a262a6a14de11caa3b9e91 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Tue, 21 Apr 2026 19:05:30 +0200 Subject: [PATCH] =?UTF-8?q?feat(ui/alerts):=20SilenceRuleMenu=20=E2=80=94?= =?UTF-8?q?=201h/8h/24h/custom=20duration=20menu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Used by InboxPage row + bulk actions to silence an alert's underlying rule for a chosen preset window. 'Custom…' routes to /alerts/silences?ruleId= (T13 adds the prefill wire). Co-Authored-By: Claude Opus 4.7 (1M context) --- ui/src/pages/Alerts/SilenceRuleMenu.tsx | 71 +++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 ui/src/pages/Alerts/SilenceRuleMenu.tsx 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} + /> + ); +}