Files
Deep-Package-Inspection/src/components/threats/ThreatRecommendationsDrawer.tsx
T

204 lines
8.4 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { X, ShieldAlert, CheckCircle2, Server, HelpCircle } from "lucide-react";
import { Badge } from "@/components/ui/Badge";
import { ThreatStat } from "@/lib/api";
import { formatAppLabel } from "@/lib/utils";
import { getMitigationSteps } from "./getMitigationSteps";
interface ThreatRecommendationsDrawerProps {
threat: ThreatStat | null;
onClose: () => void;
}
export function ThreatRecommendationsDrawer({
threat,
onClose,
}: ThreatRecommendationsDrawerProps) {
const [isClosing, setIsClosing] = useState(false);
const [activeThreat, setActiveThreat] = useState<ThreatStat | null>(null);
// Sync threat to activeThreat and handle mounting/resetting close state
useEffect(() => {
if (threat) {
setActiveThreat(threat);
setIsClosing(false);
document.body.style.overflow = "hidden";
} else {
document.body.style.overflow = "";
}
return () => {
document.body.style.overflow = "";
};
}, [threat]);
if (!threat && !activeThreat) return null;
const handleClose = () => {
setIsClosing(true);
setTimeout(() => {
onClose();
setActiveThreat(null);
setIsClosing(false);
}, 480); // Match 500ms keyframe animation duration
};
const displayThreat = threat || activeThreat;
if (!displayThreat) return null;
const sev = (displayThreat.severity || "").toLowerCase();
let badgeVariant: "red" | "orange" | "yellow" | "blue" | "green" = "blue";
if (sev.includes("high") || sev.includes("critical")) badgeVariant = "red";
else if (sev.includes("med")) badgeVariant = "orange";
else if (sev.includes("low")) badgeVariant = "yellow";
const mitigation = getMitigationSteps(displayThreat.threat_type || "");
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center p-4 cursor-default"
onClick={handleClose}
>
{/* Backdrop overlay (fade in/out over 0.5s via keyframes) */}
<div
className={`absolute inset-0 bg-black/60 backdrop-blur-sm ${
isClosing ? "animate-overlay-out" : "animate-overlay-in"
}`}
/>
{/* Centered Modal Content Box (fades and scales over 0.5s via keyframes) */}
<div
className={`relative bg-slate-950/85 border border-slate-800/60 rounded-3xl w-full max-w-2xl h-[75vh] flex flex-col shadow-2xl shadow-blue-900/20 overflow-hidden backdrop-blur-xl ${
isClosing ? "animate-modal-out" : "animate-modal-in"
}`}
onClick={(e) => e.stopPropagation()}
>
{/* Header */}
<div className="px-6 py-5 border-b border-white/5 bg-slate-950/40 flex items-center justify-between">
<div className="flex items-center gap-2.5">
<ShieldAlert className="w-5 h-5 text-red-500 shrink-0" />
<h2 className="text-base font-bold text-white tracking-wide">
Mitigation Guide
</h2>
</div>
<button
onClick={handleClose}
className="text-slate-400 hover:text-white rounded-lg p-1.5 hover:bg-white/5 transition-all outline-none cursor-pointer"
>
<X className="w-4 h-4" />
</button>
</div>
{/* Scrollable Body */}
<div className="flex-1 overflow-y-auto px-6 py-5 space-y-6 custom-scrollbar">
{/* Threat Metadata Card */}
<div className="bg-slate-950/30 border border-white/5 rounded-xl p-4.5 space-y-3.5">
<div>
<span className="text-[10px] text-slate-400 font-semibold uppercase tracking-wider block mb-1">
Threat Type
</span>
<div className="flex items-center gap-2 flex-wrap">
<span className="text-sm font-bold text-white leading-tight">
{displayThreat.threat_type || "Unknown Threat"}
</span>
<Badge variant={badgeVariant} className="text-[10px] uppercase font-bold shrink-0">
{displayThreat.severity}
</Badge>
</div>
</div>
<div>
<span className="text-[10px] text-slate-400 font-semibold uppercase tracking-wider block mb-1">
Description
</span>
<p className="text-xs text-slate-300 leading-relaxed">
{displayThreat.description || "No detailed description available."}
</p>
</div>
</div>
{/* Technical details block */}
<div className="space-y-3">
<h3 className="text-xs font-semibold text-slate-400 uppercase tracking-wider flex items-center gap-1.5">
<Server className="w-3.5 h-3.5" />
Technical Details
</h3>
<div className="bg-slate-950/20 border border-white/5 rounded-xl divide-y divide-white/5">
<div className="px-4 py-2.5 flex items-center justify-between text-xs">
<span className="text-slate-400">Source IP</span>
<span className="font-mono font-medium text-white">{displayThreat.ip_address || "-"}</span>
</div>
<div className="px-4 py-2.5 flex items-center justify-between text-xs">
<span className="text-slate-400">Source MAC</span>
<span className="font-mono font-medium text-white">{displayThreat.mac_address || "-"}</span>
</div>
<div className="px-4 py-2.5 flex items-center justify-between text-xs">
<span className="text-slate-400">Destination IP</span>
<span className="font-mono font-medium text-white">{displayThreat.dst_ip || "-"}</span>
</div>
<div className="px-4 py-2.5 flex items-center justify-between text-xs">
<span className="text-slate-400">Application</span>
<span className="font-medium text-white">{formatAppLabel(displayThreat.app_label)}</span>
</div>
<div className="px-4 py-2.5 flex items-center justify-between text-xs">
<span className="text-slate-400">Domain</span>
<span className="font-mono font-medium text-white truncate max-w-[200px]" title={displayThreat.domain || ""}>
{displayThreat.domain || "-"}
</span>
</div>
<div className="px-4 py-2.5 flex items-center justify-between text-xs">
<span className="text-slate-400">Detected At</span>
<span className="text-slate-300">
{displayThreat.detected_at ? new Date(displayThreat.detected_at).toLocaleString() : "-"}
</span>
</div>
</div>
</div>
{/* Recommendations checklist */}
<div className="space-y-3">
<h3 className="text-xs font-semibold text-slate-400 uppercase tracking-wider flex items-center gap-1.5">
<HelpCircle className="w-3.5 h-3.5 text-primary" />
Recommendations & Actions
</h3>
<p className="text-xs text-slate-400 leading-relaxed italic mb-1">
{mitigation.intro}
</p>
<div className="space-y-3">
{mitigation.steps.map((step, idx) => (
<div
key={idx}
className="flex items-start gap-3 bg-slate-950/20 hover:bg-slate-950/45 transition-colors p-3.5 border border-white/5 rounded-xl"
>
<div className="p-0.5 rounded bg-emerald-500/10 text-emerald-400 shrink-0">
<CheckCircle2 className="w-4 h-4" />
</div>
<div className="space-y-0.5">
<h4 className="text-xs font-bold text-white flex items-center gap-2">
{step.title}
</h4>
<p className="text-[11px] text-slate-400 leading-normal">
{step.desc}
</p>
</div>
</div>
))}
</div>
</div>
</div>
{/* Footer Action */}
<div className="px-6 py-4.5 border-t border-white/5 bg-slate-950/40 flex justify-end">
<button
onClick={handleClose}
className="px-4 py-2 text-xs font-semibold rounded-lg bg-slate-800 text-slate-200 hover:bg-slate-700 hover:text-white transition-all cursor-pointer border border-white/5"
>
Close Guide
</button>
</div>
</div>
</div>
);
}