diff --git a/frontend/src/components/settings/NotificationRoutingSection.tsx b/frontend/src/components/settings/NotificationRoutingSection.tsx index c8f5d299..33843e4c 100644 --- a/frontend/src/components/settings/NotificationRoutingSection.tsx +++ b/frontend/src/components/settings/NotificationRoutingSection.tsx @@ -10,23 +10,7 @@ import type { ComboboxOption } from '@/components/ui/combobox'; import { Tabs, TabsList, TabsTrigger, TabsHighlight, TabsHighlightItem } from '@/components/ui/tabs'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { springs } from '@/lib/motion'; -import { - Dialog, - DialogContent, - DialogTitle, - DialogDescription, -} from '@/components/ui/dialog'; -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from '@/components/ui/alert-dialog'; +import { Modal, ModalHeader, ModalBody, ModalFooter, ConfirmModal } from '@/components/ui/modal'; import { toast } from '@/components/ui/toast-store'; import { apiFetch } from '@/lib/api'; import { useNodes } from '@/context/NodeContext'; @@ -76,6 +60,7 @@ export function NotificationRoutingSection() { const [showForm, setShowForm] = useState(false); const [editingId, setEditingId] = useState(null); const [testingId, setTestingId] = useState(null); + const [deleteRouteId, setDeleteRouteId] = useState(null); const [stackOptions, setStackOptions] = useState([]); const [labelOptions, setLabelOptions] = useState([]); @@ -202,9 +187,10 @@ export function NotificationRoutingSection() { } }; - const handleDelete = async (id: number) => { + const handleDelete = async () => { + if (deleteRouteId == null) return; try { - const res = await apiFetch(`/notification-routes/${id}`, { method: 'DELETE' }); + const res = await apiFetch(`/notification-routes/${deleteRouteId}`, { method: 'DELETE' }); if (res.ok) { toast.success('Route deleted.'); fetchRoutes(); @@ -214,9 +200,13 @@ export function NotificationRoutingSection() { } } catch { toast.error('Network error.'); + } finally { + setDeleteRouteId(null); } }; + const deleteTargetRoute = deleteRouteId != null ? routes.find(r => r.id === deleteRouteId) : null; + const handleTest = async (id: number) => { setTestingId(id); try { @@ -317,14 +307,13 @@ export function NotificationRoutingSection() { - { if (!open) resetForm(); }}> - - {editingId ? 'Edit Route' : 'New Routing Rule'} - - {editingId ? 'Edit a notification routing rule' : 'Create a notification routing rule'} - - -
+ { if (!open) resetForm(); }} size="lg"> + +
-
- - - {saving ? <>Saving : editingId ? 'Update' : 'Create'} - -
-
-
-
+ + Cancel + } + primary={ + + {saving ? <>Saving : editingId ? 'Update' : 'Create'} + + } + /> + {loading && (
@@ -554,35 +546,15 @@ export function NotificationRoutingSection() { - - - - - - - Delete routing rule? - - Deleting {route.name} will remove this routing rule. Alerts for the associated stacks will fall back to your global notification channels. - - - - Cancel - handleDelete(route.id)} - className="bg-destructive text-destructive-foreground hover:bg-destructive/90" - > - Delete - - - - +
@@ -616,6 +588,20 @@ export function NotificationRoutingSection() {
))} + + { if (!open) setDeleteRouteId(null); }} + variant="destructive" + kicker="ROUTING · DELETE · IRREVERSIBLE" + title="Delete routing rule" + confirmLabel="Delete" + onConfirm={handleDelete} + > +

+ Deletes {deleteTargetRoute?.name ?? 'this rule'}. Alerts for the associated stacks will fall back to your global notification channels. +

+
diff --git a/frontend/src/components/settings/SecuritySection.tsx b/frontend/src/components/settings/SecuritySection.tsx index cb14b7c1..e164858e 100644 --- a/frontend/src/components/settings/SecuritySection.tsx +++ b/frontend/src/components/settings/SecuritySection.tsx @@ -6,24 +6,7 @@ import { Badge } from '@/components/ui/badge'; import { TogglePill } from '@/components/ui/toggle-pill'; import { Skeleton } from '@/components/ui/skeleton'; import { Combobox } from '@/components/ui/combobox'; -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, - DialogDescription, - DialogFooter, -} from '@/components/ui/dialog'; -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, -} from '@/components/ui/alert-dialog'; +import { Modal, ModalHeader, ModalBody, ModalFooter, ConfirmModal } from '@/components/ui/modal'; import { toast } from '@/components/ui/toast-store'; import { apiFetch } from '@/lib/api'; import { ShieldCheck, Plus, Trash2, Pencil, Download, RefreshCw, Loader2, Info } from 'lucide-react'; @@ -462,120 +445,108 @@ export function SecuritySection({ isPaid }: { isPaid: boolean }) { {isPaid && ( <> - - - - {editingId ? 'Edit Policy' : 'New Policy'} - - Configure the severity threshold and scope for this scan policy. - - -
-
- - setForm({ ...form, name: e.target.value })} - /> -
-
- - setForm({ ...form, stack_pattern: e.target.value })} - /> -

- Glob-style pattern matched against stack names. Leave blank to apply to all stacks. -

-
-
- - setForm({ ...form, max_severity: v as VulnSeverity })} - /> -
-
-
- + + + +
+ + setForm({ ...form, name: e.target.value })} + /> +
+
+ + setForm({ ...form, stack_pattern: e.target.value })} + />

- Emit a critical alert when this policy is violated after a deploy. + Glob-style pattern matched against stack names. Leave blank to apply to all stacks.

- setForm({ ...form, block_on_deploy: c })} - /> -
-
-
- -

Disabled policies are skipped during evaluation.

+
+ + setForm({ ...form, max_severity: v as VulnSeverity })} + />
- setForm({ ...form, enabled: c })} - /> -
-
- - - - {saving ? 'Saving...' : editingId ? 'Update' : 'Create'} - - - -
+
+
+ +

+ Emit a critical alert when this policy is violated after a deploy. +

+
+ setForm({ ...form, block_on_deploy: c })} + /> +
+
+
+ +

Disabled policies are skipped during evaluation.

+
+ setForm({ ...form, enabled: c })} + /> +
+ + setDialogOpen(false)}> + Cancel + + } + primary={ + + {saving ? 'Saving...' : editingId ? 'Update' : 'Create'} + + } + /> + - !open && setDeleteId(null)}> - - - Delete scan policy? - - This removes the policy immediately. Existing scans are not affected. - - - - Cancel - - Delete - - - - + !open && setDeleteId(null)} + variant="destructive" + kicker="SECURITY · DELETE · IRREVERSIBLE" + title="Delete scan policy" + confirmLabel="Delete" + onConfirm={handleDelete} + > +

+ Removes the policy immediately. Existing scans are not affected. +

+
)} - - - - Remove Trivy? - - This removes the managed Trivy binary. Vulnerability scanning will stop working until - Trivy is reinstalled or a host binary is provided. - - - - Cancel - - Remove - - - - + +

+ Removes the managed Trivy binary. Vulnerability scanning stops working until Trivy is reinstalled or a host binary is provided. +

+
); }