From 3e815724773f104cc79ace92550c464a080a8229 Mon Sep 17 00:00:00 2001
From: hsiegeln <37154749+hsiegeln@users.noreply.github.com>
Date: Tue, 21 Apr 2026 10:14:19 +0200
Subject: [PATCH] refactor(alerts/ui): rewrite Silences with DataTable +
FormField + ConfirmDialog
Replaces raw
with DataTable, inline-styled form with proper
FormField hints, and native confirm() end-early with ConfirmDialog
(warning variant). Adds DS EmptyState for no-silences case.
Co-Authored-By: Claude Opus 4.7 (1M context)
---
ui/src/pages/Alerts/SilencesPage.tsx | 127 +++++++++++++++++----------
1 file changed, 80 insertions(+), 47 deletions(-)
diff --git a/ui/src/pages/Alerts/SilencesPage.tsx b/ui/src/pages/Alerts/SilencesPage.tsx
index 2703b34b..77596eed 100644
--- a/ui/src/pages/Alerts/SilencesPage.tsx
+++ b/ui/src/pages/Alerts/SilencesPage.tsx
@@ -1,5 +1,10 @@
import { useState } from 'react';
-import { Button, FormField, Input, SectionHeader, useToast } from '@cameleer/design-system';
+import { BellOff } from 'lucide-react';
+import {
+ Button, FormField, Input, SectionHeader, useToast, DataTable,
+ EmptyState, ConfirmDialog, MonoText,
+} from '@cameleer/design-system';
+import type { Column } from '@cameleer/design-system';
import { PageLoader } from '../../components/PageLoader';
import {
useAlertSilences,
@@ -8,6 +13,8 @@ import {
type AlertSilenceResponse,
} from '../../api/queries/alertSilences';
import sectionStyles from '../../styles/section-card.module.css';
+import tableStyles from '../../styles/table-section.module.css';
+import css from './alerts-page.module.css';
export default function SilencesPage() {
const { data, isLoading, error } = useAlertSilences();
@@ -19,9 +26,12 @@ export default function SilencesPage() {
const [matcherRuleId, setMatcherRuleId] = useState('');
const [matcherAppSlug, setMatcherAppSlug] = useState('');
const [hours, setHours] = useState(1);
+ const [pendingEnd, setPendingEnd] = useState(null);
if (isLoading) return ;
- if (error) return Failed to load silences: {String(error)}
;
+ if (error) return Failed to load silences: {String(error)}
;
+
+ const rows = data ?? [];
const onCreate = async () => {
const now = new Date();
@@ -50,30 +60,58 @@ export default function SilencesPage() {
}
};
- const onRemove = async (s: AlertSilenceResponse) => {
- if (!confirm(`End silence early?`)) return;
+ const confirmEnd = async () => {
+ if (!pendingEnd) return;
try {
- await remove.mutateAsync(s.id!);
+ await remove.mutateAsync(pendingEnd.id!);
toast({ title: 'Silence removed', variant: 'success' });
} catch (e) {
toast({ title: 'Remove failed', description: String(e), variant: 'error' });
+ } finally {
+ setPendingEnd(null);
}
};
- const rows = data ?? [];
+ const columns: Column[] = [
+ {
+ key: 'matcher', header: 'Matcher',
+ render: (_, s) => {JSON.stringify(s.matcher)},
+ },
+ { key: 'reason', header: 'Reason', render: (_, s) => s.reason ?? '—' },
+ { key: 'startsAt', header: 'Starts', width: '200px' },
+ { key: 'endsAt', header: 'Ends', width: '200px' },
+ {
+ key: 'actions', header: '', width: '90px',
+ render: (_, s) => (
+
+ ),
+ },
+ ];
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} |
-
-
- |
-
- ))}
-
-
- )}
-
+
+
+ {rows.length === 0 ? (
+ }
+ title="No silences"
+ description="Nothing is currently silenced in this environment."
+ />
+ ) : (
+
+
+ columns={columns}
+ data={rows.map((s) => ({ ...s, id: s.id ?? '' }))}
+ flush
+ />
+
+ )}
+
+ setPendingEnd(null)}
+ onConfirm={confirmEnd}
+ title="End silence?"
+ message="End this silence early? Affected rules will resume firing."
+ confirmText="End silence"
+ variant="warning"
+ loading={remove.isPending}
+ />
);
}