diff --git a/core/graph_manager.py b/core/graph_manager.py index 735f50e..57b066a 100644 --- a/core/graph_manager.py +++ b/core/graph_manager.py @@ -163,7 +163,8 @@ class GraphManager: 'to': target, 'label': attrs.get('relationship_type', ''), 'source_provider': attrs.get('source_provider', ''), - 'discovery_timestamp': attrs.get('discovery_timestamp') + 'discovery_timestamp': attrs.get('discovery_timestamp'), + 'raw_data': attrs.get('raw_data', {}) }) return { diff --git a/core/scanner.py b/core/scanner.py index 3d88b26..ea5e095 100644 --- a/core/scanner.py +++ b/core/scanner.py @@ -929,7 +929,7 @@ class Scanner: # Re-enqueue the node for full processing is_ip = _is_valid_ip(node_id) - eligible_providers = self._get_eligible_providers(node_id, is_ip, False) + eligible_providers = self._get_eligible_providers(node_id, is_ip, False, is_extracted=True) for provider in eligible_providers: provider_name = provider.get_name() priority = self._get_priority(provider_name) @@ -1133,7 +1133,7 @@ class Scanner: self.logger.logger.warning(f"Error initializing provider states for {target}: {e}") - def _get_eligible_providers(self, target: str, is_ip: bool, dns_only: bool) -> List: + def _get_eligible_providers(self, target: str, is_ip: bool, dns_only: bool, is_extracted: bool = False) -> List: """ FIXED: Improved provider eligibility checking with better filtering. """ @@ -1145,7 +1145,7 @@ class Scanner: # Check if the target is part of a large entity is_in_large_entity = False - if self.graph.graph.has_node(target): + if self.graph.graph.has_node(target) and not is_extracted: metadata = self.graph.graph.nodes[target].get('metadata', {}) if 'large_entity_id' in metadata: is_in_large_entity = True diff --git a/providers/correlation_provider.py b/providers/correlation_provider.py index db8cd1b..c34d8bd 100644 --- a/providers/correlation_provider.py +++ b/providers/correlation_provider.py @@ -1,4 +1,4 @@ -# DNScope/providers/correlation_provider.py +# dnsrecon-reduced/providers/correlation_provider.py import re from typing import Dict, Any, List @@ -24,6 +24,10 @@ class CorrelationProvider(BaseProvider): self.date_pattern = re.compile(r'^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}') self.EXCLUDED_KEYS = [ 'cert_source', + 'a_records', + 'mx_records', + 'ns_records', + 'ptr_records', 'cert_issuer_ca_id', 'cert_common_name', 'cert_validity_period_days', @@ -38,6 +42,8 @@ class CorrelationProvider(BaseProvider): 'updated_timestamp', 'discovery_timestamp', 'query_timestamp', + 'shodan_ip_str', + 'shodan_a_record', ] def get_name(self) -> str: @@ -83,7 +89,7 @@ class CorrelationProvider(BaseProvider): def _find_correlations(self, node_id: str) -> ProviderResult: """ Find correlations for a given node with enhanced filtering and error handling. - UPDATED: Enhanced with discovery timestamps for time-based edge coloring. + UPDATED: Enhanced with discovery timestamps for time-based edge coloring and list value processing. """ result = ProviderResult() discovery_time = datetime.now(timezone.utc) @@ -109,38 +115,46 @@ class CorrelationProvider(BaseProvider): attr_value = attr.get('value') attr_provider = attr.get('provider', 'unknown') - # Enhanced filtering logic - should_exclude = self._should_exclude_attribute(attr_name, attr_value) - - if should_exclude: - continue + # Prepare a list of values to iterate over + values_to_process = [] + if isinstance(attr_value, list): + values_to_process.extend(attr_value) + else: + values_to_process.append(attr_value) - # Build correlation index - if attr_value not in self.correlation_index: - self.correlation_index[attr_value] = { - 'nodes': set(), - 'sources': [] + for value_item in values_to_process: + # Enhanced filtering logic + should_exclude = self._should_exclude_attribute(attr_name, value_item) + + if should_exclude: + continue + + # Build correlation index + if value_item not in self.correlation_index: + self.correlation_index[value_item] = { + 'nodes': set(), + 'sources': [] + } + + self.correlation_index[value_item]['nodes'].add(node_id) + + source_info = { + 'node_id': node_id, + 'provider': attr_provider, + 'attribute': attr_name, + 'path': f"{attr_provider}_{attr_name}" } - self.correlation_index[attr_value]['nodes'].add(node_id) + # Avoid duplicate sources + existing_sources = [s for s in self.correlation_index[value_item]['sources'] + if s['node_id'] == node_id and s['path'] == source_info['path']] + if not existing_sources: + self.correlation_index[value_item]['sources'].append(source_info) - source_info = { - 'node_id': node_id, - 'provider': attr_provider, - 'attribute': attr_name, - 'path': f"{attr_provider}_{attr_name}" - } - - # Avoid duplicate sources - existing_sources = [s for s in self.correlation_index[attr_value]['sources'] - if s['node_id'] == node_id and s['path'] == source_info['path']] - if not existing_sources: - self.correlation_index[attr_value]['sources'].append(source_info) - - # Create correlation if we have multiple nodes with this value - if len(self.correlation_index[attr_value]['nodes']) > 1: - self._create_correlation_relationships(attr_value, self.correlation_index[attr_value], result, discovery_time) - correlations_found += 1 + # Create correlation if we have multiple nodes with this value + if len(self.correlation_index[value_item]['nodes']) > 1: + self._create_correlation_relationships(value_item, self.correlation_index[value_item], result, discovery_time) + correlations_found += 1 # Log correlation results if correlations_found > 0: diff --git a/static/css/main.css b/static/css/main.css index f0aae48..18a74d1 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -401,7 +401,7 @@ input[type="text"]:focus, select:focus { gap: 0.3rem; position: absolute; top: 10px; - left: 10px; + right: 10px; background: rgba(26, 26, 26, 0.9); padding: 0.5rem; border-radius: 6px; @@ -1406,7 +1406,7 @@ input[type="password"]:focus { .graph-controls { position: relative; top: auto; - left: auto; + right: auto; margin-bottom: 1rem; min-width: auto; } diff --git a/static/js/graph.js b/static/js/graph.js index d2cec72..b315302 100644 --- a/static/js/graph.js +++ b/static/js/graph.js @@ -2,7 +2,7 @@ /** * Graph visualization module for DNScope * Handles network graph rendering using vis.js with proper large entity node hiding - * UPDATED: Added time-based blue gradient edge coloring system + * UPDATED: Fixed time-based blue gradient edge coloring system and simplified logic. */ const contextMenuCSS = ` .graph-context-menu { @@ -106,22 +106,16 @@ class GraphManager { this.history = []; this.filterPanel = null; this.initialTargetIds = new Set(); - // Track large entity members for proper hiding this.largeEntityMembers = new Set(); this.isScanning = false; - - // Manual refresh button for polling optimization this.manualRefreshButton = null; - this.manualRefreshHandler = null; // Store the handler - - // Time-based gradient settings - this.timeOfInterest = new Date(); // Default to now - this.edgeTimestamps = new Map(); // Store edge ID -> timestamp mapping + this.manualRefreshHandler = null; + this.timeOfInterest = new Date(); + this.edgeTimestamps = new Map(); - // Gradient colors: grey-ish dark to retina-melting light blue this.gradientColors = { - dark: '#6b7280', // Grey-ish dark - light: '#00bfff' // Retina-melting light blue + dark: '#6b7280', + light: '#00bfff' }; this.options = { @@ -226,19 +220,17 @@ class GraphManager { randomSeed: 2 } }; - if (typeof document !== 'undefined') { - const style = document.createElement('style'); - style.textContent = contextMenuCSS; - document.head.appendChild(style); - } + + if (typeof document !== 'undefined') { + const style = document.createElement('style'); + style.textContent = contextMenuCSS; + document.head.appendChild(style); + } this.createNodeInfoPopup(); this.createContextMenu(); document.body.addEventListener('click', () => this.hideContextMenu()); } - /** - * Create floating node info popup - */ createNodeInfoPopup() { this.nodeInfoPopup = document.createElement('div'); this.nodeInfoPopup.className = 'node-info-popup'; @@ -246,11 +238,7 @@ class GraphManager { document.body.appendChild(this.nodeInfoPopup); } - /** - * Create context menu - */ createContextMenu() { - // Remove existing context menu if it exists const existing = document.getElementById('graph-context-menu'); if (existing) { existing.remove(); @@ -261,7 +249,6 @@ class GraphManager { this.contextMenu.className = 'graph-context-menu'; this.contextMenu.style.display = 'none'; - // Prevent body click listener from firing when clicking the menu itself this.contextMenu.addEventListener('click', (event) => { event.stopPropagation(); }); @@ -269,31 +256,20 @@ class GraphManager { document.body.appendChild(this.contextMenu); } - /** - * Initialize the network graph - */ initialize() { - if (this.isInitialized) { - return; - } + if (this.isInitialized) return; try { - const data = { - nodes: this.nodes, - edges: this.edges - }; - + const data = { nodes: this.nodes, edges: this.edges }; this.network = new vis.Network(this.container, data, this.options); this.setupNetworkEvents(); this.isInitialized = true; - // Hide placeholder const placeholder = this.container.querySelector('.graph-placeholder'); if (placeholder) { placeholder.style.display = 'none'; } - // Add graph controls this.addGraphControls(); this.addFilterPanel(); @@ -304,15 +280,10 @@ class GraphManager { } } - /** - * Add interactive graph controls with time of interest control - * UPDATED: Added time-based edge coloring controls - */ addGraphControls() { const controlsContainer = document.createElement('div'); controlsContainer.className = 'graph-controls'; - // Format current date/time for the input const currentDateTime = this.formatDateTimeForInput(this.timeOfInterest); controlsContainer.innerHTML = ` @@ -336,31 +307,23 @@ class GraphManager { this.container.appendChild(controlsContainer); - // Add control event listeners document.getElementById('graph-fit').addEventListener('click', () => this.fitView()); document.getElementById('graph-physics').addEventListener('click', () => this.togglePhysics()); document.getElementById('graph-cluster').addEventListener('click', () => this.toggleClustering()); document.getElementById('graph-unhide').addEventListener('click', () => this.unhideAll()); document.getElementById('graph-revert').addEventListener('click', () => this.revertLastAction()); - // Time of interest control document.getElementById('time-of-interest').addEventListener('change', (e) => { this.timeOfInterest = new Date(e.target.value); - console.log('Time of interest updated:', this.timeOfInterest); this.updateEdgeColors(); }); - // Manual refresh button - handler will be set by main app this.manualRefreshButton = document.getElementById('graph-manual-refresh'); - // If a handler was set before the button existed, attach it now if (this.manualRefreshButton && this.manualRefreshHandler) { this.manualRefreshButton.addEventListener('click', this.manualRefreshHandler); } } - /** - * Format date for datetime-local input - */ formatDateTimeForInput(date) { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); @@ -370,92 +333,41 @@ class GraphManager { return `${year}-${month}-${day}T${hours}:${minutes}`; } - /** - * Extract relevant timestamp from edge raw_data based on provider - */ extractEdgeTimestamp(edge) { const rawData = edge.raw_data || {}; - const provider = edge.source_provider || ''; - // Check for standardized relevance_timestamp first if (rawData.relevance_timestamp) { return new Date(rawData.relevance_timestamp); } - // Provider-specific timestamp extraction - switch (provider.toLowerCase()) { - case 'shodan': - // Use last_seen timestamp for Shodan - if (rawData.last_seen) { - return new Date(rawData.last_seen); - } - break; - - case 'crtsh': - // Use certificate issue date (not_before) for certificates - if (rawData.cert_not_before) { - return new Date(rawData.cert_not_before); - } - break; - - case 'dns': - case 'correlation': - default: - // Use discovery timestamp for DNS and correlation - if (edge.discovery_timestamp) { - return new Date(edge.discovery_timestamp); - } - break; - } - - // Fallback to discovery timestamp or current time if (edge.discovery_timestamp) { return new Date(edge.discovery_timestamp); } - return new Date(); // Default to now if no timestamp available + return new Date(); } - /** - * Calculate time-based blue gradient color - */ - calculateTimeGradientColor(timestamp) { + calculateTimeGradientColor(timestamp, maxTimeDiff) { if (!timestamp || !this.timeOfInterest) { - return this.gradientColors.dark; // Default to dark grey + return this.gradientColors.dark; } - // Calculate time difference in milliseconds const timeDiff = Math.abs(timestamp.getTime() - this.timeOfInterest.getTime()); - // Find maximum time difference across all edges for normalization - let maxTimeDiff = 0; - this.edgeTimestamps.forEach((edgeTimestamp) => { - const diff = Math.abs(edgeTimestamp.getTime() - this.timeOfInterest.getTime()); - if (diff > maxTimeDiff) { - maxTimeDiff = diff; - } - }); - if (maxTimeDiff === 0) { - return this.gradientColors.light; // All timestamps are the same + return this.gradientColors.light; } - // Calculate gradient position (0 = closest to time of interest, 1 = furthest) const gradientPosition = timeDiff / maxTimeDiff; - // Interpolate between light blue (close) and dark grey (far) return this.interpolateColor( - this.gradientColors.light, // Close to time of interest - this.gradientColors.dark, // Far from time of interest + this.gradientColors.light, + this.gradientColors.dark, gradientPosition ); } - /** - * Interpolate between two hex colors - */ interpolateColor(color1, color2, factor) { - // Parse hex colors const hex1 = color1.replace('#', ''); const hex2 = color2.replace('#', ''); @@ -467,57 +379,45 @@ class GraphManager { const g2 = parseInt(hex2.substring(2, 4), 16); const b2 = parseInt(hex2.substring(4, 6), 16); - // Interpolate const r = Math.round(r1 + (r2 - r1) * factor); const g = Math.round(g1 + (g2 - g1) * factor); const b = Math.round(b1 + (b2 - b1) * factor); - // Convert back to hex return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`; } - /** - * Update all edge colors based on current time of interest - */ updateEdgeColors() { const edgeUpdates = []; - + let maxTimeDiff = 0; + this.edgeTimestamps.forEach((edgeTimestamp) => { + const diff = Math.abs(edgeTimestamp.getTime() - this.timeOfInterest.getTime()); + if (diff > maxTimeDiff) { + maxTimeDiff = diff; + } + }); + this.edges.forEach((edge) => { const timestamp = this.edgeTimestamps.get(edge.id); - const color = this.calculateTimeGradientColor(timestamp); + const color = this.calculateTimeGradientColor(timestamp, maxTimeDiff); edgeUpdates.push({ id: edge.id, - color: { - color: color, - highlight: '#00ff41', - hover: '#ff9900' - } + color: { color: color, highlight: '#00ff41', hover: '#ff9900' } }); }); if (edgeUpdates.length > 0) { this.edges.update(edgeUpdates); - console.log(`Updated ${edgeUpdates.length} edge colors based on time gradient`); } } - /** - * Set the manual refresh button click handler - * @param {Function} handler - Function to call when manual refresh is clicked - */ setManualRefreshHandler(handler) { this.manualRefreshHandler = handler; - // If the button already exists, attach the handler if (this.manualRefreshButton && typeof handler === 'function') { this.manualRefreshButton.addEventListener('click', handler); } } - /** - * Show or hide the manual refresh button - * @param {boolean} show - Whether to show the button - */ showManualRefreshButton(show) { if (this.manualRefreshButton) { this.manualRefreshButton.style.display = show ? 'inline-block' : 'none'; @@ -530,33 +430,20 @@ class GraphManager { this.container.appendChild(this.filterPanel); } - /** - * Setup network event handlers - */ setupNetworkEvents() { if (!this.network) return; - // FIXED: Right-click context menu this.container.addEventListener('contextmenu', (event) => { event.preventDefault(); - - // Get coordinates relative to the canvas - const pointer = { - x: event.offsetX, - y: event.offsetY - }; - + const pointer = { x: event.offsetX, y: event.offsetY }; const nodeId = this.network.getNodeAt(pointer); - if (nodeId) { - // Pass the original client event for positioning this.showContextMenu(nodeId, event); } else { this.hideContextMenu(); } }); - // Node click event with details this.network.on('click', (params) => { this.hideContextMenu(); if (params.nodes.length > 0) { @@ -575,27 +462,16 @@ class GraphManager { } }); - // Hover events this.network.on('hoverNode', (params) => { - const nodeId = params.node; - const node = this.nodes.get(nodeId); - if (node) { - this.highlightConnectedNodes(nodeId, true); - } - }); - - // Stabilization events with progress - this.network.on('stabilizationProgress', (params) => { - const progress = params.iterations / params.total; + this.highlightConnectedNodes(params.node, true); }); this.network.on('stabilizationIterationsDone', () => { this.onStabilizationComplete(); }); - // Click away to hide context menu document.addEventListener('click', (e) => { - if (!this.contextMenu.contains(e.target)) { + if (this.contextMenu && !this.contextMenu.contains(e.target)) { this.hideContextMenu(); } }); @@ -628,65 +504,65 @@ class GraphManager { const nodeMap = new Map(graphData.nodes.map(node => [node.id, node])); - // FIXED: Process all nodes first, then apply hiding logic correctly - const processedNodes = graphData.nodes.map(node => { - const processed = this.processNode(node); - - // FIXED: Only hide if node is still a large entity member - if (node.metadata && node.metadata.large_entity_id) { - processed.hidden = true; - } else { - // FIXED: Ensure extracted nodes are visible - processed.hidden = false; - } - - return processed; - }); - - const processedEdges = graphData.edges.map(edge => { + // --- START: TWO-PASS LOGIC FOR ACCURATE GRADIENTS --- + + // 1. First Pass: Re-route edges and gather all timestamps to find the time range + const rawEdges = graphData.edges.map(edge => { let fromNode = nodeMap.get(edge.from); let toNode = nodeMap.get(edge.to); let fromId = edge.from; let toId = edge.to; - // FIXED: Only re-route if nodes are STILL in large entities - if (fromNode && fromNode.metadata && fromNode.metadata.large_entity_id) { + if (fromNode?.metadata?.large_entity_id) { fromId = fromNode.metadata.large_entity_id; } - if (toNode && toNode.metadata && toNode.metadata.large_entity_id) { + if (toNode?.metadata?.large_entity_id) { toId = toNode.metadata.large_entity_id; } - // Avoid self-referencing edges from re-routing - if (fromId === toId) { - return null; + if (fromId === toId) return null; + return { ...edge, from: fromId, to: toId }; + }).filter(Boolean); + + this.edgeTimestamps.clear(); + rawEdges.forEach(edge => { + const edgeId = `${edge.from}-${edge.to}-${edge.label}`; + this.edgeTimestamps.set(edgeId, this.extractEdgeTimestamp(edge)); + }); + + // 2. Calculate the global maxTimeDiff for this update + let maxTimeDiff = 0; + this.edgeTimestamps.forEach((edgeTimestamp) => { + const diff = Math.abs(edgeTimestamp.getTime() - this.timeOfInterest.getTime()); + if (diff > maxTimeDiff) { + maxTimeDiff = diff; } + }); - const reRoutedEdge = { ...edge, from: fromId, to: toId }; - return this.processEdge(reRoutedEdge); - }).filter(Boolean); // Remove nulls from self-referencing edges + // 3. Second Pass: Process nodes and edges with the correct time context + const processedNodes = graphData.nodes.map(node => { + const processed = this.processNode(node); + processed.hidden = !!node.metadata?.large_entity_id; + return processed; + }); + const processedEdges = rawEdges.map(edge => this.processEdge(edge, maxTimeDiff)); - const existingNodeIds = this.nodes.getIds(); - const existingEdgeIds = this.edges.getIds(); + // --- END: TWO-PASS LOGIC --- - const newNodes = processedNodes.filter(node => !existingNodeIds.includes(node.id)); - const newEdges = processedEdges.filter(edge => !existingEdgeIds.includes(edge.id)); - - // FIXED: Update all nodes to ensure extracted nodes become visible this.nodes.update(processedNodes); this.edges.update(processedEdges); - // Update edge timestamps and colors for time-based gradient - this.updateEdgeTimestampsAndColors(graphData.edges); - this.updateFilterControls(); this.applyAllFilters(); + const newNodes = processedNodes.filter(node => !this.nodes.get(node.id)); + const newEdges = processedEdges.filter(edge => !this.edges.get(edge.id)); + if (newNodes.length > 0 || newEdges.length > 0) { setTimeout(() => this.highlightNewElements(newNodes, newEdges), 100); } - if (this.nodes.length <= 10 || existingNodeIds.length === 0) { + if (this.nodes.length <= 10 || this.nodes.getIds().length === 0) { setTimeout(() => this.fitView(), 800); } @@ -695,20 +571,25 @@ class GraphManager { this.showError('Failed to update visualization'); } } - - /** - * Update edge timestamps and apply time-based gradient colors - */ - updateEdgeTimestampsAndColors(edgeData) { - // Extract timestamps from raw edge data - edgeData.forEach(edge => { - const edgeId = `${edge.from}-${edge.to}-${edge.label}`; - const timestamp = this.extractEdgeTimestamp(edge); - this.edgeTimestamps.set(edgeId, timestamp); - }); + + processEdge(edge, maxTimeDiff) { + const edgeId = `${edge.from}-${edge.to}-${edge.label}`; + const timestamp = this.edgeTimestamps.get(edgeId); + const timeGradientColor = this.calculateTimeGradientColor(timestamp, maxTimeDiff); - // Update edge colors based on new timestamps - this.updateEdgeColors(); + return { + id: edgeId, + from: edge.from, + to: edge.to, + label: edge.label, + title: this.createEdgeTooltip(edge), + color: { color: timeGradientColor, highlight: '#00ff41', hover: '#ff9900' }, + metadata: { + relationship_type: edge.label, + source_provider: edge.source_provider, + discovery_timestamp: edge.discovery_timestamp + } + }; } analyzeCertificateInfo(attributes) { @@ -718,14 +599,10 @@ class GraphManager { for (const attr of attributes) { const attrName = (attr.name || '').toLowerCase(); - const attrProvider = (attr.provider || '').toLowerCase(); const attrValue = attr.value; - // Look for certificate attributes from crtsh provider - if (attrProvider === 'crtsh' || attrName.startsWith('cert_')) { + if (attrName.startsWith('cert_')) { hasCertificates = true; - - // Check certificate validity using raw attribute names if (attrName === 'cert_is_currently_valid') { if (attrValue === true) { hasValidCertificates = true; @@ -733,13 +610,6 @@ class GraphManager { hasExpiredCertificates = true; } } - // Check for expiry indicators - else if (attrName === 'cert_expires_soon' && attrValue === true) { - hasExpiredCertificates = true; - } - else if (attrName.includes('expired') && attrValue === true) { - hasExpiredCertificates = true; - } } } @@ -751,12 +621,6 @@ class GraphManager { }; } - /** - * UPDATED: Helper method to find an attribute by name in the standardized attributes list - * @param {Array} attributes - List of StandardAttribute objects - * @param {string} name - Attribute name to find - * @returns {Object|null} The attribute object if found, null otherwise - */ findAttributeByName(attributes, name) { if (!Array.isArray(attributes)) { return null; @@ -764,11 +628,6 @@ class GraphManager { return attributes.find(attr => attr.name === name) || null; } - /** - * UPDATED: Process node data with styling and metadata for the flat data model - * @param {Object} node - Raw node data with standardized attributes - * @returns {Object} Processed node data - */ processNode(node) { const processedNode = { id: node.id, @@ -786,26 +645,20 @@ class GraphManager { }; if (node.max_depth_reached) { - processedNode.borderColor = '#ff0000'; // Red border for max depth + processedNode.borderColor = '#ff0000'; } - // FIXED: Certificate-based domain coloring if (node.type === 'domain' && Array.isArray(node.attributes)) { const certInfo = this.analyzeCertificateInfo(node.attributes); - if (certInfo.hasExpiredOnly) { - // Red for domains with only expired/invalid certificates processedNode.color = '#ff6b6b'; processedNode.borderColor = '#cc5555'; } else if (!certInfo.hasCertificates) { - // Grey for domains with no certificates processedNode.color = '#c7c7c7'; processedNode.borderColor = '#999999'; } - // Green for valid certificates (default color) } - // Handle merged correlation objects if (node.type === 'correlation_object') { const correlationValueAttr = this.findAttributeByName(node.attributes, 'correlation_value'); const value = correlationValueAttr ? correlationValueAttr.value : 'Unknown'; @@ -818,48 +671,6 @@ class GraphManager { return processedNode; } - /** - * Process edge data with styling, metadata, and time-based gradient colors - * @param {Object} edge - Raw edge data - * @returns {Object} Processed edge data - */ - processEdge(edge) { - const edgeId = `${edge.from}-${edge.to}-${edge.label}`; - - // Extract timestamp for this edge - const timestamp = this.extractEdgeTimestamp(edge); - this.edgeTimestamps.set(edgeId, timestamp); - - // Calculate time-based gradient color - const timeGradientColor = this.calculateTimeGradientColor(timestamp); - - const processedEdge = { - id: edgeId, - from: edge.from, - to: edge.to, - label: edge.label, // Correctly access the label directly - title: this.createEdgeTooltip(edge), - color: { - color: timeGradientColor, - highlight: '#00ff41', - hover: '#ff9900' - }, - metadata: { - relationship_type: edge.label, - source_provider: edge.source_provider, - discovery_timestamp: edge.discovery_timestamp - } - }; - - return processedEdge; - } - - /** - * Format node label for display - * @param {string} nodeId - Node identifier - * @param {string} nodeType - Node type - * @returns {string} Formatted label - */ formatNodeLabel(nodeId, nodeType) { if (typeof nodeId !== 'string') return ''; if (nodeId.length > 20) { @@ -868,80 +679,38 @@ class GraphManager { return nodeId; } - - - /** - * Get node color based on type - * @param {string} nodeType - Node type - * @returns {string} Color value - */ getNodeColor(nodeType) { const colors = { - 'domain': '#00ff41', // Green - 'ip': '#ff9900', // Amber - 'isp': '#00aaff', // Blue - 'ca': '#ff6b6b', // Red - 'large_entity': '#ff6b6b', // Red for large entities - 'correlation_object': '#9620c0ff' + 'domain': '#00ff41', 'ip': '#ff9900', 'isp': '#00aaff', + 'ca': '#ff6b6b', 'large_entity': '#ff6b6b', 'correlation_object': '#9620c0ff' }; return colors[nodeType] || '#ffffff'; } - /** - * Get node border color based on type - * @param {string} nodeType - Node type - * @returns {string} Border color value - */ getNodeBorderColor(nodeType) { const borderColors = { - 'domain': '#00aa2e', - 'ip': '#cc7700', - 'isp': '#0088cc', - 'ca': '#cc5555', - 'correlation_object': '#c235c9ff' + 'domain': '#00aa2e', 'ip': '#cc7700', 'isp': '#0088cc', + 'ca': '#cc5555', 'correlation_object': '#c235c9ff' }; return borderColors[nodeType] || '#666666'; } - /** - * Get node size based on type - * @param {string} nodeType - Node type - * @returns {number} Node size - */ getNodeSize(nodeType) { const sizes = { - 'domain': 12, - 'ip': 14, - 'isp': 16, - 'ca': 16, - 'correlation_object': 8, - 'large_entity': 25 + 'domain': 12, 'ip': 14, 'isp': 16, 'ca': 16, + 'correlation_object': 8, 'large_entity': 25 }; return sizes[nodeType] || 12; } - /** - * Get node shape based on type - * @param {string} nodeType - Node type - * @returns {string} Shape name - */ getNodeShape(nodeType) { const shapes = { - 'domain': 'dot', - 'ip': 'square', - 'isp': 'triangle', - 'ca': 'diamond', - 'correlation_object': 'hexagon', - 'large_entity': 'dot' + 'domain': 'dot', 'ip': 'square', 'isp': 'triangle', 'ca': 'diamond', + 'correlation_object': 'hexagon', 'large_entity': 'dot' }; return shapes[nodeType] || 'dot'; } - /** - * Create edge tooltip with correct provider information and timestamp - * @param {Object} edge - Edge data - * @returns {string} HTML tooltip content - */ createEdgeTooltip(edge) { let tooltip = `