|
|
class PortScanner { |
|
|
constructor() { |
|
|
this.isScanning = false; |
|
|
this.progressInterval = null; |
|
|
this.initializeEventListeners(); |
|
|
} |
|
|
|
|
|
initializeEventListeners() { |
|
|
|
|
|
this.targetIpInput = document.getElementById('target-ip'); |
|
|
this.startPortInput = document.getElementById('start-port'); |
|
|
this.endPortInput = document.getElementById('end-port'); |
|
|
this.timeoutInput = document.getElementById('timeout'); |
|
|
this.threadsInput = document.getElementById('threads'); |
|
|
|
|
|
|
|
|
this.scanBtn = document.getElementById('scan-btn'); |
|
|
this.pingBtn = document.getElementById('ping-btn'); |
|
|
this.exportBtn = document.getElementById('export-btn'); |
|
|
this.clearBtn = document.getElementById('clear-btn'); |
|
|
|
|
|
|
|
|
this.progressSection = document.getElementById('progress-section'); |
|
|
this.resultsSection = document.getElementById('results-section'); |
|
|
|
|
|
|
|
|
this.scanBtn.addEventListener('click', () => this.startScan()); |
|
|
this.pingBtn.addEventListener('click', () => this.pingHost()); |
|
|
this.exportBtn.addEventListener('click', () => this.exportResults()); |
|
|
this.clearBtn.addEventListener('click', () => this.clearResults()); |
|
|
|
|
|
|
|
|
document.querySelectorAll('.preset-btn').forEach(btn => { |
|
|
btn.addEventListener('click', (e) => { |
|
|
const start = e.target.dataset.start; |
|
|
const end = e.target.dataset.end; |
|
|
this.startPortInput.value = start; |
|
|
this.endPortInput.value = end; |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
this.targetIpInput.addEventListener('input', () => this.validateForm()); |
|
|
this.startPortInput.addEventListener('input', () => this.validateForm()); |
|
|
this.endPortInput.addEventListener('input', () => this.validateForm()); |
|
|
} |
|
|
|
|
|
validateForm() { |
|
|
const ip = this.targetIpInput.value.trim(); |
|
|
const startPort = parseInt(this.startPortInput.value); |
|
|
const endPort = parseInt(this.endPortInput.value); |
|
|
|
|
|
let isValid = true; |
|
|
|
|
|
|
|
|
if (!ip || !this.isValidIP(ip)) { |
|
|
isValid = false; |
|
|
} |
|
|
|
|
|
|
|
|
if (startPort < 1 || endPort > 65535 || startPort > endPort) { |
|
|
isValid = false; |
|
|
} |
|
|
|
|
|
this.scanBtn.disabled = !isValid || this.isScanning; |
|
|
return isValid; |
|
|
} |
|
|
|
|
|
isValidIP(ip) { |
|
|
const ipRegex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; |
|
|
return ipRegex.test(ip); |
|
|
} |
|
|
|
|
|
async pingHost() { |
|
|
const ip = this.targetIpInput.value.trim(); |
|
|
|
|
|
if (!ip || !this.isValidIP(ip)) { |
|
|
this.showNotification('Please enter a valid IP address', 'error'); |
|
|
return; |
|
|
} |
|
|
|
|
|
this.pingBtn.disabled = true; |
|
|
this.pingBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Pinging...'; |
|
|
|
|
|
try { |
|
|
const response = await fetch('/api/ping', { |
|
|
method: 'POST', |
|
|
headers: { |
|
|
'Content-Type': 'application/json', |
|
|
}, |
|
|
body: JSON.stringify({ ip: ip }) |
|
|
}); |
|
|
|
|
|
const data = await response.json(); |
|
|
|
|
|
if (response.ok) { |
|
|
const status = data.status === 'reachable' ? 'Host is reachable' : 'Host appears unreachable'; |
|
|
const type = data.status === 'reachable' ? 'success' : 'error'; |
|
|
this.showNotification(status, type); |
|
|
} else { |
|
|
this.showNotification(data.error || 'Ping failed', 'error'); |
|
|
} |
|
|
} catch (error) { |
|
|
this.showNotification('Network error during ping', 'error'); |
|
|
} finally { |
|
|
this.pingBtn.disabled = false; |
|
|
this.pingBtn.innerHTML = '<i class="fas fa-satellite-dish"></i> Ping'; |
|
|
} |
|
|
} |
|
|
|
|
|
async startScan() { |
|
|
if (!this.validateForm()) { |
|
|
this.showNotification('Please check your input values', 'error'); |
|
|
return; |
|
|
} |
|
|
|
|
|
const scanData = { |
|
|
ip: this.targetIpInput.value.trim(), |
|
|
start_port: parseInt(this.startPortInput.value), |
|
|
end_port: parseInt(this.endPortInput.value), |
|
|
timeout: parseFloat(this.timeoutInput.value), |
|
|
threads: parseInt(this.threadsInput.value) |
|
|
}; |
|
|
|
|
|
|
|
|
const portRange = scanData.end_port - scanData.start_port + 1; |
|
|
if (portRange > 10000) { |
|
|
this.showNotification('Port range too large (max 10000 ports)', 'error'); |
|
|
return; |
|
|
} |
|
|
|
|
|
this.isScanning = true; |
|
|
this.scanBtn.disabled = true; |
|
|
this.scanBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Scanning...'; |
|
|
|
|
|
|
|
|
this.progressSection.style.display = 'block'; |
|
|
this.resultsSection.style.display = 'none'; |
|
|
|
|
|
|
|
|
this.startProgressMonitoring(); |
|
|
|
|
|
try { |
|
|
const response = await fetch('/api/scan', { |
|
|
method: 'POST', |
|
|
headers: { |
|
|
'Content-Type': 'application/json', |
|
|
}, |
|
|
body: JSON.stringify(scanData) |
|
|
}); |
|
|
|
|
|
const data = await response.json(); |
|
|
|
|
|
if (response.ok) { |
|
|
this.displayResults(data); |
|
|
this.showNotification(`Scan completed! Found ${data.total_open} open ports`, 'success'); |
|
|
} else { |
|
|
this.showNotification(data.error || 'Scan failed', 'error'); |
|
|
} |
|
|
} catch (error) { |
|
|
this.showNotification('Network error during scan', 'error'); |
|
|
} finally { |
|
|
this.stopProgressMonitoring(); |
|
|
this.isScanning = false; |
|
|
this.scanBtn.disabled = false; |
|
|
this.scanBtn.innerHTML = '<i class="fas fa-search"></i> Start Scan'; |
|
|
this.progressSection.style.display = 'none'; |
|
|
} |
|
|
} |
|
|
|
|
|
startProgressMonitoring() { |
|
|
this.progressInterval = setInterval(async () => { |
|
|
try { |
|
|
const response = await fetch('/api/progress'); |
|
|
const data = await response.json(); |
|
|
|
|
|
const progressFill = document.getElementById('progress-fill'); |
|
|
const progressText = document.getElementById('progress-text'); |
|
|
|
|
|
progressFill.style.width = `${data.percentage}%`; |
|
|
progressText.textContent = `Scanning... ${data.progress}/${data.total} ports (${data.percentage}%)`; |
|
|
} catch (error) { |
|
|
console.error('Error fetching progress:', error); |
|
|
} |
|
|
}, 500); |
|
|
} |
|
|
|
|
|
stopProgressMonitoring() { |
|
|
if (this.progressInterval) { |
|
|
clearInterval(this.progressInterval); |
|
|
this.progressInterval = null; |
|
|
} |
|
|
} |
|
|
|
|
|
displayResults(data) { |
|
|
const resultsSection = document.getElementById('results-section'); |
|
|
const resultsSummary = document.getElementById('results-summary'); |
|
|
const resultsTableBody = document.getElementById('results-tbody'); |
|
|
|
|
|
|
|
|
resultsSummary.innerHTML = ` |
|
|
<strong>Target:</strong> ${data.target_ip} | |
|
|
<strong>Range:</strong> ${data.scan_range} | |
|
|
<strong>Open Ports:</strong> ${data.total_open}/${data.total_scanned} | |
|
|
<strong>Duration:</strong> ${data.scan_duration}s |
|
|
`; |
|
|
|
|
|
|
|
|
resultsTableBody.innerHTML = ''; |
|
|
|
|
|
|
|
|
if (data.open_ports && data.open_ports.length > 0) { |
|
|
data.open_ports.forEach(port => { |
|
|
const row = document.createElement('tr'); |
|
|
row.innerHTML = ` |
|
|
<td><strong>${port.port}</strong></td> |
|
|
<td><span class="status-${port.status}">${port.status.toUpperCase()}</span></td> |
|
|
<td>${port.service || 'unknown'}</td> |
|
|
<td>TCP</td> |
|
|
`; |
|
|
resultsTableBody.appendChild(row); |
|
|
}); |
|
|
} else { |
|
|
const row = document.createElement('tr'); |
|
|
row.innerHTML = '<td colspan="4" style="text-align: center; color: #666;">No open ports found</td>'; |
|
|
resultsTableBody.appendChild(row); |
|
|
} |
|
|
|
|
|
|
|
|
resultsSection.style.display = 'block'; |
|
|
|
|
|
|
|
|
this.lastScanResults = data; |
|
|
} |
|
|
|
|
|
exportResults() { |
|
|
if (!this.lastScanResults) { |
|
|
this.showNotification('No results to export', 'error'); |
|
|
return; |
|
|
} |
|
|
|
|
|
const data = this.lastScanResults; |
|
|
let csvContent = 'Port,Status,Service,Protocol\n'; |
|
|
|
|
|
if (data.open_ports && data.open_ports.length > 0) { |
|
|
data.open_ports.forEach(port => { |
|
|
csvContent += `${port.port},${port.status},${port.service || 'unknown'},TCP\n`; |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
const blob = new Blob([csvContent], { type: 'text/csv' }); |
|
|
const url = window.URL.createObjectURL(blob); |
|
|
const a = document.createElement('a'); |
|
|
a.href = url; |
|
|
a.download = `port_scan_${data.target_ip}_${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.csv`; |
|
|
document.body.appendChild(a); |
|
|
a.click(); |
|
|
document.body.removeChild(a); |
|
|
window.URL.revokeObjectURL(url); |
|
|
|
|
|
this.showNotification('Results exported successfully', 'success'); |
|
|
} |
|
|
|
|
|
clearResults() { |
|
|
this.resultsSection.style.display = 'none'; |
|
|
this.lastScanResults = null; |
|
|
this.showNotification('Results cleared', 'info'); |
|
|
} |
|
|
|
|
|
showNotification(message, type = 'info') { |
|
|
const notification = document.getElementById('notification'); |
|
|
notification.textContent = message; |
|
|
notification.className = `notification ${type}`; |
|
|
notification.classList.add('show'); |
|
|
|
|
|
setTimeout(() => { |
|
|
notification.classList.remove('show'); |
|
|
}, 4000); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
|
new PortScanner(); |
|
|
}); |
|
|
|
|
|
|
|
|
document.addEventListener('keydown', (e) => { |
|
|
if (e.key === 'Enter' && e.target.tagName === 'INPUT') { |
|
|
const scanner = new PortScanner(); |
|
|
if (scanner.validateForm()) { |
|
|
scanner.startScan(); |
|
|
} |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
window.addEventListener('load', () => { |
|
|
document.getElementById('target-ip').focus(); |
|
|
}); |
|
|
|
|
|
|