import { ShieldAlert } from 'lucide-react'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog'; import { Button } from '@/components/ui/button'; import { SeverityChip } from '@/components/VulnerabilityScanSheet'; import type { VulnSeverity } from '@/types/security'; export interface PolicyBlockViolation { imageRef: string; severity: VulnSeverity | string; criticalCount: number; highCount: number; scanId: number; } export interface PolicyBlockPayload { error: string; policy: { id: number; name: string; maxSeverity: string } | null; violations: PolicyBlockViolation[]; } interface PolicyBlockDialogProps { open: boolean; payload: PolicyBlockPayload | null; stackName: string; canBypass: boolean; bypassing: boolean; onClose: () => void; onBypass: () => void; } const KNOWN_SEVERITIES: VulnSeverity[] = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW', 'UNKNOWN']; function normalizeSeverity(value: string): VulnSeverity { const upper = value.toUpperCase(); return (KNOWN_SEVERITIES as string[]).includes(upper) ? (upper as VulnSeverity) : 'UNKNOWN'; } export function PolicyBlockDialog({ open, payload, stackName, canBypass, bypassing, onClose, onBypass, }: PolicyBlockDialogProps) { const policyName = payload?.policy?.name ?? 'policy'; const maxSeverity = payload?.policy?.maxSeverity ?? ''; const violations = payload?.violations ?? []; return ( { if (!next) onClose(); }}>
Policy block · {stackName}
Deploy blocked by security policy Policy {policyName} blocks deploys when any image meets or exceeds {maxSeverity}. The following {violations.length === 1 ? 'image' : `${violations.length} images`} triggered the block.
{violations.length === 0 ? (
No violation details were returned. Check the scan history for this stack for more context.
) : ( violations.map((v) => (
{v.imageRef}
{v.criticalCount} critical · {v.highCount} high
)) )}
Close {canBypass ? ( ) : ( Admin required to bypass )}
); }