feat(tests): attack_success 3-value UI, data classification badge, reorder containment options
Aegis CI / lint-and-test (push) Has been cancelled
Snyk Security Scan / Python vulnerabilities (backend) (push) Has been cancelled
Snyk Security Scan / npm vulnerabilities (frontend) (push) Has been cancelled
Snyk Security Scan / Docker image vulnerabilities (backend) (push) Has been cancelled
Aegis CI / lint-and-test (push) Has been cancelled
Snyk Security Scan / Python vulnerabilities (backend) (push) Has been cancelled
Snyk Security Scan / npm vulnerabilities (frontend) (push) Has been cancelled
Snyk Security Scan / Docker image vulnerabilities (backend) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
import { useState } from "react";
|
||||
import { ChevronDown, ChevronUp, Loader2, Lock } from "lucide-react";
|
||||
import type { DataClassification } from "../../types/models";
|
||||
|
||||
// ── Options ────────────────────────────────────────────────────────
|
||||
|
||||
const CLASSIFICATION_OPTIONS: { value: DataClassification; label: string; color: string }[] = [
|
||||
{ value: "public", label: "Public", color: "border-gray-500/30 bg-gray-800/50 text-gray-400" },
|
||||
{ value: "internal", label: "Internal", color: "border-blue-500/30 bg-blue-900/30 text-blue-400" },
|
||||
{ value: "sensitive", label: "Sensitive", color: "border-amber-500/30 bg-amber-900/30 text-amber-400" },
|
||||
{ value: "restricted", label: "Restricted", color: "border-red-500/30 bg-red-900/30 text-red-400" },
|
||||
];
|
||||
|
||||
// ── Props ──────────────────────────────────────────────────────────
|
||||
|
||||
interface DataClassificationBadgeProps {
|
||||
value: DataClassification;
|
||||
canEdit: boolean;
|
||||
isSaving: boolean;
|
||||
onChange: (value: DataClassification) => void;
|
||||
}
|
||||
|
||||
// ── Component ──────────────────────────────────────────────────────
|
||||
|
||||
export default function DataClassificationBadge({
|
||||
value,
|
||||
canEdit,
|
||||
isSaving,
|
||||
onChange,
|
||||
}: DataClassificationBadgeProps) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const current = CLASSIFICATION_OPTIONS.find((o) => o.value === value) ?? CLASSIFICATION_OPTIONS[1];
|
||||
|
||||
if (!canEdit) {
|
||||
return (
|
||||
<span
|
||||
className={`inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[10px] font-medium ${current.color}`}
|
||||
>
|
||||
<Lock className="h-2.5 w-2.5" />
|
||||
{current.label}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative inline-block">
|
||||
<button
|
||||
onClick={() => setExpanded((v) => !v)}
|
||||
className={`inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[10px] font-medium transition-colors hover:opacity-80 ${current.color}`}
|
||||
>
|
||||
<Lock className="h-2.5 w-2.5" />
|
||||
{current.label}
|
||||
{expanded ? <ChevronUp className="h-2.5 w-2.5" /> : <ChevronDown className="h-2.5 w-2.5" />}
|
||||
</button>
|
||||
|
||||
{expanded && (
|
||||
<div className="absolute left-0 top-full z-20 mt-1 w-44 rounded-lg border border-gray-700 bg-gray-900 p-1.5 shadow-xl">
|
||||
{isSaving ? (
|
||||
<div className="flex items-center justify-center py-2">
|
||||
<Loader2 className="h-4 w-4 animate-spin text-gray-500" />
|
||||
</div>
|
||||
) : (
|
||||
CLASSIFICATION_OPTIONS.map((opt) => (
|
||||
<button
|
||||
key={opt.value}
|
||||
onClick={() => {
|
||||
onChange(opt.value);
|
||||
setExpanded(false);
|
||||
}}
|
||||
className={`block w-full rounded px-2 py-1.5 text-left text-xs transition-colors ${
|
||||
opt.value === value
|
||||
? "bg-gray-800 text-white"
|
||||
: "text-gray-400 hover:bg-gray-800 hover:text-white"
|
||||
}`}
|
||||
>
|
||||
{opt.label}
|
||||
</button>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user