mirror of
https://github.com/FunnyWolf/agentic-soc-platform.git
synced 2026-08-22 13:12:56 +02:00
add dark mode js
This commit is contained in:
@@ -0,0 +1,272 @@
|
||||
function SecurityIncidentReport({ value }) {
|
||||
let rawData = null;
|
||||
|
||||
if (!value) {
|
||||
return <div className="text-gray-500 text-center font-mono">No data</div>;
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
try {
|
||||
rawData = JSON.parse(value);
|
||||
} catch (e) {
|
||||
throw new Error("Failed to parse JSON data in SecurityIncidentReport");
|
||||
}
|
||||
} else if (typeof value === 'object' && value !== null) {
|
||||
rawData = value;
|
||||
} else {
|
||||
throw new Error("Invalid data type for SecurityIncidentReport");
|
||||
}
|
||||
|
||||
const report = rawData.report || {};
|
||||
|
||||
const getSeverityBadgeClass = (level) => {
|
||||
if (level === 'High' || level === 'Critical') {
|
||||
return 'bg-red-500/10 text-red-400 border-red-500/30';
|
||||
}
|
||||
if (level === 'Medium') {
|
||||
return 'bg-orange-500/10 text-orange-400 border-orange-500/30';
|
||||
}
|
||||
if (level === 'Low') {
|
||||
return 'bg-blue-500/10 text-blue-400 border-blue-500/30';
|
||||
}
|
||||
return 'bg-gray-500/10 text-gray-400 border-gray-500/30';
|
||||
};
|
||||
|
||||
const getVerdictBadgeClass = (verdict) => {
|
||||
if (verdict === 'True Positive') {
|
||||
return 'bg-red-500/10 text-red-400 border-red-500/30';
|
||||
}
|
||||
if (verdict === 'Suspicious') {
|
||||
return 'bg-orange-500/10 text-orange-400 border-orange-500/30';
|
||||
}
|
||||
if (verdict === 'False Positive') {
|
||||
return 'bg-green-500/10 text-green-400 border-green-500/30';
|
||||
}
|
||||
return 'bg-gray-500/10 text-gray-400 border-gray-500/30';
|
||||
};
|
||||
|
||||
const formatLocalTime = (utcString) => {
|
||||
if (!utcString) return 'N/A';
|
||||
const date = new Date(utcString);
|
||||
if (isNaN(date.getTime())) {
|
||||
return utcString;
|
||||
}
|
||||
return date.toLocaleString();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6 w-full h-full overflow-y-auto bg-[#161616] text-gray-300 font-sans">
|
||||
<div className="flex flex-wrap items-center gap-x-6 gap-y-2 text-xs text-gray-500 font-mono shrink-0">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<LucideIcon name="PlayCircle" size="14" />
|
||||
<span>Started: {formatLocalTime(rawData.analysis_last_started_at)}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<LucideIcon name="CheckCircle2" size="14" />
|
||||
<span>Completed: {formatLocalTime(rawData.analysis_last_completed_at)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-4 shrink-0">
|
||||
<div className={`flex flex-col items-center justify-center px-4 py-2 rounded-lg border ${getVerdictBadgeClass(report.verdict)}`}>
|
||||
<span className="text-[10px] uppercase tracking-wider opacity-80 mb-0.5">Verdict</span>
|
||||
<span className="text-sm font-bold text-center leading-tight">{report.verdict}</span>
|
||||
</div>
|
||||
<div className={`flex flex-col items-center justify-center px-4 py-2 rounded-lg border ${getSeverityBadgeClass(report.severity)}`}>
|
||||
<span className="text-[10px] uppercase tracking-wider opacity-80 mb-0.5">Severity</span>
|
||||
<span className="text-sm font-bold text-center leading-tight">{report.severity}</span>
|
||||
</div>
|
||||
<div className={`flex flex-col items-center justify-center px-4 py-2 rounded-lg border ${getSeverityBadgeClass(report.impact)}`}>
|
||||
<span className="text-[10px] uppercase tracking-wider opacity-80 mb-0.5">Impact</span>
|
||||
<span className="text-sm font-bold text-center leading-tight">{report.impact}</span>
|
||||
</div>
|
||||
<div className={`flex flex-col items-center justify-center px-4 py-2 rounded-lg border ${getSeverityBadgeClass(report.priority)}`}>
|
||||
<span className="text-[10px] uppercase tracking-wider opacity-80 mb-0.5">Priority</span>
|
||||
<span className="text-sm font-bold text-center leading-tight">{report.priority}</span>
|
||||
</div>
|
||||
<div className={`flex flex-col items-center justify-center px-4 py-2 rounded-lg border ${getSeverityBadgeClass(report.confidence)}`}>
|
||||
<span className="text-[10px] uppercase tracking-wider opacity-80 mb-0.5">Confidence</span>
|
||||
<span className="text-sm font-bold text-center leading-tight">{report.confidence}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-[#1e1e1e] p-5 rounded-lg border border-gray-800 shrink-0">
|
||||
<h3 className="flex items-center gap-2 text-sm font-bold text-gray-200 mb-3">
|
||||
<LucideIcon name="FileText" size="18" className="text-gray-400" />
|
||||
Incident Digest
|
||||
</h3>
|
||||
<p className="text-sm text-gray-400 leading-relaxed text-justify">
|
||||
{report.digest}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 shrink-0">
|
||||
<div className="flex flex-col gap-4">
|
||||
<h3 className="flex items-center gap-2 text-sm font-bold text-gray-200 border-b border-gray-800 pb-2">
|
||||
<LucideIcon name="Server" size="18" className="text-blue-400" />
|
||||
Affected Assets
|
||||
</h3>
|
||||
<div className="flex flex-col gap-2">
|
||||
{Array.isArray(report.affected_assets) && report.affected_assets.map((asset, index) => (
|
||||
<div key={index} className="flex flex-col sm:flex-row sm:items-center justify-between p-3 bg-[#1a1a1a] rounded-md border border-gray-800 gap-2">
|
||||
<span className="text-[11px] font-bold text-gray-400 px-2 py-1 bg-[#222] border border-gray-700 rounded whitespace-nowrap w-max">
|
||||
{asset.asset_type}
|
||||
</span>
|
||||
<span className="text-xs text-gray-300 font-mono break-all sm:text-right">
|
||||
{asset.asset_value}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<h3 className="flex items-center gap-2 text-sm font-bold text-gray-200 border-b border-gray-800 pb-2">
|
||||
<LucideIcon name="Target" size="18" className="text-red-400" />
|
||||
IOC Indicators
|
||||
</h3>
|
||||
<div className="flex flex-col gap-2">
|
||||
{Array.isArray(report.ioc_indicators) && report.ioc_indicators.map((ioc, index) => (
|
||||
<div key={index} className="p-3 bg-red-500/5 rounded-md border border-red-500/20 flex flex-col gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[11px] font-bold text-red-400 px-2 py-0.5 bg-red-500/10 rounded">
|
||||
{ioc.indicator_type}
|
||||
</span>
|
||||
<span className="text-sm font-mono text-red-400 font-bold">
|
||||
{ioc.value}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-xs text-red-300/70 leading-normal">
|
||||
{ioc.context}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4 pt-2 shrink-0">
|
||||
<h3 className="flex items-center gap-2 text-sm font-bold text-gray-200 border-b border-gray-800 pb-2">
|
||||
<LucideIcon name="Search" size="18" className="text-cyan-400" />
|
||||
Evidence Findings
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{Array.isArray(report.evidence_findings) && report.evidence_findings.map((finding, index) => (
|
||||
<div key={index} className="flex flex-col gap-3 p-4 bg-[#1a1a1a] rounded-lg border border-gray-800">
|
||||
<div className="flex flex-col sm:flex-row sm:items-start justify-between gap-2 border-b border-gray-800 pb-2">
|
||||
<span className="text-sm font-bold text-gray-200">{finding.title}</span>
|
||||
<span className="text-[10px] font-bold text-cyan-400 px-2 py-0.5 bg-cyan-500/10 border border-cyan-500/20 rounded uppercase tracking-wider w-max shrink-0">
|
||||
{finding.finding_type}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 text-xs">
|
||||
<div className="flex gap-2">
|
||||
<span className="font-bold text-gray-500 shrink-0 w-20">Subject:</span>
|
||||
<span className="text-gray-300">{finding.subject}</span>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<span className="font-bold text-gray-500 shrink-0 w-20">Evidence:</span>
|
||||
<span className="text-gray-400 font-mono bg-[#111] px-1.5 py-0.5 rounded border border-[#222] break-all">{finding.evidence}</span>
|
||||
</div>
|
||||
<div className="flex gap-2 mt-1">
|
||||
<span className="font-bold text-gray-500 shrink-0 w-20">Conclusion:</span>
|
||||
<span className="text-gray-300 font-medium leading-relaxed">{finding.conclusion}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4 pt-2 shrink-0">
|
||||
<h3 className="flex items-center gap-2 text-sm font-bold text-gray-200 border-b border-gray-800 pb-2">
|
||||
<LucideIcon name="GitMerge" size="18" className="text-indigo-400" />
|
||||
Attack Chain
|
||||
</h3>
|
||||
<div className="flex flex-col gap-3">
|
||||
{Array.isArray(report.attack_chain) && report.attack_chain.map((chain, index) => (
|
||||
<div key={index} className="flex flex-col gap-2 p-4 bg-indigo-500/5 rounded-lg border border-indigo-500/20">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs font-bold text-indigo-400 bg-indigo-500/10 px-2 py-1 rounded">
|
||||
Stage
|
||||
</span>
|
||||
<span className="text-sm font-bold text-indigo-300">
|
||||
{chain.attack_stage}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-gray-400 leading-relaxed text-justify">
|
||||
{chain.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4 pt-2 shrink-0">
|
||||
<h3 className="flex items-center gap-2 text-sm font-bold text-gray-200 border-b border-gray-800 pb-2">
|
||||
<LucideIcon name="Clock" size="18" className="text-purple-400" />
|
||||
Timeline
|
||||
</h3>
|
||||
<div className="relative border-l-2 border-purple-500/30 ml-3 pl-5 py-2 flex flex-col gap-8">
|
||||
{Array.isArray(report.attack_timeline) && report.attack_timeline.map((event, index) => (
|
||||
<div key={index} className="relative">
|
||||
<div className="absolute -left-[26px] top-1 w-3 h-3 rounded-full bg-purple-500 ring-4 ring-[#161616]"></div>
|
||||
<div className="text-[11px] font-bold font-mono text-purple-400 mb-1">
|
||||
{formatLocalTime(event.timestamp)}
|
||||
</div>
|
||||
<div className="text-sm text-gray-200 font-medium mb-2 leading-relaxed">
|
||||
{event.attack_behavior}
|
||||
</div>
|
||||
<div className="text-[11px] text-gray-400 bg-[#1a1a1a] p-2.5 rounded-md border border-gray-800 font-mono break-all mt-1">
|
||||
{event.evidence_field}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4 pt-2 shrink-0">
|
||||
<h3 className="flex items-center gap-2 text-sm font-bold text-gray-200 border-b border-gray-800 pb-2">
|
||||
<LucideIcon name="HelpCircle" size="18" className="text-orange-400" />
|
||||
Unknowns
|
||||
</h3>
|
||||
<div className="bg-orange-500/5 p-4 rounded-lg border border-orange-500/20">
|
||||
<ul className="list-disc list-outside ml-4 flex flex-col gap-2">
|
||||
{Array.isArray(report.unknowns) && report.unknowns.map((item, index) => (
|
||||
<li key={index} className="text-sm text-orange-300/90 leading-relaxed">
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4 pt-2 shrink-0">
|
||||
<h3 className="flex items-center gap-2 text-sm font-bold text-gray-200 border-b border-gray-800 pb-2">
|
||||
<LucideIcon name="Wrench" size="18" className="text-green-400" />
|
||||
Remediation Recommendations
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 gap-3">
|
||||
{Array.isArray(report.remediations) && report.remediations.map((rec, index) => (
|
||||
<div key={index} className="flex gap-4 p-4 bg-green-500/5 rounded-lg border border-green-500/20">
|
||||
<LucideIcon name="CheckCircle" size="20" className="text-green-500 shrink-0 mt-0.5" />
|
||||
<div className="flex flex-col gap-1.5 w-full">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm font-bold text-green-400">
|
||||
{rec.action_type}
|
||||
</span>
|
||||
<span className={`text-[10px] font-bold px-2 py-0.5 rounded border uppercase tracking-wider ${getSeverityBadgeClass(rec.priority)}`}>
|
||||
{rec.priority}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-xs text-green-300/80 leading-relaxed text-justify">
|
||||
{rec.description}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
function JsonViewer({ value }) {
|
||||
const rawData = value;
|
||||
let jsonData;
|
||||
let isParsingError = false;
|
||||
|
||||
try {
|
||||
if (typeof rawData === 'string' && rawData.trim() !== '') {
|
||||
jsonData = JSON.parse(rawData);
|
||||
} else if (typeof rawData === 'object' && rawData !== null) {
|
||||
jsonData = rawData;
|
||||
} else {
|
||||
jsonData = null;
|
||||
}
|
||||
} catch (e) {
|
||||
isParsingError = true;
|
||||
}
|
||||
|
||||
const [collapsedNodes, setCollapsedNodes] = React.useState({});
|
||||
|
||||
const renderNode = (data, path = '', isLastItem = true) => {
|
||||
const renderValue = (val) => {
|
||||
const type = typeof val;
|
||||
let colorClass = 'text-gray-300';
|
||||
let displayValue = String(val);
|
||||
|
||||
if (type === 'string') {
|
||||
colorClass = 'text-green-400';
|
||||
displayValue = `"${val}"`;
|
||||
} else if (type === 'number') {
|
||||
colorClass = 'text-yellow-400';
|
||||
} else if (type === 'boolean') {
|
||||
colorClass = 'text-pink-400';
|
||||
} else if (val === null) {
|
||||
colorClass = 'text-pink-400';
|
||||
displayValue = 'null';
|
||||
}
|
||||
|
||||
return <span className={`font-mono ${colorClass}`}>{displayValue}</span>;
|
||||
};
|
||||
|
||||
if (typeof data === 'object' && data !== null) {
|
||||
const isArray = Array.isArray(data);
|
||||
const isCollapsed = collapsedNodes[path];
|
||||
const keys = Object.keys(data);
|
||||
const hasChildren = keys.length > 0;
|
||||
const displayBrackets = keys.length === 0;
|
||||
|
||||
const toggleCollapse = (e) => {
|
||||
e.stopPropagation();
|
||||
setCollapsedNodes(prev => ({
|
||||
...prev,
|
||||
[path]: !isCollapsed
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<span
|
||||
className="flex items-center space-x-1 cursor-pointer hover:bg-gray-700 rounded-sm py-1 px-1"
|
||||
onClick={toggleCollapse}
|
||||
>
|
||||
{hasChildren && (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-gray-400">
|
||||
{isCollapsed ? (
|
||||
<path d="m9 18 6-6-6-6" />
|
||||
) : (
|
||||
<path d="m6 9 6 6 6-6" />
|
||||
)}
|
||||
</svg>
|
||||
)}
|
||||
<span className="text-gray-300">
|
||||
{isArray ? '[' : '{'}
|
||||
</span>
|
||||
{isCollapsed && (
|
||||
<span className="text-gray-400 italic">
|
||||
{isArray ? ` ${data.length} items` : ` ${keys.length} keys`}
|
||||
{isArray ? ']' : '}'}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
{!isCollapsed && hasChildren && (
|
||||
<div className={`border-l border-gray-700 pl-4 mt-1`}>
|
||||
{keys.map((key, index) => (
|
||||
<div key={key} className="flex items-baseline space-x-1">
|
||||
<span className="text-blue-400 font-bold pr-1">
|
||||
{isArray ? '' : `"${key}": `}
|
||||
</span>
|
||||
<div>
|
||||
{renderNode(data[key], `${path}.${key}`, index === keys.length - 1)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{!isCollapsed && (
|
||||
<span className="text-gray-300">
|
||||
{isArray ? ']' : '}'}
|
||||
</span>
|
||||
)}
|
||||
{!isLastItem && <span className="text-gray-300">,</span>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="flex items-baseline space-x-1">
|
||||
{renderValue(data)}
|
||||
{!isLastItem && <span className="text-gray-300">,</span>}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
if (!jsonData) {
|
||||
return (
|
||||
<div className="text-gray-400 p-4 font-mono bg-gray-800 rounded-lg shadow-sm">
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (isParsingError) {
|
||||
return (
|
||||
<div className="text-gray-400 p-4 font-mono bg-gray-800 rounded-lg shadow-sm">
|
||||
JSON Format Error
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-4 bg-gray-800 rounded-lg shadow-sm font-mono text-gray-300">
|
||||
{renderNode(jsonData, 'root')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user