1141 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1141 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/**
 | 
						||
 * Main application logic for DNSRecon web interface
 | 
						||
 * Handles UI interactions, API communication, and data flow
 | 
						||
 * DEBUG VERSION WITH EXTRA LOGGING
 | 
						||
 */
 | 
						||
 | 
						||
class DNSReconApp {
 | 
						||
    constructor() {
 | 
						||
        console.log('DNSReconApp constructor called');
 | 
						||
        this.graphManager = null;
 | 
						||
        this.scanStatus = 'idle';
 | 
						||
        this.pollInterval = null;
 | 
						||
        this.currentSessionId = null;
 | 
						||
        
 | 
						||
        // UI Elements
 | 
						||
        this.elements = {};
 | 
						||
        
 | 
						||
        // Application state
 | 
						||
        this.isScanning = false;
 | 
						||
        this.lastGraphUpdate = null;
 | 
						||
        
 | 
						||
        this.init();
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Initialize the application
 | 
						||
     */
 | 
						||
    init() {
 | 
						||
        console.log('DNSReconApp init called');
 | 
						||
        document.addEventListener('DOMContentLoaded', () => {
 | 
						||
            console.log('DOM loaded, initializing application...');
 | 
						||
            try {
 | 
						||
                this.initializeElements();
 | 
						||
                this.setupEventHandlers();
 | 
						||
                this.initializeGraph();
 | 
						||
                this.updateStatus();
 | 
						||
                this.loadProviders();
 | 
						||
                
 | 
						||
                console.log('DNSRecon application initialized successfully');
 | 
						||
            } catch (error) {
 | 
						||
                console.error('Failed to initialize DNSRecon application:', error);
 | 
						||
                this.showError(`Initialization failed: ${error.message}`);
 | 
						||
            }
 | 
						||
        });
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Initialize DOM element references
 | 
						||
     */
 | 
						||
    initializeElements() {
 | 
						||
        console.log('Initializing DOM elements...');
 | 
						||
        this.elements = {
 | 
						||
            // Form elements
 | 
						||
            targetDomain: document.getElementById('target-domain'),
 | 
						||
            maxDepth: document.getElementById('max-depth'),
 | 
						||
            startScan: document.getElementById('start-scan'),
 | 
						||
            stopScan: document.getElementById('stop-scan'),
 | 
						||
            exportResults: document.getElementById('export-results'),
 | 
						||
            configureApiKeys: document.getElementById('configure-api-keys'),
 | 
						||
            
 | 
						||
            // Status elements
 | 
						||
            scanStatus: document.getElementById('scan-status'),
 | 
						||
            targetDisplay: document.getElementById('target-display'),
 | 
						||
            depthDisplay: document.getElementById('depth-display'),
 | 
						||
            progressDisplay: document.getElementById('progress-display'),
 | 
						||
            indicatorsDisplay: document.getElementById('indicators-display'),
 | 
						||
            relationshipsDisplay: document.getElementById('relationships-display'),
 | 
						||
            progressFill: document.getElementById('progress-fill'),
 | 
						||
            
 | 
						||
            // Provider elements
 | 
						||
            providerList: document.getElementById('provider-list'),
 | 
						||
            
 | 
						||
            // Node Modal elements
 | 
						||
            nodeModal: document.getElementById('node-modal'),
 | 
						||
            modalTitle: document.getElementById('modal-title'),
 | 
						||
            modalDetails: document.getElementById('modal-details'),
 | 
						||
            modalClose: document.getElementById('modal-close'),
 | 
						||
 | 
						||
            // API Key Modal elements
 | 
						||
            apiKeyModal: document.getElementById('api-key-modal'),
 | 
						||
            apiKeyModalClose: document.getElementById('api-key-modal-close'),
 | 
						||
            virustotalApiKey: document.getElementById('virustotal-api-key'),
 | 
						||
            shodanApiKey: document.getElementById('shodan-api-key'),
 | 
						||
            saveApiKeys: document.getElementById('save-api-keys'),
 | 
						||
            resetApiKeys: document.getElementById('reset-api-keys'),
 | 
						||
 | 
						||
            // Other elements
 | 
						||
            sessionId: document.getElementById('session-id'),
 | 
						||
            connectionStatus: document.getElementById('connection-status')
 | 
						||
        };
 | 
						||
 | 
						||
        // Verify critical elements exist
 | 
						||
        const requiredElements = ['targetDomain', 'startScan', 'scanStatus'];
 | 
						||
        for (const elementName of requiredElements) {
 | 
						||
            if (!this.elements[elementName]) {
 | 
						||
                throw new Error(`Required element '${elementName}' not found in DOM`);
 | 
						||
            }
 | 
						||
        }
 | 
						||
 | 
						||
        console.log('DOM elements initialized successfully');
 | 
						||
        this.createMessageContainer();
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * Create a message container for showing user feedback
 | 
						||
     */
 | 
						||
    createMessageContainer() {
 | 
						||
        // Check if message container already exists
 | 
						||
        let messageContainer = document.getElementById('message-container');
 | 
						||
        if (!messageContainer) {
 | 
						||
            messageContainer = document.createElement('div');
 | 
						||
            messageContainer.id = 'message-container';
 | 
						||
            messageContainer.className = 'message-container';
 | 
						||
            messageContainer.style.cssText = `
 | 
						||
                position: fixed;
 | 
						||
                top: 20px;
 | 
						||
                right: 20px;
 | 
						||
                z-index: 1000;
 | 
						||
                max-width: 400px;
 | 
						||
            `;
 | 
						||
            document.body.appendChild(messageContainer);
 | 
						||
            console.log('Message container created');
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Setup event handlers
 | 
						||
     */
 | 
						||
    setupEventHandlers() {
 | 
						||
        console.log('Setting up event handlers...');
 | 
						||
        
 | 
						||
        try {
 | 
						||
            // Form interactions
 | 
						||
            this.elements.startScan.addEventListener('click', (e) => {
 | 
						||
                console.log('Start scan button clicked');
 | 
						||
                e.preventDefault();
 | 
						||
                this.startScan();
 | 
						||
            });
 | 
						||
            
 | 
						||
            this.elements.stopScan.addEventListener('click', (e) => {
 | 
						||
                console.log('Stop scan button clicked');
 | 
						||
                e.preventDefault();
 | 
						||
                this.stopScan();
 | 
						||
            });
 | 
						||
            
 | 
						||
            this.elements.exportResults.addEventListener('click', (e) => {
 | 
						||
                console.log('Export results button clicked');
 | 
						||
                e.preventDefault();
 | 
						||
                this.exportResults();
 | 
						||
            });
 | 
						||
 | 
						||
            this.elements.configureApiKeys.addEventListener('click', () => this.showApiKeyModal());
 | 
						||
            
 | 
						||
            // Enter key support for target domain input
 | 
						||
            this.elements.targetDomain.addEventListener('keypress', (e) => {
 | 
						||
                if (e.key === 'Enter' && !this.isScanning) {
 | 
						||
                    console.log('Enter key pressed in domain input');
 | 
						||
                    this.startScan();
 | 
						||
                }
 | 
						||
            });
 | 
						||
            
 | 
						||
            // Node Modal interactions
 | 
						||
            if (this.elements.modalClose) {
 | 
						||
                this.elements.modalClose.addEventListener('click', () => this.hideModal());
 | 
						||
            }
 | 
						||
            if (this.elements.nodeModal) {
 | 
						||
                this.elements.nodeModal.addEventListener('click', (e) => {
 | 
						||
                    if (e.target === this.elements.nodeModal) this.hideModal();
 | 
						||
                });
 | 
						||
            }
 | 
						||
 | 
						||
            // API Key Modal interactions
 | 
						||
            if (this.elements.apiKeyModalClose) {
 | 
						||
                this.elements.apiKeyModalClose.addEventListener('click', () => this.hideApiKeyModal());
 | 
						||
            }
 | 
						||
            if (this.elements.apiKeyModal) {
 | 
						||
                this.elements.apiKeyModal.addEventListener('click', (e) => {
 | 
						||
                    if (e.target === this.elements.apiKeyModal) this.hideApiKeyModal();
 | 
						||
                });
 | 
						||
            }
 | 
						||
            if (this.elements.saveApiKeys) {
 | 
						||
                this.elements.saveApiKeys.addEventListener('click', () => this.saveApiKeys());
 | 
						||
            }
 | 
						||
            if (this.elements.resetApiKeys) {
 | 
						||
                this.elements.resetApiKeys.addEventListener('click', () => this.resetApiKeys());
 | 
						||
            }
 | 
						||
 | 
						||
            // Custom events
 | 
						||
            document.addEventListener('nodeSelected', (e) => {
 | 
						||
                this.showNodeModal(e.detail.nodeId, e.detail.node);
 | 
						||
            });
 | 
						||
            
 | 
						||
            // Keyboard shortcuts
 | 
						||
            document.addEventListener('keydown', (e) => {
 | 
						||
                if (e.key === 'Escape') {
 | 
						||
                    this.hideModal();
 | 
						||
                    this.hideApiKeyModal();
 | 
						||
                }
 | 
						||
            });
 | 
						||
            
 | 
						||
            // Window events
 | 
						||
            window.addEventListener('beforeunload', () => {
 | 
						||
                if (this.isScanning) {
 | 
						||
                    return 'A scan is currently in progress. Are you sure you want to leave?';
 | 
						||
                }
 | 
						||
            });
 | 
						||
            
 | 
						||
            console.log('Event handlers set up successfully');
 | 
						||
            
 | 
						||
        } catch (error) {
 | 
						||
            console.error('Failed to setup event handlers:', error);
 | 
						||
            throw error;
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Initialize graph visualization
 | 
						||
     */
 | 
						||
    initializeGraph() {
 | 
						||
        try {
 | 
						||
            console.log('Initializing graph manager...');
 | 
						||
            this.graphManager = new GraphManager('network-graph');
 | 
						||
            console.log('Graph manager initialized successfully');
 | 
						||
        } catch (error) {
 | 
						||
            console.error('Failed to initialize graph manager:', error);
 | 
						||
            this.showError('Failed to initialize graph visualization');
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Start a reconnaissance scan
 | 
						||
     */
 | 
						||
    async startScan() {
 | 
						||
        console.log('=== STARTING SCAN ===');
 | 
						||
        
 | 
						||
        try {
 | 
						||
            const targetDomain = this.elements.targetDomain.value.trim();
 | 
						||
            const maxDepth = parseInt(this.elements.maxDepth.value);
 | 
						||
            
 | 
						||
            console.log(`Target domain: "${targetDomain}", Max depth: ${maxDepth}`);
 | 
						||
            
 | 
						||
            // Validation
 | 
						||
            if (!targetDomain) {
 | 
						||
                console.log('Validation failed: empty domain');
 | 
						||
                this.showError('Please enter a target domain');
 | 
						||
                this.elements.targetDomain.focus();
 | 
						||
                return;
 | 
						||
            }
 | 
						||
            
 | 
						||
            if (!this.isValidDomain(targetDomain)) {
 | 
						||
                console.log(`Validation failed: invalid domain format for "${targetDomain}"`);
 | 
						||
                this.showError('Please enter a valid domain name (e.g., example.com)');
 | 
						||
                this.elements.targetDomain.focus();
 | 
						||
                return;
 | 
						||
            }
 | 
						||
            
 | 
						||
            console.log('Validation passed, setting UI state to scanning...');
 | 
						||
            this.setUIState('scanning');
 | 
						||
            this.showInfo('Starting reconnaissance scan...');
 | 
						||
            
 | 
						||
            console.log('Making API call to start scan...');
 | 
						||
            
 | 
						||
            const requestData = {
 | 
						||
                target_domain: targetDomain,
 | 
						||
                max_depth: maxDepth
 | 
						||
            };
 | 
						||
            
 | 
						||
            console.log('Request data:', requestData);
 | 
						||
            
 | 
						||
            const response = await this.apiCall('/api/scan/start', 'POST', requestData);
 | 
						||
            
 | 
						||
            console.log('API response received:', response);
 | 
						||
            
 | 
						||
            if (response.success) {
 | 
						||
                this.currentSessionId = response.scan_id;
 | 
						||
                console.log('Starting polling with session ID:', this.currentSessionId);
 | 
						||
                this.startPolling();
 | 
						||
                this.showSuccess('Reconnaissance scan started successfully');
 | 
						||
                
 | 
						||
                // Clear previous graph
 | 
						||
                this.graphManager.clear();
 | 
						||
                
 | 
						||
                console.log(`Scan started for ${targetDomain} with depth ${maxDepth}`);
 | 
						||
                
 | 
						||
                // Force an immediate status update
 | 
						||
                console.log('Forcing immediate status update...');
 | 
						||
                setTimeout(() => {
 | 
						||
                    this.updateStatus();
 | 
						||
                    this.updateGraph();
 | 
						||
                }, 100);
 | 
						||
                
 | 
						||
            } else {
 | 
						||
                throw new Error(response.error || 'Failed to start scan');
 | 
						||
            }
 | 
						||
            
 | 
						||
        } catch (error) {
 | 
						||
            console.error('Failed to start scan:', error);
 | 
						||
            this.showError(`Failed to start scan: ${error.message}`);
 | 
						||
            this.setUIState('idle');
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Stop the current scan
 | 
						||
     */
 | 
						||
    async stopScan() {
 | 
						||
        try {
 | 
						||
            console.log('Stopping scan...');
 | 
						||
            const response = await this.apiCall('/api/scan/stop', 'POST');
 | 
						||
            
 | 
						||
            if (response.success) {
 | 
						||
                this.showSuccess('Scan stop requested');
 | 
						||
                console.log('Scan stop requested');
 | 
						||
            } else {
 | 
						||
                throw new Error(response.error || 'Failed to stop scan');
 | 
						||
            }
 | 
						||
            
 | 
						||
        } catch (error) {
 | 
						||
            console.error('Failed to stop scan:', error);
 | 
						||
            this.showError(`Failed to stop scan: ${error.message}`);
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Export scan results
 | 
						||
     */
 | 
						||
    async exportResults() {
 | 
						||
        try {
 | 
						||
            console.log('Exporting results...');
 | 
						||
            
 | 
						||
            // Create a temporary link to trigger download
 | 
						||
            const link = document.createElement('a');
 | 
						||
            link.href = '/api/export';
 | 
						||
            link.download = ''; // Let server determine filename
 | 
						||
            document.body.appendChild(link);
 | 
						||
            link.click();
 | 
						||
            document.body.removeChild(link);
 | 
						||
            
 | 
						||
            this.showSuccess('Results export initiated');
 | 
						||
            console.log('Results export initiated');
 | 
						||
            
 | 
						||
        } catch (error) {
 | 
						||
            console.error('Failed to export results:', error);
 | 
						||
            this.showError(`Failed to export results: ${error.message}`);
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Start polling for scan updates
 | 
						||
     */
 | 
						||
    startPolling() {
 | 
						||
        console.log('=== STARTING POLLING ===');
 | 
						||
        
 | 
						||
        if (this.pollInterval) {
 | 
						||
            console.log('Clearing existing poll interval');
 | 
						||
            clearInterval(this.pollInterval);
 | 
						||
        }
 | 
						||
        
 | 
						||
        this.pollInterval = setInterval(() => {
 | 
						||
            console.log('--- Polling tick ---');
 | 
						||
            this.updateStatus();
 | 
						||
            this.updateGraph();
 | 
						||
            this.loadProviders();
 | 
						||
        }, 1000); // Poll every 1 second for debugging
 | 
						||
        
 | 
						||
        console.log('Polling started with 1 second interval');
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Stop polling for updates
 | 
						||
     */
 | 
						||
    stopPolling() {
 | 
						||
        console.log('=== STOPPING POLLING ===');
 | 
						||
        if (this.pollInterval) {
 | 
						||
            clearInterval(this.pollInterval);
 | 
						||
            this.pollInterval = null;
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Update scan status from server
 | 
						||
     */
 | 
						||
    async updateStatus() {
 | 
						||
        try {
 | 
						||
            console.log('Updating status...');
 | 
						||
            const response = await this.apiCall('/api/scan/status');
 | 
						||
            
 | 
						||
            console.log('Status response:', response);
 | 
						||
            
 | 
						||
            if (response.success) {
 | 
						||
                const status = response.status;
 | 
						||
                console.log('Current scan status:', status.status);
 | 
						||
                console.log('Current progress:', status.progress_percentage + '%');
 | 
						||
                console.log('Graph stats:', status.graph_statistics);
 | 
						||
                
 | 
						||
                this.updateStatusDisplay(status);
 | 
						||
                
 | 
						||
                // Handle status changes
 | 
						||
                if (status.status !== this.scanStatus) {
 | 
						||
                    console.log(`*** STATUS CHANGED: ${this.scanStatus} -> ${status.status} ***`);
 | 
						||
                    this.handleStatusChange(status.status);
 | 
						||
                }
 | 
						||
                
 | 
						||
                this.scanStatus = status.status;
 | 
						||
            } else {
 | 
						||
                console.error('Status update failed:', response);
 | 
						||
            }
 | 
						||
            
 | 
						||
        } catch (error) {
 | 
						||
            console.error('Failed to update status:', error);
 | 
						||
            this.showConnectionError();
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Update graph from server
 | 
						||
     */
 | 
						||
    async updateGraph() {
 | 
						||
        try {
 | 
						||
            console.log('Updating graph...');
 | 
						||
            const response = await this.apiCall('/api/graph');
 | 
						||
            
 | 
						||
            console.log('Graph response:', response);
 | 
						||
            
 | 
						||
            if (response.success) {
 | 
						||
                const graphData = response.graph;
 | 
						||
                
 | 
						||
                console.log('Graph data received:');
 | 
						||
                console.log('- Nodes:', graphData.nodes ? graphData.nodes.length : 0);
 | 
						||
                console.log('- Edges:', graphData.edges ? graphData.edges.length : 0);
 | 
						||
                
 | 
						||
                if (graphData.nodes) {
 | 
						||
                    graphData.nodes.forEach(node => {
 | 
						||
                        console.log(`  Node: ${node.id} (${node.type})`);
 | 
						||
                    });
 | 
						||
                }
 | 
						||
                
 | 
						||
                if (graphData.edges) {
 | 
						||
                    graphData.edges.forEach(edge => {
 | 
						||
                        console.log(`  Edge: ${edge.from} -> ${edge.to} (${edge.label})`);
 | 
						||
                    });
 | 
						||
                }
 | 
						||
                
 | 
						||
                // Only update if data has changed
 | 
						||
                if (this.hasGraphChanged(graphData)) {
 | 
						||
                    console.log('*** GRAPH DATA CHANGED - UPDATING VISUALIZATION ***');
 | 
						||
                    this.graphManager.updateGraph(graphData);
 | 
						||
                    this.lastGraphUpdate = Date.now();
 | 
						||
                    
 | 
						||
                    // Update relationship count in status
 | 
						||
                    const edgeCount = graphData.edges ? graphData.edges.length : 0;
 | 
						||
                    if (this.elements.relationshipsDisplay) {
 | 
						||
                        this.elements.relationshipsDisplay.textContent = edgeCount;
 | 
						||
                    }
 | 
						||
                } else {
 | 
						||
                    console.log('Graph data unchanged, skipping update');
 | 
						||
                }
 | 
						||
            } else {
 | 
						||
                console.error('Graph update failed:', response);
 | 
						||
            }
 | 
						||
            
 | 
						||
        } catch (error) {
 | 
						||
            console.error('Failed to update graph:', error);
 | 
						||
            // Don't show error for graph updates to avoid spam
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Update status display elements
 | 
						||
     * @param {Object} status - Status object from server
 | 
						||
     */
 | 
						||
    updateStatusDisplay(status) {
 | 
						||
        try {
 | 
						||
            console.log('Updating status display...');
 | 
						||
            
 | 
						||
            // Update status text with animation
 | 
						||
            if (this.elements.scanStatus) {
 | 
						||
                const formattedStatus = this.formatStatus(status.status);
 | 
						||
                if (this.elements.scanStatus.textContent !== formattedStatus) {
 | 
						||
                    this.elements.scanStatus.textContent = formattedStatus;
 | 
						||
                    this.elements.scanStatus.classList.add('fade-in');
 | 
						||
                    setTimeout(() => this.elements.scanStatus.classList.remove('fade-in'), 300);
 | 
						||
                }
 | 
						||
                
 | 
						||
                // Add status-specific classes for styling
 | 
						||
                this.elements.scanStatus.className = `status-value status-${status.status}`;
 | 
						||
            }
 | 
						||
            
 | 
						||
            if (this.elements.targetDisplay) {
 | 
						||
                this.elements.targetDisplay.textContent = status.target_domain || 'None';
 | 
						||
            }
 | 
						||
            if (this.elements.depthDisplay) {
 | 
						||
                this.elements.depthDisplay.textContent = `${status.current_depth}/${status.max_depth}`;
 | 
						||
            }
 | 
						||
            if (this.elements.progressDisplay) {
 | 
						||
                this.elements.progressDisplay.textContent = `${status.progress_percentage.toFixed(1)}%`;
 | 
						||
            }
 | 
						||
            if (this.elements.indicatorsDisplay) {
 | 
						||
                this.elements.indicatorsDisplay.textContent = status.indicators_processed || 0;
 | 
						||
            }
 | 
						||
            
 | 
						||
            // Update progress bar with smooth animation
 | 
						||
            if (this.elements.progressFill) {
 | 
						||
                this.elements.progressFill.style.width = `${status.progress_percentage}%`;
 | 
						||
                
 | 
						||
                // Add pulsing animation for active scans
 | 
						||
                if (status.status === 'running') {
 | 
						||
                    this.elements.progressFill.parentElement.classList.add('scanning');
 | 
						||
                } else {
 | 
						||
                    this.elements.progressFill.parentElement.classList.remove('scanning');
 | 
						||
                }
 | 
						||
            }
 | 
						||
            
 | 
						||
            // Update session ID
 | 
						||
            if (this.currentSessionId && this.elements.sessionId) {
 | 
						||
                this.elements.sessionId.textContent = `Session: ${this.currentSessionId}`;
 | 
						||
            }
 | 
						||
            
 | 
						||
            console.log('Status display updated successfully');
 | 
						||
        } catch (error) {
 | 
						||
            console.error('Error updating status display:', error);
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Handle status changes
 | 
						||
     * @param {string} newStatus - New scan status
 | 
						||
     */
 | 
						||
    handleStatusChange(newStatus) {
 | 
						||
        console.log(`=== STATUS CHANGE: ${this.scanStatus} -> ${newStatus} ===`);
 | 
						||
        
 | 
						||
        switch (newStatus) {
 | 
						||
            case 'running':
 | 
						||
                this.setUIState('scanning');
 | 
						||
                this.showSuccess('Scan is running');
 | 
						||
                // Reset polling frequency for active scans
 | 
						||
                this.pollFrequency = 2000;
 | 
						||
                this.updateConnectionStatus('active');
 | 
						||
                break;
 | 
						||
                
 | 
						||
            case 'completed':
 | 
						||
                this.setUIState('completed');
 | 
						||
                this.stopPolling();
 | 
						||
                this.showSuccess('Scan completed successfully');
 | 
						||
                this.updateConnectionStatus('completed');
 | 
						||
                this.loadProviders();
 | 
						||
                // Force a final graph update
 | 
						||
                console.log('Scan completed - forcing final graph update');
 | 
						||
                setTimeout(() => this.updateGraph(), 100);
 | 
						||
                break;
 | 
						||
                
 | 
						||
            case 'failed':
 | 
						||
                this.setUIState('failed');
 | 
						||
                this.stopPolling();
 | 
						||
                this.showError('Scan failed');
 | 
						||
                this.updateConnectionStatus('error');
 | 
						||
                this.loadProviders();
 | 
						||
                break;
 | 
						||
                
 | 
						||
            case 'stopped':
 | 
						||
                this.setUIState('stopped');
 | 
						||
                this.stopPolling();
 | 
						||
                this.showSuccess('Scan stopped');
 | 
						||
                this.updateConnectionStatus('stopped');
 | 
						||
                this.loadProviders();
 | 
						||
                break;
 | 
						||
                
 | 
						||
            case 'idle':
 | 
						||
                this.setUIState('idle');
 | 
						||
                this.stopPolling();
 | 
						||
                this.updateConnectionStatus('idle');
 | 
						||
                break;
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * Update connection status indicator
 | 
						||
     * @param {string} status - Connection status
 | 
						||
     */
 | 
						||
    updateConnectionStatus(status) {
 | 
						||
        if (!this.elements.connectionStatus) return;
 | 
						||
        
 | 
						||
        const statusColors = {
 | 
						||
            'idle': '#c7c7c7',
 | 
						||
            'active': '#00ff41',
 | 
						||
            'completed': '#00aa2e',
 | 
						||
            'stopped': '#ff9900',
 | 
						||
            'error': '#ff6b6b'
 | 
						||
        };
 | 
						||
        
 | 
						||
        this.elements.connectionStatus.style.backgroundColor = statusColors[status] || '#c7c7c7';
 | 
						||
        
 | 
						||
        const statusText = this.elements.connectionStatus.parentElement?.querySelector('.status-text');
 | 
						||
        if (statusText) {
 | 
						||
            const statusTexts = {
 | 
						||
                'idle': 'System Ready',
 | 
						||
                'active': 'Scanning Active',
 | 
						||
                'completed': 'Scan Complete',
 | 
						||
                'stopped': 'Scan Stopped',
 | 
						||
                'error': 'Connection Error'
 | 
						||
            };
 | 
						||
            
 | 
						||
            statusText.textContent = statusTexts[status] || 'System Online';
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Set UI state based on scan status
 | 
						||
     * @param {string} state - UI state
 | 
						||
     */
 | 
						||
    setUIState(state) {
 | 
						||
        console.log(`Setting UI state to: ${state}`);
 | 
						||
        
 | 
						||
        switch (state) {
 | 
						||
            case 'scanning':
 | 
						||
                this.isScanning = true;
 | 
						||
                if (this.elements.startScan) {
 | 
						||
                    this.elements.startScan.disabled = true;
 | 
						||
                    this.elements.startScan.classList.add('loading');
 | 
						||
                }
 | 
						||
                if (this.elements.stopScan) {
 | 
						||
                    this.elements.stopScan.disabled = false;
 | 
						||
                    this.elements.stopScan.classList.remove('loading');
 | 
						||
                }
 | 
						||
                if (this.elements.targetDomain) this.elements.targetDomain.disabled = true;
 | 
						||
                if (this.elements.maxDepth) this.elements.maxDepth.disabled = true;
 | 
						||
                if (this.elements.configureApiKeys) this.elements.configureApiKeys.disabled = true;
 | 
						||
                break;
 | 
						||
                
 | 
						||
            case 'idle':
 | 
						||
            case 'completed':
 | 
						||
            case 'failed':
 | 
						||
            case 'stopped':
 | 
						||
                this.isScanning = false;
 | 
						||
                if (this.elements.startScan) {
 | 
						||
                    this.elements.startScan.disabled = false;
 | 
						||
                    this.elements.startScan.classList.remove('loading');
 | 
						||
                }
 | 
						||
                if (this.elements.stopScan) {
 | 
						||
                    this.elements.stopScan.disabled = true;
 | 
						||
                }
 | 
						||
                if (this.elements.targetDomain) this.elements.targetDomain.disabled = false;
 | 
						||
                if (this.elements.maxDepth) this.elements.maxDepth.disabled = false;
 | 
						||
                if (this.elements.configureApiKeys) this.elements.configureApiKeys.disabled = false;
 | 
						||
                break;
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Load provider information
 | 
						||
     */
 | 
						||
    async loadProviders() {
 | 
						||
        try {
 | 
						||
            console.log('Loading providers...');
 | 
						||
            const response = await this.apiCall('/api/providers');
 | 
						||
            
 | 
						||
            if (response.success) {
 | 
						||
                this.updateProviderDisplay(response.providers);
 | 
						||
                console.log('Providers loaded successfully');
 | 
						||
            }
 | 
						||
            
 | 
						||
        } catch (error) {
 | 
						||
            console.error('Failed to load providers:', error);
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Update provider display
 | 
						||
     * @param {Object} providers - Provider information
 | 
						||
     */
 | 
						||
    updateProviderDisplay(providers) {
 | 
						||
        if (!this.elements.providerList) return;
 | 
						||
        
 | 
						||
        this.elements.providerList.innerHTML = '';
 | 
						||
        
 | 
						||
        for (const [name, info] of Object.entries(providers)) {
 | 
						||
            const providerItem = document.createElement('div');
 | 
						||
            providerItem.className = 'provider-item fade-in';
 | 
						||
            
 | 
						||
            let statusClass = 'disabled';
 | 
						||
            let statusText = 'Disabled';
 | 
						||
            
 | 
						||
            if (info.enabled) {
 | 
						||
                statusClass = 'enabled';
 | 
						||
                statusText = 'Enabled';
 | 
						||
            } else if (info.requires_api_key) {
 | 
						||
                statusClass = 'api-key-required';
 | 
						||
                statusText = 'API Key Required';
 | 
						||
            }
 | 
						||
            
 | 
						||
            providerItem.innerHTML = `
 | 
						||
                <div class="provider-header">
 | 
						||
                    <div class="provider-name">${name.toUpperCase()}</div>
 | 
						||
                    <div class="provider-status ${statusClass}">${statusText}</div>
 | 
						||
                </div>
 | 
						||
                <div class="provider-stats">
 | 
						||
                    <div class="provider-stat">
 | 
						||
                        <span class="provider-stat-label">Requests:</span>
 | 
						||
                        <span class="provider-stat-value">${info.statistics.total_requests || 0}</span>
 | 
						||
                    </div>
 | 
						||
                    <div class="provider-stat">
 | 
						||
                        <span class="provider-stat-label">Success Rate:</span>
 | 
						||
                        <span class="provider-stat-value">${(info.statistics.success_rate || 0).toFixed(1)}%</span>
 | 
						||
                    </div>
 | 
						||
                    <div class="provider-stat">
 | 
						||
                        <span class="provider-stat-label">Relationships:</span>
 | 
						||
                        <span class="provider-stat-value">${info.statistics.relationships_found || 0}</span>
 | 
						||
                    </div>
 | 
						||
                    <div class="provider-stat">
 | 
						||
                        <span class="provider-stat-label">Rate Limit:</span>
 | 
						||
                        <span class="provider-stat-value">${info.rate_limit}/min</span>
 | 
						||
                    </div>
 | 
						||
                </div>
 | 
						||
            `;
 | 
						||
            
 | 
						||
            this.elements.providerList.appendChild(providerItem);
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Show node details modal
 | 
						||
     * @param {string} nodeId - Node identifier
 | 
						||
     * @param {Object} node - Node data
 | 
						||
     */
 | 
						||
    showNodeModal(nodeId, node) {
 | 
						||
        if (!this.elements.nodeModal) return;
 | 
						||
        
 | 
						||
        if (this.elements.modalTitle) {
 | 
						||
            this.elements.modalTitle.textContent = `Node Details: ${nodeId}`;
 | 
						||
        }
 | 
						||
        
 | 
						||
        let detailsHtml = '';
 | 
						||
        detailsHtml += `<div class="detail-row"><span class="detail-label">Identifier:</span><span class="detail-value">${nodeId}</span></div>`;
 | 
						||
        detailsHtml += `<div class="detail-row"><span class="detail-label">Type:</span><span class="detail-value">${node.metadata.type || node.type || 'Unknown'}</span></div>`;
 | 
						||
        
 | 
						||
        if (node.metadata) {
 | 
						||
            for (const [key, value] of Object.entries(node.metadata)) {
 | 
						||
                if (key !== 'type') {
 | 
						||
                    detailsHtml += `<div class="detail-row"><span class="detail-label">${this.formatLabel(key)}:</span><span class="detail-value">${this.formatValue(value)}</span></div>`;
 | 
						||
                }
 | 
						||
            }
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Add timestamps if available
 | 
						||
        if (node.added_timestamp) {
 | 
						||
            const addedDate = new Date(node.added_timestamp);
 | 
						||
            detailsHtml += `<div class="detail-row"><span class="detail-label">Added:</span><span class="detail-value">${addedDate.toLocaleString()}</span></div>`;
 | 
						||
        }
 | 
						||
        
 | 
						||
        if (this.elements.modalDetails) {
 | 
						||
            this.elements.modalDetails.innerHTML = detailsHtml;
 | 
						||
        }
 | 
						||
        this.elements.nodeModal.style.display = 'block';
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Hide the modal
 | 
						||
     */
 | 
						||
    hideModal() {
 | 
						||
        if (this.elements.nodeModal) {
 | 
						||
            this.elements.nodeModal.style.display = 'none';
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * Show API Key modal
 | 
						||
     */
 | 
						||
    showApiKeyModal() {
 | 
						||
        if (this.elements.apiKeyModal) {
 | 
						||
            this.elements.apiKeyModal.style.display = 'block';
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * Hide API Key modal
 | 
						||
     */
 | 
						||
    hideApiKeyModal() {
 | 
						||
        if (this.elements.apiKeyModal) {
 | 
						||
            this.elements.apiKeyModal.style.display = 'none';
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * Save API Keys
 | 
						||
     */
 | 
						||
    async saveApiKeys() {
 | 
						||
        const shodanKey = this.elements.shodanApiKey.value.trim();
 | 
						||
        const virustotalKey = this.elements.virustotalApiKey.value.trim();
 | 
						||
 | 
						||
        const keys = {};
 | 
						||
        if (shodanKey) keys.shodan = shodanKey;
 | 
						||
        if (virustotalKey) keys.virustotal = virustotalKey;
 | 
						||
 | 
						||
        if (Object.keys(keys).length === 0) {
 | 
						||
            this.showWarning('No API keys were entered.');
 | 
						||
            return;
 | 
						||
        }
 | 
						||
 | 
						||
        try {
 | 
						||
            const response = await this.apiCall('/api/config/api-keys', 'POST', keys);
 | 
						||
            if (response.success) {
 | 
						||
                this.showSuccess(response.message);
 | 
						||
                this.hideApiKeyModal();
 | 
						||
                this.loadProviders(); // Refresh provider status
 | 
						||
            } else {
 | 
						||
                throw new Error(response.error || 'Failed to save API keys');
 | 
						||
            }
 | 
						||
        } catch (error) {
 | 
						||
            this.showError(`Error saving API keys: ${error.message}`);
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * Reset API Key fields
 | 
						||
     */
 | 
						||
    resetApiKeys() {
 | 
						||
        this.elements.shodanApiKey.value = '';
 | 
						||
        this.elements.virustotalApiKey.value = '';
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Check if graph data has changed
 | 
						||
     * @param {Object} graphData - New graph data
 | 
						||
     * @returns {boolean} True if data has changed
 | 
						||
     */
 | 
						||
    hasGraphChanged(graphData) {
 | 
						||
        // Simple check based on node and edge counts and timestamps
 | 
						||
        const currentStats = this.graphManager.getStatistics();
 | 
						||
        const newNodeCount = graphData.nodes ? graphData.nodes.length : 0;
 | 
						||
        const newEdgeCount = graphData.edges ? graphData.edges.length : 0;
 | 
						||
        
 | 
						||
        // Check if counts changed
 | 
						||
        const countsChanged = currentStats.nodeCount !== newNodeCount || currentStats.edgeCount !== newEdgeCount;
 | 
						||
        
 | 
						||
        // Also check if we have new timestamp data
 | 
						||
        const hasNewTimestamp = graphData.statistics && 
 | 
						||
                               graphData.statistics.last_modified && 
 | 
						||
                               graphData.statistics.last_modified !== this.lastGraphTimestamp;
 | 
						||
        
 | 
						||
        if (hasNewTimestamp) {
 | 
						||
            this.lastGraphTimestamp = graphData.statistics.last_modified;
 | 
						||
        }
 | 
						||
        
 | 
						||
        const changed = countsChanged || hasNewTimestamp;
 | 
						||
        
 | 
						||
        console.log(`Graph change check: Current(${currentStats.nodeCount}n, ${currentStats.edgeCount}e) vs New(${newNodeCount}n, ${newEdgeCount}e) = ${changed}`);
 | 
						||
        
 | 
						||
        return changed;
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Make API call to server
 | 
						||
     * @param {string} endpoint - API endpoint
 | 
						||
     * @param {string} method - HTTP method
 | 
						||
     * @param {Object} data - Request data
 | 
						||
     * @returns {Promise<Object>} Response data
 | 
						||
     */
 | 
						||
    async apiCall(endpoint, method = 'GET', data = null) {
 | 
						||
        console.log(`Making API call: ${method} ${endpoint}`, data ? data : '(no data)');
 | 
						||
        
 | 
						||
        try {
 | 
						||
            const options = {
 | 
						||
                method: method,
 | 
						||
                headers: {
 | 
						||
                    'Content-Type': 'application/json'
 | 
						||
                }
 | 
						||
            };
 | 
						||
            
 | 
						||
            if (data && method !== 'GET') {
 | 
						||
                options.body = JSON.stringify(data);
 | 
						||
                console.log('Request body:', options.body);
 | 
						||
            }
 | 
						||
            
 | 
						||
            console.log('Fetch options:', options);
 | 
						||
            const response = await fetch(endpoint, options);
 | 
						||
            
 | 
						||
            console.log('Response status:', response.status, response.statusText);
 | 
						||
            
 | 
						||
            if (!response.ok) {
 | 
						||
                throw new Error(`HTTP ${response.status}: ${response.statusText}`);
 | 
						||
            }
 | 
						||
            
 | 
						||
            const result = await response.json();
 | 
						||
            console.log('Response data:', result);
 | 
						||
            
 | 
						||
            return result;
 | 
						||
            
 | 
						||
        } catch (error) {
 | 
						||
            console.error(`API call failed for ${method} ${endpoint}:`, error);
 | 
						||
            throw error;
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Validate domain name - improved validation
 | 
						||
     * @param {string} domain - Domain to validate
 | 
						||
     * @returns {boolean} True if valid
 | 
						||
     */
 | 
						||
    isValidDomain(domain) {
 | 
						||
        console.log(`Validating domain: "${domain}"`);
 | 
						||
        
 | 
						||
        // Basic checks
 | 
						||
        if (!domain || typeof domain !== 'string') {
 | 
						||
            console.log('Validation failed: empty or non-string domain');
 | 
						||
            return false;
 | 
						||
        }
 | 
						||
        if (domain.length > 253) {
 | 
						||
            console.log('Validation failed: domain too long');
 | 
						||
            return false;
 | 
						||
        }
 | 
						||
        if (domain.startsWith('.') || domain.endsWith('.')) {
 | 
						||
            console.log('Validation failed: domain starts or ends with dot');
 | 
						||
            return false;
 | 
						||
        }
 | 
						||
        if (domain.includes('..')) {
 | 
						||
            console.log('Validation failed: domain contains double dots');
 | 
						||
            return false;
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Split into parts and validate each
 | 
						||
        const parts = domain.split('.');
 | 
						||
        if (parts.length < 2) {
 | 
						||
            console.log('Validation failed: domain has less than 2 parts');
 | 
						||
            return false;
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Check each part
 | 
						||
        for (const part of parts) {
 | 
						||
            if (!part || part.length > 63) {
 | 
						||
                console.log(`Validation failed: invalid part "${part}"`);
 | 
						||
                return false;
 | 
						||
            }
 | 
						||
            if (part.startsWith('-') || part.endsWith('-')) {
 | 
						||
                console.log(`Validation failed: part "${part}" starts or ends with hyphen`);
 | 
						||
                return false;
 | 
						||
            }
 | 
						||
            if (!/^[a-zA-Z0-9-]+$/.test(part)) {
 | 
						||
                console.log(`Validation failed: part "${part}" contains invalid characters`);
 | 
						||
                return false;
 | 
						||
            }
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Check TLD (last part) is alphabetic
 | 
						||
        const tld = parts[parts.length - 1];
 | 
						||
        if (!/^[a-zA-Z]{2,}$/.test(tld)) {
 | 
						||
            console.log(`Validation failed: invalid TLD "${tld}"`);
 | 
						||
            return false;
 | 
						||
        }
 | 
						||
        
 | 
						||
        console.log('Domain validation passed');
 | 
						||
        return true;
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Format status text for display
 | 
						||
     * @param {string} status - Raw status
 | 
						||
     * @returns {string} Formatted status
 | 
						||
     */
 | 
						||
    formatStatus(status) {
 | 
						||
        const statusMap = {
 | 
						||
            'idle': 'Idle',
 | 
						||
            'running': 'Running',
 | 
						||
            'completed': 'Completed',
 | 
						||
            'failed': 'Failed',
 | 
						||
            'stopped': 'Stopped'
 | 
						||
        };
 | 
						||
        return statusMap[status] || status;
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Format label for display
 | 
						||
     * @param {string} label - Raw label
 | 
						||
     * @returns {string} Formatted label
 | 
						||
     */
 | 
						||
    formatLabel(label) {
 | 
						||
        return label.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Format value for display
 | 
						||
     * @param {*} value - Raw value
 | 
						||
     * @returns {string} Formatted value
 | 
						||
     */
 | 
						||
    formatValue(value) {
 | 
						||
        if (Array.isArray(value)) {
 | 
						||
            return value.join(', ');
 | 
						||
        } else if (typeof value === 'object') {
 | 
						||
            return JSON.stringify(value, null, 2);
 | 
						||
        } else {
 | 
						||
            return String(value);
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Show success message
 | 
						||
     * @param {string} message - Success message
 | 
						||
     */
 | 
						||
    showSuccess(message) {
 | 
						||
        this.showMessage(message, 'success');
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * Show info message
 | 
						||
     * @param {string} message - Info message
 | 
						||
     */
 | 
						||
    showInfo(message) {
 | 
						||
        this.showMessage(message, 'info');
 | 
						||
    }
 | 
						||
    
 | 
						||
    showWarning(message) {
 | 
						||
        this.showMessage(message, 'warning');
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * Show error message
 | 
						||
     * @param {string} message - Error message
 | 
						||
     */
 | 
						||
    showError(message) {
 | 
						||
        this.showMessage(message, 'error');
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Show connection error
 | 
						||
     */
 | 
						||
    showConnectionError() {
 | 
						||
        this.updateConnectionStatus('error');
 | 
						||
    }
 | 
						||
    
 | 
						||
    /**
 | 
						||
     * Show message with visual feedback
 | 
						||
     * @param {string} message - Message text
 | 
						||
     * @param {string} type - Message type (success, error, warning, info)
 | 
						||
     */
 | 
						||
    showMessage(message, type = 'info') {
 | 
						||
        console.log(`${type.toUpperCase()}: ${message}`);
 | 
						||
        
 | 
						||
        // Create message element
 | 
						||
        const messageElement = document.createElement('div');
 | 
						||
        messageElement.className = `message-toast message-${type}`;
 | 
						||
        
 | 
						||
        messageElement.innerHTML = `
 | 
						||
            <div style="display: flex; justify-content: space-between; align-items: center; padding: 12px 20px;">
 | 
						||
                <span style="flex: 1;">${message}</span>
 | 
						||
                <button onclick="this.parentElement.parentElement.remove()" 
 | 
						||
                        style="background: none; border: none; color: #fff; cursor: pointer; font-size: 16px; margin-left: 10px; opacity: 0.7;">×</button>
 | 
						||
            </div>
 | 
						||
        `;
 | 
						||
        
 | 
						||
        // Add to container
 | 
						||
        const container = document.getElementById('message-container');
 | 
						||
        if (container) {
 | 
						||
            container.appendChild(messageElement);
 | 
						||
        
 | 
						||
            // Auto-remove after delay
 | 
						||
            setTimeout(() => {
 | 
						||
                if (messageElement.parentNode) {
 | 
						||
                    messageElement.style.animation = 'slideOutRight 0.3s ease-out';
 | 
						||
                    setTimeout(() => {
 | 
						||
                        if (messageElement.parentNode) {
 | 
						||
                            messageElement.remove();
 | 
						||
                        }
 | 
						||
                    }, 300);
 | 
						||
                }
 | 
						||
            }, type === 'error' ? 8000 : 5000); // Errors stay longer
 | 
						||
        }
 | 
						||
        
 | 
						||
        // Update connection status to show activity
 | 
						||
        if (type === 'success' && this.consecutiveErrors === 0) {
 | 
						||
            this.updateConnectionStatus(this.isScanning ? 'active' : 'idle');
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * Get message background color based on type
 | 
						||
     * @param {string} type - Message type
 | 
						||
     * @returns {string} CSS color
 | 
						||
     */
 | 
						||
    getMessageColor(type) {
 | 
						||
        const colors = {
 | 
						||
            'success': '#2c5c34',
 | 
						||
            'error': '#5c2c2c', 
 | 
						||
            'warning': '#5c4c2c',
 | 
						||
            'info': '#2c3e5c'
 | 
						||
        };
 | 
						||
        return colors[type] || colors.info;
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * Get message border color based on type
 | 
						||
     * @param {string} type - Message type
 | 
						||
     * @returns {string} CSS color
 | 
						||
     */
 | 
						||
    getMessageBorderColor(type) {
 | 
						||
        const colors = {
 | 
						||
            'success': '#00ff41',
 | 
						||
            'error': '#ff6b6b',
 | 
						||
            'warning': '#ff9900', 
 | 
						||
            'info': '#00aaff'
 | 
						||
        };
 | 
						||
        return colors[type] || colors.info;
 | 
						||
    }
 | 
						||
}
 | 
						||
 | 
						||
// Add CSS animations for message toasts
 | 
						||
const style = document.createElement('style');
 | 
						||
style.textContent = `
 | 
						||
    @keyframes slideInRight {
 | 
						||
        from {
 | 
						||
            transform: translateX(100%);
 | 
						||
            opacity: 0;
 | 
						||
        }
 | 
						||
        to {
 | 
						||
            transform: translateX(0);
 | 
						||
            opacity: 1;
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    @keyframes slideOutRight {
 | 
						||
        from {
 | 
						||
            transform: translateX(0);
 | 
						||
            opacity: 1;
 | 
						||
        }
 | 
						||
        to {
 | 
						||
            transform: translateX(100%);
 | 
						||
            opacity: 0;
 | 
						||
        }
 | 
						||
    }
 | 
						||
    
 | 
						||
    .message-container {
 | 
						||
        pointer-events: auto;
 | 
						||
    }
 | 
						||
    
 | 
						||
    .message-toast {
 | 
						||
        pointer-events: auto;
 | 
						||
    }
 | 
						||
`;
 | 
						||
document.head.appendChild(style);
 | 
						||
 | 
						||
// Initialize application when page loads
 | 
						||
console.log('Creating DNSReconApp instance...');
 | 
						||
const app = new DNSReconApp(); |