diff --git a/ui/src/pages/Alerts/SilencesPage.tsx b/ui/src/pages/Alerts/SilencesPage.tsx index 156c8960..2703b34b 100644 --- a/ui/src/pages/Alerts/SilencesPage.tsx +++ b/ui/src/pages/Alerts/SilencesPage.tsx @@ -1,3 +1,130 @@ +import { useState } from 'react'; +import { Button, FormField, Input, SectionHeader, useToast } from '@cameleer/design-system'; +import { PageLoader } from '../../components/PageLoader'; +import { + useAlertSilences, + useCreateSilence, + useDeleteSilence, + type AlertSilenceResponse, +} from '../../api/queries/alertSilences'; +import sectionStyles from '../../styles/section-card.module.css'; + export default function SilencesPage() { - return
SilencesPage — coming soon
; + const { data, isLoading, error } = useAlertSilences(); + const create = useCreateSilence(); + const remove = useDeleteSilence(); + const { toast } = useToast(); + + const [reason, setReason] = useState(''); + const [matcherRuleId, setMatcherRuleId] = useState(''); + const [matcherAppSlug, setMatcherAppSlug] = useState(''); + const [hours, setHours] = useState(1); + + if (isLoading) return ; + if (error) return
Failed to load silences: {String(error)}
; + + const onCreate = async () => { + const now = new Date(); + const endsAt = new Date(now.getTime() + hours * 3600_000); + const matcher: Record = {}; + if (matcherRuleId) matcher.ruleId = matcherRuleId; + if (matcherAppSlug) matcher.appSlug = matcherAppSlug; + if (Object.keys(matcher).length === 0) { + toast({ title: 'Silence needs at least one matcher field', variant: 'error' }); + return; + } + try { + await create.mutateAsync({ + matcher, + reason: reason || undefined, + startsAt: now.toISOString(), + endsAt: endsAt.toISOString(), + }); + setReason(''); + setMatcherRuleId(''); + setMatcherAppSlug(''); + setHours(1); + toast({ title: 'Silence created', variant: 'success' }); + } catch (e) { + toast({ title: 'Create failed', description: String(e), variant: 'error' }); + } + }; + + const onRemove = async (s: AlertSilenceResponse) => { + if (!confirm(`End silence early?`)) return; + try { + await remove.mutateAsync(s.id!); + toast({ title: 'Silence removed', variant: 'success' }); + } catch (e) { + toast({ title: 'Remove failed', description: String(e), variant: 'error' }); + } + }; + + const rows = data ?? []; + + return ( +
+ Alert silences +
+
+ + setMatcherRuleId(e.target.value)} /> + + + setMatcherAppSlug(e.target.value)} /> + + + setHours(Number(e.target.value))} + /> + + + setReason(e.target.value)} + placeholder="Maintenance window" + /> + + +
+
+
+ {rows.length === 0 ? ( +

No active or scheduled silences.

+ ) : ( + + + + + + + + + + + + {rows.map((s) => ( + + + + + + + + ))} + +
MatcherReasonStartsEnds
{JSON.stringify(s.matcher)}{s.reason ?? '—'}{s.startsAt}{s.endsAt} + +
+ )} +
+
+ ); }