import { useEffect, useMemo, useState, useCallback, useRef } from 'react' import { useParams, useNavigate } from 'react-router-dom' import { Bot, RefreshCw, FileText, CheckCircle, XCircle, Clock, Target, Shield, ChevronDown, ChevronRight, ExternalLink, Copy, Download, StopCircle, Terminal, Brain, Send, Code, Globe, AlertTriangle, SkipForward, MinusCircle, Pause, Play, Sparkles, X, WifiOff } from 'lucide-react' import Card from '../components/common/Card' import Button from '../components/common/Button' import { SeverityBadge } from '../components/common/Badge' import { agentApi, reportsApi } from '../services/api' import type { AgentStatus, AgentLog, AgentFinding } from '../types' /* ------------------------------------------------------------------ */ /* Constants */ /* ------------------------------------------------------------------ */ const PHASE_ICONS: Record = { initializing: , reconnaissance: , 'reconnaissance complete': , recon: , 'starting reconnaissance': , scanning: , analysis: , 'attack surface analyzed': , testing: , 'vulnerability testing complete': , enhancement: , 'findings enhanced': , reporting: , 'assessment complete': , completed: , stopped: , error: , } const SCAN_PHASES = [ { key: 'recon', label: 'Reconnaissance', progress: 20 }, { key: 'analysis', label: 'Analysis', progress: 30 }, { key: 'testing', label: 'Testing', progress: 70 }, { key: 'enhancement', label: 'Enhancement', progress: 90 }, { key: 'completed', label: 'Completed', progress: 100 }, ] const MODE_LABELS: Record = { full_auto: 'Full Auto', recon_only: 'Recon Only', prompt_only: 'AI Prompt Mode', analyze_only: 'Analyze Only', } type Severity = 'critical' | 'high' | 'medium' | 'low' | 'info' const SEVERITY_ORDER: Severity[] = ['critical', 'high', 'medium', 'low', 'info'] /* ------------------------------------------------------------------ */ /* Helpers */ /* ------------------------------------------------------------------ */ function getPhaseIndex(phase: string): number { const p = phase.toLowerCase() if (p.includes('recon') || p.includes('initializing')) return 0 if (p.includes('analysis') || p.includes('attack surface')) return 1 if (p.includes('test') || p.includes('vuln')) return 2 if (p.includes('enhance') || p.includes('finding')) return 3 if (p.includes('complete') || p.includes('report')) return 4 return 0 } function relativeTime(dateStr: string): string { const diff = Math.floor((Date.now() - new Date(dateStr).getTime()) / 1000) if (diff < 0) return 'just now' if (diff < 60) return `${diff}s ago` if (diff < 3600) return `${Math.floor(diff / 60)}m ago` if (diff < 86400) return `${Math.floor(diff / 3600)}h ago` return `${Math.floor(diff / 86400)}d ago` } /* ------------------------------------------------------------------ */ /* Toast System */ /* ------------------------------------------------------------------ */ interface Toast { id: number message: string type: 'success' | 'error' | 'info' } let _toastSeq = 0 function ToastContainer({ toasts, onDismiss }: { toasts: Toast[]; onDismiss: (id: number) => void }) { if (toasts.length === 0) return null const borderColor: Record = { success: 'border-green-500', error: 'border-red-500', info: 'border-blue-500', } return (
{toasts.map(t => (
{t.message}
))}
) } /* ================================================================== */ /* Main Component */ /* ================================================================== */ export default function AgentStatusPage() { const { agentId } = useParams<{ agentId: string }>() const navigate = useNavigate() const scriptLogsEndRef = useRef(null) const llmLogsEndRef = useRef(null) const consecutiveErrorsRef = useRef(0) const [status, setStatus] = useState(null) const [logs, setLogs] = useState([]) const [isLoading, setIsLoading] = useState(true) const [error, setError] = useState(null) const [expandedFindings, setExpandedFindings] = useState>(new Set()) const [isGeneratingReport, setIsGeneratingReport] = useState(false) const [isStopping, setIsStopping] = useState(false) const [autoScroll, setAutoScroll] = useState(true) const [refreshing, setRefreshing] = useState(false) // Toast state const [toasts, setToasts] = useState([]) const [connectionLost, setConnectionLost] = useState(false) // Custom prompt state const [customPrompt, setCustomPrompt] = useState('') const [isSubmittingPrompt, setIsSubmittingPrompt] = useState(false) // Phase skip state const [skipConfirm, setSkipConfirm] = useState(null) const [isSkipping, setIsSkipping] = useState(false) const [skippedPhases, setSkippedPhases] = useState>(new Set()) // AI report state const [isGeneratingAiReport, setIsGeneratingAiReport] = useState(false) /* ── Toast helpers ──────────────────────────────────────────── */ const addToast = useCallback((message: string, type: Toast['type'] = 'info') => { const id = ++_toastSeq setToasts(prev => [...prev.slice(-4), { id, message, type }]) setTimeout(() => setToasts(prev => prev.filter(t => t.id !== id)), 5000) }, []) const dismissToast = useCallback((id: number) => { setToasts(prev => prev.filter(t => t.id !== id)) }, []) /* ── Derived log streams (memoized) ────────────────────────── */ const scriptLogs = useMemo( () => logs.filter(l => l.source === 'script' || (!l.source && !l.message.includes('[LLM]') && !l.message.includes('[AI]'))), [logs] ) const llmLogs = useMemo( () => logs.filter(l => l.source === 'llm' || l.message.includes('[LLM]') || l.message.includes('[AI]')), [logs] ) /* ── Severity counts (memoized) ────────────────────────────── */ const severityCounts = useMemo(() => { if (!status) return { critical: 0, high: 0, medium: 0, low: 0, info: 0 } const counts: Record = { critical: 0, high: 0, medium: 0, low: 0, info: 0 } for (const f of status.findings) { if (f.severity in counts) counts[f.severity]++ } return counts }, [status]) /* ── Data fetch ────────────────────────────────────────────── */ const fetchStatus = useCallback(async () => { if (!agentId) return try { const [statusData, logsData] = await Promise.all([ agentApi.getStatus(agentId), agentApi.getLogs(agentId, 500), ]) setStatus(statusData) setLogs(logsData.logs) setError(null) if (consecutiveErrorsRef.current >= 3) { setConnectionLost(false) addToast('Connection restored', 'success') } consecutiveErrorsRef.current = 0 } catch (err: unknown) { const apiErr = err as { response?: { status?: number } } if (apiErr.response?.status === 404) { setError('Agent not found') } else { console.error('Failed to fetch agent status:', err) consecutiveErrorsRef.current++ if (consecutiveErrorsRef.current >= 3) setConnectionLost(true) } } finally { setIsLoading(false) } }, [agentId, addToast]) // Poll for status updates useEffect(() => { if (!agentId) return fetchStatus() const interval = setInterval(() => { if (status?.status === 'running' || status?.status === 'paused') { fetchStatus() } }, 5000) return () => clearInterval(interval) }, [agentId, status?.status, fetchStatus]) // Auto-scroll logs useEffect(() => { if (autoScroll) { scriptLogsEndRef.current?.scrollIntoView({ behavior: 'smooth' }) llmLogsEndRef.current?.scrollIntoView({ behavior: 'smooth' }) } }, [logs, autoScroll]) // Track skipped phases from status updates useEffect(() => { if (!status) return const phase = status.phase.toLowerCase() if (phase.includes('_skipped')) { const skippedKey = phase.replace('_skipped', '') setSkippedPhases(prev => new Set(prev).add(skippedKey)) } }, [status?.phase]) /* ── Handlers ──────────────────────────────────────────────── */ const handleRefresh = useCallback(async () => { setRefreshing(true) await fetchStatus() setRefreshing(false) addToast('Status refreshed', 'info') }, [fetchStatus, addToast]) const toggleFinding = useCallback((id: string) => { setExpandedFindings(prev => { const next = new Set(prev) if (next.has(id)) { next.delete(id) } else { next.add(id) } return next }) }, []) const copyToClipboard = useCallback((text: string) => { navigator.clipboard.writeText(text) addToast('Copied to clipboard', 'success') }, [addToast]) /* ── Report generation ──────────────────────────────────────── */ const generateReportData = useCallback(() => { if (!status) return null const severityBreakdown: Record = { critical: 0, high: 0, medium: 0, low: 0, info: 0 } for (const f of status.findings) { if (f.severity in severityBreakdown) severityBreakdown[f.severity]++ } return { report_info: { agent_id: agentId, target: status.target, mode: status.mode, status: status.status, started_at: status.started_at, completed_at: status.completed_at || new Date().toISOString(), total_findings: status.findings.length, severity_breakdown: severityBreakdown, }, findings: status.findings.map(f => ({ id: f.id, title: f.title, severity: f.severity, type: f.vulnerability_type, cvss_score: f.cvss_score, cvss_vector: f.cvss_vector, cwe_id: f.cwe_id, affected_endpoint: f.affected_endpoint, parameter: f.parameter, payload: f.payload, evidence: f.evidence, request: f.request, response: f.response, description: f.description, impact: f.impact, poc_code: f.poc_code, remediation: f.remediation, references: f.references, ai_verified: f.ai_verified, confidence: f.confidence, })), logs: logs.slice(-100), } }, [status, agentId, logs]) const generateHTMLReport = useCallback(() => { if (!status) return '' const esc = (s: string | undefined | null): string => (s || '').replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"') const sevColors: Record = { critical:'#ef4444', high:'#f97316', medium:'#eab308', low:'#3b82f6', info:'#6b7280' } const sevBg: Record = { critical:'rgba(239,68,68,.08)', high:'rgba(249,115,22,.08)', medium:'rgba(234,179,8,.08)', low:'rgba(59,130,246,.08)', info:'rgba(107,114,128,.08)' } const owaspMap: Record = { sqli:'A03:2021 Injection', 'sql_injection':'A03:2021 Injection', xss:'A03:2021 Injection', 'xss_reflected':'A03:2021 Injection', 'xss_stored':'A03:2021 Injection', 'command_injection':'A03:2021 Injection', ssrf:'A10:2021 SSRF', idor:'A01:2021 Broken Access Control', bola:'A01:2021 Broken Access Control', csrf:'A01:2021 Broken Access Control', 'auth_bypass':'A07:2021 Auth Failures', 'open_redirect':'A01:2021 Broken Access Control', lfi:'A01:2021 Broken Access Control', 'path_traversal':'A01:2021 Broken Access Control', ssti:'A03:2021 Injection', xxe:'A05:2021 Misconfiguration', cors:'A05:2021 Misconfiguration', 'security_headers':'A05:2021 Misconfiguration', 'deserialization':'A08:2021 Integrity Failures', 'cryptographic_failures':'A02:2021 Crypto Failures', } const getOwasp = (type: string): string => owaspMap[type] || owaspMap[type.split('_')[0]] || '' // Sort findings by severity order const sevOrder = ['critical','high','medium','low','info'] const sorted = [...status.findings].sort((a,b) => sevOrder.indexOf(a.severity) - sevOrder.indexOf(b.severity)) const sc: Record = { critical:0, high:0, medium:0, low:0, info:0 } for (const f of sorted) { if (f.severity in sc) sc[f.severity]++ } const total = sorted.length const riskScore = Math.min(100, sc.critical*25 + sc.high*15 + sc.medium*8 + sc.low*3) const riskLevel = riskScore >= 75 ? 'CRITICAL' : riskScore >= 50 ? 'HIGH' : riskScore >= 25 ? 'MEDIUM' : 'LOW' const riskColor = riskScore >= 75 ? '#ef4444' : riskScore >= 50 ? '#f97316' : riskScore >= 25 ? '#eab308' : '#22c55e' // Severity distribution bar widths const barPcts = sevOrder.map(s => total > 0 ? Math.round((sc[s]/total)*100) : 0) // Table of contents const tocHtml = sorted.map((f, i) => ` ${f.severity.toUpperCase()} ${esc(f.title)} ${esc(f.vulnerability_type)} ` ).join('') // Build each finding card const findingsHtml = sorted.map((f, idx) => { const color = sevColors[f.severity] const bg = sevBg[f.severity] const owasp = getOwasp(f.vulnerability_type) const cweLink = f.cwe_id ? `https://cwe.mitre.org/data/definitions/${f.cwe_id.replace('CWE-','')}.html` : '' const confScore = f.confidence_score || 0 const confColor = confScore >= 80 ? '#22c55e' : confScore >= 50 ? '#eab308' : '#ef4444' const confLabel = confScore >= 80 ? 'Confirmed' : confScore >= 50 ? 'Likely' : 'Unconfirmed' const section = (title: string, content: string, icon: string = '') => `
${icon ? `${icon}` : ''}

${title}

${content}
` const codeBlock = (text: string, maxLen = 3000) => `
${esc(text.slice(0,maxLen))}
` return `
${f.severity} FINDING #${idx+1} of ${total} ${owasp ? `${owasp}` : ''} ${confScore > 0 ? `${confScore}% ${confLabel}` : ''}

${esc(f.title)}

${esc(f.affected_endpoint)}
${f.cvss_score ? `
CVSS 3.1
${f.cvss_score}
${f.cvss_vector ? `
${esc(f.cvss_vector)}
` : ''}
` : ''} ${f.cwe_id ? ` ` : ''}
TYPE
${esc(f.vulnerability_type)}
${f.parameter ? `
PARAMETER
${esc(f.parameter)}
` : ''}
${f.description ? section('Description', `

${esc(f.description)}

`, '📋') : ''} ${f.evidence ? section('Evidence', codeBlock(f.evidence), '🔍') : ''} ${f.payload ? section('Payload', codeBlock(f.payload, 1000), '💉') : ''} ${f.request ? section('HTTP Request', codeBlock(f.request, 2000), '📤') : ''} ${f.response ? section('HTTP Response (excerpt)', codeBlock(f.response, 2000), '📥') : ''} ${f.poc_code ? section('Proof of Concept Code', codeBlock(f.poc_code, 4000), '⚡') : ''} ${f.proof_of_execution ? section('Proof of Execution', `

${esc(f.proof_of_execution)}

`, '✅') : ''} ${f.impact ? section('Impact', `

${esc(f.impact)}

`, '⚠️') : ''} ${f.remediation ? `
🛡️

Remediation

${esc(f.remediation)}

` : ''} ${f.references && f.references.length > 0 ? section('References', ``, '📚') : ''}
` }).join('') // Unique affected endpoints const uniqueEndpoints = [...new Set(sorted.map(f => f.affected_endpoint).filter(Boolean))] const uniqueTypes = [...new Set(sorted.map(f => f.vulnerability_type).filter(Boolean))] return ` Security Assessment Report - ${esc(status.target)}
Confidential Security Report

Penetration Test Report

Target: ${esc(status.target)}
${new Date().toLocaleDateString('en-US', { weekday:'long', year:'numeric', month:'long', day:'numeric' })}  •  Agent: ${esc(agentId || '')}  •  Mode: ${esc(MODE_LABELS[status.mode] || status.mode)}
Risk Level
${riskScore}
${riskLevel}
Findings Breakdown
${total}
Total
${sevOrder.map(s => `
${sc[s]}
${s}
`).join('')}
${total > 0 ? `
${sevOrder.map((s,i) => barPcts[i] > 0 ? `
` : '').join('')}
` : ''}

Executive Summary

A security assessment was performed against ${esc(status.target)} using NeuroSploit AI-powered penetration testing. The assessment identified ${total} security finding${total !== 1 ? 's' : ''} across ${uniqueEndpoints.length} unique endpoint${uniqueEndpoints.length !== 1 ? 's' : ''} covering ${uniqueTypes.length} distinct vulnerability type${uniqueTypes.length !== 1 ? 's' : ''}. ${sc.critical > 0 ? `

⚠ ${sc.critical} critical-severity finding${sc.critical > 1 ? 's' : ''} require${sc.critical === 1 ? 's' : ''} immediate remediation.` : ''} ${sc.high > 0 ? ` ${sc.high} high-severity finding${sc.high > 1 ? 's' : ''} should be addressed promptly.` : ''} ${sc.critical === 0 && sc.high === 0 && total > 0 ? ` No critical or high-severity vulnerabilities were identified.` : ''} ${total === 0 ? ` No vulnerabilities were identified during this assessment.` : ''}

${total > 0 ? `

Findings Index

${tocHtml}
Severity Finding Type

Detailed Findings (${total})

${findingsHtml}
` : ''}

Scope & Methodology

Target URL${esc(status.target)}
Assessment Mode${esc(MODE_LABELS[status.mode] || status.mode)}
Agent ID${esc(agentId || '')}
Start Time${status.started_at ? new Date(status.started_at).toLocaleString() : 'N/A'}
End Time${status.completed_at ? new Date(status.completed_at).toLocaleString() : 'N/A'}
Endpoints Tested${uniqueEndpoints.length}
Vulnerability Types${uniqueTypes.length}

This assessment was conducted using NeuroSploit v3 AI-powered penetration testing platform with 100 vulnerability type coverage, automated payload generation, and AI-driven validation. Findings were validated through negative control testing, proof-of-execution verification, and confidence scoring.

Generated by NeuroSploit v3 — AI-Powered Penetration Testing Platform
${new Date().toISOString()}
CONFIDENTIAL — This document contains sensitive security information. Distribution is restricted to authorized personnel only.
` }, [status, agentId]) const handleGenerateReport = useCallback(async (format: 'json' | 'html' = 'json') => { if (!agentId || !status) return setIsGeneratingReport(true) try { if (format === 'html') { const htmlContent = generateHTMLReport() const blob = new Blob([htmlContent], { type: 'text/html' }) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = `neurosploit-report-${agentId}-${new Date().toISOString().split('T')[0]}.html` a.click() URL.revokeObjectURL(url) addToast('HTML report downloaded', 'success') } else { const reportData = status.report || generateReportData() const blob = new Blob([JSON.stringify(reportData, null, 2)], { type: 'application/json' }) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = `neurosploit-report-${agentId}-${new Date().toISOString().split('T')[0]}.json` a.click() URL.revokeObjectURL(url) addToast('JSON report downloaded', 'success') } } finally { setIsGeneratingReport(false) } }, [agentId, status, generateHTMLReport, generateReportData, addToast]) const handleGenerateAiReport = useCallback(async () => { if (!status?.scan_id) return setIsGeneratingAiReport(true) try { const report = await reportsApi.generateAiReport({ scan_id: status.scan_id, title: `AI Report - ${status.target || 'Agent Scan'}`, }) window.open(reportsApi.getViewUrl(report.id), '_blank') addToast('AI report generated successfully', 'success') } catch (err) { console.error('Failed to generate AI report:', err) addToast('Failed to generate AI report', 'error') } finally { setIsGeneratingAiReport(false) } }, [status, addToast]) /* ── Scan controls ──────────────────────────────────────────── */ const handleStopScan = useCallback(async () => { if (!agentId) return setIsStopping(true) try { await agentApi.stop(agentId) const statusData = await agentApi.getStatus(agentId) setStatus(statusData) addToast('Agent stopped', 'info') } catch (err) { console.error('Failed to stop agent:', err) addToast('Failed to stop agent', 'error') } finally { setIsStopping(false) } }, [agentId, addToast]) const handlePauseScan = useCallback(async () => { if (!agentId) return try { await agentApi.pause(agentId) const statusData = await agentApi.getStatus(agentId) setStatus(statusData) addToast('Agent paused', 'info') } catch (err) { console.error('Failed to pause agent:', err) addToast('Failed to pause agent', 'error') } }, [agentId, addToast]) const handleResumeScan = useCallback(async () => { if (!agentId) return try { await agentApi.resume(agentId) const statusData = await agentApi.getStatus(agentId) setStatus(statusData) addToast('Agent resumed', 'success') } catch (err) { console.error('Failed to resume agent:', err) addToast('Failed to resume agent', 'error') } }, [agentId, addToast]) /* ── Custom prompt ──────────────────────────────────────────── */ const handleSubmitPrompt = useCallback(async () => { if (!customPrompt.trim() || !agentId) return setIsSubmittingPrompt(true) const sentPrompt = customPrompt try { await agentApi.sendPrompt(agentId, customPrompt) setCustomPrompt('') addToast(`Prompt sent: "${sentPrompt.slice(0, 50)}${sentPrompt.length > 50 ? '...' : ''}"`, 'success') const [statusData, logsData] = await Promise.all([ agentApi.getStatus(agentId), agentApi.getLogs(agentId, 200), ]) setStatus(statusData) setLogs(logsData.logs || []) } catch (err) { console.error('Failed to send prompt:', err) addToast('Failed to send prompt', 'error') } finally { setIsSubmittingPrompt(false) } }, [customPrompt, agentId, addToast]) /* ── Phase skip ─────────────────────────────────────────────── */ const handleSkipToPhase = useCallback(async (targetPhase: string) => { if (!agentId) return setIsSkipping(true) try { await agentApi.skipToPhase(agentId, targetPhase) const currentIndex = status ? getPhaseIndex(status.phase) : 0 const targetIndex = SCAN_PHASES.findIndex(p => p.key === targetPhase) const newSkipped = new Set(skippedPhases) for (let i = currentIndex; i < targetIndex; i++) { newSkipped.add(SCAN_PHASES[i].key) } setSkippedPhases(newSkipped) setSkipConfirm(null) addToast(`Skipped to ${targetPhase}`, 'info') } catch (err) { console.error('Failed to skip phase:', err) addToast('Failed to skip phase', 'error') } finally { setIsSkipping(false) } }, [agentId, status, skippedPhases, addToast]) /* ── Sub-renderers ──────────────────────────────────────────── */ const renderFindingDetails = useCallback((finding: AgentFinding) => (
{/* CVSS & Meta Info */}
CVSS: = 9 ? 'text-red-500' : finding.cvss_score >= 7 ? 'text-orange-500' : finding.cvss_score >= 4 ? 'text-yellow-500' : 'text-blue-500' }`}> {finding.cvss_score?.toFixed(1) || 'N/A'}
{finding.cwe_id && ( )} {finding.vulnerability_type} {finding.confidence && ( {finding.confidence} confidence )} {typeof finding.confidence_score === 'number' && ( = 90 ? 'bg-green-500/15 text-green-400 border-green-500/30' : finding.confidence_score >= 60 ? 'bg-yellow-500/15 text-yellow-400 border-yellow-500/30' : 'bg-red-500/15 text-red-400 border-red-500/30' }`}> {finding.confidence_score}/100 )}
{/* CVSS Vector */} {finding.cvss_vector && (
{finding.cvss_vector}
)} {/* Technical Details Section */}

Technical Details

{/* Affected Endpoint */}
Endpoint:
{finding.affected_endpoint}
{/* Parameter */} {finding.parameter && (
Vulnerable Parameter: {finding.parameter}
)} {/* Payload */} {finding.payload && (
Payload Used:
{finding.payload}
)} {/* HTTP Request */} {finding.request && (
HTTP Request:
              {finding.request}
            
)} {/* HTTP Response */} {finding.response && (
HTTP Response (excerpt):
              {finding.response}
            
)} {/* Evidence */} {finding.evidence && (
Evidence:

{finding.evidence}

)}
{/* Description */} {finding.description && (

Description

{finding.description}

)} {/* Impact */} {finding.impact && (

Impact

{finding.impact}

)} {/* PoC Code */} {finding.poc_code && (

Proof of Concept

            {finding.poc_code}
          
)} {/* Remediation */} {finding.remediation && (

Remediation

{finding.remediation}

)} {/* Confidence Breakdown */} {finding.confidence_breakdown && Object.keys(finding.confidence_breakdown).length > 0 && (

Confidence Breakdown

{Object.entries(finding.confidence_breakdown).map(([key, val]) => (
{key.replace(/_/g, ' ')} 0 ? 'text-green-400' : val < 0 ? 'text-red-400' : 'text-dark-500' }`}> {val > 0 ? '+' : ''}{val}
))}
)} {/* Proof of Execution */} {finding.proof_of_execution && (

Proof of Execution

{finding.proof_of_execution}

)} {/* References */} {finding.references && finding.references.length > 0 && (

References

)}
), [copyToClipboard]) const renderLogViewer = useCallback(( logsToShow: AgentLog[], endRef: React.RefObject, title: string, icon: React.ReactNode, ) => (
{logsToShow.length === 0 ? (

No {title.toLowerCase()} activity yet...

) : ( logsToShow.map((log, i) => { const isUserPrompt = log.message.includes('[USER PROMPT]') const isAIResponse = log.message.includes('[AI RESPONSE]') || log.message.includes('[AI]') return (
{new Date(log.time).toLocaleTimeString()} {isUserPrompt ? : isAIResponse ? : icon} {log.message}
) }) )}
), []) /* ── Loading state ──────────────────────────────────────────── */ if (isLoading) { return (
) } /* ── Error state ────────────────────────────────────────────── */ if (error) { return (

{error}

) } if (!status) return null /* ── Main render ────────────────────────────────────────────── */ return ( <>
{/* Connection Lost Banner */} {connectionLost && (
Connection issues detected. Retrying...
)} {/* Header */}

Agent: {agentId}

{PHASE_ICONS[status.status]} {status.status.charAt(0).toUpperCase() + status.status.slice(1)} Mode: {MODE_LABELS[status.mode] || status.mode} {status.task && Task: {status.task}} {status.started_at && ( Started {relativeTime(status.started_at)} )}
{/* Refresh button */} {status.status === 'running' && ( <> )} {status.status === 'paused' && ( <> )} {status.scan_id && ( )} {/* Always show export if there are findings */} {(status.findings.length > 0 || status.report) && ( <> {status.scan_id && ( )} )}
{/* Progress with Phase Steps */} {(status.status === 'running' || status.status === 'completed' || status.status === 'stopped' || status.status === 'paused') && (
{/* Phase Steps with Skip */}
{SCAN_PHASES.map((phase, index) => { const currentIndex = status.status === 'completed' ? 4 : getPhaseIndex(status.phase) const isActive = index === currentIndex const isCompleted = index < currentIndex || status.status === 'completed' const isStopped = status.status === 'stopped' && index > currentIndex const isSkipped = skippedPhases.has(phase.key) const canSkipTo = (status.status === 'running' || status.status === 'paused') && index > currentIndex && phase.key !== 'completed' return (
{/* Connector line */} {index > 0 && (
)} {/* Phase node */}
canSkipTo && setSkipConfirm(phase.key)} > {isSkipped ? : isCompleted ? : isActive ? (PHASE_ICONS[phase.key === 'recon' ? 'reconnaissance' : phase.key] || {index + 1}) : isStopped ? : canSkipTo ? : {index + 1}}
{isSkipped ? `${phase.label} (skipped)` : phase.label} {/* Skip tooltip on hover */} {canSkipTo && (
Skip to {phase.label}
)} {/* Inline skip confirmation */} {skipConfirm === phase.key && (

Skip to {phase.label}?

)}
) })}
{/* Progress Bar */}
{PHASE_ICONS[status.phase.toLowerCase()] || } {status.phase.replace(/_/g, ' ')}
{status.progress}%
)} {/* Stats */}
{/* Total */}

