import { useState, useEffect, useRef, useCallback, useMemo } from 'react' import { useNavigate } from 'react-router-dom' import { Crosshair, Shield, ChevronDown, ChevronUp, Loader2, AlertTriangle, CheckCircle2, Globe, Lock, Bug, FileText, ScrollText, X, ExternalLink, Download, Sparkles, Brain, Trash2, Clock, Search, Activity, Terminal } from 'lucide-react' import { PieChart, Pie, Cell, Tooltip as RechartsTooltip, ResponsiveContainer } from 'recharts' import { agentApi, reportsApi } from '../services/api' import type { AgentStatus, AgentFinding, AgentLog, ToolExecution, ContainerStatus } from '../types' // ─── Constants ──────────────────────────────────────────────────────────────── const PHASES = [ { key: 'recon', label: 'AI Recon', icon: Globe, range: [0, 25] as const }, { key: 'testing', label: 'AI Testing', icon: Bug, range: [25, 70] as const }, { key: 'postexploit', label: 'Post-Exploitation', icon: Brain, range: [70, 85] as const }, { key: 'report', label: 'Report', icon: Shield, range: [85, 100] as const }, ] const SEVERITY_COLORS: Record = { critical: 'bg-red-500', high: 'bg-orange-500', medium: 'bg-yellow-500', low: 'bg-blue-500', info: 'bg-gray-500', } const SEVERITY_BORDER: Record = { critical: 'border-red-500/40', high: 'border-orange-500/40', medium: 'border-yellow-500/40', low: 'border-blue-500/40', info: 'border-gray-500/40', } const SEVERITY_CHART_COLORS: Record = { critical: '#ef4444', high: '#f97316', medium: '#eab308', low: '#3b82f6', info: '#6b7280', } const CONFIDENCE_STYLES: Record = { green: 'bg-green-500/15 text-green-400 border-green-500/30', yellow: 'bg-yellow-500/15 text-yellow-400 border-yellow-500/30', red: 'bg-red-500/15 text-red-400 border-red-500/30', } const LOG_FILTERS = [ { key: 'all', label: 'All', color: '' }, { key: 'llm', label: 'LLM Pentest', color: 'text-red-400' }, { key: 'ai', label: 'AI Decisions', color: 'text-purple-400' }, { key: 'error', label: 'Errors', color: 'text-red-400' }, ] const SESSION_KEY = 'neurosploit_fullia_session' const POLL_INTERVAL = 1500 const POLL_INTERVAL_ERROR = 5000 const TOAST_DURATION = 5000 const MAX_TOASTS = 5 // ─── Utility Functions ──────────────────────────────────────────────────────── function phaseFromProgress(progress: number): number { if (progress < 25) return 0 if (progress < 70) return 1 if (progress < 85) return 2 return 3 } function formatElapsed(totalSeconds: number): string { const h = Math.floor(totalSeconds / 3600) const m = Math.floor((totalSeconds % 3600) / 60) const s = totalSeconds % 60 return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}` } function logMessageColor(message: string): string { if (message.startsWith('[LLM PENTEST]')) return 'text-red-400' if (message.startsWith('[STREAM 1]')) return 'text-blue-400' if (message.startsWith('[STREAM 2]')) return 'text-purple-400' if (message.startsWith('[STREAM 3]')) return 'text-orange-400' if (message.startsWith('[TOOL]')) return 'text-orange-300' if (message.startsWith('[DEEP]')) return 'text-cyan-400' if (message.startsWith('[FINAL]')) return 'text-green-400' if (message.startsWith('[CONTAINER]')) return 'text-cyan-300' if (message.startsWith('[PHASE]')) return 'text-yellow-400' if (message.startsWith('[PHASE FAIL]')) return 'text-red-400' if (message.startsWith('[BANNER]')) return 'text-teal-400' if (message.startsWith('[WAF]')) return 'text-amber-400' if (message.startsWith('[PLAYBOOK]')) return 'text-indigo-400' if (message.startsWith('[SITE ANALYZER]')) return 'text-emerald-400' return '' } function matchLogFilter(log: AgentLog, filter: string): boolean { if (filter === 'all') return true if (filter === 'llm') return log.message.startsWith('[LLM PENTEST]') if (filter === 'ai') return log.source === 'llm' || log.message.includes('[AI]') || log.message.includes('[LLM]') if (filter === 'error') return log.level === 'error' || log.level === 'warning' return true } function getConfidenceDisplay(finding: { confidence_score?: number; confidence?: string }): { score: number; color: string; label: string } | null { let score: number | null = null if (typeof finding.confidence_score === 'number') { score = finding.confidence_score } else if (finding.confidence) { const parsed = Number(finding.confidence) if (!isNaN(parsed)) score = parsed else { const map: Record = { high: 90, medium: 60, low: 30 } score = map[finding.confidence.toLowerCase()] ?? null } } if (score === null) return null const color = score >= 90 ? 'green' : score >= 60 ? 'yellow' : 'red' const label = score >= 90 ? 'Confirmed' : score >= 60 ? 'Likely' : 'Low' return { score, color, label } } // ─── Toast Type ─────────────────────────────────────────────────────────────── interface Toast { id: string message: string severity: string timestamp: number } // ─── Sub-Components ─────────────────────────────────────────────────────────── function LiveStatsDashboard({ status, elapsedSeconds, toolExecutions }: { status: AgentStatus; elapsedSeconds: number; toolExecutions: ToolExecution[] }) { return (
Elapsed
{formatElapsed(elapsedSeconds)}
Findings
{status.findings_count} {(status.rejected_findings_count ?? 0) > 0 && ( +{status.rejected_findings_count} rej )}
Tools Run
{toolExecutions.length}
Progress
{status.progress}% {status.phase || 'Init'}
) } function ToolExecutionRow({ exec, expanded, onToggle }: { exec: ToolExecution; expanded: boolean; onToggle: () => void }) { const hasExpandable = !!(exec.stdout_preview || exec.stderr_preview || exec.reason) return (
{expanded && (
{exec.reason && (

Reason: {exec.reason}

)} {exec.stdout_preview && (

stdout

{exec.stdout_preview}
)} {exec.stderr_preview && (

stderr

{exec.stderr_preview}
)} {exec.container_name && (

Container: {exec.container_name}

)}
)}
) } function SeverityMiniChart({ sevCounts }: { sevCounts: Record }) { const data = ['critical', 'high', 'medium', 'low', 'info'] .filter(s => (sevCounts[s] || 0) > 0) .map(s => ({ name: s, value: sevCounts[s] || 0 })) if (data.length === 0) return null return (
{data.map(entry => ( ))} [`${value}`, name.charAt(0).toUpperCase() + name.slice(1)]} />
) } function LogViewer({ logs, logFilter, setLogFilter, logSearch, setLogSearch, logsEndRef }: { logs: AgentLog[]; logFilter: string; setLogFilter: (f: string) => void logSearch: string; setLogSearch: (s: string) => void; logsEndRef: React.RefObject }) { const filteredLogs = useMemo(() => logs.filter(log => { if (!matchLogFilter(log, logFilter)) return false if (logSearch && !log.message.toLowerCase().includes(logSearch.toLowerCase())) return false return true }), [logs, logFilter, logSearch] ) return (
{LOG_FILTERS.map(f => ( ))}
setLogSearch(e.target.value)} placeholder="Search..." className="pl-6 pr-2 py-1 bg-dark-800 border border-dark-700 rounded text-xs text-white placeholder-dark-500 focus:outline-none focus:border-dark-500 w-32 sm:w-40" />
{filteredLogs.length}/{logs.length}
{filteredLogs.length === 0 ? (

{logs.length === 0 ? 'Waiting for logs...' : 'No logs match filter'}

) : ( filteredLogs.map((log, i) => (
{log.time?.slice(11, 19) || ''} {log.level} {log.message}
)) )}
) } function ToastContainer({ toasts, onDismiss }: { toasts: Toast[]; onDismiss: (id: string) => void }) { if (toasts.length === 0) return null return (
{toasts.map(toast => (
{toast.severity === 'completed' ? ( ) : toast.severity === 'error' || toast.severity === 'critical' ? ( ) : ( )} {toast.message}
))}
) } // ─── Main Component ─────────────────────────────────────────────────────────── export default function FullIATestingPage() { const navigate = useNavigate() // Form state const [target, setTarget] = useState('') const [showAuth, setShowAuth] = useState(false) const [authType, setAuthType] = useState('') const [authValue, setAuthValue] = useState('') const [availableModels, setAvailableModels] = useState>([]) const [selectedProvider, setSelectedProvider] = useState('') const [selectedModel, setSelectedModel] = useState('') // Prompt state const [promptContent, setPromptContent] = useState(null) const [promptLoading, setPromptLoading] = useState(true) const [promptError, setPromptError] = useState(null) const [showPromptPreview, setShowPromptPreview] = useState(false) // Agent state const [agentId, setAgentId] = useState(null) const [status, setStatus] = useState(null) const [isRunning, setIsRunning] = useState(false) const [logs, setLogs] = useState([]) const [error, setError] = useState(null) // Live stats const [elapsedSeconds, setElapsedSeconds] = useState(0) // UI state const [activeTab, setActiveTab] = useState<'findings' | 'logs'>('findings') const [expandedFinding, setExpandedFinding] = useState(null) const [expandedTool, setExpandedTool] = useState(null) const [findingsFilter, setFindingsFilter] = useState<'confirmed' | 'rejected' | 'all'>('all') const [logFilter, setLogFilter] = useState('all') const [logSearch, setLogSearch] = useState('') // Toast notifications const [toasts, setToasts] = useState([]) // Finding animations const [newFindingIds, setNewFindingIds] = useState>(new Set()) // Connection state const [connectionLost, setConnectionLost] = useState(false) // Report const [generatingReport, setGeneratingReport] = useState(false) const [reportId, setReportId] = useState(null) // Refs const pollRef = useRef | null>(null) const logsEndRef = useRef(null) const seenFindingIdsRef = useRef>(new Set()) const prevPhaseRef = useRef(null) const prevStatusRef = useRef(null) const consecutiveErrorsRef = useRef(0) const newFindingTimerRef = useRef | null>(null) // ─── Toast Helper ───────────────────────────────────────────────────────── const addToast = useCallback((message: string, severity: string = 'info') => { const id = `${Date.now()}-${Math.random().toString(36).slice(2, 6)}` setToasts(prev => [...prev.slice(-(MAX_TOASTS - 1)), { id, message, severity, timestamp: Date.now() }]) setTimeout(() => setToasts(prev => prev.filter(t => t.id !== id)), TOAST_DURATION) }, []) const dismissToast = useCallback((id: string) => { setToasts(prev => prev.filter(t => t.id !== id)) }, []) // ─── Mount: load prompt + models + restore session ──────────────────────── useEffect(() => { fetch('/api/v1/full-ia/prompt') .then(r => r.json()) .then(data => { setPromptContent(data.content); setPromptLoading(false) }) .catch(() => { setPromptError('Failed to load pentest prompt.'); setPromptLoading(false) }) fetch('/api/v1/providers/available-models') .then(r => r.json()) .then(data => setAvailableModels(data.models || [])) .catch(() => {}) try { const saved = localStorage.getItem(SESSION_KEY) if (saved) { const sess = JSON.parse(saved) setAgentId(sess.agentId) setTarget(sess.target || '') setIsRunning(sess.status === 'running') } } catch { /* ignore */ } }, []) // ─── Elapsed Time Ticker ────────────────────────────────────────────────── useEffect(() => { if (!status?.started_at) return const startTime = new Date(status.started_at).getTime() if (isRunning) { const tick = () => setElapsedSeconds(Math.floor((Date.now() - startTime) / 1000)) tick() const id = setInterval(tick, 1000) return () => clearInterval(id) } else { const endTime = status.completed_at ? new Date(status.completed_at).getTime() : Date.now() setElapsedSeconds(Math.max(0, Math.floor((endTime - startTime) / 1000))) } }, [isRunning, status?.started_at, status?.completed_at]) // ─── Polling ────────────────────────────────────────────────────────────── useEffect(() => { if (!agentId) return const poll = async () => { try { const s = await agentApi.getStatus(agentId) consecutiveErrorsRef.current = 0 if (connectionLost) setConnectionLost(false) setStatus(s) const running = s.status === 'running' || s.status === 'paused' setIsRunning(running) // Persist session try { const saved = localStorage.getItem(SESSION_KEY) if (saved) { const sess = JSON.parse(saved) sess.status = s.status localStorage.setItem(SESSION_KEY, JSON.stringify(sess)) } } catch { /* ignore */ } // Phase change detection if (prevPhaseRef.current && s.phase && s.phase !== prevPhaseRef.current) { addToast(`Phase: ${s.phase}`, 'info') } prevPhaseRef.current = s.phase || null // Status transition detection if (prevStatusRef.current === 'running' && s.status === 'completed') { addToast(`Pentest complete! ${s.findings_count} findings`, 'completed') } else if (prevStatusRef.current === 'running' && s.status === 'error') { addToast('Pentest failed', 'error') } else if (prevStatusRef.current === 'running' && s.status === 'stopped') { addToast('Pentest stopped', 'info') } prevStatusRef.current = s.status // New finding detection const currentIds = new Set((s.findings || []).map((f: AgentFinding) => f.id)) if (seenFindingIdsRef.current.size > 0) { const newIds = [...currentIds].filter(id => !seenFindingIdsRef.current.has(id)) if (newIds.length > 0) { newIds.forEach(id => { const f = s.findings?.find((x: AgentFinding) => x.id === id) if (f) addToast(`${f.severity.toUpperCase()}: ${f.title}`, f.severity) }) setNewFindingIds(new Set(newIds)) if (newFindingTimerRef.current) clearTimeout(newFindingTimerRef.current) newFindingTimerRef.current = setTimeout(() => setNewFindingIds(new Set()), 3000) } } seenFindingIdsRef.current = currentIds } catch { consecutiveErrorsRef.current += 1 if (consecutiveErrorsRef.current >= 3) setConnectionLost(true) } // Fetch logs try { const logData = await agentApi.getLogs(agentId, 300) setLogs(logData.logs || []) } catch { /* ignore */ } } poll() const interval = consecutiveErrorsRef.current >= 3 ? POLL_INTERVAL_ERROR : POLL_INTERVAL pollRef.current = setInterval(poll, interval) return () => { if (pollRef.current) clearInterval(pollRef.current) } }, [agentId, connectionLost, addToast]) // ─── Auto-scroll logs ───────────────────────────────────────────────────── useEffect(() => { if (activeTab === 'logs' && logsEndRef.current) { logsEndRef.current.scrollIntoView({ behavior: 'smooth' }) } }, [logs, activeTab]) // ─── Actions ────────────────────────────────────────────────────────────── const handleStart = async () => { const primaryTarget = target.trim() if (!primaryTarget || !promptContent) return setError(null) setLogs([]) setReportId(null) seenFindingIdsRef.current = new Set() prevPhaseRef.current = null prevStatusRef.current = null consecutiveErrorsRef.current = 0 try { const resp = await agentApi.autoPentest(primaryTarget, { mode: 'full_llm_pentest', prompt: promptContent, enable_kali_sandbox: false, auth_type: authType || undefined, auth_value: authValue || undefined, preferred_provider: selectedProvider || undefined, preferred_model: selectedModel || undefined, }) setAgentId(resp.agent_id) setIsRunning(true) addToast('Full LLM Pentest started', 'info') localStorage.setItem(SESSION_KEY, JSON.stringify({ agentId: resp.agent_id, target: primaryTarget, startedAt: new Date().toISOString(), status: 'running', })) } catch (err: any) { if (err?.response?.status === 429) { setError(err.response.data.detail) } else { setError(err?.response?.data?.detail || err?.message || 'Failed to start FULL AI pentest') } } } const handleStop = async () => { if (!agentId) return try { await agentApi.stop(agentId) setIsRunning(false) } catch { /* ignore */ } } const handleClear = () => { setAgentId(null) setStatus(null) setIsRunning(false) setLogs([]) setError(null) setReportId(null) setElapsedSeconds(0) setActiveTab('findings') setNewFindingIds(new Set()) setConnectionLost(false) seenFindingIdsRef.current = new Set() prevPhaseRef.current = null prevStatusRef.current = null localStorage.removeItem(SESSION_KEY) } const handleGenerateAiReport = useCallback(async () => { if (!status?.scan_id) return setGeneratingReport(true) try { const report = await reportsApi.generateAiReport({ scan_id: status.scan_id, title: `FULL AI Report - ${target}`, preferred_provider: selectedProvider || undefined, preferred_model: selectedModel || undefined, }) setReportId(report.id) addToast('AI Report generated', 'completed') } catch (err: any) { setError(err?.response?.data?.detail || 'Failed to generate AI report') } finally { setGeneratingReport(false) } }, [status?.scan_id, target, selectedProvider, selectedModel, addToast]) // ─── Derived State ──────────────────────────────────────────────────────── const currentPhaseIdx = status ? phaseFromProgress(status.progress) : -1 const findings = status?.findings || [] const rejectedFindings = status?.rejected_findings || [] const allFindings = useMemo(() => [...findings, ...rejectedFindings], [findings, rejectedFindings]) const displayFindings = findingsFilter === 'confirmed' ? findings : findingsFilter === 'rejected' ? rejectedFindings : allFindings const sevCounts = useMemo(() => findings.reduce((acc, f) => { acc[f.severity] = (acc[f.severity] || 0) + 1; return acc }, {} as Record), [findings] ) const toolExecutions: ToolExecution[] = status?.tool_executions || [] const containerStatus: ContainerStatus | undefined = status?.container_status // ─── Render ─────────────────────────────────────────────────────────────── return (
{/* Inline keyframes for animations */} {/* Toast Notifications */} {/* Connection Lost Banner */} {connectionLost && (
Connection issues — retrying...
)} {/* Header */}

