improving the display
This commit is contained in:
		
							parent
							
								
									733e1da640
								
							
						
					
					
						commit
						229746e1ec
					
				@ -56,15 +56,22 @@ class GraphManager:
 | 
			
		||||
        self.date_pattern = re.compile(r'^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}')
 | 
			
		||||
 | 
			
		||||
    def process_correlations_for_node(self, node_id: str):
 | 
			
		||||
        """Process correlations for a given node based on its attributes."""
 | 
			
		||||
        """
 | 
			
		||||
        UPDATED: Process correlations for a given node with enhanced tracking.
 | 
			
		||||
        Now properly tracks which attribute/provider created each correlation.
 | 
			
		||||
        """
 | 
			
		||||
        if not self.graph.has_node(node_id):
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        node_attributes = self.graph.nodes[node_id].get('attributes', [])
 | 
			
		||||
        
 | 
			
		||||
        # Process each attribute for potential correlations
 | 
			
		||||
        for attr in node_attributes:
 | 
			
		||||
            attr_name = attr.get('name')
 | 
			
		||||
            attr_value = attr.get('value')
 | 
			
		||||
            attr_provider = attr.get('provider', 'unknown')
 | 
			
		||||
 | 
			
		||||
            # Skip excluded attributes and invalid values
 | 
			
		||||
            if attr_name in self.EXCLUDED_KEYS or not isinstance(attr_value, (str, int, float, bool)) or attr_value is None:
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
@ -74,25 +81,91 @@ class GraphManager:
 | 
			
		||||
            if isinstance(attr_value, str) and (len(attr_value) < 4 or self.date_pattern.match(attr_value)):
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            # Initialize correlation tracking for this value
 | 
			
		||||
            if attr_value not in self.correlation_index:
 | 
			
		||||
                self.correlation_index[attr_value] = set()
 | 
			
		||||
                self.correlation_index[attr_value] = {
 | 
			
		||||
                    'nodes': set(),
 | 
			
		||||
                    'sources': []  # Track which provider/attribute combinations contributed
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            self.correlation_index[attr_value].add(node_id)
 | 
			
		||||
            # Add this node and source information
 | 
			
		||||
            self.correlation_index[attr_value]['nodes'].add(node_id)
 | 
			
		||||
            
 | 
			
		||||
            # Track the source of this correlation value
 | 
			
		||||
            source_info = {
 | 
			
		||||
                'node_id': node_id,
 | 
			
		||||
                'provider': attr_provider,
 | 
			
		||||
                'attribute': attr_name,
 | 
			
		||||
                'path': f"{attr_provider}_{attr_name}"
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            # Add source if not already present (avoid duplicates)
 | 
			
		||||
            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)
 | 
			
		||||
 | 
			
		||||
            if len(self.correlation_index[attr_value]) > 1:
 | 
			
		||||
                self._create_correlation_node_and_edges(attr_value, self.correlation_index[attr_value])
 | 
			
		||||
            # Create correlation node if we have multiple nodes with this value
 | 
			
		||||
            if len(self.correlation_index[attr_value]['nodes']) > 1:
 | 
			
		||||
                self._create_enhanced_correlation_node_and_edges(attr_value, self.correlation_index[attr_value])
 | 
			
		||||
 | 
			
		||||
    def _create_correlation_node_and_edges(self, value, nodes):
 | 
			
		||||
        """Create a correlation node and edges to the correlated nodes."""
 | 
			
		||||
        correlation_node_id = f"corr_{value}"
 | 
			
		||||
    def _create_enhanced_correlation_node_and_edges(self, value, correlation_data):
 | 
			
		||||
        """
 | 
			
		||||
        UPDATED: Create correlation node and edges with detailed provider tracking.
 | 
			
		||||
        """
 | 
			
		||||
        correlation_node_id = f"corr_{hash(str(value)) & 0x7FFFFFFF}"
 | 
			
		||||
        nodes = correlation_data['nodes']
 | 
			
		||||
        sources = correlation_data['sources']
 | 
			
		||||
        
 | 
			
		||||
        # Create or update correlation node
 | 
			
		||||
        if not self.graph.has_node(correlation_node_id):
 | 
			
		||||
            self.add_node(correlation_node_id, NodeType.CORRELATION_OBJECT,
 | 
			
		||||
                        metadata={'value': value, 'correlated_nodes': list(nodes)})
 | 
			
		||||
            # Determine the most common provider/attribute combination
 | 
			
		||||
            provider_counts = {}
 | 
			
		||||
            for source in sources:
 | 
			
		||||
                key = f"{source['provider']}_{source['attribute']}"
 | 
			
		||||
                provider_counts[key] = provider_counts.get(key, 0) + 1
 | 
			
		||||
            
 | 
			
		||||
            # Use the most common provider/attribute as the primary label
 | 
			
		||||
            primary_source = max(provider_counts.items(), key=lambda x: x[1])[0] if provider_counts else "unknown_correlation"
 | 
			
		||||
            
 | 
			
		||||
            metadata = {
 | 
			
		||||
                'value': value,
 | 
			
		||||
                'correlated_nodes': list(nodes),
 | 
			
		||||
                'sources': sources,
 | 
			
		||||
                'primary_source': primary_source,
 | 
			
		||||
                'correlation_count': len(nodes)
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            self.add_node(correlation_node_id, NodeType.CORRELATION_OBJECT, metadata=metadata)
 | 
			
		||||
            print(f"Created correlation node {correlation_node_id} for value '{value}' with {len(nodes)} nodes")
 | 
			
		||||
 | 
			
		||||
        for node_id in nodes:
 | 
			
		||||
        # Create edges from each node to the correlation node
 | 
			
		||||
        for source in sources:
 | 
			
		||||
            node_id = source['node_id']
 | 
			
		||||
            provider = source['provider']
 | 
			
		||||
            attribute = source['attribute']
 | 
			
		||||
            
 | 
			
		||||
            if self.graph.has_node(node_id) and not self.graph.has_edge(node_id, correlation_node_id):
 | 
			
		||||
                self.add_edge(node_id, correlation_node_id, "correlation", confidence_score=0.9)
 | 
			
		||||
 | 
			
		||||
                # Format relationship label as "provider: attribute"
 | 
			
		||||
                display_provider = provider
 | 
			
		||||
                display_attribute = attribute.replace('_', ' ').replace('cert ', '').strip()
 | 
			
		||||
                
 | 
			
		||||
                relationship_label = f"{display_provider}: {display_attribute}"
 | 
			
		||||
                
 | 
			
		||||
                self.add_edge(
 | 
			
		||||
                    source_id=node_id,
 | 
			
		||||
                    target_id=correlation_node_id,
 | 
			
		||||
                    relationship_type=relationship_label,
 | 
			
		||||
                    confidence_score=0.9,
 | 
			
		||||
                    source_provider=provider,
 | 
			
		||||
                    raw_data={
 | 
			
		||||
                        'correlation_value': value,
 | 
			
		||||
                        'original_attribute': attribute,
 | 
			
		||||
                        'correlation_type': 'attribute_matching'
 | 
			
		||||
                    }
 | 
			
		||||
                )
 | 
			
		||||
                
 | 
			
		||||
                print(f"Added correlation edge: {node_id} -> {correlation_node_id} ({relationship_label})")
 | 
			
		||||
 | 
			
		||||
    def add_node(self, node_id: str, node_type: NodeType, attributes: Optional[List[Dict[str, Any]]] = None,
 | 
			
		||||
                description: str = "", metadata: Optional[Dict[str, Any]] = None) -> bool:
 | 
			
		||||
@ -335,26 +408,56 @@ class GraphManager:
 | 
			
		||||
    def get_graph_data(self) -> Dict[str, Any]:
 | 
			
		||||
        """
 | 
			
		||||
        Export graph data formatted for frontend visualization.
 | 
			
		||||
        Compatible with unified data model - preserves all attribute information for frontend display.
 | 
			
		||||
        UPDATED: Fixed certificate validity styling logic for unified data model.
 | 
			
		||||
        """
 | 
			
		||||
        nodes = []
 | 
			
		||||
        for node_id, attrs in self.graph.nodes(data=True):
 | 
			
		||||
            node_data = {'id': node_id, 'label': node_id, 'type': attrs.get('type', 'unknown'),
 | 
			
		||||
                         'attributes': attrs.get('attributes', []), # Ensure attributes is a list
 | 
			
		||||
                         'description': attrs.get('description', ''),
 | 
			
		||||
                         'metadata': attrs.get('metadata', {}),
 | 
			
		||||
                         'added_timestamp': attrs.get('added_timestamp')}
 | 
			
		||||
                        'attributes': attrs.get('attributes', []), # Ensure attributes is a list
 | 
			
		||||
                        'description': attrs.get('description', ''),
 | 
			
		||||
                        'metadata': attrs.get('metadata', {}),
 | 
			
		||||
                        'added_timestamp': attrs.get('added_timestamp')}
 | 
			
		||||
            
 | 
			
		||||
            # Customize node appearance based on type and attributes
 | 
			
		||||
            # UPDATED: Fixed certificate validity styling logic
 | 
			
		||||
            node_type = node_data['type']
 | 
			
		||||
            attributes_list = node_data['attributes']
 | 
			
		||||
            
 | 
			
		||||
            # CORRECTED LOGIC: Handle certificate validity styling
 | 
			
		||||
            if node_type == 'domain' and isinstance(attributes_list, list):
 | 
			
		||||
                # Find the certificates attribute in the list
 | 
			
		||||
                cert_attr = next((attr for attr in attributes_list if attr.get('name') == 'certificates'), None)
 | 
			
		||||
                if cert_attr and cert_attr.get('value', {}).get('has_valid_cert') is False:
 | 
			
		||||
                    node_data['color'] = {'background': '#c7c7c7', 'border': '#999'} # Gray for invalid cert
 | 
			
		||||
                # Check for certificate-related attributes
 | 
			
		||||
                has_certificates = False
 | 
			
		||||
                has_valid_certificates = False
 | 
			
		||||
                has_expired_certificates = False
 | 
			
		||||
                
 | 
			
		||||
                for attr in attributes_list:
 | 
			
		||||
                    attr_name = attr.get('name', '').lower()
 | 
			
		||||
                    attr_provider = attr.get('provider', '').lower()
 | 
			
		||||
                    attr_value = attr.get('value')
 | 
			
		||||
                    
 | 
			
		||||
                    # Look for certificate attributes from crt.sh provider
 | 
			
		||||
                    if attr_provider == 'crtsh' or 'cert' in attr_name:
 | 
			
		||||
                        has_certificates = True
 | 
			
		||||
                        
 | 
			
		||||
                        # Check certificate validity
 | 
			
		||||
                        if attr_name == 'cert_is_currently_valid':
 | 
			
		||||
                            if attr_value is True:
 | 
			
		||||
                                has_valid_certificates = True
 | 
			
		||||
                            elif attr_value is False:
 | 
			
		||||
                                has_expired_certificates = True
 | 
			
		||||
                        
 | 
			
		||||
                        # Also check for certificate expiry indicators
 | 
			
		||||
                        elif 'expires_soon' in attr_name and attr_value is True:
 | 
			
		||||
                            has_expired_certificates = True
 | 
			
		||||
                        elif 'expired' in attr_name and attr_value is True:
 | 
			
		||||
                            has_expired_certificates = True
 | 
			
		||||
                
 | 
			
		||||
                # Apply styling based on certificate status
 | 
			
		||||
                if has_expired_certificates and not has_valid_certificates:
 | 
			
		||||
                    # Red for expired/invalid certificates
 | 
			
		||||
                    node_data['color'] = {'background': '#ff6b6b', 'border': '#cc5555'}
 | 
			
		||||
                elif not has_certificates:
 | 
			
		||||
                    # Grey for domains with no certificates
 | 
			
		||||
                    node_data['color'] = {'background': '#c7c7c7', 'border': '#999999'}
 | 
			
		||||
                # Default green styling is handled by the frontend for domains with valid certificates
 | 
			
		||||
            
 | 
			
		||||
            # Add incoming and outgoing edges to node data
 | 
			
		||||
            if self.graph.has_node(node_id):
 | 
			
		||||
@ -366,10 +469,10 @@ class GraphManager:
 | 
			
		||||
        edges = []
 | 
			
		||||
        for source, target, attrs in self.graph.edges(data=True):
 | 
			
		||||
            edges.append({'from': source, 'to': target,
 | 
			
		||||
                          'label': attrs.get('relationship_type', ''),
 | 
			
		||||
                          'confidence_score': attrs.get('confidence_score', 0),
 | 
			
		||||
                          'source_provider': attrs.get('source_provider', ''),
 | 
			
		||||
                          'discovery_timestamp': attrs.get('discovery_timestamp')})
 | 
			
		||||
                        'label': attrs.get('relationship_type', ''),
 | 
			
		||||
                        'confidence_score': attrs.get('confidence_score', 0),
 | 
			
		||||
                        'source_provider': attrs.get('source_provider', ''),
 | 
			
		||||
                        'discovery_timestamp': attrs.get('discovery_timestamp')})
 | 
			
		||||
        return {
 | 
			
		||||
            'nodes': nodes, 'edges': edges,
 | 
			
		||||
            'statistics': self.get_statistics()['basic_metrics']
 | 
			
		||||
 | 
			
		||||
@ -822,8 +822,8 @@ class DNSReconApp {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * UPDATED: Enhanced node details HTML generation for unified data model
 | 
			
		||||
     * Now processes StandardAttribute objects instead of simple key-value pairs
 | 
			
		||||
     * Node details HTML generation for unified data model
 | 
			
		||||
     * processes StandardAttribute objects 
 | 
			
		||||
     */
 | 
			
		||||
    generateNodeDetailsHtml(node) {
 | 
			
		||||
        if (!node) return '<div class="detail-row"><span class="detail-value">Details not available.</span></div>';
 | 
			
		||||
@ -871,7 +871,7 @@ class DNSReconApp {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * UPDATED: Generate details for standard nodes using unified data model
 | 
			
		||||
     * Generate details for standard nodes using unified data model
 | 
			
		||||
     */
 | 
			
		||||
    generateStandardNodeDetails(node) {
 | 
			
		||||
        let html = '';
 | 
			
		||||
@ -879,9 +879,9 @@ class DNSReconApp {
 | 
			
		||||
        // Relationships sections
 | 
			
		||||
        html += this.generateRelationshipsSection(node);
 | 
			
		||||
        
 | 
			
		||||
        // UPDATED: Simplified attributes section for the flat model
 | 
			
		||||
        // Attributes section with grouping
 | 
			
		||||
        if (node.attributes && Array.isArray(node.attributes) && node.attributes.length > 0) {
 | 
			
		||||
            html += this.generateAttributesSection(node.attributes);
 | 
			
		||||
            html += this.generateEnhancedAttributesSection(node.attributes, node.type);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Description section
 | 
			
		||||
@ -893,6 +893,242 @@ class DNSReconApp {
 | 
			
		||||
        return html;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    generateEnhancedAttributesSection(attributes, nodeType) {
 | 
			
		||||
        if (!Array.isArray(attributes) || attributes.length === 0) {
 | 
			
		||||
            return '';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Group attributes by provider and type for better organization
 | 
			
		||||
        const groupedAttributes = this.groupAttributesIntelligently(attributes, nodeType);
 | 
			
		||||
        
 | 
			
		||||
        let html = '';
 | 
			
		||||
        
 | 
			
		||||
        for (const [groupName, groupData] of Object.entries(groupedAttributes)) {
 | 
			
		||||
            const isDefaultOpen = groupData.priority === 'high';
 | 
			
		||||
            
 | 
			
		||||
            html += `
 | 
			
		||||
                <div class="modal-section">
 | 
			
		||||
                    <details ${isDefaultOpen ? 'open' : ''}>
 | 
			
		||||
                        <summary>
 | 
			
		||||
                            <span>${groupData.icon} ${groupName}</span>
 | 
			
		||||
                            <span class="count-badge">${groupData.attributes.length}</span>
 | 
			
		||||
                        </summary>
 | 
			
		||||
                        <div class="modal-section-content">
 | 
			
		||||
                            <div class="attribute-list">
 | 
			
		||||
            `;
 | 
			
		||||
 | 
			
		||||
            groupData.attributes.forEach(attr => {
 | 
			
		||||
                html += `
 | 
			
		||||
                    <div class="attribute-item-compact">
 | 
			
		||||
                        <span class="attribute-key-compact">${this.formatAttributeLabel(attr.name)}</span>
 | 
			
		||||
                        <span class="attribute-value-compact">${this.formatAttributeValueEnhanced(attr)}</span>
 | 
			
		||||
                    </div>
 | 
			
		||||
                `;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            html += '</div></div></details></div>';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return html;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    formatAttributeValueEnhanced(attr) {
 | 
			
		||||
        const value = attr.value;
 | 
			
		||||
        
 | 
			
		||||
        if (value === null || value === undefined) {
 | 
			
		||||
            return '<em>None</em>';
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (Array.isArray(value)) {
 | 
			
		||||
            if (value.length === 0) return '<em>None</em>';
 | 
			
		||||
            if (value.length === 1) return this.escapeHtml(String(value[0]));
 | 
			
		||||
            
 | 
			
		||||
            // Complex array - make it collapsible
 | 
			
		||||
            const previewItems = value.slice(0, 2);
 | 
			
		||||
            const hasMore = value.length > 2;
 | 
			
		||||
            
 | 
			
		||||
            let html = '<div class="expandable-array">';
 | 
			
		||||
            html += `<div class="array-preview">`;
 | 
			
		||||
            
 | 
			
		||||
            previewItems.forEach(item => {
 | 
			
		||||
                html += `<div class="array-item-preview">${this.escapeHtml(String(item))}</div>`;
 | 
			
		||||
            });
 | 
			
		||||
            
 | 
			
		||||
            if (hasMore) {
 | 
			
		||||
                html += `
 | 
			
		||||
                    <button class="expand-array-btn" onclick="this.parentElement.style.display='none'; this.parentElement.nextElementSibling.style.display='block';">
 | 
			
		||||
                        +${value.length - 2} more...
 | 
			
		||||
                    </button>
 | 
			
		||||
                `;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            html += '</div>';
 | 
			
		||||
            
 | 
			
		||||
            if (hasMore) {
 | 
			
		||||
                html += `<div class="array-full" style="display: none;">`;
 | 
			
		||||
                value.forEach(item => {
 | 
			
		||||
                    html += `<div class="array-item-full">${this.escapeHtml(String(item))}</div>`;
 | 
			
		||||
                });
 | 
			
		||||
                html += `
 | 
			
		||||
                    <button class="collapse-array-btn" onclick="this.parentElement.style.display='none'; this.parentElement.previousElementSibling.style.display='block';">
 | 
			
		||||
                        Show less
 | 
			
		||||
                    </button>
 | 
			
		||||
                `;
 | 
			
		||||
                html += '</div>';
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            html += '</div>';
 | 
			
		||||
            return html;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (typeof value === 'object' && value !== null) {
 | 
			
		||||
            return this.formatObjectExpandable(value);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return this.escapeHtml(String(value));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * NEW: Format objects as expandable content
 | 
			
		||||
     */
 | 
			
		||||
    formatObjectExpandable(obj) {
 | 
			
		||||
        if (!obj || typeof obj !== 'object') return '';
 | 
			
		||||
        
 | 
			
		||||
        const entries = Object.entries(obj);
 | 
			
		||||
        if (entries.length === 0) return '<em>Empty</em>';
 | 
			
		||||
        
 | 
			
		||||
        if (entries.length <= 3) {
 | 
			
		||||
            // Simple inline display for small objects
 | 
			
		||||
            let html = '<div class="simple-object">';
 | 
			
		||||
            entries.forEach(([key, value]) => {
 | 
			
		||||
                html += `<div><strong>${key}:</strong> ${this.escapeHtml(String(value))}</div>`;
 | 
			
		||||
            });
 | 
			
		||||
            html += '</div>';
 | 
			
		||||
            return html;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Expandable display for complex objects
 | 
			
		||||
        let html = '<div class="expandable-object">';
 | 
			
		||||
        html += `
 | 
			
		||||
            <div class="object-preview">
 | 
			
		||||
                <strong>${entries[0][0]}:</strong> ${this.escapeHtml(String(entries[0][1]))}
 | 
			
		||||
                <button class="expand-object-btn" onclick="this.parentElement.style.display='none'; this.parentElement.nextElementSibling.style.display='block';">
 | 
			
		||||
                    +${entries.length - 1} more properties...
 | 
			
		||||
                </button>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="object-full" style="display: none;">
 | 
			
		||||
        `;
 | 
			
		||||
        
 | 
			
		||||
        entries.forEach(([key, value]) => {
 | 
			
		||||
            html += `<div class="object-property"><strong>${key}:</strong> ${this.escapeHtml(String(value))}</div>`;
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
        html += `
 | 
			
		||||
                <button class="collapse-object-btn" onclick="this.parentElement.style.display='none'; this.parentElement.previousElementSibling.style.display='block';">
 | 
			
		||||
                    Show less
 | 
			
		||||
                </button>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        `;
 | 
			
		||||
        
 | 
			
		||||
        return html;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    formatAttributeLabel(name) {
 | 
			
		||||
        // Handle provider prefixed attributes
 | 
			
		||||
        if (name.includes('_')) {
 | 
			
		||||
            const parts = name.split('_');
 | 
			
		||||
            if (parts.length >= 2) {
 | 
			
		||||
                const provider = parts[0];
 | 
			
		||||
                const attribute = parts.slice(1).join('_');
 | 
			
		||||
                return `${this.provider}: ${this.formatLabel(attribute)}`;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return this.formatLabel(name);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    groupAttributesIntelligently(attributes, nodeType) {
 | 
			
		||||
        const groups = {};
 | 
			
		||||
 | 
			
		||||
        // Define group configurations
 | 
			
		||||
        const groupConfigs = {
 | 
			
		||||
            'DNS Records': {
 | 
			
		||||
                icon: '🔍',
 | 
			
		||||
                priority: 'high',
 | 
			
		||||
                keywords: ['dns', 'record', 'a_record', 'cname', 'mx', 'ns', 'txt', 'ptr'],
 | 
			
		||||
                providers: ['dns']
 | 
			
		||||
            },
 | 
			
		||||
            'Certificate Information': {
 | 
			
		||||
                icon: '🔒',
 | 
			
		||||
                priority: 'high', 
 | 
			
		||||
                keywords: ['cert', 'certificate', 'ssl', 'tls', 'issuer', 'validity', 'san'],
 | 
			
		||||
                providers: ['crtsh']
 | 
			
		||||
            },
 | 
			
		||||
            'Network Information': {
 | 
			
		||||
                icon: '🌐',
 | 
			
		||||
                priority: 'high',
 | 
			
		||||
                keywords: ['port', 'service', 'banner', 'asn', 'organization', 'country', 'city'],
 | 
			
		||||
                providers: ['shodan']
 | 
			
		||||
            },
 | 
			
		||||
            'Correlation Data': {
 | 
			
		||||
                icon: '🔗',
 | 
			
		||||
                priority: 'medium',
 | 
			
		||||
                keywords: ['correlation', 'shared', 'common'],
 | 
			
		||||
                providers: []
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Initialize groups
 | 
			
		||||
        Object.entries(groupConfigs).forEach(([name, config]) => {
 | 
			
		||||
            groups[name] = {
 | 
			
		||||
                ...config,
 | 
			
		||||
                attributes: []
 | 
			
		||||
            };
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Add a catch-all group
 | 
			
		||||
        groups['Other Information'] = {
 | 
			
		||||
            icon: '📋',
 | 
			
		||||
            priority: 'low',
 | 
			
		||||
            attributes: []
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Classify attributes into groups
 | 
			
		||||
        attributes.forEach(attr => {
 | 
			
		||||
            let assigned = false;
 | 
			
		||||
            
 | 
			
		||||
            // Try to assign to a specific group based on provider or keywords
 | 
			
		||||
            for (const [groupName, config] of Object.entries(groupConfigs)) {
 | 
			
		||||
                const matchesProvider = config.providers.includes(attr.provider);
 | 
			
		||||
                const matchesKeyword = config.keywords.some(keyword => 
 | 
			
		||||
                    attr.name.toLowerCase().includes(keyword) || 
 | 
			
		||||
                    attr.type.toLowerCase().includes(keyword)
 | 
			
		||||
                );
 | 
			
		||||
                
 | 
			
		||||
                if (matchesProvider || matchesKeyword) {
 | 
			
		||||
                    groups[groupName].attributes.push(attr);
 | 
			
		||||
                    assigned = true;
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            // If not assigned to any specific group, put in "Other"
 | 
			
		||||
            if (!assigned) {
 | 
			
		||||
                groups['Other Information'].attributes.push(attr);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Remove empty groups
 | 
			
		||||
        Object.keys(groups).forEach(groupName => {
 | 
			
		||||
            if (groups[groupName].attributes.length === 0) {
 | 
			
		||||
                delete groups[groupName];
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return groups;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * UPDATED: Generate large entity details using unified data model
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user