{status.findings_count}

Total Findings

{/* Per-severity cards */} {SEVERITY_ORDER.map((sev, idx) => { const colorClass: Record = { critical: 'text-red-500', high: 'text-orange-500', medium: 'text-yellow-500', low: 'text-blue-500', info: 'text-gray-400', } const borderClass: Record = { critical: 'border-red-500/20', high: 'border-orange-500/20', medium: 'border-yellow-500/20', low: 'border-blue-500/20', info: 'border-dark-700', } return (

{severityCounts[sev]}

{sev}

) })}
{/* Custom Prompt Input */} {status.status === 'running' && (

Custom AI Prompt

Send a custom instruction to the AI agent. Example: "Test for IDOR on /api/users/[id]" or "Check for XXE in XML endpoints"

setCustomPrompt(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleSubmitPrompt()} placeholder="Enter custom vulnerability test prompt..." className="flex-1 bg-dark-800 border border-dark-600 rounded-lg px-4 py-2 text-white placeholder-dark-400 focus:outline-none focus:border-primary-500 transition-colors" />
)} {/* Findings */}
{status.findings.length === 0 ? (

{status.status === 'running' ? 'Scanning for vulnerabilities...' : 'No vulnerabilities found'}

{status.status === 'running' && (

Findings will appear here as they are discovered

)}
) : ( status.findings.map((finding) => (
{/* Finding Header */}
toggleFinding(finding.id)} >
{expandedFindings.has(finding.id) ? ( ) : ( )}

{finding.title}

{finding.affected_endpoint}

{finding.parameter && (

Parameter: {finding.parameter}

)}
{finding.ai_verified && ( AI Verified )} {typeof finding.confidence_score === 'number' && ( = 90 ? 'bg-green-500/15 text-green-400 border-green-500/30' : finding.confidence_score >= 60 ? 'bg-yellow-500/15 text-yellow-400 border-yellow-500/30' : 'bg-red-500/15 text-red-400 border-red-500/30' }`}> {finding.confidence_score} )}
{/* Finding Details */} {expandedFindings.has(finding.id) && renderFindingDetails(finding)}
)) )}
{/* Split Log Viewers */}
{/* Script Activity Log */} Script Activity {scriptLogs.length}
} subtitle="Tool executions, HTTP requests, scanning progress" > {renderLogViewer(scriptLogs, scriptLogsEndRef, 'Script', )} {/* LLM Activity Log */} AI Analysis {llmLogs.length}
} subtitle="LLM reasoning, vulnerability analysis, decisions" > {renderLogViewer(llmLogs, llmLogsEndRef, 'AI', )}
{/* Auto-scroll toggle */}
{/* Report Summary */} {(status.status === 'completed' || status.status === 'stopped') && (status.report || status.findings.length > 0) && (() => { const reportData = status.report || { summary: { target: status.target, mode: status.mode, duration: status.started_at ? `${Math.round((new Date(status.completed_at || new Date().toISOString()).getTime() - new Date(status.started_at).getTime()) / 60000)} min` : 'N/A', total_findings: status.findings.length, severity_breakdown: { critical: status.findings.filter(f => f.severity === 'critical').length, high: status.findings.filter(f => f.severity === 'high').length, medium: status.findings.filter(f => f.severity === 'medium').length, low: status.findings.filter(f => f.severity === 'low').length, info: status.findings.filter(f => f.severity === 'info').length, }, }, executive_summary: status.status === 'stopped' ? `Scan was stopped by user. ${status.findings.length} finding(s) discovered before stopping.` : undefined, recommendations: [] as string[], } return (
{status.status === 'stopped' && (
Scan was stopped - showing partial results
)}

Target

{reportData.summary.target}

Mode

{MODE_LABELS[reportData.summary.mode] || reportData.summary.mode}

Duration

{reportData.summary.duration}

Total Findings

{reportData.summary.total_findings}

{reportData.executive_summary && (

Executive Summary

{reportData.executive_summary}

)} {reportData.recommendations && reportData.recommendations.length > 0 && (

Recommendations

    {reportData.recommendations.map((rec: string, i: number) => (
  • {rec}
  • ))}
)}
) })()} {/* Error Display */} {status.error && (

Agent Error

{status.error}

)}
) }