import { useCallback, useEffect, useState } from 'react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Badge } from '@/components/ui/badge'; import { Skeleton } from '@/components/ui/skeleton'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Modal, ModalHeader, ModalBody, ModalFooter, ConfirmModal } from '@/components/ui/modal'; import { ChevronLeft, ChevronRight, Plus, ShieldCheck, Trash2 } from 'lucide-react'; import { toast } from '@/components/ui/toast-store'; import { apiFetch } from '@/lib/api'; import type { MisconfigAcknowledgement } from '@/types/security'; const RULE_RE = /^[A-Z0-9][A-Z0-9_-]{0,199}$/i; const PAGE_SIZE = 8; interface AckFormState { ruleId: string; stackPattern: string; reason: string; expiresInDays: string; } const EMPTY_FORM: AckFormState = { ruleId: '', stackPattern: '', reason: '', expiresInDays: '', }; interface MisconfigAckPanelProps { isReplica: boolean; } export function MisconfigAckPanel({ isReplica }: MisconfigAckPanelProps) { const [rows, setRows] = useState([]); const [loading, setLoading] = useState(true); const [dialogOpen, setDialogOpen] = useState(false); const [form, setForm] = useState(EMPTY_FORM); const [saving, setSaving] = useState(false); const [deleteRow, setDeleteRow] = useState(null); const [page, setPage] = useState(0); const load = useCallback(async () => { try { const res = await apiFetch('/security/misconfig-acks', { localOnly: true }); if (res.ok) { const data = await res.json(); setRows(Array.isArray(data) ? data : []); } } catch (err) { console.error('Failed to load misconfig acknowledgements:', err); toast.error('Failed to load acknowledgements'); } finally { setLoading(false); } }, []); useEffect(() => { load(); }, [load]); const totalPages = Math.max(1, Math.ceil(rows.length / PAGE_SIZE)); const safePage = Math.min(page, totalPages - 1); const pageItems = rows.slice(safePage * PAGE_SIZE, (safePage + 1) * PAGE_SIZE); const needsPagination = rows.length > PAGE_SIZE; const openCreate = () => { setForm(EMPTY_FORM); setDialogOpen(true); }; const handleSave = async () => { const ruleId = form.ruleId.trim(); if (!RULE_RE.test(ruleId)) { toast.error('Rule id must be alpha-numeric (e.g. "DS002" or "AVD-DS-0002").'); return; } const reason = form.reason.trim(); if (!reason) { toast.error('A reason is required.'); return; } let expiresAt: number | null = null; const days = form.expiresInDays.trim(); if (days) { const n = Number(days); if (!Number.isFinite(n) || n <= 0) { toast.error('Expiry must be a positive number of days or blank.'); return; } expiresAt = Date.now() + n * 24 * 60 * 60 * 1000; } setSaving(true); try { const res = await apiFetch('/security/misconfig-acks', { method: 'POST', localOnly: true, body: JSON.stringify({ rule_id: ruleId, stack_pattern: form.stackPattern.trim() || null, reason, expires_at: expiresAt, }), }); if (!res.ok) { const body = await res.json().catch(() => ({})); throw new Error(body?.error || 'Failed to create acknowledgement'); } toast.success('Acknowledgement created'); setDialogOpen(false); await load(); } catch (err) { toast.error((err as Error)?.message || 'Failed to create acknowledgement'); } finally { setSaving(false); } }; const handleDelete = async () => { if (!deleteRow) return; try { const res = await apiFetch(`/security/misconfig-acks/${deleteRow.id}`, { method: 'DELETE', localOnly: true, }); if (!res.ok) { const body = await res.json().catch(() => ({})); throw new Error(body?.error || 'Failed to delete acknowledgement'); } toast.success('Acknowledgement removed'); await load(); } catch (err) { toast.error((err as Error)?.message || 'Failed to delete acknowledgement'); } finally { setDeleteRow(null); } }; const formatExpiry = (row: MisconfigAcknowledgement): string => { if (row.expires_at === null) return 'Never'; const d = new Date(row.expires_at); return d.toLocaleDateString(); }; return (
Misconfig Acknowledgements {rows.length}
{needsPagination && ( <> {safePage + 1} / {totalPages} )} {!isReplica && ( )}

Accept known-benign misconfigurations so they stop triggering alerts. Acknowledgements apply at read time across every instance in the fleet and never modify stored scan data.

{loading && (
)} {!loading && rows.length === 0 && (
No acknowledgements yet. Acknowledge a misconfig from any scan result to silence it fleet-wide.
)} {!loading && rows.length > 0 && (
    {pageItems.map((row) => (
  • {row.rule_id} {row.stack_pattern && ( {row.stack_pattern} )} {!row.active && ( expired )} {row.replicated_from_control === 1 && ( replicated )}
    {row.reason}
    by {row.created_by} · expires {formatExpiry(row)}
    {!isReplica && row.replicated_from_control === 0 && ( )}
  • ))}
)}
setForm({ ...form, ruleId: e.target.value })} />
setForm({ ...form, stackPattern: e.target.value })} />