FULL LLM PENTEST

The LLM drives the entire pentest cycle. AI plans HTTP requests, system executes, AI analyzes and adapts.

{promptContent && ( )}
{/* Prompt Preview */} {showPromptPreview && promptContent && (

Pentest Methodology Prompt

            {promptContent}
          
)} {/* Prompt Loading / Error */} {promptLoading && (
Loading pentest prompt...
)} {promptError && (
{promptError}
)} {/* ═══ START FORM ═══ */} {!agentId && (
setTarget(e.target.value)} placeholder="https://example.com" className="w-full px-4 py-4 bg-dark-900 border border-dark-600 rounded-xl text-white text-lg placeholder-dark-500 focus:outline-none focus:border-red-500 focus:ring-1 focus:ring-red-500 transition-colors" />
LLM-Driven Pentest AI Plans & Executes HTTP Full Validation Pipeline
{availableModels.length > 0 && (
)}
{showAuth && (
{authType && ( setAuthValue(e.target.value)} placeholder={ authType === 'bearer' ? 'eyJhbGciOiJIUzI1NiIs...' : authType === 'cookie' ? 'session=abc123; token=xyz' : authType === 'basic' ? 'admin:password123' : 'X-API-Key:your-api-key' } className="w-full px-3 py-2 bg-dark-900 border border-dark-600 rounded-lg text-white text-sm placeholder-dark-500 focus:outline-none focus:border-red-500 transition-colors" /> )}
)}
{error && (
{error}
)}
)} {/* ═══ ACTIVE SESSION VIEW ═══ */} {agentId && (
{/* Session Header */}

{isRunning ? 'Full LLM Pentest Running' : status?.status === 'completed' ? 'LLM Pentest Complete' : status?.status === 'error' ? 'LLM Pentest Failed' : 'LLM Pentest Stopped'}

{target}
{isRunning && ( )} {!isRunning && ( <> )}
{/* Live Stats Dashboard */} {status && ( )} {/* Progress Panel */} {status && (
{status.phase || 'Initializing...'} {status.progress}%
{/* Enhanced Progress Bar */}
{isRunning && (
)}
{/* Phase Indicators */}
{PHASES.map((phase, idx) => { const Icon = phase.icon const isActive = idx === currentPhaseIdx && isRunning const isDone = idx < currentPhaseIdx || status.status === 'completed' || status.status === 'stopped' return (
{isActive ? : isDone ? : } {phase.label} {phase.range[0]}-{phase.range[1]}%
) })}
)} {/* Container Telemetry */} {containerStatus && (

Container Telemetry

{containerStatus.online ? 'ONLINE' : 'OFFLINE'}
{containerStatus.container_id && ID: {containerStatus.container_id.slice(0, 12)}} {containerStatus.container_name && {containerStatus.container_name}}
{toolExecutions.length > 0 ? (
Task Tool Command Exit Duration Finds
{toolExecutions.map((exec, i) => ( setExpandedTool(expandedTool === (exec.task_id || String(i)) ? null : (exec.task_id || String(i)))} /> ))}
) : (
{isRunning ? ( Waiting for tool executions... ) : 'No tool executions recorded'}
)} {/* Last command summary */} {toolExecutions.length > 0 && (() => { const last = toolExecutions[toolExecutions.length - 1] return (
Last: {last.tool} exit:{last.exit_code} {last.duration !== null ? `${last.duration.toFixed(1)}s` : ''} {last.findings_count > 0 && {last.findings_count} findings}
) })()}
)} {/* ═══ Findings & Logs Tabs ═══ */}
{/* Tab bar */}

{activeTab === 'findings' ? `Findings (${findings.length})` : 'Activity Log'}

{activeTab === 'findings' && (
{['critical', 'high', 'medium', 'low', 'info'].map(sev => { const count = sevCounts[sev] || 0 if (count === 0) return null return ( {count} ) })}
)}
{rejectedFindings.length > 0 && ( )}
{/* Filter sub-tabs for findings */} {activeTab === 'findings' && allFindings.length > 0 && rejectedFindings.length > 0 && (
{(['all', 'confirmed', 'rejected'] as const).map(f => ( ))}
)} {/* Findings List */} {activeTab === 'findings' && ( displayFindings.length > 0 ? (
{displayFindings.map((f: AgentFinding) => { const isNew = newFindingIds.has(f.id) return (
{expandedFinding === f.id && (
{f.affected_endpoint && (
Endpoint: {f.affected_endpoint}
)} {f.parameter && (
Parameter: {f.parameter}
)} {f.cwe_id && (
CWE: {f.cwe_id}
)} {f.cvss_score > 0 && (
CVSS: {f.cvss_score}
)}
{f.description && (

{f.description.substring(0, 400)}{f.description.length > 400 ? '...' : ''}

)} {f.payload && (
Payload: {f.payload.substring(0, 300)}
)} {f.evidence && (
Evidence: {f.evidence.substring(0, 400)}
)} {f.poc_code && (

PoC Code

{f.poc_code}
)} {f.ai_status === 'rejected' && f.rejection_reason && (
Rejection: {f.rejection_reason}
)}
{f.ai_status === 'rejected' ? 'AI Rejected' : f.ai_verified ? 'AI Verified' : 'Tool Detected'} {(() => { const conf = getConfidenceDisplay(f) if (!conf) return null return ( Confidence: {conf.score}/100 ({conf.label}) ) })()}
{(() => { const hasBreakdown = f.confidence_breakdown && Object.keys(f.confidence_breakdown).length > 0 const hasProof = !!f.proof_of_execution const hasControls = !!f.negative_controls if (!hasBreakdown && !hasProof && !hasControls) return null return (
{hasBreakdown && (
{Object.entries(f.confidence_breakdown!).map(([key, val]) => (
{key.replace(/_/g, ' ')} 0 ? 'text-green-400' : Number(val) < 0 ? 'text-red-400' : 'text-dark-500' }`}>{Number(val) > 0 ? '+' : ''}{val}
))}
)} {hasProof && (

Proof: {f.proof_of_execution}

)} {hasControls && (

Controls: {f.negative_controls}

)}
) })()}
)}
) })}
) : (
{isRunning ? ( Full LLM Pentest in progress... AI is planning and executing tests. ) : ( 'No findings' )}
) )} {/* Activity Log */} {activeTab === 'logs' && ( )}
{/* Completion / Stopped Actions */} {(status?.status === 'completed' || status?.status === 'stopped') && (
{status.status === 'completed' ? ( ) : ( )}

{status.status === 'completed' ? 'Full LLM Pentest Complete' : 'LLM Pentest Stopped'}

{status.status === 'completed' ? `Found ${findings.length} vulnerabilities across ${target}.` : `Stopped at ${status.progress}% — found ${findings.length} finding${findings.length !== 1 ? 's' : ''}.`}

{elapsedSeconds > 0 && ( Duration: {formatElapsed(elapsedSeconds)} )}
{generatingReport && (

Generating AI Report...

Analyzing findings and writing executive summary.

)}
{!reportId ? ( ) : ( <> View Report Download ZIP )}
)} {/* Error State */} {status?.status === 'error' && (

Pentest Failed

{status.error || 'An unexpected error occurred.'}

)}
)}
) }