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 { 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 Card from '../components/common/Card'
|
||||||
import Button from '../components/common/Button'
|
import Button from '../components/common/Button'
|
||||||
import { SeverityBadge } from '../components/common/Badge'
|
import { SeverityBadge } from '../components/common/Badge'
|
||||||
import { dashboardApi } from '../services/api'
|
import { dashboardApi } from '../services/api'
|
||||||
import { useDashboardStore } from '../store'
|
import { useDashboardStore } from '../store'
|
||||||
|
import type { ActivityFeedItem } from '../types'
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
const { stats, recentScans, recentVulnerabilities, setStats, setRecentScans, setRecentVulnerabilities, setLoading } = useDashboardStore()
|
const { stats, recentScans, recentVulnerabilities, setStats, setRecentScans, setRecentVulnerabilities, setLoading } = useDashboardStore()
|
||||||
|
const [activityFeed, setActivityFeed] = useState<ActivityFeedItem[]>([])
|
||||||
|
|
||||||
useEffect(() => {
|
const fetchData = useCallback(async () => {
|
||||||
const fetchData = async () => {
|
|
||||||
setLoading(true)
|
|
||||||
try {
|
try {
|
||||||
const [statsData, recentData] = await Promise.all([
|
const [statsData, recentData, activityData] = await Promise.all([
|
||||||
dashboardApi.getStats(),
|
dashboardApi.getStats(),
|
||||||
dashboardApi.getRecent(5)
|
dashboardApi.getRecent(5),
|
||||||
|
dashboardApi.getActivityFeed(15)
|
||||||
])
|
])
|
||||||
setStats(statsData)
|
setStats(statsData)
|
||||||
setRecentScans(recentData.recent_scans)
|
setRecentScans(recentData.recent_scans)
|
||||||
setRecentVulnerabilities(recentData.recent_vulnerabilities)
|
setRecentVulnerabilities(recentData.recent_vulnerabilities)
|
||||||
|
setActivityFeed(activityData.activities)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch dashboard data:', error)
|
console.error('Failed to fetch dashboard data:', error)
|
||||||
} finally {
|
|
||||||
setLoading(false)
|
|
||||||
}
|
}
|
||||||
}
|
}, [setStats, setRecentScans, setRecentVulnerabilities])
|
||||||
fetchData()
|
|
||||||
}, [])
|
useEffect(() => {
|
||||||
|
// 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 = [
|
const statCards = [
|
||||||
{
|
{
|
||||||
@@ -39,26 +48,57 @@ export default function HomePage() {
|
|||||||
bgColor: 'bg-blue-500/10',
|
bgColor: 'bg-blue-500/10',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Running Scans',
|
label: 'Running',
|
||||||
value: stats?.scans.running || 0,
|
value: stats?.scans.running || 0,
|
||||||
icon: Shield,
|
icon: Shield,
|
||||||
color: 'text-green-400',
|
color: 'text-green-400',
|
||||||
bgColor: 'bg-green-500/10',
|
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,
|
value: stats?.vulnerabilities.total || 0,
|
||||||
icon: AlertTriangle,
|
icon: AlertTriangle,
|
||||||
color: 'text-red-400',
|
color: 'text-red-400',
|
||||||
bgColor: 'bg-red-500/10',
|
bgColor: 'bg-red-500/10',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Critical Issues',
|
label: 'Critical',
|
||||||
value: stats?.vulnerabilities.critical || 0,
|
value: stats?.vulnerabilities.critical || 0,
|
||||||
icon: AlertTriangle,
|
icon: AlertTriangle,
|
||||||
color: 'text-red-500',
|
color: 'text-red-500',
|
||||||
bgColor: 'bg-red-600/10',
|
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 (
|
return (
|
||||||
@@ -77,8 +117,8 @@ export default function HomePage() {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Stats Grid */}
|
{/* Scan Stats Grid */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||||
{statCards.map((stat) => (
|
{statCards.map((stat) => (
|
||||||
<Card key={stat.label} className="hover:border-dark-700 transition-colors">
|
<Card key={stat.label} className="hover:border-dark-700 transition-colors">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
@@ -94,6 +134,23 @@ export default function HomePage() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</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 */}
|
{/* Severity Distribution */}
|
||||||
{stats && stats.vulnerabilities.total > 0 && (
|
{stats && stats.vulnerabilities.total > 0 && (
|
||||||
<Card title="Vulnerability Distribution">
|
<Card title="Vulnerability Distribution">
|
||||||
@@ -205,6 +262,76 @@ export default function HomePage() {
|
|||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,31 +2,33 @@ import { useEffect, useMemo, useState } from 'react'
|
|||||||
import { useParams, useNavigate } from 'react-router-dom'
|
import { useParams, useNavigate } from 'react-router-dom'
|
||||||
import {
|
import {
|
||||||
Globe, FileText, StopCircle, RefreshCw, ChevronDown, ChevronRight,
|
Globe, FileText, StopCircle, RefreshCw, ChevronDown, ChevronRight,
|
||||||
ExternalLink, Copy, Shield, AlertTriangle
|
ExternalLink, Copy, Shield, AlertTriangle, Cpu, CheckCircle, XCircle, Clock
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import Card from '../components/common/Card'
|
import Card from '../components/common/Card'
|
||||||
import Button from '../components/common/Button'
|
import Button from '../components/common/Button'
|
||||||
import { SeverityBadge } from '../components/common/Badge'
|
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 { wsService } from '../services/websocket'
|
||||||
import { useScanStore } from '../store'
|
import { useScanStore } from '../store'
|
||||||
import type { Endpoint, Vulnerability, WSMessage } from '../types'
|
import type { Endpoint, Vulnerability, WSMessage, ScanAgentTask, Report } from '../types'
|
||||||
|
|
||||||
export default function ScanDetailsPage() {
|
export default function ScanDetailsPage() {
|
||||||
const { scanId } = useParams<{ scanId: string }>()
|
const { scanId } = useParams<{ scanId: string }>()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const {
|
const {
|
||||||
currentScan, endpoints, vulnerabilities, logs,
|
currentScan, endpoints, vulnerabilities, logs, agentTasks,
|
||||||
setCurrentScan, setEndpoints, setVulnerabilities,
|
setCurrentScan, setEndpoints, setVulnerabilities,
|
||||||
addEndpoint, addVulnerability, addLog, updateScan,
|
addEndpoint, addVulnerability, addLog, updateScan,
|
||||||
|
addAgentTask, updateAgentTask, setAgentTasks,
|
||||||
loadScanData, saveScanData, getVulnCounts
|
loadScanData, saveScanData, getVulnCounts
|
||||||
} = useScanStore()
|
} = useScanStore()
|
||||||
|
|
||||||
const [isGeneratingReport, setIsGeneratingReport] = useState(false)
|
const [isGeneratingReport, setIsGeneratingReport] = useState(false)
|
||||||
const [expandedVulns, setExpandedVulns] = useState<Set<string>>(new Set())
|
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 [isLoading, setIsLoading] = useState(true)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
const [autoGeneratedReport, setAutoGeneratedReport] = useState<Report | null>(null)
|
||||||
|
|
||||||
// Calculate vulnerability counts from actual data
|
// Calculate vulnerability counts from actual data
|
||||||
const vulnCounts = useMemo(() => getVulnCounts(), [vulnerabilities])
|
const vulnCounts = useMemo(() => getVulnCounts(), [vulnerabilities])
|
||||||
@@ -45,9 +47,11 @@ export default function ScanDetailsPage() {
|
|||||||
const scan = await scansApi.get(scanId)
|
const scan = await scansApi.get(scanId)
|
||||||
setCurrentScan(scan)
|
setCurrentScan(scan)
|
||||||
|
|
||||||
const [endpointsData, vulnsData] = await Promise.all([
|
const [endpointsData, vulnsData, tasksData, reportsData] = await Promise.all([
|
||||||
scansApi.getEndpoints(scanId),
|
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
|
// Only set if we have data from API
|
||||||
@@ -57,6 +61,13 @@ export default function ScanDetailsPage() {
|
|||||||
if (vulnsData.vulnerabilities?.length > 0) {
|
if (vulnsData.vulnerabilities?.length > 0) {
|
||||||
setVulnerabilities(vulnsData.vulnerabilities)
|
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) {
|
} catch (err: any) {
|
||||||
console.error('Failed to fetch scan:', err)
|
console.error('Failed to fetch scan:', err)
|
||||||
setError(err?.response?.data?.detail || 'Failed to load scan')
|
setError(err?.response?.data?.detail || 'Failed to load scan')
|
||||||
@@ -73,9 +84,10 @@ export default function ScanDetailsPage() {
|
|||||||
const scan = await scansApi.get(scanId)
|
const scan = await scansApi.get(scanId)
|
||||||
setCurrentScan(scan)
|
setCurrentScan(scan)
|
||||||
|
|
||||||
const [endpointsData, vulnsData] = await Promise.all([
|
const [endpointsData, vulnsData, tasksData] = await Promise.all([
|
||||||
scansApi.getEndpoints(scanId),
|
scansApi.getEndpoints(scanId),
|
||||||
scansApi.getVulnerabilities(scanId)
|
scansApi.getVulnerabilities(scanId),
|
||||||
|
agentTasksApi.list(scanId).catch(() => ({ tasks: [] }))
|
||||||
])
|
])
|
||||||
|
|
||||||
if (endpointsData.endpoints?.length > 0) {
|
if (endpointsData.endpoints?.length > 0) {
|
||||||
@@ -84,6 +96,9 @@ export default function ScanDetailsPage() {
|
|||||||
if (vulnsData.vulnerabilities?.length > 0) {
|
if (vulnsData.vulnerabilities?.length > 0) {
|
||||||
setVulnerabilities(vulnsData.vulnerabilities)
|
setVulnerabilities(vulnsData.vulnerabilities)
|
||||||
}
|
}
|
||||||
|
if (tasksData.tasks?.length > 0) {
|
||||||
|
setAgentTasks(tasksData.tasks)
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Poll error:', err)
|
console.error('Poll error:', err)
|
||||||
}
|
}
|
||||||
@@ -113,6 +128,29 @@ export default function ScanDetailsPage() {
|
|||||||
addVulnerability(message.vulnerability as Vulnerability)
|
addVulnerability(message.vulnerability as Vulnerability)
|
||||||
addLog('warning', `Found: ${(message.vulnerability as Vulnerability).title}`)
|
addLog('warning', `Found: ${(message.vulnerability as Vulnerability).title}`)
|
||||||
break
|
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':
|
case 'log_message':
|
||||||
addLog(message.level as string, message.message as string)
|
addLog(message.level as string, message.message as string)
|
||||||
break
|
break
|
||||||
@@ -122,6 +160,65 @@ export default function ScanDetailsPage() {
|
|||||||
// Save data when scan completes
|
// Save data when scan completes
|
||||||
saveScanData(scanId)
|
saveScanData(scanId)
|
||||||
break
|
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':
|
case 'error':
|
||||||
addLog('error', message.error as string)
|
addLog('error', message.error as string)
|
||||||
break
|
break
|
||||||
@@ -236,10 +333,19 @@ export default function ScanDetailsPage() {
|
|||||||
Stop Scan
|
Stop Scan
|
||||||
</Button>
|
</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}>
|
<Button onClick={handleGenerateReport} isLoading={isGeneratingReport}>
|
||||||
<FileText className="w-4 h-4 mr-2" />
|
<FileText className="w-4 h-4 mr-2" />
|
||||||
Generate Report
|
{autoGeneratedReport ? 'New Report' : 'Generate Report'}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -263,6 +369,41 @@ export default function ScanDetailsPage() {
|
|||||||
</Card>
|
</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 */}
|
{/* Stats */}
|
||||||
<div className="grid grid-cols-2 md:grid-cols-6 gap-4">
|
<div className="grid grid-cols-2 md:grid-cols-6 gap-4">
|
||||||
<Card>
|
<Card>
|
||||||
@@ -319,6 +460,13 @@ export default function ScanDetailsPage() {
|
|||||||
<Globe className="w-4 h-4 mr-2" />
|
<Globe className="w-4 h-4 mr-2" />
|
||||||
Endpoints ({endpoints.length})
|
Endpoints ({endpoints.length})
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={activeTab === 'tasks' ? 'primary' : 'ghost'}
|
||||||
|
onClick={() => setActiveTab('tasks')}
|
||||||
|
>
|
||||||
|
<Cpu className="w-4 h-4 mr-2" />
|
||||||
|
Agent Tasks ({agentTasks.length})
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Vulnerabilities Tab */}
|
{/* Vulnerabilities Tab */}
|
||||||
@@ -553,6 +701,98 @@ export default function ScanDetailsPage() {
|
|||||||
</Card>
|
</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 */}
|
{/* Activity Log */}
|
||||||
<Card title="Activity Log">
|
<Card title="Activity Log">
|
||||||
<div className="space-y-1 max-h-60 overflow-auto font-mono text-xs">
|
<div className="space-y-1 max-h-60 overflow-auto font-mono text-xs">
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import type {
|
import type {
|
||||||
Scan, Vulnerability, Prompt, PromptPreset, Report, DashboardStats,
|
Scan, Vulnerability, Prompt, PromptPreset, Report, DashboardStats,
|
||||||
AgentTask, AgentRequest, AgentResponse, AgentStatus, AgentLog, AgentMode
|
AgentTask, AgentRequest, AgentResponse, AgentStatus, AgentLog, AgentMode,
|
||||||
|
ScanAgentTask, ActivityFeedItem
|
||||||
} from '../types'
|
} from '../types'
|
||||||
|
|
||||||
const api = axios.create({
|
const api = axios.create({
|
||||||
@@ -127,9 +128,12 @@ export const promptsApi = {
|
|||||||
|
|
||||||
// Reports API
|
// Reports API
|
||||||
export const reportsApi = {
|
export const reportsApi = {
|
||||||
list: async (scanId?: string): Promise<{ reports: Report[]; total: number }> => {
|
list: async (options?: { scanId?: string; autoGenerated?: boolean }): Promise<{ reports: Report[]; total: number }> => {
|
||||||
const params = scanId ? `?scan_id=${scanId}` : ''
|
const params = new URLSearchParams()
|
||||||
const response = await api.get(`/reports${params}`)
|
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
|
return response.data
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -183,6 +187,16 @@ export const dashboardApi = {
|
|||||||
const response = await api.get('/dashboard/vulnerability-types')
|
const response = await api.get('/dashboard/vulnerability-types')
|
||||||
return response.data
|
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
|
// 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)
|
// Agent API (Autonomous AI Agent like PentAGI)
|
||||||
export const agentApi = {
|
export const agentApi = {
|
||||||
// Run the autonomous agent
|
// Run the autonomous agent
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { create } from 'zustand'
|
import { create } from 'zustand'
|
||||||
import { persist } from 'zustand/middleware'
|
import { persist } from 'zustand/middleware'
|
||||||
import type { Scan, Vulnerability, Endpoint, DashboardStats } from '../types'
|
import type { Scan, Vulnerability, Endpoint, DashboardStats, ScanAgentTask } from '../types'
|
||||||
|
|
||||||
interface LogEntry {
|
interface LogEntry {
|
||||||
level: string
|
level: string
|
||||||
@@ -12,6 +12,7 @@ interface ScanDataCache {
|
|||||||
endpoints: Endpoint[]
|
endpoints: Endpoint[]
|
||||||
vulnerabilities: Vulnerability[]
|
vulnerabilities: Vulnerability[]
|
||||||
logs: LogEntry[]
|
logs: LogEntry[]
|
||||||
|
agentTasks: ScanAgentTask[]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ScanState {
|
interface ScanState {
|
||||||
@@ -20,6 +21,7 @@ interface ScanState {
|
|||||||
endpoints: Endpoint[]
|
endpoints: Endpoint[]
|
||||||
vulnerabilities: Vulnerability[]
|
vulnerabilities: Vulnerability[]
|
||||||
logs: LogEntry[]
|
logs: LogEntry[]
|
||||||
|
agentTasks: ScanAgentTask[]
|
||||||
scanDataCache: Record<string, ScanDataCache>
|
scanDataCache: Record<string, ScanDataCache>
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
error: string | null
|
error: string | null
|
||||||
@@ -33,6 +35,9 @@ interface ScanState {
|
|||||||
setVulnerabilities: (vulnerabilities: Vulnerability[]) => void
|
setVulnerabilities: (vulnerabilities: Vulnerability[]) => void
|
||||||
addLog: (level: string, message: string) => void
|
addLog: (level: string, message: string) => void
|
||||||
setLogs: (logs: LogEntry[]) => void
|
setLogs: (logs: LogEntry[]) => void
|
||||||
|
addAgentTask: (task: ScanAgentTask) => void
|
||||||
|
updateAgentTask: (taskId: string, updates: Partial<ScanAgentTask>) => void
|
||||||
|
setAgentTasks: (tasks: ScanAgentTask[]) => void
|
||||||
setLoading: (loading: boolean) => void
|
setLoading: (loading: boolean) => void
|
||||||
setError: (error: string | null) => void
|
setError: (error: string | null) => void
|
||||||
loadScanData: (scanId: string) => void
|
loadScanData: (scanId: string) => void
|
||||||
@@ -51,6 +56,7 @@ export const useScanStore = create<ScanState>()(
|
|||||||
endpoints: [],
|
endpoints: [],
|
||||||
vulnerabilities: [],
|
vulnerabilities: [],
|
||||||
logs: [],
|
logs: [],
|
||||||
|
agentTasks: [],
|
||||||
scanDataCache: {},
|
scanDataCache: {},
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
error: null,
|
error: null,
|
||||||
@@ -84,6 +90,27 @@ export const useScanStore = create<ScanState>()(
|
|||||||
logs: [...state.logs, { level, message, time: new Date().toISOString() }].slice(-200)
|
logs: [...state.logs, { level, message, time: new Date().toISOString() }].slice(-200)
|
||||||
})),
|
})),
|
||||||
setLogs: (logs) => set({ logs }),
|
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 }),
|
setLoading: (isLoading) => set({ isLoading }),
|
||||||
setError: (error) => set({ error }),
|
setError: (error) => set({ error }),
|
||||||
|
|
||||||
@@ -94,7 +121,8 @@ export const useScanStore = create<ScanState>()(
|
|||||||
set({
|
set({
|
||||||
endpoints: cached.endpoints,
|
endpoints: cached.endpoints,
|
||||||
vulnerabilities: cached.vulnerabilities,
|
vulnerabilities: cached.vulnerabilities,
|
||||||
logs: cached.logs
|
logs: cached.logs,
|
||||||
|
agentTasks: cached.agentTasks || []
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -107,7 +135,8 @@ export const useScanStore = create<ScanState>()(
|
|||||||
[scanId]: {
|
[scanId]: {
|
||||||
endpoints: state.endpoints,
|
endpoints: state.endpoints,
|
||||||
vulnerabilities: state.vulnerabilities,
|
vulnerabilities: state.vulnerabilities,
|
||||||
logs: state.logs
|
logs: state.logs,
|
||||||
|
agentTasks: state.agentTasks
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -120,6 +149,7 @@ export const useScanStore = create<ScanState>()(
|
|||||||
endpoints: [],
|
endpoints: [],
|
||||||
vulnerabilities: [],
|
vulnerabilities: [],
|
||||||
logs: [],
|
logs: [],
|
||||||
|
agentTasks: [],
|
||||||
scanDataCache: {},
|
scanDataCache: {},
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
error: null,
|
error: null,
|
||||||
@@ -130,6 +160,7 @@ export const useScanStore = create<ScanState>()(
|
|||||||
endpoints: [],
|
endpoints: [],
|
||||||
vulnerabilities: [],
|
vulnerabilities: [],
|
||||||
logs: [],
|
logs: [],
|
||||||
|
agentTasks: [],
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getVulnCounts: () => {
|
getVulnCounts: () => {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export interface Scan {
|
|||||||
created_at: string
|
created_at: string
|
||||||
started_at: string | null
|
started_at: string | null
|
||||||
completed_at: string | null
|
completed_at: string | null
|
||||||
|
duration: number | null // Duration in seconds
|
||||||
error_message: string | null
|
error_message: string | null
|
||||||
total_endpoints: number
|
total_endpoints: number
|
||||||
total_vulnerabilities: number
|
total_vulnerabilities: number
|
||||||
@@ -101,6 +102,8 @@ export interface Report {
|
|||||||
format: 'html' | 'pdf' | 'json'
|
format: 'html' | 'pdf' | 'json'
|
||||||
file_path: string | null
|
file_path: string | null
|
||||||
executive_summary: string | null
|
executive_summary: string | null
|
||||||
|
auto_generated: boolean
|
||||||
|
is_partial: boolean
|
||||||
generated_at: string
|
generated_at: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,6 +113,9 @@ export interface DashboardStats {
|
|||||||
total: number
|
total: number
|
||||||
running: number
|
running: number
|
||||||
completed: number
|
completed: number
|
||||||
|
stopped: number
|
||||||
|
failed: number
|
||||||
|
pending: number
|
||||||
recent: number
|
recent: number
|
||||||
}
|
}
|
||||||
vulnerabilities: {
|
vulnerabilities: {
|
||||||
@@ -133,6 +139,26 @@ export interface WSMessage {
|
|||||||
[key: string]: unknown
|
[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
|
// Agent types
|
||||||
export type AgentMode = 'full_auto' | 'recon_only' | 'prompt_only' | 'analyze_only'
|
export type AgentMode = 'full_auto' | 'recon_only' | 'prompt_only' | 'analyze_only'
|
||||||
|
|
||||||
@@ -289,3 +315,16 @@ export interface RealtimeSessionSummary {
|
|||||||
findings_count: number
|
findings_count: number
|
||||||
messages_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