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

This commit is contained in:
kitos
2026-07-06 16:19:50 +02:00
parent 022c1c756a
commit c0a88fc489
9 changed files with 236 additions and 50 deletions
@@ -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>
);
}