refactor(frontend): migrate Labels and Suppressions dialogs to Modal chrome (#947)

Bring the two settings panels onto §10 Modal primitives.

LabelsSection:
- Create/edit form -> Modal at sm with kicker LABELS · NEW or
  LABELS · EDIT
- Delete confirm -> destructive ConfirmModal with kicker
  LABELS · DELETE · IRREVERSIBLE
- handleDelete now closes from finally so the dialog clears on errors
  too (the new ConfirmModal Promise-aware behaviour keeps it open until
  state closes it)

SuppressionsPanel:
- New suppression form -> Modal at md with kicker SUPPRESSIONS · NEW
- Remove confirm -> destructive ConfirmModal with kicker
  SUPPRESSIONS · REMOVE · IRREVERSIBLE
- handleDelete already closed from finally; no change needed there
This commit is contained in:
Anso
2026-05-06 20:16:53 -04:00
committed by GitHub
parent 141683d240
commit 165caf2102
2 changed files with 137 additions and 173 deletions
@@ -2,23 +2,7 @@ import { useState, useEffect, useCallback } from 'react';
import { Plus, Pencil, Trash2 } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog';
import {
Dialog,
DialogContent,
DialogTitle,
DialogDescription,
} from '@/components/ui/dialog';
import { VisuallyHidden } from '@radix-ui/react-visually-hidden';
import { Modal, ModalHeader, ModalBody, ModalFooter, ConfirmModal } from '@/components/ui/modal';
import { apiFetch } from '@/lib/api';
import { toast } from '@/components/ui/toast-store';
import { SENCHO_LABELS_CHANGED } from '@/lib/events';
@@ -132,12 +116,13 @@ export function LabelsSection({ onLabelsChanged }: LabelsSectionProps = {}) {
throw new Error(data?.error || 'Failed to delete label.');
}
toast.success('Label deleted.');
setDeleteTarget(null);
fetchLabels();
onLabelsChanged?.();
window.dispatchEvent(new Event(SENCHO_LABELS_CHANGED));
} catch (err: unknown) {
toast.error((err as Error)?.message || 'Something went wrong.');
} finally {
setDeleteTarget(null);
}
};
@@ -193,65 +178,66 @@ export function LabelsSection({ onLabelsChanged }: LabelsSectionProps = {}) {
</div>
</div>
{/* Create / Edit Dialog */}
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
<DialogContent className="sm:max-w-[380px]">
<DialogTitle>{editingLabel ? 'Edit Label' : 'Create Label'}</DialogTitle>
<VisuallyHidden><DialogDescription>Manage label properties</DialogDescription></VisuallyHidden>
<div className="space-y-4 mt-2">
<Input
placeholder="Label name"
value={formName}
onChange={e => setFormName(e.target.value)}
className="font-mono"
maxLength={30}
autoFocus
onKeyDown={e => { if (e.key === 'Enter') handleSave(); }}
/>
<div>
<div className="text-xs text-muted-foreground mb-2">Color</div>
<div className="flex flex-wrap gap-2">
{LABEL_COLORS.map(c => (
<button
key={c}
type="button"
className={`w-7 h-7 rounded-full border-2 transition-colors ${c === formColor ? 'border-foreground scale-110' : 'border-transparent hover:border-muted-foreground/30'}`}
style={{ backgroundColor: `var(--label-${c})` }}
onClick={() => setFormColor(c)}
/>
))}
</div>
</div>
<div className="flex justify-end gap-2">
<Button variant="ghost" onClick={() => setDialogOpen(false)}>Cancel</Button>
<Button onClick={handleSave} disabled={saving || !formName.trim()}>
{saving ? 'Saving...' : editingLabel ? 'Save' : 'Create'}
</Button>
{/* Create / Edit Modal */}
<Modal open={dialogOpen} onOpenChange={setDialogOpen} size="sm">
<ModalHeader
kicker={editingLabel ? 'LABELS · EDIT' : 'LABELS · NEW'}
title={editingLabel ? 'Edit label' : 'Create label'}
description="Manage label properties"
/>
<ModalBody>
<Input
placeholder="Label name"
value={formName}
onChange={e => setFormName(e.target.value)}
className="font-mono"
maxLength={30}
autoFocus
onKeyDown={e => { if (e.key === 'Enter') handleSave(); }}
/>
<div>
<div className="text-xs text-muted-foreground mb-2">Color</div>
<div className="flex flex-wrap gap-2">
{LABEL_COLORS.map(c => (
<button
key={c}
type="button"
className={`w-7 h-7 rounded-full border-2 transition-colors ${c === formColor ? 'border-foreground scale-110' : 'border-transparent hover:border-muted-foreground/30'}`}
style={{ backgroundColor: `var(--label-${c})` }}
onClick={() => setFormColor(c)}
/>
))}
</div>
</div>
</DialogContent>
</Dialog>
</ModalBody>
<ModalFooter
secondary={
<Button variant="outline" size="sm" onClick={() => setDialogOpen(false)} disabled={saving}>
Cancel
</Button>
}
primary={
<Button size="sm" onClick={handleSave} disabled={saving || !formName.trim()}>
{saving ? 'Saving...' : editingLabel ? 'Save' : 'Create'}
</Button>
}
/>
</Modal>
{/* Delete Confirmation */}
<AlertDialog open={!!deleteTarget} onOpenChange={open => !open && setDeleteTarget(null)}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Delete label &ldquo;{deleteTarget?.name}&rdquo;?</AlertDialogTitle>
<AlertDialogDescription>
This will remove the label from all stacks. This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
onClick={handleDelete}
>
Delete
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<ConfirmModal
open={!!deleteTarget}
onOpenChange={open => !open && setDeleteTarget(null)}
variant="destructive"
kicker="LABELS · DELETE · IRREVERSIBLE"
title={`Delete label "${deleteTarget?.name ?? ''}"`}
confirmLabel="Delete"
onConfirm={handleDelete}
>
<p className="text-sm text-stat-subtitle">
Removes the label from every stack across the fleet.
</p>
</ConfirmModal>
</CapabilityGate>
</PaidGate>
);
@@ -5,24 +5,7 @@ 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 {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} 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 { ChevronLeft, ChevronRight, Plus, ShieldOff, Trash2 } from 'lucide-react';
import { toast } from '@/components/ui/toast-store';
import { apiFetch } from '@/lib/api';
@@ -273,94 +256,89 @@ export function SuppressionsPanel({ isReplica }: SuppressionsPanelProps) {
</ScrollArea>
)}
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>New Suppression</DialogTitle>
<DialogDescription className="sr-only">
Accept a CVE as known-benign so it stops triggering alerts across the fleet.
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-2">
<div className="space-y-2">
<Label htmlFor="s-cve">CVE or advisory ID</Label>
<Input
id="s-cve"
placeholder="CVE-2024-12345 or GHSA-xxxx-xxxx-xxxx"
value={form.cveId}
onChange={(e) => setForm({ ...form, cveId: e.target.value })}
/>
</div>
<div className="space-y-2">
<Label htmlFor="s-pkg">Package (optional)</Label>
<Input
id="s-pkg"
placeholder="e.g. openssl (leave blank to match every package)"
value={form.pkgName}
onChange={(e) => setForm({ ...form, pkgName: e.target.value })}
/>
</div>
<div className="space-y-2">
<Label htmlFor="s-image">Image pattern (optional)</Label>
<Input
id="s-image"
placeholder="e.g. registry.internal/* (leave blank for all images)"
value={form.imagePattern}
onChange={(e) => setForm({ ...form, imagePattern: e.target.value })}
/>
</div>
<div className="space-y-2">
<Label htmlFor="s-reason">Reason</Label>
<textarea
id="s-reason"
className="flex min-h-[72px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
placeholder="Why is this CVE safe to accept?"
value={form.reason}
onChange={(e) => setForm({ ...form, reason: e.target.value })}
/>
</div>
<div className="space-y-2">
<Label htmlFor="s-expiry">Expires in (days, optional)</Label>
<Input
id="s-expiry"
type="number"
min="1"
placeholder="Leave blank for no expiry"
value={form.expiresInDays}
onChange={(e) => setForm({ ...form, expiresInDays: e.target.value })}
/>
</div>
<Modal open={dialogOpen} onOpenChange={setDialogOpen} size="md">
<ModalHeader
kicker="SUPPRESSIONS · NEW"
title="New suppression"
description="Accept a CVE as known-benign so it stops triggering alerts across the fleet."
/>
<ModalBody>
<div className="space-y-2">
<Label htmlFor="s-cve">CVE or advisory ID</Label>
<Input
id="s-cve"
placeholder="CVE-2024-12345 or GHSA-xxxx-xxxx-xxxx"
value={form.cveId}
onChange={(e) => setForm({ ...form, cveId: e.target.value })}
/>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setDialogOpen(false)} disabled={saving}>
<div className="space-y-2">
<Label htmlFor="s-pkg">Package (optional)</Label>
<Input
id="s-pkg"
placeholder="e.g. openssl (leave blank to match every package)"
value={form.pkgName}
onChange={(e) => setForm({ ...form, pkgName: e.target.value })}
/>
</div>
<div className="space-y-2">
<Label htmlFor="s-image">Image pattern (optional)</Label>
<Input
id="s-image"
placeholder="e.g. registry.internal/* (leave blank for all images)"
value={form.imagePattern}
onChange={(e) => setForm({ ...form, imagePattern: e.target.value })}
/>
</div>
<div className="space-y-2">
<Label htmlFor="s-reason">Reason</Label>
<textarea
id="s-reason"
className="flex min-h-[72px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
placeholder="Why is this CVE safe to accept?"
value={form.reason}
onChange={(e) => setForm({ ...form, reason: e.target.value })}
/>
</div>
<div className="space-y-2">
<Label htmlFor="s-expiry">Expires in (days, optional)</Label>
<Input
id="s-expiry"
type="number"
min="1"
placeholder="Leave blank for no expiry"
value={form.expiresInDays}
onChange={(e) => setForm({ ...form, expiresInDays: e.target.value })}
/>
</div>
</ModalBody>
<ModalFooter
secondary={
<Button variant="outline" size="sm" onClick={() => setDialogOpen(false)} disabled={saving}>
Cancel
</Button>
<Button onClick={handleSave} disabled={saving}>
}
primary={
<Button size="sm" onClick={handleSave} disabled={saving}>
{saving ? 'Saving...' : 'Create'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
}
/>
</Modal>
<AlertDialog open={deleteRow !== null} onOpenChange={(open) => !open && setDeleteRow(null)}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Remove suppression?</AlertDialogTitle>
<AlertDialogDescription>
Future scan results will surface {deleteRow?.cve_id} again wherever it applies.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={handleDelete}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
Remove
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<ConfirmModal
open={deleteRow !== null}
onOpenChange={(open) => !open && setDeleteRow(null)}
variant="destructive"
kicker="SUPPRESSIONS · REMOVE · IRREVERSIBLE"
title="Remove suppression"
confirmLabel="Remove"
onConfirm={handleDelete}
>
<p className="text-sm text-stat-subtitle">
Future scan results will surface <span className="font-mono font-medium text-stat-value">{deleteRow?.cve_id}</span> again wherever it applies.
</p>
</ConfirmModal>
</div>
);
}