1016 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1016 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
// DNS Reconnaissance Tool - Enhanced Frontend JavaScript with Forensic Data Support
 | 
						|
 | 
						|
class ReconTool {
 | 
						|
    constructor() {
 | 
						|
        this.currentScanId = null;
 | 
						|
        this.pollInterval = null;
 | 
						|
        this.liveDataInterval = null;
 | 
						|
        this.currentReport = null;
 | 
						|
        this.debugMode = true;
 | 
						|
        this.graphVisualization = null; // Add this line
 | 
						|
        this.init();
 | 
						|
    }
 | 
						|
 | 
						|
    debug(message, data = null) {
 | 
						|
        if (this.debugMode) {
 | 
						|
            if (data) {
 | 
						|
                console.log(`🔍 DEBUG: ${message}`, data);
 | 
						|
            } else {
 | 
						|
                console.log(`🔍 DEBUG: ${message}`);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    init() {
 | 
						|
        this.bindEvents();
 | 
						|
        this.setupRealtimeElements();
 | 
						|
        
 | 
						|
        // Handle window resize for graph
 | 
						|
        window.addEventListener('resize', () => {
 | 
						|
            if (this.graphVisualization) {
 | 
						|
                this.graphVisualization.handleResize();
 | 
						|
            }
 | 
						|
        });
 | 
						|
    }
 | 
						|
    
 | 
						|
    setupRealtimeElements() {
 | 
						|
        // Create live discovery container if it doesn't exist
 | 
						|
        if (!document.getElementById('liveDiscoveries')) {
 | 
						|
            const progressSection = document.getElementById('progressSection');
 | 
						|
            const liveDiv = document.createElement('div');
 | 
						|
            liveDiv.id = 'liveDiscoveries';
 | 
						|
            liveDiv.innerHTML = `
 | 
						|
                <div class="live-discoveries" style="display: none;">
 | 
						|
                    <h3>🔍 Live Discoveries</h3>
 | 
						|
                    <div class="stats-grid">
 | 
						|
                        <div class="stat-item">
 | 
						|
                            <span class="stat-label">Hostnames:</span>
 | 
						|
                            <span id="liveHostnames" class="stat-value">0</span>
 | 
						|
                        </div>
 | 
						|
                        <div class="stat-item">
 | 
						|
                            <span class="stat-label">IP Addresses:</span>
 | 
						|
                            <span id="liveIPs" class="stat-value">0</span>
 | 
						|
                        </div>
 | 
						|
                        <div class="stat-item">
 | 
						|
                            <span class="stat-label">Discovery Edges:</span>
 | 
						|
                            <span id="liveEdges" class="stat-value">0</span>
 | 
						|
                        </div>
 | 
						|
                        <div class="stat-item">
 | 
						|
                            <span class="stat-label">Operations:</span>
 | 
						|
                            <span id="liveOperations" class="stat-value">0</span>
 | 
						|
                        </div>
 | 
						|
                        <div class="stat-item">
 | 
						|
                            <span class="stat-label">DNS Records:</span>
 | 
						|
                            <span id="liveDNS" class="stat-value">0</span>
 | 
						|
                        </div>
 | 
						|
                        <div class="stat-item">
 | 
						|
                            <span class="stat-label">Certificates:</span>
 | 
						|
                            <span id="liveCerts" class="stat-value">0</span>
 | 
						|
                        </div>
 | 
						|
                        <div class="stat-item">
 | 
						|
                            <span class="stat-label">Shodan Results:</span>
 | 
						|
                            <span id="liveShodan" class="stat-value">0</span>
 | 
						|
                        </div>
 | 
						|
                        <div class="stat-item">
 | 
						|
                            <span class="stat-label">VirusTotal:</span>
 | 
						|
                            <span id="liveVT" class="stat-value">0</span>
 | 
						|
                        </div>
 | 
						|
                    </div>
 | 
						|
                    <div class="discoveries-list">
 | 
						|
                        <h4>📋 Recent Discoveries</h4>
 | 
						|
                        <div id="recentHostnames" class="discovery-section">
 | 
						|
                            <strong>Hostnames:</strong>
 | 
						|
                            <div class="hostname-list"></div>
 | 
						|
                        </div>
 | 
						|
                        <div id="recentIPs" class="discovery-section">
 | 
						|
                            <strong>IP Addresses:</strong>
 | 
						|
                            <div class="ip-list"></div>
 | 
						|
                        </div>
 | 
						|
                        <div id="activityLog" class="discovery-section">
 | 
						|
                            <strong>Activity Log:</strong>
 | 
						|
                            <div class="activity-list"></div>
 | 
						|
                        </div>
 | 
						|
                    </div>
 | 
						|
                </div>
 | 
						|
            `;
 | 
						|
            progressSection.appendChild(liveDiv);
 | 
						|
            this.debug("Live discoveries container created");
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    bindEvents() {
 | 
						|
        // Start scan button
 | 
						|
        document.getElementById('startScan').addEventListener('click', () => {
 | 
						|
            this.startScan();
 | 
						|
        });
 | 
						|
 | 
						|
        // New scan button
 | 
						|
        document.getElementById('newScan').addEventListener('click', () => {
 | 
						|
            this.resetToForm();
 | 
						|
        });
 | 
						|
 | 
						|
        // Report view toggles
 | 
						|
        document.getElementById('showJson').addEventListener('click', () => {
 | 
						|
            this.showReport('json');
 | 
						|
        });
 | 
						|
 | 
						|
        document.getElementById('showText').addEventListener('click', () => {
 | 
						|
            this.showReport('text');
 | 
						|
        });
 | 
						|
 | 
						|
        // Download buttons
 | 
						|
        document.getElementById('downloadJson').addEventListener('click', () => {
 | 
						|
            this.downloadReport('json');
 | 
						|
        });
 | 
						|
 | 
						|
        document.getElementById('downloadText').addEventListener('click', () => {
 | 
						|
            this.downloadReport('text');
 | 
						|
        });
 | 
						|
 | 
						|
        // Enter key in target field
 | 
						|
        document.getElementById('target').addEventListener('keypress', (e) => {
 | 
						|
            if (e.key === 'Enter') {
 | 
						|
                this.startScan();
 | 
						|
            }
 | 
						|
        });
 | 
						|
        document.getElementById('showGraphView').addEventListener('click', () => {
 | 
						|
            this.showGraphView();
 | 
						|
        });
 | 
						|
    }
 | 
						|
 | 
						|
    showGraphView() {
 | 
						|
        if (this.graphVisualization && this.currentScanId) {
 | 
						|
            document.getElementById('graphSection').style.display = 'block';
 | 
						|
            
 | 
						|
            // Update button states
 | 
						|
            document.getElementById('showGraphView').classList.add('active');
 | 
						|
            document.getElementById('showJson').classList.remove('active');
 | 
						|
            document.getElementById('showText').classList.remove('active');
 | 
						|
            
 | 
						|
            // Scroll to graph section
 | 
						|
            document.getElementById('graphSection').scrollIntoView({
 | 
						|
                behavior: 'smooth'
 | 
						|
            });
 | 
						|
        } else {
 | 
						|
            alert('Graph data not available yet. Please wait for scan completion.');
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    async startScan() {
 | 
						|
        const target = document.getElementById('target').value.trim();
 | 
						|
        
 | 
						|
        if (!target) {
 | 
						|
            alert('Please enter a target domain or hostname');
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        const scanData = {
 | 
						|
            target: target,
 | 
						|
            max_depth: parseInt(document.getElementById('maxDepth').value),
 | 
						|
            shodan_key: document.getElementById('shodanKey').value.trim() || null,
 | 
						|
            virustotal_key: document.getElementById('virustotalKey').value.trim() || null
 | 
						|
        };
 | 
						|
 | 
						|
        try {
 | 
						|
            // Show progress section
 | 
						|
            this.showProgressSection();
 | 
						|
            this.updateProgress(0, 'Starting forensic scan...');
 | 
						|
 | 
						|
            this.debug('Starting scan with data:', scanData);
 | 
						|
 | 
						|
            const response = await fetch('/api/scan', {
 | 
						|
                method: 'POST',
 | 
						|
                headers: {
 | 
						|
                    'Content-Type': 'application/json',
 | 
						|
                },
 | 
						|
                body: JSON.stringify(scanData)
 | 
						|
            });
 | 
						|
 | 
						|
            if (!response.ok) {
 | 
						|
                throw new Error(`HTTP error! status: ${response.status}`);
 | 
						|
            }
 | 
						|
 | 
						|
            const result = await response.json();
 | 
						|
            
 | 
						|
            if (result.error) {
 | 
						|
                throw new Error(result.error);
 | 
						|
            }
 | 
						|
 | 
						|
            this.currentScanId = result.scan_id;
 | 
						|
            this.debug('Scan started with ID:', this.currentScanId);
 | 
						|
            
 | 
						|
            this.startPolling();
 | 
						|
            this.startLiveDataPolling();
 | 
						|
 | 
						|
        } catch (error) {
 | 
						|
            console.error('❌ Failed to start scan:', error);
 | 
						|
            this.showError(`Failed to start scan: ${error.message}`);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    startPolling() {
 | 
						|
        this.debug('Starting status polling...');
 | 
						|
        // Poll every 2 seconds for status updates
 | 
						|
        this.pollInterval = setInterval(() => {
 | 
						|
            this.checkScanStatus();
 | 
						|
        }, 2000);
 | 
						|
        
 | 
						|
        // Also check immediately
 | 
						|
        this.checkScanStatus();
 | 
						|
    }
 | 
						|
    
 | 
						|
    startLiveDataPolling() {
 | 
						|
        this.debug('Starting live data polling...');
 | 
						|
        // Poll every 3 seconds for live data updates
 | 
						|
        this.liveDataInterval = setInterval(() => {
 | 
						|
            this.updateLiveData();
 | 
						|
        }, 3000);
 | 
						|
        
 | 
						|
        // Show the live discoveries section
 | 
						|
        const liveSection = document.querySelector('.live-discoveries');
 | 
						|
        if (liveSection) {
 | 
						|
            liveSection.style.display = 'block';
 | 
						|
            this.debug('Live discoveries section made visible');
 | 
						|
        } else {
 | 
						|
            this.debug('ERROR: Live discoveries section not found!');
 | 
						|
        }
 | 
						|
        
 | 
						|
        // Also update immediately
 | 
						|
        this.updateLiveData();
 | 
						|
    }
 | 
						|
 | 
						|
    async checkScanStatus() {
 | 
						|
        if (!this.currentScanId) {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        try {
 | 
						|
            const response = await fetch(`/api/scan/${this.currentScanId}/status`);
 | 
						|
            
 | 
						|
            if (!response.ok) {
 | 
						|
                throw new Error(`HTTP error! status: ${response.status}`);
 | 
						|
            }
 | 
						|
 | 
						|
            const status = await response.json();
 | 
						|
            
 | 
						|
            if (status.error) {
 | 
						|
                throw new Error(status.error);
 | 
						|
            }
 | 
						|
 | 
						|
            // Update progress
 | 
						|
            this.updateProgress(status.progress, status.message);
 | 
						|
            
 | 
						|
            // Update live stats (handle new forensic format)
 | 
						|
            if (status.live_stats) {
 | 
						|
                this.debug('Received live stats:', status.live_stats);
 | 
						|
                this.updateLiveStats(status.live_stats);
 | 
						|
            }
 | 
						|
 | 
						|
            // Check if completed
 | 
						|
            if (status.status === 'completed') {
 | 
						|
                this.debug('Scan completed, loading report...');
 | 
						|
                this.stopPolling();
 | 
						|
                await this.loadScanReport();
 | 
						|
            } else if (status.status === 'error') {
 | 
						|
                this.stopPolling();
 | 
						|
                throw new Error(status.error || 'Scan failed');
 | 
						|
            }
 | 
						|
 | 
						|
        } catch (error) {
 | 
						|
            console.error('❌ Error checking scan status:', error);
 | 
						|
            this.stopPolling();
 | 
						|
            this.showError(`Error checking scan status: ${error.message}`);
 | 
						|
        }
 | 
						|
    }
 | 
						|
    
 | 
						|
    async updateLiveData() {
 | 
						|
        if (!this.currentScanId) {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
        
 | 
						|
        this.debug(`Fetching live data for scan: ${this.currentScanId}`);
 | 
						|
        
 | 
						|
        try {
 | 
						|
            const response = await fetch(`/api/scan/${this.currentScanId}/live-data`);
 | 
						|
            
 | 
						|
            if (!response.ok) {
 | 
						|
                this.debug(`Live data request failed: HTTP ${response.status}`);
 | 
						|
                return; // Silently fail for live data
 | 
						|
            }
 | 
						|
            
 | 
						|
            const data = await response.json();
 | 
						|
            
 | 
						|
            if (data.error) {
 | 
						|
                this.debug('Live data error:', data.error);
 | 
						|
                return; // Silently fail for live data
 | 
						|
            }
 | 
						|
            
 | 
						|
            this.debug('Received live data:', data);
 | 
						|
            
 | 
						|
            // Update live discoveries (handle new forensic format)
 | 
						|
            this.updateLiveDiscoveries(data);
 | 
						|
            
 | 
						|
        } catch (error) {
 | 
						|
            // Silently fail for live data updates
 | 
						|
            this.debug('Live data update failed:', error);
 | 
						|
        }
 | 
						|
    }
 | 
						|
    
 | 
						|
    updateLiveStats(stats) {
 | 
						|
        this.debug('Updating live stats:', stats);
 | 
						|
        
 | 
						|
        // Handle both old and new stat formats for compatibility
 | 
						|
        const statMappings = {
 | 
						|
            'liveHostnames': stats.hostnames || stats.hostnames || 0,
 | 
						|
            'liveIPs': stats.ip_addresses || stats.ips || 0,
 | 
						|
            'liveEdges': stats.discovery_edges || 0, // New forensic field
 | 
						|
            'liveOperations': stats.operations_performed || 0, // New forensic field
 | 
						|
            'liveDNS': stats.dns_records || 0,
 | 
						|
            'liveCerts': stats.certificates_total || stats.certificates || 0,
 | 
						|
            'liveShodan': stats.shodan_results || 0,
 | 
						|
            'liveVT': stats.virustotal_results || 0
 | 
						|
        };
 | 
						|
        
 | 
						|
        Object.entries(statMappings).forEach(([elementId, value]) => {
 | 
						|
            const element = document.getElementById(elementId);
 | 
						|
            if (element) {
 | 
						|
                const currentValue = element.textContent;
 | 
						|
                element.textContent = value;
 | 
						|
                
 | 
						|
                if (currentValue !== value.toString()) {
 | 
						|
                    this.debug(`Updated ${elementId}: ${currentValue} -> ${value}`);
 | 
						|
                    // Add a brief highlight effect when value changes
 | 
						|
                    element.style.backgroundColor = '#ff9900';
 | 
						|
                    setTimeout(() => {
 | 
						|
                        element.style.backgroundColor = '';
 | 
						|
                    }, 1000);
 | 
						|
                }
 | 
						|
            } else {
 | 
						|
                this.debug(`ERROR: Element ${elementId} not found!`);
 | 
						|
            }
 | 
						|
        });
 | 
						|
    }
 | 
						|
    
 | 
						|
    updateLiveDiscoveries(data) {
 | 
						|
        this.debug('Updating live discoveries with data:', data);
 | 
						|
        
 | 
						|
        // Handle new forensic data format
 | 
						|
        let hostnames = [];
 | 
						|
        let ipAddresses = [];
 | 
						|
        let activities = [];
 | 
						|
        
 | 
						|
        // Extract data from forensic format or fallback to old format
 | 
						|
        if (data.hostnames && Array.isArray(data.hostnames)) {
 | 
						|
            hostnames = data.hostnames;
 | 
						|
        } else if (data.stats && data.stats.hostnames) {
 | 
						|
            // If we only have stats, create a placeholder list
 | 
						|
            hostnames = [`${data.stats.hostnames} discovered`];
 | 
						|
        }
 | 
						|
        
 | 
						|
        if (data.ip_addresses && Array.isArray(data.ip_addresses)) {
 | 
						|
            ipAddresses = data.ip_addresses;
 | 
						|
        } else if (data.stats && data.stats.ip_addresses) {
 | 
						|
            // If we only have stats, create a placeholder list
 | 
						|
            ipAddresses = [`${data.stats.ip_addresses} discovered`];
 | 
						|
        }
 | 
						|
        
 | 
						|
        // Handle activity log from forensic format
 | 
						|
        if (data.latest_discoveries && Array.isArray(data.latest_discoveries)) {
 | 
						|
            activities = data.latest_discoveries;
 | 
						|
        }
 | 
						|
        
 | 
						|
        // Update hostnames list
 | 
						|
        const hostnameList = document.querySelector('#recentHostnames .hostname-list');
 | 
						|
        if (hostnameList && hostnames.length > 0) {
 | 
						|
            // Show last 10 hostnames
 | 
						|
            const recentHostnames = hostnames.slice(-10);
 | 
						|
            hostnameList.innerHTML = recentHostnames.map(hostname => 
 | 
						|
                `<span class="discovery-item">${hostname}</span>`
 | 
						|
            ).join('');
 | 
						|
            this.debug(`Updated hostname list with ${recentHostnames.length} items`);
 | 
						|
        } else if (hostnameList) {
 | 
						|
            this.debug(`No hostnames to display (${hostnames.length} total)`);
 | 
						|
        }
 | 
						|
        
 | 
						|
        // Update IP addresses list
 | 
						|
        const ipList = document.querySelector('#recentIPs .ip-list');
 | 
						|
        if (ipList && ipAddresses.length > 0) {
 | 
						|
            // Show last 10 IPs
 | 
						|
            const recentIPs = ipAddresses.slice(-10);
 | 
						|
            ipList.innerHTML = recentIPs.map(ip => 
 | 
						|
                `<span class="discovery-item">${ip}</span>`
 | 
						|
            ).join('');
 | 
						|
            this.debug(`Updated IP list with ${recentIPs.length} items`);
 | 
						|
        } else if (ipList) {
 | 
						|
            this.debug(`No IPs to display (${ipAddresses.length} total)`);
 | 
						|
        }
 | 
						|
        
 | 
						|
        // Update activity log
 | 
						|
        const activityList = document.querySelector('#activityLog .activity-list');
 | 
						|
        if (activityList) {
 | 
						|
            if (activities.length > 0) {
 | 
						|
                const recentActivities = activities.slice(-5); // Last 5 activities
 | 
						|
                activityList.innerHTML = recentActivities.map(activity => {
 | 
						|
                    const time = new Date(activity.timestamp * 1000).toLocaleTimeString();
 | 
						|
                    return `<div class="activity-item">[${time}] ${activity.message}</div>`;
 | 
						|
                }).join('');
 | 
						|
                this.debug(`Updated activity log with ${recentActivities.length} items`);
 | 
						|
            } else {
 | 
						|
                // Fallback: show generic activity based on stats
 | 
						|
                const stats = data.stats || {};
 | 
						|
                const genericActivities = [];
 | 
						|
                
 | 
						|
                if (stats.operations_performed > 0) {
 | 
						|
                    genericActivities.push(`${stats.operations_performed} operations performed`);
 | 
						|
                }
 | 
						|
                if (stats.hostnames > 0) {
 | 
						|
                    genericActivities.push(`${stats.hostnames} hostnames discovered`);
 | 
						|
                }
 | 
						|
                if (stats.dns_records > 0) {
 | 
						|
                    genericActivities.push(`${stats.dns_records} DNS records collected`);
 | 
						|
                }
 | 
						|
                
 | 
						|
                if (genericActivities.length > 0) {
 | 
						|
                    const now = new Date().toLocaleTimeString();
 | 
						|
                    activityList.innerHTML = genericActivities.map(activity => 
 | 
						|
                        `<div class="activity-item">[${now}] ${activity}</div>`
 | 
						|
                    ).join('');
 | 
						|
                    this.debug(`Updated activity log with ${genericActivities.length} generic items`);
 | 
						|
                } else {
 | 
						|
                    this.debug('No activities to display');
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    async loadScanReport() {
 | 
						|
        try {
 | 
						|
            this.debug('Loading scan report...');
 | 
						|
            const response = await fetch(`/api/scan/${this.currentScanId}/report`);
 | 
						|
            
 | 
						|
            if (!response.ok) {
 | 
						|
                throw new Error(`HTTP error! status: ${response.status}`);
 | 
						|
            }
 | 
						|
 | 
						|
            const report = await response.json();
 | 
						|
            
 | 
						|
            if (report.error) {
 | 
						|
                throw new Error(report.error);
 | 
						|
            }
 | 
						|
 | 
						|
            this.currentReport = report;
 | 
						|
            this.debug('Report loaded successfully');
 | 
						|
            this.showResultsSection();
 | 
						|
            this.showReport('text'); // Default to text view
 | 
						|
            
 | 
						|
            // Load and show graph
 | 
						|
            if (!this.graphVisualization) {
 | 
						|
                this.graphVisualization = new GraphVisualization();
 | 
						|
            }
 | 
						|
            await this.graphVisualization.loadAndShowGraph(this.currentScanId);
 | 
						|
 | 
						|
        } catch (error) {
 | 
						|
            console.error('⚠️ Error loading report:', error);
 | 
						|
            this.showError(`Error loading report: ${error.message}`);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    stopPolling() {
 | 
						|
        this.debug('Stopping polling intervals...');
 | 
						|
        if (this.pollInterval) {
 | 
						|
            clearInterval(this.pollInterval);
 | 
						|
            this.pollInterval = null;
 | 
						|
        }
 | 
						|
        if (this.liveDataInterval) {
 | 
						|
            clearInterval(this.liveDataInterval);
 | 
						|
            this.liveDataInterval = null;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    showProgressSection() {
 | 
						|
        document.getElementById('scanForm').style.display = 'none';
 | 
						|
        document.getElementById('progressSection').style.display = 'block';
 | 
						|
        document.getElementById('resultsSection').style.display = 'none';
 | 
						|
        this.debug('Showing progress section');
 | 
						|
    }
 | 
						|
 | 
						|
    showResultsSection() {
 | 
						|
        document.getElementById('scanForm').style.display = 'none';
 | 
						|
        document.getElementById('progressSection').style.display = 'block'; // Keep visible
 | 
						|
        document.getElementById('resultsSection').style.display = 'block';
 | 
						|
        
 | 
						|
        // Change the title to show it's the final summary
 | 
						|
        const liveSection = document.querySelector('.live-discoveries');
 | 
						|
        if (liveSection) {
 | 
						|
            const title = liveSection.querySelector('h3');
 | 
						|
            if (title) {
 | 
						|
                title.textContent = '📊 Final Forensic Summary';
 | 
						|
            }
 | 
						|
            liveSection.style.display = 'block';
 | 
						|
        }
 | 
						|
        
 | 
						|
        // Hide just the progress bar and scan controls
 | 
						|
        const progressBar = document.querySelector('.progress-bar');
 | 
						|
        const progressMessage = document.getElementById('progressMessage');
 | 
						|
        const scanControls = document.querySelector('.scan-controls');
 | 
						|
        
 | 
						|
        if (progressBar) progressBar.style.display = 'none';
 | 
						|
        if (progressMessage) progressMessage.style.display = 'none';
 | 
						|
        if (scanControls) scanControls.style.display = 'none';
 | 
						|
        
 | 
						|
        this.debug('Showing results section with forensic discoveries');
 | 
						|
    }
 | 
						|
 | 
						|
    resetToForm() {
 | 
						|
        this.stopPolling();
 | 
						|
        this.currentScanId = null;
 | 
						|
        this.currentReport = null;
 | 
						|
        
 | 
						|
        document.getElementById('scanForm').style.display = 'block';
 | 
						|
        document.getElementById('progressSection').style.display = 'none';
 | 
						|
        document.getElementById('resultsSection').style.display = 'none';
 | 
						|
        
 | 
						|
        // Show progress elements again
 | 
						|
        const progressBar = document.querySelector('.progress-bar');
 | 
						|
        const progressMessage = document.getElementById('progressMessage');
 | 
						|
        const scanControls = document.querySelector('.scan-controls');
 | 
						|
        
 | 
						|
        if (progressBar) progressBar.style.display = 'block';
 | 
						|
        if (progressMessage) progressMessage.style.display = 'block';
 | 
						|
        if (scanControls) scanControls.style.display = 'block';
 | 
						|
        
 | 
						|
        // Hide live discoveries and reset title
 | 
						|
        const liveSection = document.querySelector('.live-discoveries');
 | 
						|
        if (liveSection) {
 | 
						|
            liveSection.style.display = 'none';
 | 
						|
            const title = liveSection.querySelector('h3');
 | 
						|
            if (title) {
 | 
						|
                title.textContent = '🔍 Live Discoveries';
 | 
						|
            }
 | 
						|
        }
 | 
						|
        
 | 
						|
        // Clear form
 | 
						|
        document.getElementById('target').value = '';
 | 
						|
        document.getElementById('shodanKey').value = '';
 | 
						|
        document.getElementById('virustotalKey').value = '';
 | 
						|
        document.getElementById('maxDepth').value = '2';
 | 
						|
        
 | 
						|
        this.debug('Reset to form view');
 | 
						|
    }
 | 
						|
 | 
						|
    updateProgress(percentage, message) {
 | 
						|
        const progressFill = document.getElementById('progressFill');
 | 
						|
        const progressMessage = document.getElementById('progressMessage');
 | 
						|
        
 | 
						|
        progressFill.style.width = `${percentage || 0}%`;
 | 
						|
        progressMessage.textContent = message || 'Processing...';
 | 
						|
    }
 | 
						|
 | 
						|
    showError(message) {
 | 
						|
        // Update progress section to show error
 | 
						|
        this.updateProgress(0, `Error: ${message}`);
 | 
						|
        
 | 
						|
        // Also alert the user
 | 
						|
        alert(`Error: ${message}`);
 | 
						|
    }
 | 
						|
 | 
						|
    showReport(type) {
 | 
						|
        if (!this.currentReport) {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        const reportContent = document.getElementById('reportContent');
 | 
						|
        const showJsonBtn = document.getElementById('showJson');
 | 
						|
        const showTextBtn = document.getElementById('showText');
 | 
						|
 | 
						|
        if (type === 'json') {
 | 
						|
            // Show JSON report
 | 
						|
            try {
 | 
						|
                // The json_report should already be a string from the server
 | 
						|
                let jsonData;
 | 
						|
                if (typeof this.currentReport.json_report === 'string') {
 | 
						|
                    jsonData = JSON.parse(this.currentReport.json_report);
 | 
						|
                } else {
 | 
						|
                    jsonData = this.currentReport.json_report;
 | 
						|
                }
 | 
						|
                reportContent.textContent = JSON.stringify(jsonData, null, 2);
 | 
						|
            } catch (e) {
 | 
						|
                console.error('Error parsing JSON report:', e);
 | 
						|
                reportContent.textContent = this.currentReport.json_report;
 | 
						|
            }
 | 
						|
            
 | 
						|
            showJsonBtn.classList.add('active');
 | 
						|
            showTextBtn.classList.remove('active');
 | 
						|
        } else {
 | 
						|
            // Show text report
 | 
						|
            reportContent.textContent = this.currentReport.text_report;
 | 
						|
            
 | 
						|
            showTextBtn.classList.add('active');
 | 
						|
            showJsonBtn.classList.remove('active');
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    downloadReport(type) {
 | 
						|
        if (!this.currentReport) {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        let content, filename, mimeType;
 | 
						|
 | 
						|
        if (type === 'json') {
 | 
						|
            content = typeof this.currentReport.json_report === 'string' 
 | 
						|
                ? this.currentReport.json_report 
 | 
						|
                : JSON.stringify(this.currentReport.json_report, null, 2);
 | 
						|
            filename = `forensic-recon-report-${this.currentScanId}.json`;
 | 
						|
            mimeType = 'application/json';
 | 
						|
        } else {
 | 
						|
            content = this.currentReport.text_report;
 | 
						|
            filename = `forensic-recon-report-${this.currentScanId}.txt`;
 | 
						|
            mimeType = 'text/plain';
 | 
						|
        }
 | 
						|
 | 
						|
        // Create download link
 | 
						|
        const blob = new Blob([content], { type: mimeType });
 | 
						|
        const url = window.URL.createObjectURL(blob);
 | 
						|
        const a = document.createElement('a');
 | 
						|
        a.href = url;
 | 
						|
        a.download = filename;
 | 
						|
        document.body.appendChild(a);
 | 
						|
        a.click();
 | 
						|
        window.URL.revokeObjectURL(url);
 | 
						|
        document.body.removeChild(a);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
class GraphVisualization {
 | 
						|
    constructor() {
 | 
						|
        this.svg = null;
 | 
						|
        this.simulation = null;
 | 
						|
        this.graphData = null;
 | 
						|
        this.showLabels = false;
 | 
						|
        this.selectedNode = null;
 | 
						|
        this.zoom = null;
 | 
						|
        this.container = null;
 | 
						|
    }
 | 
						|
 | 
						|
    async loadAndShowGraph(scanId) {
 | 
						|
        try {
 | 
						|
            console.log('🕸️ Loading graph data...');
 | 
						|
            const response = await fetch(`/api/scan/${scanId}/graph`);
 | 
						|
            
 | 
						|
            if (!response.ok) {
 | 
						|
                throw new Error(`HTTP error! status: ${response.status}`);
 | 
						|
            }
 | 
						|
 | 
						|
            const graphData = await response.json();
 | 
						|
            
 | 
						|
            if (graphData.error) {
 | 
						|
                throw new Error(graphData.error);
 | 
						|
            }
 | 
						|
 | 
						|
            this.graphData = graphData;
 | 
						|
            this.showGraphSection();
 | 
						|
            this.initializeGraph();
 | 
						|
            this.updateGraphStats(graphData.stats);
 | 
						|
            
 | 
						|
            console.log('✅ Graph loaded successfully', graphData.stats);
 | 
						|
            
 | 
						|
        } catch (error) {
 | 
						|
            console.error('⚠️ Error loading graph:', error);
 | 
						|
            alert(`Failed to load graph: ${error.message}`);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    showGraphSection() {
 | 
						|
        document.getElementById('graphSection').style.display = 'block';
 | 
						|
        this.bindGraphEvents();
 | 
						|
    }
 | 
						|
 | 
						|
    bindGraphEvents() {
 | 
						|
        document.getElementById('showGraph').addEventListener('click', () => {
 | 
						|
            this.showGraph();
 | 
						|
        });
 | 
						|
 | 
						|
        document.getElementById('hideGraph').addEventListener('click', () => {
 | 
						|
            this.hideGraph();
 | 
						|
        });
 | 
						|
 | 
						|
        document.getElementById('resetZoom').addEventListener('click', () => {
 | 
						|
            this.resetZoom();
 | 
						|
        });
 | 
						|
 | 
						|
        document.getElementById('toggleLabels').addEventListener('click', () => {
 | 
						|
            this.toggleLabels();
 | 
						|
        });
 | 
						|
    }
 | 
						|
 | 
						|
    initializeGraph() {
 | 
						|
        if (!this.graphData) return;
 | 
						|
 | 
						|
        // Clear existing graph
 | 
						|
        d3.select('#discoveryGraph').selectAll('*').remove();
 | 
						|
 | 
						|
        // Set up SVG
 | 
						|
        const container = d3.select('#discoveryGraph');
 | 
						|
        const containerNode = container.node();
 | 
						|
        const width = containerNode.clientWidth;
 | 
						|
        const height = containerNode.clientHeight;
 | 
						|
 | 
						|
        this.svg = container
 | 
						|
            .attr('width', width)
 | 
						|
            .attr('height', height);
 | 
						|
 | 
						|
        // Set up zoom behavior
 | 
						|
        this.zoom = d3.zoom()
 | 
						|
            .scaleExtent([0.1, 3])
 | 
						|
            .on('zoom', (event) => {
 | 
						|
                this.container.attr('transform', event.transform);
 | 
						|
            });
 | 
						|
 | 
						|
        this.svg.call(this.zoom);
 | 
						|
 | 
						|
        // Create container for graph elements
 | 
						|
        this.container = this.svg.append('g');
 | 
						|
 | 
						|
        // Set up force simulation
 | 
						|
        this.simulation = d3.forceSimulation(this.graphData.nodes)
 | 
						|
            .force('link', d3.forceLink(this.graphData.edges)
 | 
						|
                .id(d => d.id)
 | 
						|
                .distance(80)
 | 
						|
                .strength(0.5))
 | 
						|
            .force('charge', d3.forceManyBody()
 | 
						|
                .strength(-300)
 | 
						|
                .distanceMax(400))
 | 
						|
            .force('center', d3.forceCenter(width / 2, height / 2))
 | 
						|
            .force('collision', d3.forceCollide()
 | 
						|
                .radius(d => d.size + 5));
 | 
						|
 | 
						|
        this.drawGraph();
 | 
						|
        this.startSimulation();
 | 
						|
    }
 | 
						|
 | 
						|
    drawGraph() {
 | 
						|
        // Draw links
 | 
						|
        const links = this.container.append('g')
 | 
						|
            .selectAll('line')
 | 
						|
            .data(this.graphData.edges)
 | 
						|
            .enter().append('line')
 | 
						|
            .attr('class', 'graph-link')
 | 
						|
            .attr('stroke', d => d.color)
 | 
						|
            .on('mouseover', (event, d) => {
 | 
						|
                this.showTooltip(event, `Discovery: ${d.method}<br>From: ${d.source}<br>To: ${d.target}`);
 | 
						|
            })
 | 
						|
            .on('mouseout', () => {
 | 
						|
                this.hideTooltip();
 | 
						|
            });
 | 
						|
 | 
						|
        // Draw nodes
 | 
						|
        const nodes = this.container.append('g')
 | 
						|
            .selectAll('circle')
 | 
						|
            .data(this.graphData.nodes)
 | 
						|
            .enter().append('circle')
 | 
						|
            .attr('class', 'graph-node')
 | 
						|
            .attr('r', d => d.size)
 | 
						|
            .attr('fill', d => d.color)
 | 
						|
            .style('opacity', 0.8)
 | 
						|
            .on('mouseover', (event, d) => {
 | 
						|
                this.showNodeTooltip(event, d);
 | 
						|
                this.highlightConnections(d);
 | 
						|
            })
 | 
						|
            .on('mouseout', (event, d) => {
 | 
						|
                this.hideTooltip();
 | 
						|
                this.unhighlightConnections();
 | 
						|
            })
 | 
						|
            .on('click', (event, d) => {
 | 
						|
                this.selectNode(d);
 | 
						|
            })
 | 
						|
            .call(d3.drag()
 | 
						|
                .on('start', (event, d) => {
 | 
						|
                    if (!event.active) this.simulation.alphaTarget(0.3).restart();
 | 
						|
                    d.fx = d.x;
 | 
						|
                    d.fy = d.y;
 | 
						|
                })
 | 
						|
                .on('drag', (event, d) => {
 | 
						|
                    d.fx = event.x;
 | 
						|
                    d.fy = event.y;
 | 
						|
                })
 | 
						|
                .on('end', (event, d) => {
 | 
						|
                    if (!event.active) this.simulation.alphaTarget(0);
 | 
						|
                    d.fx = null;
 | 
						|
                    d.fy = null;
 | 
						|
                }));
 | 
						|
 | 
						|
        // Draw labels (initially hidden)
 | 
						|
        const labels = this.container.append('g')
 | 
						|
            .selectAll('text')
 | 
						|
            .data(this.graphData.nodes)
 | 
						|
            .enter().append('text')
 | 
						|
            .attr('class', 'graph-label')
 | 
						|
            .attr('dy', '.35em')
 | 
						|
            .style('opacity', this.showLabels ? 1 : 0)
 | 
						|
            .text(d => d.label);
 | 
						|
 | 
						|
        // Store references
 | 
						|
        this.links = links;
 | 
						|
        this.nodes = nodes;
 | 
						|
        this.labels = labels;
 | 
						|
    }
 | 
						|
 | 
						|
    startSimulation() {
 | 
						|
        this.simulation.on('tick', () => {
 | 
						|
            this.links
 | 
						|
                .attr('x1', d => d.source.x)
 | 
						|
                .attr('y1', d => d.source.y)
 | 
						|
                .attr('x2', d => d.target.x)
 | 
						|
                .attr('y2', d => d.target.y);
 | 
						|
 | 
						|
            this.nodes
 | 
						|
                .attr('cx', d => d.x)
 | 
						|
                .attr('cy', d => d.y);
 | 
						|
 | 
						|
            this.labels
 | 
						|
                .attr('x', d => d.x)
 | 
						|
                .attr('y', d => d.y + d.size + 12);
 | 
						|
        });
 | 
						|
    }
 | 
						|
 | 
						|
    showNodeTooltip(event, node) {
 | 
						|
        const tooltip = `
 | 
						|
            <strong>${node.label}</strong><br>
 | 
						|
            Depth: ${node.depth}<br>
 | 
						|
            DNS Records: ${node.dns_records}<br>
 | 
						|
            Certificates: ${node.certificates}<br>
 | 
						|
            IPs: ${node.ip_addresses.length}<br>
 | 
						|
            Discovery: ${node.discovery_methods.join(', ')}<br>
 | 
						|
            First Seen: ${node.first_seen ? new Date(node.first_seen).toLocaleString() : 'Unknown'}
 | 
						|
        `;
 | 
						|
        this.showTooltip(event, tooltip);
 | 
						|
    }
 | 
						|
 | 
						|
    showTooltip(event, content) {
 | 
						|
        const tooltip = document.getElementById('graphTooltip');
 | 
						|
        tooltip.innerHTML = content;
 | 
						|
        tooltip.className = 'graph-tooltip visible';
 | 
						|
        
 | 
						|
        const rect = tooltip.getBoundingClientRect();
 | 
						|
        const containerRect = document.getElementById('discoveryGraph').getBoundingClientRect();
 | 
						|
        
 | 
						|
        tooltip.style.left = `${event.clientX - containerRect.left + 10}px`;
 | 
						|
        tooltip.style.top = `${event.clientY - containerRect.top - 10}px`;
 | 
						|
        
 | 
						|
        // Adjust position if tooltip goes off screen
 | 
						|
        if (event.clientX + rect.width > window.innerWidth) {
 | 
						|
            tooltip.style.left = `${event.clientX - containerRect.left - rect.width - 10}px`;
 | 
						|
        }
 | 
						|
        
 | 
						|
        if (event.clientY - rect.height < 0) {
 | 
						|
            tooltip.style.top = `${event.clientY - containerRect.top + 20}px`;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    hideTooltip() {
 | 
						|
        const tooltip = document.getElementById('graphTooltip');
 | 
						|
        tooltip.className = 'graph-tooltip';
 | 
						|
    }
 | 
						|
 | 
						|
    highlightConnections(node) {
 | 
						|
        // Highlight connected links
 | 
						|
        this.links
 | 
						|
            .style('opacity', d => (d.source.id === node.id || d.target.id === node.id) ? 1 : 0.2)
 | 
						|
            .classed('highlighted', d => d.source.id === node.id || d.target.id === node.id);
 | 
						|
 | 
						|
        // Highlight connected nodes
 | 
						|
        this.nodes
 | 
						|
            .style('opacity', d => {
 | 
						|
                if (d.id === node.id) return 1;
 | 
						|
                const connected = this.graphData.edges.some(edge => 
 | 
						|
                    (edge.source.id === node.id && edge.target.id === d.id) ||
 | 
						|
                    (edge.target.id === node.id && edge.source.id === d.id)
 | 
						|
                );
 | 
						|
                return connected ? 0.8 : 0.3;
 | 
						|
            });
 | 
						|
    }
 | 
						|
 | 
						|
    unhighlightConnections() {
 | 
						|
        this.links
 | 
						|
            .style('opacity', 0.6)
 | 
						|
            .classed('highlighted', false);
 | 
						|
 | 
						|
        this.nodes
 | 
						|
            .style('opacity', 0.8);
 | 
						|
    }
 | 
						|
 | 
						|
    selectNode(node) {
 | 
						|
        // Update selected node styling
 | 
						|
        this.nodes
 | 
						|
            .classed('selected', d => d.id === node.id);
 | 
						|
 | 
						|
        // Show node details
 | 
						|
        this.showNodeDetails(node);
 | 
						|
        this.selectedNode = node;
 | 
						|
    }
 | 
						|
 | 
						|
    showNodeDetails(node) {
 | 
						|
        const detailsContainer = document.getElementById('nodeDetails');
 | 
						|
        const selectedInfo = document.getElementById('selectedNodeInfo');
 | 
						|
        
 | 
						|
        const details = `
 | 
						|
            <div class="detail-item">
 | 
						|
                <span class="detail-label">Hostname:</span>
 | 
						|
                <span class="detail-value">${node.label}</span>
 | 
						|
            </div>
 | 
						|
            <div class="detail-item">
 | 
						|
                <span class="detail-label">Discovery Depth:</span>
 | 
						|
                <span class="detail-value">${node.depth}</span>
 | 
						|
            </div>
 | 
						|
            <div class="detail-item">
 | 
						|
                <span class="detail-label">DNS Records:</span>
 | 
						|
                <span class="detail-value">${node.dns_records}</span>
 | 
						|
            </div>
 | 
						|
            <div class="detail-item">
 | 
						|
                <span class="detail-label">Certificates:</span>
 | 
						|
                <span class="detail-value">${node.certificates}</span>
 | 
						|
            </div>
 | 
						|
            <div class="detail-item">
 | 
						|
                <span class="detail-label">IP Addresses:</span>
 | 
						|
                <span class="detail-value">${node.ip_addresses.join(', ') || 'None'}</span>
 | 
						|
            </div>
 | 
						|
            <div class="detail-item">
 | 
						|
                <span class="detail-label">Discovery Methods:</span>
 | 
						|
                <span class="detail-value">${node.discovery_methods.join(', ')}</span>
 | 
						|
            </div>
 | 
						|
            <div class="detail-item">
 | 
						|
                <span class="detail-label">First Seen:</span>
 | 
						|
                <span class="detail-value">${node.first_seen ? new Date(node.first_seen).toLocaleString() : 'Unknown'}</span>
 | 
						|
            </div>
 | 
						|
        `;
 | 
						|
        
 | 
						|
        detailsContainer.innerHTML = details;
 | 
						|
        selectedInfo.style.display = 'block';
 | 
						|
    }
 | 
						|
 | 
						|
    toggleLabels() {
 | 
						|
        this.showLabels = !this.showLabels;
 | 
						|
        if (this.labels) {
 | 
						|
            this.labels.transition()
 | 
						|
                .duration(300)
 | 
						|
                .style('opacity', this.showLabels ? 1 : 0);
 | 
						|
        }
 | 
						|
        
 | 
						|
        const button = document.getElementById('toggleLabels');
 | 
						|
        button.textContent = this.showLabels ? 'Hide Labels' : 'Show Labels';
 | 
						|
    }
 | 
						|
 | 
						|
    resetZoom() {
 | 
						|
        if (this.svg && this.zoom) {
 | 
						|
            this.svg.transition()
 | 
						|
                .duration(750)
 | 
						|
                .call(this.zoom.transform, d3.zoomIdentity);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    showGraph() {
 | 
						|
        if (this.container) {
 | 
						|
            this.container.style('display', 'block');
 | 
						|
        }
 | 
						|
        document.getElementById('showGraph').classList.add('active');
 | 
						|
        document.getElementById('hideGraph').classList.remove('active');
 | 
						|
    }
 | 
						|
 | 
						|
    hideGraph() {
 | 
						|
        if (this.container) {
 | 
						|
            this.container.style('display', 'none');
 | 
						|
        }
 | 
						|
        document.getElementById('hideGraph').classList.add('active');
 | 
						|
        document.getElementById('showGraph').classList.remove('active');
 | 
						|
    }
 | 
						|
 | 
						|
    updateGraphStats(stats) {
 | 
						|
        document.getElementById('graphNodes').textContent = stats.node_count || 0;
 | 
						|
        document.getElementById('graphEdges').textContent = stats.edge_count || 0;
 | 
						|
        document.getElementById('graphDepth').textContent = stats.max_depth || 0;
 | 
						|
    }
 | 
						|
 | 
						|
    // Handle window resize
 | 
						|
    handleResize() {
 | 
						|
        if (this.svg && this.graphData) {
 | 
						|
            const containerNode = document.getElementById('discoveryGraph');
 | 
						|
            const width = containerNode.clientWidth;
 | 
						|
            const height = containerNode.clientHeight;
 | 
						|
            
 | 
						|
            this.svg
 | 
						|
                .attr('width', width)
 | 
						|
                .attr('height', height);
 | 
						|
                
 | 
						|
            this.simulation
 | 
						|
                .force('center', d3.forceCenter(width / 2, height / 2))
 | 
						|
                .restart();
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
// Initialize the application when DOM is loaded
 | 
						|
document.addEventListener('DOMContentLoaded', () => {
 | 
						|
    console.log('🌐 Forensic DNS Reconnaissance Tool initialized with debug mode');
 | 
						|
    new ReconTool();
 | 
						|
}); |