mirror of
https://github.com/mblanke/StrikePackageGPT.git
synced 2026-03-01 06:10:21 -05:00
feat: Add real-time progress tracking for network scans
- Progress bar shows X/255 (or calculated range size) - Percentage display during scan - Currently scanning IP shown in real-time - Live host count as hosts are discovered - Remaining hosts counter - Faster polling (2s instead of 5s) for smoother updates - Backend calculates total hosts from CIDR or range notation - Parse nmap --stats-every output for progress info
This commit is contained in:
@@ -395,12 +395,16 @@ async def start_network_scan(request: NetworkScanRequest):
|
||||
import uuid
|
||||
scan_id = str(uuid.uuid4())[:8]
|
||||
|
||||
# Calculate total hosts in target range
|
||||
total_hosts = calculate_target_hosts(request.target)
|
||||
|
||||
# Build nmap command based on scan type
|
||||
# Use --stats-every to get progress updates
|
||||
scan_commands = {
|
||||
"ping": f"nmap -sn {request.target} -oX -",
|
||||
"quick": f"nmap -T4 -F -O --osscan-limit {request.target} -oX -",
|
||||
"os": f"nmap -O -sV --version-light {request.target} -oX -",
|
||||
"full": f"nmap -sS -sV -O -p- --version-all {request.target} -oX -"
|
||||
"ping": f"nmap -sn {request.target} -oX - --stats-every 2s",
|
||||
"quick": f"nmap -T4 -F -O --osscan-limit {request.target} -oX - --stats-every 2s",
|
||||
"os": f"nmap -O -sV --version-light {request.target} -oX - --stats-every 2s",
|
||||
"full": f"nmap -sS -sV -O -p- --version-all {request.target} -oX - --stats-every 2s"
|
||||
}
|
||||
|
||||
command = scan_commands.get(request.scan_type, scan_commands["os"])
|
||||
@@ -411,21 +415,57 @@ async def start_network_scan(request: NetworkScanRequest):
|
||||
"scan_type": request.scan_type,
|
||||
"status": "running",
|
||||
"hosts": [],
|
||||
"command": command
|
||||
"command": command,
|
||||
"progress": {
|
||||
"total": total_hosts,
|
||||
"scanned": 0,
|
||||
"current_ip": "",
|
||||
"hosts_found": 0,
|
||||
"percent": 0
|
||||
}
|
||||
}
|
||||
|
||||
# Execute scan asynchronously
|
||||
# Execute scan asynchronously with progress tracking
|
||||
import asyncio
|
||||
asyncio.create_task(execute_network_scan(scan_id, command))
|
||||
asyncio.create_task(execute_network_scan_with_progress(scan_id, command, request.target))
|
||||
|
||||
return {"scan_id": scan_id, "status": "running"}
|
||||
return {"scan_id": scan_id, "status": "running", "total_hosts": total_hosts}
|
||||
|
||||
|
||||
async def execute_network_scan(scan_id: str, command: str):
|
||||
"""Execute network scan and parse results"""
|
||||
def calculate_target_hosts(target: str) -> int:
|
||||
"""Calculate the number of hosts in a target specification"""
|
||||
import ipaddress
|
||||
|
||||
# Handle CIDR notation
|
||||
if '/' in target:
|
||||
try:
|
||||
network = ipaddress.ip_network(target, strict=False)
|
||||
return network.num_addresses - 2 # Subtract network and broadcast
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Handle range notation (e.g., 192.168.1.1-50)
|
||||
if '-' in target:
|
||||
try:
|
||||
parts = target.rsplit('.', 1)
|
||||
if len(parts) == 2:
|
||||
range_part = parts[1]
|
||||
if '-' in range_part:
|
||||
start, end = range_part.split('-')
|
||||
return int(end) - int(start) + 1
|
||||
except (ValueError, IndexError):
|
||||
pass
|
||||
|
||||
# Single host
|
||||
return 1
|
||||
|
||||
|
||||
async def execute_network_scan_with_progress(scan_id: str, command: str, target: str):
|
||||
"""Execute network scan with progress tracking"""
|
||||
global network_hosts
|
||||
|
||||
try:
|
||||
# Use streaming execution if available, otherwise batch
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.post(
|
||||
f"{HACKGPT_API_URL}/execute",
|
||||
@@ -437,11 +477,19 @@ async def execute_network_scan(scan_id: str, command: str):
|
||||
result = response.json()
|
||||
stdout = result.get("stdout", "")
|
||||
|
||||
# Parse nmap XML output
|
||||
# Parse progress from nmap stats output
|
||||
progress_info = parse_nmap_progress(stdout)
|
||||
if progress_info:
|
||||
network_scans[scan_id]["progress"].update(progress_info)
|
||||
|
||||
# Parse nmap XML output for hosts
|
||||
hosts = parse_nmap_xml(stdout)
|
||||
|
||||
network_scans[scan_id]["status"] = "completed"
|
||||
network_scans[scan_id]["hosts"] = hosts
|
||||
network_scans[scan_id]["progress"]["scanned"] = network_scans[scan_id]["progress"]["total"]
|
||||
network_scans[scan_id]["progress"]["hosts_found"] = len(hosts)
|
||||
network_scans[scan_id]["progress"]["percent"] = 100
|
||||
|
||||
# Update global host list
|
||||
for host in hosts:
|
||||
@@ -459,6 +507,34 @@ async def execute_network_scan(scan_id: str, command: str):
|
||||
network_scans[scan_id]["error"] = str(e)
|
||||
|
||||
|
||||
def parse_nmap_progress(output: str) -> dict:
|
||||
"""Parse nmap stats output for progress information"""
|
||||
import re
|
||||
|
||||
progress = {}
|
||||
|
||||
# Look for stats lines like: "Stats: 0:00:45 elapsed; 50 hosts completed (10 up), 5 undergoing..."
|
||||
stats_pattern = r'Stats:.*?(\d+)\s+hosts?\s+completed.*?(\d+)\s+up'
|
||||
match = re.search(stats_pattern, output, re.IGNORECASE)
|
||||
if match:
|
||||
progress['scanned'] = int(match.group(1))
|
||||
progress['hosts_found'] = int(match.group(2))
|
||||
|
||||
# Look for percentage: "About 45.00% done"
|
||||
percent_pattern = r'About\s+([\d.]+)%\s+done'
|
||||
match = re.search(percent_pattern, output, re.IGNORECASE)
|
||||
if match:
|
||||
progress['percent'] = float(match.group(1))
|
||||
|
||||
# Look for current scan target
|
||||
current_pattern = r'Scanning\s+([^\s\[]+)'
|
||||
match = re.search(current_pattern, output)
|
||||
if match:
|
||||
progress['current_ip'] = match.group(1)
|
||||
|
||||
return progress
|
||||
|
||||
|
||||
def parse_nmap_xml(xml_output: str) -> List[Dict[str, Any]]:
|
||||
"""Parse nmap XML output to extract hosts with OS info"""
|
||||
import re
|
||||
|
||||
@@ -500,9 +500,39 @@
|
||||
</div>
|
||||
|
||||
<div x-show="networkMapLoading" class="flex-1 flex items-center justify-center">
|
||||
<div class="text-center text-sp-white-muted">
|
||||
<div class="text-center text-sp-white-muted w-96">
|
||||
<div class="text-4xl mb-4 animate-spin">🔄</div>
|
||||
<p>Scanning network...</p>
|
||||
<p class="text-lg mb-4">Scanning network...</p>
|
||||
|
||||
<!-- Progress Bar -->
|
||||
<div x-show="networkScanProgress.total > 0" class="mb-4">
|
||||
<div class="flex justify-between text-sm mb-1">
|
||||
<span x-text="networkScanProgress.current + ' / ' + networkScanProgress.total"></span>
|
||||
<span x-text="Math.round((networkScanProgress.current / networkScanProgress.total) * 100) + '%'"></span>
|
||||
</div>
|
||||
<div class="w-full bg-sp-grey rounded-full h-3 overflow-hidden">
|
||||
<div class="bg-sp-red h-3 rounded-full transition-all duration-300"
|
||||
:style="'width: ' + ((networkScanProgress.current / networkScanProgress.total) * 100) + '%'"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Current IP being scanned -->
|
||||
<div x-show="networkScanProgress.currentIp" class="text-sm">
|
||||
<p class="text-sp-white-muted">Currently scanning:</p>
|
||||
<p class="font-mono text-sp-red" x-text="networkScanProgress.currentIp"></p>
|
||||
</div>
|
||||
|
||||
<!-- Live host count -->
|
||||
<div class="mt-4 flex justify-center gap-6">
|
||||
<div class="text-center">
|
||||
<div class="text-2xl font-bold text-green-400" x-text="networkScanProgress.hostsFound || networkHosts.length"></div>
|
||||
<div class="text-xs text-sp-white-muted">Hosts Found</div>
|
||||
</div>
|
||||
<div class="text-center" x-show="networkScanProgress.total > 0">
|
||||
<div class="text-2xl font-bold text-sp-white" x-text="networkScanProgress.total - networkScanProgress.current"></div>
|
||||
<div class="text-xs text-sp-white-muted">Remaining</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -861,6 +891,7 @@
|
||||
networkHosts: [],
|
||||
selectedHost: null,
|
||||
networkMapLoading: false,
|
||||
networkScanProgress: { current: 0, total: 0, currentIp: '', hostsFound: 0 },
|
||||
showRangeScanModal: false,
|
||||
rangeScanTarget: '',
|
||||
rangeScanType: 'os',
|
||||
@@ -1444,6 +1475,7 @@ Select a phase above to begin, or use the quick actions in the sidebar!`
|
||||
if (!this.rangeScanTarget) return;
|
||||
this.showRangeScanModal = false;
|
||||
this.networkMapLoading = true;
|
||||
this.networkScanProgress = { current: 0, total: 0, currentIp: '', hostsFound: 0 };
|
||||
this.activeTab = 'network-map';
|
||||
|
||||
try {
|
||||
@@ -1458,6 +1490,10 @@ Select a phase above to begin, or use the quick actions in the sidebar!`
|
||||
const data = await response.json();
|
||||
|
||||
if (data.scan_id) {
|
||||
// Set initial progress based on target range
|
||||
if (data.total_hosts) {
|
||||
this.networkScanProgress.total = data.total_hosts;
|
||||
}
|
||||
// Poll for results
|
||||
await this.pollNetworkScan(data.scan_id);
|
||||
}
|
||||
@@ -1470,24 +1506,35 @@ Select a phase above to begin, or use the quick actions in the sidebar!`
|
||||
});
|
||||
}
|
||||
this.networkMapLoading = false;
|
||||
this.networkScanProgress = { current: 0, total: 0, currentIp: '', hostsFound: 0 };
|
||||
},
|
||||
|
||||
async pollNetworkScan(scanId) {
|
||||
const maxAttempts = 120; // 10 minutes max
|
||||
for (let i = 0; i < maxAttempts; i++) {
|
||||
await new Promise(r => setTimeout(r, 5000));
|
||||
await new Promise(r => setTimeout(r, 2000)); // Poll every 2 seconds for better progress updates
|
||||
try {
|
||||
const response = await fetch(`/api/network/scan/${scanId}`);
|
||||
const data = await response.json();
|
||||
|
||||
// Update progress
|
||||
if (data.progress) {
|
||||
this.networkScanProgress = {
|
||||
current: data.progress.scanned || 0,
|
||||
total: data.progress.total || this.networkScanProgress.total,
|
||||
currentIp: data.progress.current_ip || '',
|
||||
hostsFound: data.progress.hosts_found || 0
|
||||
};
|
||||
}
|
||||
|
||||
if (data.status === 'completed') {
|
||||
this.networkHosts = data.hosts || [];
|
||||
this.$nextTick(() => this.renderNetworkMap());
|
||||
return;
|
||||
} else if (data.status === 'failed') {
|
||||
throw new Error('Scan failed');
|
||||
throw new Error(data.error || 'Scan failed');
|
||||
}
|
||||
// Update partial results
|
||||
// Update partial results as hosts are discovered
|
||||
if (data.hosts && data.hosts.length > 0) {
|
||||
this.networkHosts = data.hosts;
|
||||
this.$nextTick(() => this.renderNetworkMap());
|
||||
|
||||
Reference in New Issue
Block a user