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
+
+
+ {rows.length === 0 ? (
+
No active or scheduled silences.
+ ) : (
+
+
+
+ | Matcher |
+ Reason |
+ Starts |
+ Ends |
+ |
+
+
+
+ {rows.map((s) => (
+
+ {JSON.stringify(s.matcher)} |
+ {s.reason ?? '—'} |
+ {s.startsAt} |
+ {s.endsAt} |
+
+
+ |
+
+ ))}
+
+
+ )}
+
+
+ );
}