mirror of
https://github.com/CyberSecurityUP/NeuroSploit.git
synced 2026-02-12 14:02:45 +00:00
Add files via upload
This commit is contained in:
@@ -1,34 +1,43 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useEffect, useCallback, useState } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Activity, Shield, AlertTriangle, Plus, ArrowRight } from 'lucide-react'
|
||||
import { Activity, Shield, AlertTriangle, Plus, ArrowRight, CheckCircle, StopCircle, Clock, FileText, Cpu } from 'lucide-react'
|
||||
import Card from '../components/common/Card'
|
||||
import Button from '../components/common/Button'
|
||||
import { SeverityBadge } from '../components/common/Badge'
|
||||
import { dashboardApi } from '../services/api'
|
||||
import { useDashboardStore } from '../store'
|
||||
import type { ActivityFeedItem } from '../types'
|
||||
|
||||
export default function HomePage() {
|
||||
const { stats, recentScans, recentVulnerabilities, setStats, setRecentScans, setRecentVulnerabilities, setLoading } = useDashboardStore()
|
||||
const [activityFeed, setActivityFeed] = useState<ActivityFeedItem[]>([])
|
||||
|
||||
const fetchData = useCallback(async () => {
|
||||
try {
|
||||
const [statsData, recentData, activityData] = await Promise.all([
|
||||
dashboardApi.getStats(),
|
||||
dashboardApi.getRecent(5),
|
||||
dashboardApi.getActivityFeed(15)
|
||||
])
|
||||
setStats(statsData)
|
||||
setRecentScans(recentData.recent_scans)
|
||||
setRecentVulnerabilities(recentData.recent_vulnerabilities)
|
||||
setActivityFeed(activityData.activities)
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch dashboard data:', error)
|
||||
}
|
||||
}, [setStats, setRecentScans, setRecentVulnerabilities])
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const [statsData, recentData] = await Promise.all([
|
||||
dashboardApi.getStats(),
|
||||
dashboardApi.getRecent(5)
|
||||
])
|
||||
setStats(statsData)
|
||||
setRecentScans(recentData.recent_scans)
|
||||
setRecentVulnerabilities(recentData.recent_vulnerabilities)
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch dashboard data:', error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
fetchData()
|
||||
}, [])
|
||||
// Initial fetch
|
||||
setLoading(true)
|
||||
fetchData().finally(() => setLoading(false))
|
||||
|
||||
// Periodic refresh every 30 seconds
|
||||
const refreshInterval = setInterval(fetchData, 30000)
|
||||
|
||||
return () => clearInterval(refreshInterval)
|
||||
}, [fetchData, setLoading])
|
||||
|
||||
const statCards = [
|
||||
{
|
||||
@@ -39,26 +48,57 @@ export default function HomePage() {
|
||||
bgColor: 'bg-blue-500/10',
|
||||
},
|
||||
{
|
||||
label: 'Running Scans',
|
||||
label: 'Running',
|
||||
value: stats?.scans.running || 0,
|
||||
icon: Shield,
|
||||
color: 'text-green-400',
|
||||
bgColor: 'bg-green-500/10',
|
||||
},
|
||||
{
|
||||
label: 'Vulnerabilities',
|
||||
label: 'Completed',
|
||||
value: stats?.scans.completed || 0,
|
||||
icon: CheckCircle,
|
||||
color: 'text-emerald-400',
|
||||
bgColor: 'bg-emerald-500/10',
|
||||
},
|
||||
{
|
||||
label: 'Stopped',
|
||||
value: stats?.scans.stopped || 0,
|
||||
icon: StopCircle,
|
||||
color: 'text-yellow-400',
|
||||
bgColor: 'bg-yellow-500/10',
|
||||
},
|
||||
]
|
||||
|
||||
const vulnCards = [
|
||||
{
|
||||
label: 'Total Vulns',
|
||||
value: stats?.vulnerabilities.total || 0,
|
||||
icon: AlertTriangle,
|
||||
color: 'text-red-400',
|
||||
bgColor: 'bg-red-500/10',
|
||||
},
|
||||
{
|
||||
label: 'Critical Issues',
|
||||
label: 'Critical',
|
||||
value: stats?.vulnerabilities.critical || 0,
|
||||
icon: AlertTriangle,
|
||||
color: 'text-red-500',
|
||||
bgColor: 'bg-red-600/10',
|
||||
},
|
||||
{
|
||||
label: 'High',
|
||||
value: stats?.vulnerabilities.high || 0,
|
||||
icon: AlertTriangle,
|
||||
color: 'text-orange-400',
|
||||
bgColor: 'bg-orange-500/10',
|
||||
},
|
||||
{
|
||||
label: 'Medium',
|
||||
value: stats?.vulnerabilities.medium || 0,
|
||||
icon: AlertTriangle,
|
||||
color: 'text-yellow-400',
|
||||
bgColor: 'bg-yellow-500/10',
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
@@ -77,8 +117,8 @@ export default function HomePage() {
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Stats Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{/* Scan Stats Grid */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
{statCards.map((stat) => (
|
||||
<Card key={stat.label} className="hover:border-dark-700 transition-colors">
|
||||
<div className="flex items-center gap-4">
|
||||
@@ -94,6 +134,23 @@ export default function HomePage() {
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Vulnerability Stats Grid */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
{vulnCards.map((stat) => (
|
||||
<Card key={stat.label} className="hover:border-dark-700 transition-colors">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className={`p-3 rounded-lg ${stat.bgColor}`}>
|
||||
<stat.icon className={`w-6 h-6 ${stat.color}`} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-2xl font-bold text-white">{stat.value}</p>
|
||||
<p className="text-sm text-dark-400">{stat.label}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Severity Distribution */}
|
||||
{stats && stats.vulnerabilities.total > 0 && (
|
||||
<Card title="Vulnerability Distribution">
|
||||
@@ -205,6 +262,76 @@ export default function HomePage() {
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Activity Feed */}
|
||||
<Card
|
||||
title="Activity Feed"
|
||||
subtitle="Recent activities across all scans"
|
||||
>
|
||||
<div className="space-y-2 max-h-[400px] overflow-auto">
|
||||
{activityFeed.length === 0 ? (
|
||||
<p className="text-dark-400 text-center py-4">No recent activity.</p>
|
||||
) : (
|
||||
activityFeed.map((activity, idx) => (
|
||||
<Link
|
||||
key={`${activity.type}-${activity.timestamp}-${idx}`}
|
||||
to={activity.link}
|
||||
className="flex items-start gap-3 p-3 bg-dark-900/50 rounded-lg hover:bg-dark-900 transition-colors"
|
||||
>
|
||||
{/* Activity Icon */}
|
||||
<div className={`mt-0.5 p-2 rounded-lg ${
|
||||
activity.type === 'scan' ? 'bg-blue-500/20 text-blue-400' :
|
||||
activity.type === 'vulnerability' ? 'bg-red-500/20 text-red-400' :
|
||||
activity.type === 'agent_task' ? 'bg-purple-500/20 text-purple-400' :
|
||||
'bg-green-500/20 text-green-400'
|
||||
}`}>
|
||||
{activity.type === 'scan' ? <Shield className="w-4 h-4" /> :
|
||||
activity.type === 'vulnerability' ? <AlertTriangle className="w-4 h-4" /> :
|
||||
activity.type === 'agent_task' ? <Cpu className="w-4 h-4" /> :
|
||||
<FileText className="w-4 h-4" />}
|
||||
</div>
|
||||
|
||||
{/* Activity Content */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-dark-500 uppercase font-medium">
|
||||
{activity.type.replace('_', ' ')}
|
||||
</span>
|
||||
<span className="text-xs text-dark-600">•</span>
|
||||
<span className="text-xs text-dark-500">{activity.action}</span>
|
||||
</div>
|
||||
<p className="font-medium text-white truncate mt-0.5">{activity.title}</p>
|
||||
{activity.description && (
|
||||
<p className="text-xs text-dark-400 truncate">{activity.description}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Activity Meta */}
|
||||
<div className="flex flex-col items-end gap-1">
|
||||
{activity.severity && (
|
||||
<SeverityBadge severity={activity.severity} />
|
||||
)}
|
||||
{activity.status && !activity.severity && (
|
||||
<span className={`text-xs px-2 py-0.5 rounded font-medium ${
|
||||
activity.status === 'completed' ? 'bg-green-500/20 text-green-400' :
|
||||
activity.status === 'running' ? 'bg-blue-500/20 text-blue-400' :
|
||||
activity.status === 'failed' ? 'bg-red-500/20 text-red-400' :
|
||||
activity.status === 'stopped' ? 'bg-yellow-500/20 text-yellow-400' :
|
||||
'bg-dark-700 text-dark-300'
|
||||
}`}>
|
||||
{activity.status}
|
||||
</span>
|
||||
)}
|
||||
<span className="text-xs text-dark-500 flex items-center gap-1">
|
||||
<Clock className="w-3 h-3" />
|
||||
{new Date(activity.timestamp).toLocaleTimeString()}
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,31 +2,33 @@ import { useEffect, useMemo, useState } from 'react'
|
||||
import { useParams, useNavigate } from 'react-router-dom'
|
||||
import {
|
||||
Globe, FileText, StopCircle, RefreshCw, ChevronDown, ChevronRight,
|
||||
ExternalLink, Copy, Shield, AlertTriangle
|
||||
ExternalLink, Copy, Shield, AlertTriangle, Cpu, CheckCircle, XCircle, Clock
|
||||
} from 'lucide-react'
|
||||
import Card from '../components/common/Card'
|
||||
import Button from '../components/common/Button'
|
||||
import { SeverityBadge } from '../components/common/Badge'
|
||||
import { scansApi, reportsApi } from '../services/api'
|
||||
import { scansApi, reportsApi, agentTasksApi } from '../services/api'
|
||||
import { wsService } from '../services/websocket'
|
||||
import { useScanStore } from '../store'
|
||||
import type { Endpoint, Vulnerability, WSMessage } from '../types'
|
||||
import type { Endpoint, Vulnerability, WSMessage, ScanAgentTask, Report } from '../types'
|
||||
|
||||
export default function ScanDetailsPage() {
|
||||
const { scanId } = useParams<{ scanId: string }>()
|
||||
const navigate = useNavigate()
|
||||
const {
|
||||
currentScan, endpoints, vulnerabilities, logs,
|
||||
currentScan, endpoints, vulnerabilities, logs, agentTasks,
|
||||
setCurrentScan, setEndpoints, setVulnerabilities,
|
||||
addEndpoint, addVulnerability, addLog, updateScan,
|
||||
addAgentTask, updateAgentTask, setAgentTasks,
|
||||
loadScanData, saveScanData, getVulnCounts
|
||||
} = useScanStore()
|
||||
|
||||
const [isGeneratingReport, setIsGeneratingReport] = useState(false)
|
||||
const [expandedVulns, setExpandedVulns] = useState<Set<string>>(new Set())
|
||||
const [activeTab, setActiveTab] = useState<'endpoints' | 'vulns'>('vulns')
|
||||
const [activeTab, setActiveTab] = useState<'vulns' | 'endpoints' | 'tasks'>('vulns')
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [autoGeneratedReport, setAutoGeneratedReport] = useState<Report | null>(null)
|
||||
|
||||
// Calculate vulnerability counts from actual data
|
||||
const vulnCounts = useMemo(() => getVulnCounts(), [vulnerabilities])
|
||||
@@ -45,9 +47,11 @@ export default function ScanDetailsPage() {
|
||||
const scan = await scansApi.get(scanId)
|
||||
setCurrentScan(scan)
|
||||
|
||||
const [endpointsData, vulnsData] = await Promise.all([
|
||||
const [endpointsData, vulnsData, tasksData, reportsData] = await Promise.all([
|
||||
scansApi.getEndpoints(scanId),
|
||||
scansApi.getVulnerabilities(scanId)
|
||||
scansApi.getVulnerabilities(scanId),
|
||||
agentTasksApi.list(scanId).catch(() => ({ tasks: [] })),
|
||||
reportsApi.list({ scanId, autoGenerated: true }).catch(() => ({ reports: [] }))
|
||||
])
|
||||
|
||||
// Only set if we have data from API
|
||||
@@ -57,6 +61,13 @@ export default function ScanDetailsPage() {
|
||||
if (vulnsData.vulnerabilities?.length > 0) {
|
||||
setVulnerabilities(vulnsData.vulnerabilities)
|
||||
}
|
||||
if (tasksData.tasks?.length > 0) {
|
||||
setAgentTasks(tasksData.tasks)
|
||||
}
|
||||
// Set auto-generated report if exists
|
||||
if (reportsData.reports?.length > 0) {
|
||||
setAutoGeneratedReport(reportsData.reports[0])
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error('Failed to fetch scan:', err)
|
||||
setError(err?.response?.data?.detail || 'Failed to load scan')
|
||||
@@ -73,9 +84,10 @@ export default function ScanDetailsPage() {
|
||||
const scan = await scansApi.get(scanId)
|
||||
setCurrentScan(scan)
|
||||
|
||||
const [endpointsData, vulnsData] = await Promise.all([
|
||||
const [endpointsData, vulnsData, tasksData] = await Promise.all([
|
||||
scansApi.getEndpoints(scanId),
|
||||
scansApi.getVulnerabilities(scanId)
|
||||
scansApi.getVulnerabilities(scanId),
|
||||
agentTasksApi.list(scanId).catch(() => ({ tasks: [] }))
|
||||
])
|
||||
|
||||
if (endpointsData.endpoints?.length > 0) {
|
||||
@@ -84,6 +96,9 @@ export default function ScanDetailsPage() {
|
||||
if (vulnsData.vulnerabilities?.length > 0) {
|
||||
setVulnerabilities(vulnsData.vulnerabilities)
|
||||
}
|
||||
if (tasksData.tasks?.length > 0) {
|
||||
setAgentTasks(tasksData.tasks)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Poll error:', err)
|
||||
}
|
||||
@@ -113,6 +128,29 @@ export default function ScanDetailsPage() {
|
||||
addVulnerability(message.vulnerability as Vulnerability)
|
||||
addLog('warning', `Found: ${(message.vulnerability as Vulnerability).title}`)
|
||||
break
|
||||
case 'stats_update':
|
||||
// Real-time stats update from backend
|
||||
if (message.stats) {
|
||||
const stats = message.stats as {
|
||||
total_vulnerabilities?: number
|
||||
critical?: number
|
||||
high?: number
|
||||
medium?: number
|
||||
low?: number
|
||||
info?: number
|
||||
total_endpoints?: number
|
||||
}
|
||||
updateScan(scanId, {
|
||||
total_vulnerabilities: stats.total_vulnerabilities,
|
||||
critical_count: stats.critical,
|
||||
high_count: stats.high,
|
||||
medium_count: stats.medium,
|
||||
low_count: stats.low,
|
||||
info_count: stats.info,
|
||||
total_endpoints: stats.total_endpoints
|
||||
})
|
||||
}
|
||||
break
|
||||
case 'log_message':
|
||||
addLog(message.level as string, message.message as string)
|
||||
break
|
||||
@@ -122,6 +160,65 @@ export default function ScanDetailsPage() {
|
||||
// Save data when scan completes
|
||||
saveScanData(scanId)
|
||||
break
|
||||
case 'scan_stopped':
|
||||
// Handle scan stopped by user
|
||||
if (message.summary) {
|
||||
const summary = message.summary as {
|
||||
total_vulnerabilities?: number
|
||||
critical?: number
|
||||
high?: number
|
||||
medium?: number
|
||||
low?: number
|
||||
info?: number
|
||||
total_endpoints?: number
|
||||
duration?: number
|
||||
progress?: number
|
||||
}
|
||||
updateScan(scanId, {
|
||||
status: 'stopped',
|
||||
progress: summary.progress || currentScan?.progress,
|
||||
total_vulnerabilities: summary.total_vulnerabilities,
|
||||
critical_count: summary.critical,
|
||||
high_count: summary.high,
|
||||
medium_count: summary.medium,
|
||||
low_count: summary.low,
|
||||
info_count: summary.info,
|
||||
total_endpoints: summary.total_endpoints,
|
||||
duration: summary.duration
|
||||
})
|
||||
} else {
|
||||
updateScan(scanId, { status: 'stopped' })
|
||||
}
|
||||
addLog('warning', 'Scan stopped by user')
|
||||
saveScanData(scanId)
|
||||
break
|
||||
case 'scan_failed':
|
||||
updateScan(scanId, { status: 'failed' })
|
||||
addLog('error', `Scan failed: ${message.error || 'Unknown error'}`)
|
||||
saveScanData(scanId)
|
||||
break
|
||||
case 'agent_task':
|
||||
case 'agent_task_started':
|
||||
// Handle new or updated agent task
|
||||
if (message.task) {
|
||||
addAgentTask(message.task as ScanAgentTask)
|
||||
}
|
||||
break
|
||||
case 'agent_task_completed':
|
||||
// Handle completed agent task
|
||||
if (message.task) {
|
||||
const task = message.task as ScanAgentTask
|
||||
updateAgentTask(task.id, task)
|
||||
}
|
||||
break
|
||||
case 'report_generated':
|
||||
// Handle auto-generated report
|
||||
if (message.report) {
|
||||
const report = message.report as Report
|
||||
setAutoGeneratedReport(report)
|
||||
addLog('info', `Report generated: ${report.title}`)
|
||||
}
|
||||
break
|
||||
case 'error':
|
||||
addLog('error', message.error as string)
|
||||
break
|
||||
@@ -236,10 +333,19 @@ export default function ScanDetailsPage() {
|
||||
Stop Scan
|
||||
</Button>
|
||||
)}
|
||||
{currentScan.status === 'completed' && (
|
||||
{autoGeneratedReport && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => window.open(reportsApi.getViewUrl(autoGeneratedReport.id), '_blank')}
|
||||
>
|
||||
<FileText className="w-4 h-4 mr-2" />
|
||||
View Report
|
||||
</Button>
|
||||
)}
|
||||
{(currentScan.status === 'completed' || currentScan.status === 'stopped') && (
|
||||
<Button onClick={handleGenerateReport} isLoading={isGeneratingReport}>
|
||||
<FileText className="w-4 h-4 mr-2" />
|
||||
Generate Report
|
||||
{autoGeneratedReport ? 'New Report' : 'Generate Report'}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@@ -263,6 +369,41 @@ export default function ScanDetailsPage() {
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Auto-generated Report Notification */}
|
||||
{autoGeneratedReport && (
|
||||
<div className="bg-green-500/10 border border-green-500/30 rounded-lg p-4 flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="bg-green-500/20 rounded-full p-2">
|
||||
<FileText className="w-5 h-5 text-green-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-white font-medium">
|
||||
{autoGeneratedReport.is_partial ? 'Partial Report Generated' : 'Report Generated'}
|
||||
</p>
|
||||
<p className="text-sm text-dark-400">
|
||||
{autoGeneratedReport.title || 'Scan report is ready to view'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => window.open(reportsApi.getViewUrl(autoGeneratedReport.id), '_blank')}
|
||||
>
|
||||
<ExternalLink className="w-4 h-4 mr-2" />
|
||||
View Report
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => setAutoGeneratedReport(null)}
|
||||
>
|
||||
Dismiss
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Stats */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-6 gap-4">
|
||||
<Card>
|
||||
@@ -319,6 +460,13 @@ export default function ScanDetailsPage() {
|
||||
<Globe className="w-4 h-4 mr-2" />
|
||||
Endpoints ({endpoints.length})
|
||||
</Button>
|
||||
<Button
|
||||
variant={activeTab === 'tasks' ? 'primary' : 'ghost'}
|
||||
onClick={() => setActiveTab('tasks')}
|
||||
>
|
||||
<Cpu className="w-4 h-4 mr-2" />
|
||||
Agent Tasks ({agentTasks.length})
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Vulnerabilities Tab */}
|
||||
@@ -553,6 +701,98 @@ export default function ScanDetailsPage() {
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Agent Tasks Tab */}
|
||||
{activeTab === 'tasks' && (
|
||||
<Card title="Agent Tasks" subtitle={`${agentTasks.length} tasks executed`}>
|
||||
<div className="space-y-3 max-h-[500px] overflow-auto">
|
||||
{agentTasks.length === 0 ? (
|
||||
<p className="text-dark-400 text-center py-8">
|
||||
{currentScan.status === 'running' ? 'Agent tasks will appear here...' : 'No agent tasks recorded'}
|
||||
</p>
|
||||
) : (
|
||||
agentTasks.map((task, idx) => (
|
||||
<div
|
||||
key={task.id || `task-${idx}`}
|
||||
className="p-4 bg-dark-900/50 rounded-lg border border-dark-700"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex items-start gap-3 flex-1">
|
||||
{/* Status Icon */}
|
||||
<div className={`mt-0.5 ${
|
||||
task.status === 'completed' ? 'text-green-400' :
|
||||
task.status === 'running' ? 'text-blue-400' :
|
||||
task.status === 'failed' ? 'text-red-400' :
|
||||
'text-dark-400'
|
||||
}`}>
|
||||
{task.status === 'completed' ? <CheckCircle className="w-5 h-5" /> :
|
||||
task.status === 'running' ? <RefreshCw className="w-5 h-5 animate-spin" /> :
|
||||
task.status === 'failed' ? <XCircle className="w-5 h-5" /> :
|
||||
<Clock className="w-5 h-5" />}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="font-medium text-white">{task.task_name}</p>
|
||||
{task.description && (
|
||||
<p className="text-sm text-dark-400 mt-1">{task.description}</p>
|
||||
)}
|
||||
<div className="flex flex-wrap items-center gap-3 mt-2 text-xs">
|
||||
{task.tool_name && (
|
||||
<span className="bg-dark-700 px-2 py-1 rounded text-dark-300">
|
||||
{task.tool_name}
|
||||
</span>
|
||||
)}
|
||||
<span className={`px-2 py-1 rounded ${
|
||||
task.task_type === 'recon' ? 'bg-blue-500/20 text-blue-400' :
|
||||
task.task_type === 'analysis' ? 'bg-purple-500/20 text-purple-400' :
|
||||
task.task_type === 'testing' ? 'bg-orange-500/20 text-orange-400' :
|
||||
'bg-green-500/20 text-green-400'
|
||||
}`}>
|
||||
{task.task_type}
|
||||
</span>
|
||||
{task.duration_ms !== null && (
|
||||
<span className="text-dark-500">
|
||||
{task.duration_ms < 1000
|
||||
? `${task.duration_ms}ms`
|
||||
: `${(task.duration_ms / 1000).toFixed(1)}s`}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<span className={`text-xs px-2 py-1 rounded font-medium ${
|
||||
task.status === 'completed' ? 'bg-green-500/20 text-green-400' :
|
||||
task.status === 'running' ? 'bg-blue-500/20 text-blue-400' :
|
||||
task.status === 'failed' ? 'bg-red-500/20 text-red-400' :
|
||||
'bg-dark-700 text-dark-300'
|
||||
}`}>
|
||||
{task.status}
|
||||
</span>
|
||||
{(task.items_processed > 0 || task.items_found > 0) && (
|
||||
<p className="text-xs text-dark-500 mt-2">
|
||||
{task.items_processed > 0 && `${task.items_processed} processed`}
|
||||
{task.items_processed > 0 && task.items_found > 0 && ' / '}
|
||||
{task.items_found > 0 && `${task.items_found} found`}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{task.result_summary && (
|
||||
<p className="text-xs text-dark-400 mt-3 border-t border-dark-700 pt-3">
|
||||
{task.result_summary}
|
||||
</p>
|
||||
)}
|
||||
{task.error_message && (
|
||||
<p className="text-xs text-red-400 mt-3 border-t border-dark-700 pt-3">
|
||||
Error: {task.error_message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Activity Log */}
|
||||
<Card title="Activity Log">
|
||||
<div className="space-y-1 max-h-60 overflow-auto font-mono text-xs">
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import axios from 'axios'
|
||||
import type {
|
||||
Scan, Vulnerability, Prompt, PromptPreset, Report, DashboardStats,
|
||||
AgentTask, AgentRequest, AgentResponse, AgentStatus, AgentLog, AgentMode
|
||||
AgentTask, AgentRequest, AgentResponse, AgentStatus, AgentLog, AgentMode,
|
||||
ScanAgentTask, ActivityFeedItem
|
||||
} from '../types'
|
||||
|
||||
const api = axios.create({
|
||||
@@ -127,9 +128,12 @@ export const promptsApi = {
|
||||
|
||||
// Reports API
|
||||
export const reportsApi = {
|
||||
list: async (scanId?: string): Promise<{ reports: Report[]; total: number }> => {
|
||||
const params = scanId ? `?scan_id=${scanId}` : ''
|
||||
const response = await api.get(`/reports${params}`)
|
||||
list: async (options?: { scanId?: string; autoGenerated?: boolean }): Promise<{ reports: Report[]; total: number }> => {
|
||||
const params = new URLSearchParams()
|
||||
if (options?.scanId) params.append('scan_id', options.scanId)
|
||||
if (options?.autoGenerated !== undefined) params.append('auto_generated', String(options.autoGenerated))
|
||||
const queryString = params.toString()
|
||||
const response = await api.get(`/reports${queryString ? `?${queryString}` : ''}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
@@ -183,6 +187,16 @@ export const dashboardApi = {
|
||||
const response = await api.get('/dashboard/vulnerability-types')
|
||||
return response.data
|
||||
},
|
||||
|
||||
getAgentTasks: async (limit = 20) => {
|
||||
const response = await api.get(`/dashboard/agent-tasks?limit=${limit}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
getActivityFeed: async (limit = 30): Promise<{ activities: ActivityFeedItem[]; total: number }> => {
|
||||
const response = await api.get(`/dashboard/activity-feed?limit=${limit}`)
|
||||
return response.data
|
||||
},
|
||||
}
|
||||
|
||||
// Vulnerabilities API
|
||||
@@ -198,6 +212,41 @@ export const vulnerabilitiesApi = {
|
||||
},
|
||||
}
|
||||
|
||||
// Scan Agent Tasks API (for tracking scan-specific tasks)
|
||||
export const agentTasksApi = {
|
||||
list: async (scanId: string, status?: string, taskType?: string): Promise<{ tasks: ScanAgentTask[]; total: number; scan_id: string }> => {
|
||||
const params = new URLSearchParams()
|
||||
params.append('scan_id', scanId)
|
||||
if (status) params.append('status', status)
|
||||
if (taskType) params.append('task_type', taskType)
|
||||
const response = await api.get(`/agent-tasks?${params}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
get: async (taskId: string): Promise<ScanAgentTask> => {
|
||||
const response = await api.get(`/agent-tasks/${taskId}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
getSummary: async (scanId: string): Promise<{
|
||||
total: number
|
||||
pending: number
|
||||
running: number
|
||||
completed: number
|
||||
failed: number
|
||||
by_type: Record<string, number>
|
||||
by_tool: Record<string, number>
|
||||
}> => {
|
||||
const response = await api.get(`/agent-tasks/summary?scan_id=${scanId}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
getTimeline: async (scanId: string): Promise<{ scan_id: string; timeline: ScanAgentTask[]; total: number }> => {
|
||||
const response = await api.get(`/agent-tasks/scan/${scanId}/timeline`)
|
||||
return response.data
|
||||
},
|
||||
}
|
||||
|
||||
// Agent API (Autonomous AI Agent like PentAGI)
|
||||
export const agentApi = {
|
||||
// Run the autonomous agent
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { create } from 'zustand'
|
||||
import { persist } from 'zustand/middleware'
|
||||
import type { Scan, Vulnerability, Endpoint, DashboardStats } from '../types'
|
||||
import type { Scan, Vulnerability, Endpoint, DashboardStats, ScanAgentTask } from '../types'
|
||||
|
||||
interface LogEntry {
|
||||
level: string
|
||||
@@ -12,6 +12,7 @@ interface ScanDataCache {
|
||||
endpoints: Endpoint[]
|
||||
vulnerabilities: Vulnerability[]
|
||||
logs: LogEntry[]
|
||||
agentTasks: ScanAgentTask[]
|
||||
}
|
||||
|
||||
interface ScanState {
|
||||
@@ -20,6 +21,7 @@ interface ScanState {
|
||||
endpoints: Endpoint[]
|
||||
vulnerabilities: Vulnerability[]
|
||||
logs: LogEntry[]
|
||||
agentTasks: ScanAgentTask[]
|
||||
scanDataCache: Record<string, ScanDataCache>
|
||||
isLoading: boolean
|
||||
error: string | null
|
||||
@@ -33,6 +35,9 @@ interface ScanState {
|
||||
setVulnerabilities: (vulnerabilities: Vulnerability[]) => void
|
||||
addLog: (level: string, message: string) => void
|
||||
setLogs: (logs: LogEntry[]) => void
|
||||
addAgentTask: (task: ScanAgentTask) => void
|
||||
updateAgentTask: (taskId: string, updates: Partial<ScanAgentTask>) => void
|
||||
setAgentTasks: (tasks: ScanAgentTask[]) => void
|
||||
setLoading: (loading: boolean) => void
|
||||
setError: (error: string | null) => void
|
||||
loadScanData: (scanId: string) => void
|
||||
@@ -51,6 +56,7 @@ export const useScanStore = create<ScanState>()(
|
||||
endpoints: [],
|
||||
vulnerabilities: [],
|
||||
logs: [],
|
||||
agentTasks: [],
|
||||
scanDataCache: {},
|
||||
isLoading: false,
|
||||
error: null,
|
||||
@@ -84,6 +90,27 @@ export const useScanStore = create<ScanState>()(
|
||||
logs: [...state.logs, { level, message, time: new Date().toISOString() }].slice(-200)
|
||||
})),
|
||||
setLogs: (logs) => set({ logs }),
|
||||
|
||||
// Agent Tasks
|
||||
addAgentTask: (task) =>
|
||||
set((state) => {
|
||||
const exists = state.agentTasks.some(t => t.id === task.id)
|
||||
if (exists) {
|
||||
// Update existing task
|
||||
return {
|
||||
agentTasks: state.agentTasks.map(t => t.id === task.id ? task : t)
|
||||
}
|
||||
}
|
||||
return { agentTasks: [...state.agentTasks, task] }
|
||||
}),
|
||||
updateAgentTask: (taskId, updates) =>
|
||||
set((state) => ({
|
||||
agentTasks: state.agentTasks.map(t =>
|
||||
t.id === taskId ? { ...t, ...updates } : t
|
||||
)
|
||||
})),
|
||||
setAgentTasks: (agentTasks) => set({ agentTasks }),
|
||||
|
||||
setLoading: (isLoading) => set({ isLoading }),
|
||||
setError: (error) => set({ error }),
|
||||
|
||||
@@ -94,7 +121,8 @@ export const useScanStore = create<ScanState>()(
|
||||
set({
|
||||
endpoints: cached.endpoints,
|
||||
vulnerabilities: cached.vulnerabilities,
|
||||
logs: cached.logs
|
||||
logs: cached.logs,
|
||||
agentTasks: cached.agentTasks || []
|
||||
})
|
||||
}
|
||||
},
|
||||
@@ -107,7 +135,8 @@ export const useScanStore = create<ScanState>()(
|
||||
[scanId]: {
|
||||
endpoints: state.endpoints,
|
||||
vulnerabilities: state.vulnerabilities,
|
||||
logs: state.logs
|
||||
logs: state.logs,
|
||||
agentTasks: state.agentTasks
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -120,6 +149,7 @@ export const useScanStore = create<ScanState>()(
|
||||
endpoints: [],
|
||||
vulnerabilities: [],
|
||||
logs: [],
|
||||
agentTasks: [],
|
||||
scanDataCache: {},
|
||||
isLoading: false,
|
||||
error: null,
|
||||
@@ -130,6 +160,7 @@ export const useScanStore = create<ScanState>()(
|
||||
endpoints: [],
|
||||
vulnerabilities: [],
|
||||
logs: [],
|
||||
agentTasks: [],
|
||||
}),
|
||||
|
||||
getVulnCounts: () => {
|
||||
|
||||
@@ -13,6 +13,7 @@ export interface Scan {
|
||||
created_at: string
|
||||
started_at: string | null
|
||||
completed_at: string | null
|
||||
duration: number | null // Duration in seconds
|
||||
error_message: string | null
|
||||
total_endpoints: number
|
||||
total_vulnerabilities: number
|
||||
@@ -101,6 +102,8 @@ export interface Report {
|
||||
format: 'html' | 'pdf' | 'json'
|
||||
file_path: string | null
|
||||
executive_summary: string | null
|
||||
auto_generated: boolean
|
||||
is_partial: boolean
|
||||
generated_at: string
|
||||
}
|
||||
|
||||
@@ -110,6 +113,9 @@ export interface DashboardStats {
|
||||
total: number
|
||||
running: number
|
||||
completed: number
|
||||
stopped: number
|
||||
failed: number
|
||||
pending: number
|
||||
recent: number
|
||||
}
|
||||
vulnerabilities: {
|
||||
@@ -133,6 +139,26 @@ export interface WSMessage {
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
// Scan Agent Task (different from AgentTask which is for the task library)
|
||||
export interface ScanAgentTask {
|
||||
id: string
|
||||
scan_id: string
|
||||
task_type: 'recon' | 'analysis' | 'testing' | 'reporting'
|
||||
task_name: string
|
||||
description: string | null
|
||||
tool_name: string | null
|
||||
tool_category: string | null
|
||||
status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'
|
||||
started_at: string | null
|
||||
completed_at: string | null
|
||||
duration_ms: number | null
|
||||
items_processed: number
|
||||
items_found: number
|
||||
result_summary: string | null
|
||||
error_message: string | null
|
||||
created_at: string
|
||||
}
|
||||
|
||||
// Agent types
|
||||
export type AgentMode = 'full_auto' | 'recon_only' | 'prompt_only' | 'analyze_only'
|
||||
|
||||
@@ -289,3 +315,16 @@ export interface RealtimeSessionSummary {
|
||||
findings_count: number
|
||||
messages_count: number
|
||||
}
|
||||
|
||||
// Activity Feed types
|
||||
export interface ActivityFeedItem {
|
||||
type: 'scan' | 'vulnerability' | 'agent_task' | 'report'
|
||||
action: string
|
||||
title: string
|
||||
description: string
|
||||
status: string | null
|
||||
severity: 'critical' | 'high' | 'medium' | 'low' | 'info' | null
|
||||
timestamp: string
|
||||
scan_id: string
|
||||
link: string
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user