iteration on ws implementation
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Main application logic for DNSRecon web interface
|
||||
* Handles UI interactions, API communication, and data flow
|
||||
* UPDATED: Now compatible with a strictly flat, unified data model for attributes.
|
||||
* FIXED: Enhanced real-time WebSocket graph updates
|
||||
*/
|
||||
|
||||
class DNSReconApp {
|
||||
@@ -17,6 +17,14 @@ class DNSReconApp {
|
||||
this.isScanning = false;
|
||||
this.lastGraphUpdate = null;
|
||||
|
||||
// FIXED: Add connection state tracking
|
||||
this.isConnected = false;
|
||||
this.reconnectAttempts = 0;
|
||||
this.maxReconnectAttempts = 5;
|
||||
|
||||
// FIXED: Track last graph data for debugging
|
||||
this.lastGraphData = null;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
@@ -45,22 +53,159 @@ class DNSReconApp {
|
||||
}
|
||||
|
||||
initializeSocket() {
|
||||
this.socket = io();
|
||||
console.log('🔌 Initializing WebSocket connection...');
|
||||
|
||||
try {
|
||||
this.socket = io({
|
||||
transports: ['websocket', 'polling'],
|
||||
timeout: 10000,
|
||||
reconnection: true,
|
||||
reconnectionAttempts: 5,
|
||||
reconnectionDelay: 2000
|
||||
});
|
||||
|
||||
this.socket.on('connect', () => {
|
||||
console.log('Connected to WebSocket server');
|
||||
this.updateConnectionStatus('idle');
|
||||
this.socket.emit('get_status');
|
||||
});
|
||||
this.socket.on('connect', () => {
|
||||
console.log('✅ WebSocket connected successfully');
|
||||
this.isConnected = true;
|
||||
this.reconnectAttempts = 0;
|
||||
this.updateConnectionStatus('idle');
|
||||
|
||||
console.log('📡 Requesting initial status...');
|
||||
this.socket.emit('get_status');
|
||||
});
|
||||
|
||||
this.socket.on('scan_update', (data) => {
|
||||
if (data.status !== this.scanStatus) {
|
||||
this.handleStatusChange(data.status, data.task_queue_size);
|
||||
}
|
||||
this.scanStatus = data.status;
|
||||
this.updateStatusDisplay(data);
|
||||
this.graphManager.updateGraph(data.graph);
|
||||
});
|
||||
this.socket.on('disconnect', (reason) => {
|
||||
console.log('❌ WebSocket disconnected:', reason);
|
||||
this.isConnected = false;
|
||||
this.updateConnectionStatus('error');
|
||||
});
|
||||
|
||||
this.socket.on('connect_error', (error) => {
|
||||
console.error('❌ WebSocket connection error:', error);
|
||||
this.reconnectAttempts++;
|
||||
this.updateConnectionStatus('error');
|
||||
|
||||
if (this.reconnectAttempts >= 5) {
|
||||
this.showError('WebSocket connection failed. Please refresh the page.');
|
||||
}
|
||||
});
|
||||
|
||||
this.socket.on('reconnect', (attemptNumber) => {
|
||||
console.log('✅ WebSocket reconnected after', attemptNumber, 'attempts');
|
||||
this.isConnected = true;
|
||||
this.reconnectAttempts = 0;
|
||||
this.updateConnectionStatus('idle');
|
||||
this.socket.emit('get_status');
|
||||
});
|
||||
|
||||
// FIXED: Enhanced scan_update handler with detailed graph processing and debugging
|
||||
this.socket.on('scan_update', (data) => {
|
||||
console.log('📨 WebSocket update received:', {
|
||||
status: data.status,
|
||||
target: data.target_domain,
|
||||
progress: data.progress_percentage,
|
||||
graphNodes: data.graph?.nodes?.length || 0,
|
||||
graphEdges: data.graph?.edges?.length || 0,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
try {
|
||||
// Handle status change
|
||||
if (data.status !== this.scanStatus) {
|
||||
console.log(`📄 Status change: ${this.scanStatus} → ${data.status}`);
|
||||
this.handleStatusChange(data.status, data.task_queue_size);
|
||||
}
|
||||
this.scanStatus = data.status;
|
||||
|
||||
// Update status display
|
||||
this.updateStatusDisplay(data);
|
||||
|
||||
// FIXED: Always update graph if data is present and graph manager exists
|
||||
if (data.graph && this.graphManager) {
|
||||
console.log('📊 Processing graph update:', {
|
||||
nodes: data.graph.nodes?.length || 0,
|
||||
edges: data.graph.edges?.length || 0,
|
||||
hasNodes: Array.isArray(data.graph.nodes),
|
||||
hasEdges: Array.isArray(data.graph.edges),
|
||||
isInitialized: this.graphManager.isInitialized
|
||||
});
|
||||
|
||||
// FIXED: Initialize graph manager if not already done
|
||||
if (!this.graphManager.isInitialized) {
|
||||
console.log('🎯 Initializing graph manager...');
|
||||
this.graphManager.initialize();
|
||||
}
|
||||
|
||||
// FIXED: Force graph update and verify it worked
|
||||
const previousNodeCount = this.graphManager.nodes ? this.graphManager.nodes.length : 0;
|
||||
const previousEdgeCount = this.graphManager.edges ? this.graphManager.edges.length : 0;
|
||||
|
||||
console.log('🔄 Before update - Nodes:', previousNodeCount, 'Edges:', previousEdgeCount);
|
||||
|
||||
// Store the data for debugging
|
||||
this.lastGraphData = data.graph;
|
||||
|
||||
// Update the graph
|
||||
this.graphManager.updateGraph(data.graph);
|
||||
this.lastGraphUpdate = Date.now();
|
||||
|
||||
// Verify the update worked
|
||||
const newNodeCount = this.graphManager.nodes ? this.graphManager.nodes.length : 0;
|
||||
const newEdgeCount = this.graphManager.edges ? this.graphManager.edges.length : 0;
|
||||
|
||||
console.log('🔄 After update - Nodes:', newNodeCount, 'Edges:', newEdgeCount);
|
||||
|
||||
if (newNodeCount !== data.graph.nodes.length || newEdgeCount !== data.graph.edges.length) {
|
||||
console.warn('⚠️ Graph update mismatch!', {
|
||||
expectedNodes: data.graph.nodes.length,
|
||||
actualNodes: newNodeCount,
|
||||
expectedEdges: data.graph.edges.length,
|
||||
actualEdges: newEdgeCount
|
||||
});
|
||||
|
||||
// Force a complete rebuild if there's a mismatch
|
||||
console.log('🔧 Force rebuilding graph...');
|
||||
this.graphManager.clear();
|
||||
this.graphManager.updateGraph(data.graph);
|
||||
}
|
||||
|
||||
console.log('✅ Graph updated successfully');
|
||||
|
||||
// FIXED: Force network redraw if we're using vis.js
|
||||
if (this.graphManager.network) {
|
||||
try {
|
||||
this.graphManager.network.redraw();
|
||||
console.log('🎨 Network redrawn');
|
||||
} catch (redrawError) {
|
||||
console.warn('⚠️ Network redraw failed:', redrawError);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if (!data.graph) {
|
||||
console.log('⚠️ No graph data in WebSocket update');
|
||||
}
|
||||
if (!this.graphManager) {
|
||||
console.log('⚠️ Graph manager not available');
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error processing WebSocket update:', error);
|
||||
console.error('Update data:', data);
|
||||
console.error('Stack trace:', error.stack);
|
||||
}
|
||||
});
|
||||
|
||||
this.socket.on('error', (error) => {
|
||||
console.error('❌ WebSocket error:', error);
|
||||
this.showError('WebSocket communication error');
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to initialize WebSocket:', error);
|
||||
this.showError('Failed to establish real-time connection');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -280,12 +425,36 @@ class DNSReconApp {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize graph visualization
|
||||
* FIXED: Initialize graph visualization with enhanced debugging
|
||||
*/
|
||||
initializeGraph() {
|
||||
try {
|
||||
console.log('Initializing graph manager...');
|
||||
this.graphManager = new GraphManager('network-graph');
|
||||
|
||||
// FIXED: Add debugging hooks to graph manager
|
||||
if (this.graphManager) {
|
||||
// Override updateGraph to add debugging
|
||||
const originalUpdateGraph = this.graphManager.updateGraph.bind(this.graphManager);
|
||||
this.graphManager.updateGraph = (graphData) => {
|
||||
console.log('🔧 GraphManager.updateGraph called with:', {
|
||||
nodes: graphData?.nodes?.length || 0,
|
||||
edges: graphData?.edges?.length || 0,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
const result = originalUpdateGraph(graphData);
|
||||
|
||||
console.log('🔧 GraphManager.updateGraph completed, network state:', {
|
||||
networkExists: !!this.graphManager.network,
|
||||
nodeDataSetLength: this.graphManager.nodes?.length || 0,
|
||||
edgeDataSetLength: this.graphManager.edges?.length || 0
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
console.log('Graph manager initialized successfully');
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize graph manager:', error);
|
||||
@@ -305,7 +474,6 @@ class DNSReconApp {
|
||||
|
||||
console.log(`Target: "${target}", Max depth: ${maxDepth}`);
|
||||
|
||||
// Validation
|
||||
if (!target) {
|
||||
console.log('Validation failed: empty target');
|
||||
this.showError('Please enter a target domain or IP');
|
||||
@@ -320,6 +488,19 @@ class DNSReconApp {
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXED: Ensure WebSocket connection before starting scan
|
||||
if (!this.isConnected) {
|
||||
console.log('WebSocket not connected, attempting to connect...');
|
||||
this.socket.connect();
|
||||
|
||||
// Wait a moment for connection
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
if (!this.isConnected) {
|
||||
this.showWarning('WebSocket connection not established. Updates may be delayed.');
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Validation passed, setting UI state to scanning...');
|
||||
this.setUIState('scanning');
|
||||
this.showInfo('Starting reconnaissance scan...');
|
||||
@@ -337,16 +518,28 @@ class DNSReconApp {
|
||||
|
||||
if (response.success) {
|
||||
this.currentSessionId = response.scan_id;
|
||||
this.showSuccess('Reconnaissance scan started successfully');
|
||||
this.showSuccess('Reconnaissance scan started - watching for real-time updates');
|
||||
|
||||
if (clearGraph) {
|
||||
if (clearGraph && this.graphManager) {
|
||||
console.log('🧹 Clearing graph for new scan');
|
||||
this.graphManager.clear();
|
||||
}
|
||||
|
||||
console.log(`Scan started for ${target} with depth ${maxDepth}`);
|
||||
console.log(`✅ Scan started for ${target} with depth ${maxDepth}`);
|
||||
|
||||
// Request initial status update via WebSocket
|
||||
this.socket.emit('get_status');
|
||||
// FIXED: Immediately start listening for updates
|
||||
if (this.socket && this.isConnected) {
|
||||
console.log('📡 Requesting initial status update...');
|
||||
this.socket.emit('get_status');
|
||||
|
||||
// Set up periodic status requests as backup (every 5 seconds during scan)
|
||||
/*this.statusRequestInterval = setInterval(() => {
|
||||
if (this.isScanning && this.socket && this.isConnected) {
|
||||
console.log('📡 Periodic status request...');
|
||||
this.socket.emit('get_status');
|
||||
}
|
||||
}, 5000);*/
|
||||
}
|
||||
|
||||
} else {
|
||||
throw new Error(response.error || 'Failed to start scan');
|
||||
@@ -358,26 +551,34 @@ class DNSReconApp {
|
||||
this.setUIState('idle');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Scan stop with immediate UI feedback
|
||||
*/
|
||||
|
||||
// FIXED: Enhanced stop scan with interval cleanup
|
||||
async stopScan() {
|
||||
try {
|
||||
console.log('Stopping scan...');
|
||||
|
||||
// Immediately disable stop button and show stopping state
|
||||
// Clear status request interval
|
||||
/*if (this.statusRequestInterval) {
|
||||
clearInterval(this.statusRequestInterval);
|
||||
this.statusRequestInterval = null;
|
||||
}*/
|
||||
|
||||
if (this.elements.stopScan) {
|
||||
this.elements.stopScan.disabled = true;
|
||||
this.elements.stopScan.innerHTML = '<span class="btn-icon">[STOPPING]</span><span>Stopping...</span>';
|
||||
}
|
||||
|
||||
// Show immediate feedback
|
||||
this.showInfo('Stopping scan...');
|
||||
|
||||
const response = await this.apiCall('/api/scan/stop', 'POST');
|
||||
|
||||
if (response.success) {
|
||||
this.showSuccess('Scan stop requested');
|
||||
|
||||
// Request final status update
|
||||
if (this.socket && this.isConnected) {
|
||||
setTimeout(() => this.socket.emit('get_status'), 500);
|
||||
}
|
||||
} else {
|
||||
throw new Error(response.error || 'Failed to stop scan');
|
||||
}
|
||||
@@ -386,7 +587,6 @@ class DNSReconApp {
|
||||
console.error('Failed to stop scan:', error);
|
||||
this.showError(`Failed to stop scan: ${error.message}`);
|
||||
|
||||
// Re-enable stop button on error
|
||||
if (this.elements.stopScan) {
|
||||
this.elements.stopScan.disabled = false;
|
||||
this.elements.stopScan.innerHTML = '<span class="btn-icon">[STOP]</span><span>Terminate Scan</span>';
|
||||
@@ -543,23 +743,24 @@ class DNSReconApp {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update graph from server
|
||||
* FIXED: Update graph from server with enhanced debugging
|
||||
*/
|
||||
async updateGraph() {
|
||||
try {
|
||||
console.log('Updating graph...');
|
||||
console.log('Updating graph via API call...');
|
||||
const response = await this.apiCall('/api/graph');
|
||||
|
||||
|
||||
if (response.success) {
|
||||
const graphData = response.graph;
|
||||
|
||||
console.log('Graph data received:');
|
||||
console.log('Graph data received from API:');
|
||||
console.log('- Nodes:', graphData.nodes ? graphData.nodes.length : 0);
|
||||
console.log('- Edges:', graphData.edges ? graphData.edges.length : 0);
|
||||
|
||||
// FIXED: Always update graph, even if empty - let GraphManager handle placeholder
|
||||
if (this.graphManager) {
|
||||
console.log('🔧 Calling GraphManager.updateGraph from API response...');
|
||||
this.graphManager.updateGraph(graphData);
|
||||
this.lastGraphUpdate = Date.now();
|
||||
|
||||
@@ -568,6 +769,8 @@ class DNSReconApp {
|
||||
if (this.elements.relationshipsDisplay) {
|
||||
this.elements.relationshipsDisplay.textContent = edgeCount;
|
||||
}
|
||||
|
||||
console.log('✅ Manual graph update completed');
|
||||
}
|
||||
} else {
|
||||
console.error('Graph update failed:', response);
|
||||
@@ -663,12 +866,12 @@ class DNSReconApp {
|
||||
* @param {string} newStatus - New scan status
|
||||
*/
|
||||
handleStatusChange(newStatus, task_queue_size) {
|
||||
console.log(`=== STATUS CHANGE: ${this.scanStatus} -> ${newStatus} ===`);
|
||||
console.log(`📄 Status change handler: ${this.scanStatus} → ${newStatus}`);
|
||||
|
||||
switch (newStatus) {
|
||||
case 'running':
|
||||
this.setUIState('scanning', task_queue_size);
|
||||
this.showSuccess('Scan is running');
|
||||
this.showSuccess('Scan is running - updates in real-time');
|
||||
this.updateConnectionStatus('active');
|
||||
break;
|
||||
|
||||
@@ -677,8 +880,19 @@ class DNSReconApp {
|
||||
this.showSuccess('Scan completed successfully');
|
||||
this.updateConnectionStatus('completed');
|
||||
this.loadProviders();
|
||||
// Force a final graph update
|
||||
console.log('Scan completed - forcing final graph update');
|
||||
console.log('✅ Scan completed - requesting final graph update');
|
||||
// Request final status to ensure we have the complete graph
|
||||
setTimeout(() => {
|
||||
if (this.socket && this.isConnected) {
|
||||
this.socket.emit('get_status');
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
// Clear status request interval
|
||||
/*if (this.statusRequestInterval) {
|
||||
clearInterval(this.statusRequestInterval);
|
||||
this.statusRequestInterval = null;
|
||||
}*/
|
||||
break;
|
||||
|
||||
case 'failed':
|
||||
@@ -686,6 +900,12 @@ class DNSReconApp {
|
||||
this.showError('Scan failed');
|
||||
this.updateConnectionStatus('error');
|
||||
this.loadProviders();
|
||||
|
||||
// Clear status request interval
|
||||
/*if (this.statusRequestInterval) {
|
||||
clearInterval(this.statusRequestInterval);
|
||||
this.statusRequestInterval = null;
|
||||
}*/
|
||||
break;
|
||||
|
||||
case 'stopped':
|
||||
@@ -693,11 +913,23 @@ class DNSReconApp {
|
||||
this.showSuccess('Scan stopped');
|
||||
this.updateConnectionStatus('stopped');
|
||||
this.loadProviders();
|
||||
|
||||
// Clear status request interval
|
||||
if (this.statusRequestInterval) {
|
||||
clearInterval(this.statusRequestInterval);
|
||||
this.statusRequestInterval = null;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'idle':
|
||||
this.setUIState('idle', task_queue_size);
|
||||
this.updateConnectionStatus('idle');
|
||||
|
||||
// Clear status request interval
|
||||
/*if (this.statusRequestInterval) {
|
||||
clearInterval(this.statusRequestInterval);
|
||||
this.statusRequestInterval = null;
|
||||
}*/
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -749,6 +981,7 @@ class DNSReconApp {
|
||||
if (this.graphManager) {
|
||||
this.graphManager.isScanning = true;
|
||||
}
|
||||
|
||||
if (this.elements.startScan) {
|
||||
this.elements.startScan.disabled = true;
|
||||
this.elements.startScan.classList.add('loading');
|
||||
@@ -776,6 +1009,7 @@ class DNSReconApp {
|
||||
if (this.graphManager) {
|
||||
this.graphManager.isScanning = false;
|
||||
}
|
||||
|
||||
if (this.elements.startScan) {
|
||||
this.elements.startScan.disabled = !isQueueEmpty;
|
||||
this.elements.startScan.classList.remove('loading');
|
||||
@@ -1018,7 +1252,7 @@ class DNSReconApp {
|
||||
} else {
|
||||
// API key not configured - ALWAYS show input field
|
||||
const statusClass = info.enabled ? 'enabled' : 'api-key-required';
|
||||
const statusText = info.enabled ? '○ Ready for API Key' : '⚠️ API Key Required';
|
||||
const statusText = info.enabled ? '◯ Ready for API Key' : '⚠️ API Key Required';
|
||||
|
||||
inputGroup.innerHTML = `
|
||||
<div class="provider-header">
|
||||
@@ -2000,8 +2234,8 @@ class DNSReconApp {
|
||||
*/
|
||||
getNodeTypeIcon(nodeType) {
|
||||
const icons = {
|
||||
'domain': '🌍',
|
||||
'ip': '📍',
|
||||
'domain': '🌐',
|
||||
'ip': '🔢',
|
||||
'asn': '🏢',
|
||||
'large_entity': '📦',
|
||||
'correlation_object': '🔗'
|
||||
|
||||
Reference in New Issue
Block a user