implement new data api
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* Graph visualization module for DNSRecon
|
||||
* Handles network graph rendering using vis.js with proper large entity node hiding
|
||||
* UPDATED: Now compatible with unified data model (StandardAttribute objects)
|
||||
*/
|
||||
const contextMenuCSS = `
|
||||
.graph-context-menu {
|
||||
@@ -380,11 +381,15 @@ class GraphManager {
|
||||
const largeEntityMap = new Map();
|
||||
|
||||
graphData.nodes.forEach(node => {
|
||||
if (node.type === 'large_entity' && node.attributes && Array.isArray(node.attributes.nodes)) {
|
||||
node.attributes.nodes.forEach(nodeId => {
|
||||
largeEntityMap.set(nodeId, node.id);
|
||||
this.largeEntityMembers.add(nodeId);
|
||||
});
|
||||
if (node.type === 'large_entity' && node.attributes) {
|
||||
// UPDATED: Handle unified data model - look for 'nodes' attribute in the attributes list
|
||||
const nodesAttribute = this.findAttributeByName(node.attributes, 'nodes');
|
||||
if (nodesAttribute && Array.isArray(nodesAttribute.value)) {
|
||||
nodesAttribute.value.forEach(nodeId => {
|
||||
largeEntityMap.set(nodeId, node.id);
|
||||
this.largeEntityMembers.add(nodeId);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -466,8 +471,21 @@ class GraphManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Process node data with styling and metadata
|
||||
* @param {Object} node - Raw node data
|
||||
* 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;
|
||||
}
|
||||
return attributes.find(attr => attr.name === name) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* UPDATED: Process node data with styling and metadata for unified data model
|
||||
* @param {Object} node - Raw node data with standardized attributes
|
||||
* @returns {Object} Processed node data
|
||||
*/
|
||||
processNode(node) {
|
||||
@@ -478,7 +496,7 @@ class GraphManager {
|
||||
size: this.getNodeSize(node.type),
|
||||
borderColor: this.getNodeBorderColor(node.type),
|
||||
shape: this.getNodeShape(node.type),
|
||||
attributes: node.attributes || {},
|
||||
attributes: node.attributes || [], // Keep as standardized attributes list
|
||||
description: node.description || '',
|
||||
metadata: node.metadata || {},
|
||||
type: node.type,
|
||||
@@ -491,9 +509,10 @@ class GraphManager {
|
||||
processedNode.borderWidth = Math.max(2, Math.floor(node.confidence * 5));
|
||||
}
|
||||
|
||||
// Style based on certificate validity
|
||||
// UPDATED: Style based on certificate validity using unified data model
|
||||
if (node.type === 'domain') {
|
||||
if (node.attributes && node.attributes.certificates && node.attributes.certificates.has_valid_cert === false) {
|
||||
const certificatesAttr = this.findAttributeByName(node.attributes, 'certificates');
|
||||
if (certificatesAttr && certificatesAttr.value && certificatesAttr.value.has_valid_cert === false) {
|
||||
processedNode.color = { background: '#888888', border: '#666666' };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* Main application logic for DNSRecon web interface
|
||||
* Handles UI interactions, API communication, and data flow
|
||||
* UPDATED: Now compatible with unified data model (StandardAttribute objects)
|
||||
*/
|
||||
|
||||
class DNSReconApp {
|
||||
@@ -808,10 +809,22 @@ class DNSReconApp {
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhanced node details HTML generation with better visual hierarchy
|
||||
* File: static/js/main.js (replace generateNodeDetailsHtml method)
|
||||
* 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;
|
||||
}
|
||||
return attributes.find(attr => attr.name === name) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* UPDATED: Enhanced node details HTML generation for unified data model
|
||||
* Now processes StandardAttribute objects instead of simple key-value pairs
|
||||
*/
|
||||
generateNodeDetailsHtml(node) {
|
||||
if (!node) return '<div class="detail-row"><span class="detail-value">Details not available.</span></div>';
|
||||
|
||||
@@ -857,23 +870,28 @@ class DNSReconApp {
|
||||
return detailsHtml;
|
||||
}
|
||||
|
||||
/**
|
||||
* UPDATED: Generate details for standard nodes using unified data model
|
||||
*/
|
||||
generateStandardNodeDetails(node) {
|
||||
let html = '';
|
||||
|
||||
// Relationships sections
|
||||
html += this.generateRelationshipsSection(node);
|
||||
|
||||
// Enhanced attributes section with special certificate handling
|
||||
if (node.attributes && Object.keys(node.attributes).length > 0) {
|
||||
const { certificates, ...otherAttributes } = node.attributes;
|
||||
// UPDATED: Enhanced attributes section with special certificate handling for unified model
|
||||
if (node.attributes && Array.isArray(node.attributes) && node.attributes.length > 0) {
|
||||
// Find certificate attribute separately
|
||||
const certificatesAttr = this.findAttributeByName(node.attributes, 'certificates');
|
||||
|
||||
// Handle certificates separately with enhanced display
|
||||
if (certificates) {
|
||||
html += this.generateCertificateSection({ certificates });
|
||||
if (certificatesAttr) {
|
||||
html += this.generateCertificateSection(certificatesAttr);
|
||||
}
|
||||
|
||||
// Handle other attributes normally
|
||||
if (Object.keys(otherAttributes).length > 0) {
|
||||
// Handle other attributes normally (excluding certificates to avoid duplication)
|
||||
const otherAttributes = node.attributes.filter(attr => attr.name !== 'certificates');
|
||||
if (otherAttributes.length > 0) {
|
||||
html += this.generateAttributesSection(otherAttributes);
|
||||
}
|
||||
}
|
||||
@@ -888,10 +906,10 @@ class DNSReconApp {
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhanced certificate section generation using existing styles
|
||||
* UPDATED: Enhanced certificate section generation for unified data model
|
||||
*/
|
||||
generateCertificateSection(attributes) {
|
||||
const certificates = attributes.certificates;
|
||||
generateCertificateSection(certificatesAttr) {
|
||||
const certificates = certificatesAttr.value;
|
||||
if (!certificates || typeof certificates !== 'object') {
|
||||
return '';
|
||||
}
|
||||
@@ -1094,10 +1112,22 @@ class DNSReconApp {
|
||||
return html;
|
||||
}
|
||||
|
||||
/**
|
||||
* UPDATED: Generate large entity details using unified data model
|
||||
*/
|
||||
generateLargeEntityDetails(node) {
|
||||
const attributes = node.attributes || {};
|
||||
const nodes = attributes.nodes || [];
|
||||
const nodeType = attributes.node_type || 'nodes';
|
||||
// UPDATED: Look for attributes in the unified model structure
|
||||
const nodesAttribute = this.findAttributeByName(node.attributes, 'nodes');
|
||||
const countAttribute = this.findAttributeByName(node.attributes, 'count');
|
||||
const nodeTypeAttribute = this.findAttributeByName(node.attributes, 'node_type');
|
||||
const sourceProviderAttribute = this.findAttributeByName(node.attributes, 'source_provider');
|
||||
const discoveryDepthAttribute = this.findAttributeByName(node.attributes, 'discovery_depth');
|
||||
|
||||
const nodes = nodesAttribute ? nodesAttribute.value : [];
|
||||
const count = countAttribute ? countAttribute.value : 0;
|
||||
const nodeType = nodeTypeAttribute ? nodeTypeAttribute.value : 'nodes';
|
||||
const sourceProvider = sourceProviderAttribute ? sourceProviderAttribute.value : 'Unknown';
|
||||
const discoveryDepth = discoveryDepthAttribute ? discoveryDepthAttribute.value : 'Unknown';
|
||||
|
||||
let html = `
|
||||
<div class="modal-section">
|
||||
@@ -1107,15 +1137,15 @@ class DNSReconApp {
|
||||
<div class="attribute-list">
|
||||
<div class="attribute-item-compact">
|
||||
<span class="attribute-key-compact">Contains:</span>
|
||||
<span class="attribute-value-compact">${attributes.count} ${nodeType}s</span>
|
||||
<span class="attribute-value-compact">${count} ${nodeType}s</span>
|
||||
</div>
|
||||
<div class="attribute-item-compact">
|
||||
<span class="attribute-key-compact">Provider:</span>
|
||||
<span class="attribute-value-compact">${attributes.source_provider || 'Unknown'}</span>
|
||||
<span class="attribute-value-compact">${sourceProvider}</span>
|
||||
</div>
|
||||
<div class="attribute-item-compact">
|
||||
<span class="attribute-key-compact">Depth:</span>
|
||||
<span class="attribute-value-compact">${attributes.discovery_depth || 'Unknown'}</span>
|
||||
<span class="attribute-value-compact">${discoveryDepth}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1132,17 +1162,19 @@ class DNSReconApp {
|
||||
// Use node.id for the large_entity_id
|
||||
const largeEntityId = node.id;
|
||||
|
||||
nodes.forEach(innerNodeId => {
|
||||
html += `
|
||||
<div class="relationship-compact-item">
|
||||
<span class="node-link-compact" data-node-id="${innerNodeId}">${innerNodeId}</span>
|
||||
<button class="btn-icon-small extract-node-btn"
|
||||
title="Extract to graph"
|
||||
data-large-entity-id="${largeEntityId}"
|
||||
data-node-id="${innerNodeId}">[+]</button>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
if (Array.isArray(nodes)) {
|
||||
nodes.forEach(innerNodeId => {
|
||||
html += `
|
||||
<div class="relationship-compact-item">
|
||||
<span class="node-link-compact" data-node-id="${innerNodeId}">${innerNodeId}</span>
|
||||
<button class="btn-icon-small extract-node-btn"
|
||||
title="Extract to graph"
|
||||
data-large-entity-id="${largeEntityId}"
|
||||
data-node-id="${innerNodeId}">[+]</button>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
}
|
||||
|
||||
html += '</div></div></details></div>';
|
||||
|
||||
@@ -1255,151 +1287,6 @@ class DNSReconApp {
|
||||
return valueSourceMap;
|
||||
}
|
||||
|
||||
generateCorrelationObjectLayout(node) {
|
||||
const metadata = node.metadata || {};
|
||||
const values = metadata.values || [];
|
||||
const mergeCount = metadata.merge_count || 1;
|
||||
|
||||
let html = '<div class="correlation-layout">';
|
||||
|
||||
if (mergeCount > 1) {
|
||||
html += `
|
||||
<div class="section-card correlation-summary">
|
||||
<div class="section-header">
|
||||
<h4><span class="section-icon">🔗</span>Merged Correlations</h4>
|
||||
<div class="merge-badge">${mergeCount} values</div>
|
||||
</div>
|
||||
<div class="correlation-grid">
|
||||
`;
|
||||
|
||||
values.forEach((value, index) => {
|
||||
const displayValue = typeof value === 'string' && value.length > 50 ?
|
||||
value.substring(0, 47) + '...' : value;
|
||||
|
||||
html += `
|
||||
<div class="correlation-item" data-index="${index}">
|
||||
<div class="correlation-preview">${displayValue}</div>
|
||||
<button class="expand-btn" onclick="this.parentElement.classList.toggle('expanded')">
|
||||
<span class="expand-icon">▼</span>
|
||||
</button>
|
||||
<div class="correlation-full hidden">${value}</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
html += '</div></div>';
|
||||
} else {
|
||||
const singleValue = values.length > 0 ? values[0] : (metadata.value || 'Unknown');
|
||||
html += `
|
||||
<div class="section-card">
|
||||
<div class="section-header">
|
||||
<h4><span class="section-icon">🔗</span>Correlation Value</h4>
|
||||
</div>
|
||||
<div class="correlation-value-display">${singleValue}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Show correlated nodes
|
||||
const correlatedNodes = metadata.correlated_nodes || [];
|
||||
if (correlatedNodes.length > 0) {
|
||||
html += `
|
||||
<div class="section-card">
|
||||
<div class="section-header">
|
||||
<h4><span class="section-icon">🌐</span>Correlated Nodes</h4>
|
||||
<div class="count-badge">${correlatedNodes.length}</div>
|
||||
</div>
|
||||
<div class="node-list">
|
||||
`;
|
||||
|
||||
correlatedNodes.forEach(nodeId => {
|
||||
html += `
|
||||
<div class="node-link-item" data-node-id="${nodeId}">
|
||||
<span class="node-icon">●</span>
|
||||
<span class="node-name">${nodeId}</span>
|
||||
<button class="navigate-btn" onclick="this.click()">→</button>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
html += '</div></div>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
generateLargeEntityLayout(node) {
|
||||
const attributes = node.attributes || {};
|
||||
const nodes = attributes.nodes || [];
|
||||
const nodeType = attributes.node_type || 'nodes';
|
||||
|
||||
let html = `
|
||||
<div class="large-entity-layout">
|
||||
<div class="section-card entity-summary">
|
||||
<div class="section-header">
|
||||
<h4><span class="section-icon">📦</span>Large Entity Container</h4>
|
||||
<div class="entity-badge">${attributes.count} ${nodeType}s</div>
|
||||
</div>
|
||||
<div class="entity-stats">
|
||||
<div class="stat-row">
|
||||
<span class="stat-label">Source Provider:</span>
|
||||
<span class="stat-value">${attributes.source_provider || 'Unknown'}</span>
|
||||
</div>
|
||||
<div class="stat-row">
|
||||
<span class="stat-label">Discovery Depth:</span>
|
||||
<span class="stat-value">${attributes.discovery_depth || 'Unknown'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-card entity-contents">
|
||||
<div class="section-header">
|
||||
<h4><span class="section-icon">📋</span>Contained ${nodeType}s</h4>
|
||||
<button class="toggle-all-btn" onclick="this.toggleAllEntities()">Expand All</button>
|
||||
</div>
|
||||
<div class="entity-node-grid">
|
||||
`;
|
||||
|
||||
nodes.forEach((innerNodeId, index) => {
|
||||
const innerNode = this.graphManager.nodes.get(innerNodeId);
|
||||
html += `
|
||||
<div class="entity-node-card" data-node-id="${innerNodeId}">
|
||||
<div class="entity-node-header" onclick="this.parentElement.classList.toggle('expanded')">
|
||||
<span class="node-icon">●</span>
|
||||
<span class="node-name">${innerNodeId}</span>
|
||||
<span class="expand-indicator">▼</span>
|
||||
</div>
|
||||
<div class="entity-node-details">
|
||||
${innerNode ? this.generateStandardNodeLayout(innerNode) : '<div class="no-details">No details available</div>'}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
html += '</div></div></div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
generateStandardNodeLayout(node) {
|
||||
let html = '<div class="standard-node-layout">';
|
||||
|
||||
// Relationships section
|
||||
html += this.generateRelationshipsSection(node);
|
||||
|
||||
// Attributes section with smart categorization
|
||||
html += this.generateAttributesSection(node);
|
||||
|
||||
// Description section
|
||||
html += this.generateDescriptionSection(node);
|
||||
|
||||
// Metadata section (collapsed by default)
|
||||
html += this.generateMetadataSection(node);
|
||||
|
||||
html += '</div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
generateRelationshipsSection(node) {
|
||||
let html = '';
|
||||
|
||||
@@ -1468,12 +1355,20 @@ class DNSReconApp {
|
||||
return html;
|
||||
}
|
||||
|
||||
/**
|
||||
* UPDATED: Generate attributes section for unified data model
|
||||
* Now processes StandardAttribute objects instead of key-value pairs
|
||||
*/
|
||||
generateAttributesSection(attributes) {
|
||||
const categorized = this.categorizeAttributes(attributes);
|
||||
if (!Array.isArray(attributes) || attributes.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const categorized = this.categorizeStandardAttributes(attributes);
|
||||
let html = '';
|
||||
|
||||
Object.entries(categorized).forEach(([category, attrs]) => {
|
||||
if (Object.keys(attrs).length === 0) return;
|
||||
if (attrs.length === 0) return;
|
||||
|
||||
html += `
|
||||
<div class="modal-section">
|
||||
@@ -1482,20 +1377,16 @@ class DNSReconApp {
|
||||
<div class="modal-section-content">
|
||||
`;
|
||||
|
||||
if (category === 'Certificates' && attrs.certificates) {
|
||||
html += this.formatCertificateData(attrs.certificates);
|
||||
} else {
|
||||
html += '<div class="attribute-list">';
|
||||
Object.entries(attrs).forEach(([key, value]) => {
|
||||
html += `
|
||||
<div class="attribute-item-compact">
|
||||
<span class="attribute-key-compact">${this.formatLabel(key)}</span>
|
||||
<span class="attribute-value-compact">${this.formatAttributeValue(value)}</span>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
html += '</div>';
|
||||
}
|
||||
html += '<div class="attribute-list">';
|
||||
attrs.forEach(attr => {
|
||||
html += `
|
||||
<div class="attribute-item-compact">
|
||||
<span class="attribute-key-compact">${this.formatLabel(attr.name)}</span>
|
||||
<span class="attribute-value-compact">${this.formatStandardAttributeValue(attr)}</span>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
html += '</div>';
|
||||
|
||||
html += '</div></details></div>';
|
||||
});
|
||||
@@ -1503,47 +1394,41 @@ class DNSReconApp {
|
||||
return html;
|
||||
}
|
||||
|
||||
formatCertificateData(certData) {
|
||||
if (!certData || typeof certData !== 'object') {
|
||||
return '<p>No certificate data available</p>';
|
||||
}
|
||||
/**
|
||||
* UPDATED: Categorize StandardAttribute objects by type and content
|
||||
*/
|
||||
categorizeStandardAttributes(attributes) {
|
||||
const categories = {
|
||||
'DNS Records': [],
|
||||
'Network Info': [],
|
||||
'Provider Data': [],
|
||||
'Other': []
|
||||
};
|
||||
|
||||
let html = '<div class="certificate-list">';
|
||||
attributes.forEach(attr => {
|
||||
const lowerName = attr.name.toLowerCase();
|
||||
const attrType = attr.type ? attr.type.toLowerCase() : '';
|
||||
|
||||
if (lowerName.includes('dns') || lowerName.includes('record') || attrType.includes('dns')) {
|
||||
categories['DNS Records'].push(attr);
|
||||
} else if (lowerName.includes('ip') || lowerName.includes('asn') || lowerName.includes('network') || attrType.includes('network')) {
|
||||
categories['Network Info'].push(attr);
|
||||
} else if (lowerName.includes('shodan') || lowerName.includes('crtsh') || lowerName.includes('provider') || attrType.includes('provider')) {
|
||||
categories['Provider Data'].push(attr);
|
||||
} else {
|
||||
categories['Other'].push(attr);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle certificate summary
|
||||
if (certData.total_certificates) {
|
||||
html += `
|
||||
<div class="certificate-item">
|
||||
<div class="certificate-summary">
|
||||
<span>Total Certificates: ${certData.total_certificates}</span>
|
||||
<span class="certificate-status ${certData.has_valid_cert ? 'valid' : 'invalid'}">
|
||||
${certData.has_valid_cert ? 'Valid' : 'Invalid'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Handle unique issuers
|
||||
if (certData.unique_issuers && Array.isArray(certData.unique_issuers)) {
|
||||
html += `
|
||||
<div class="certificate-item">
|
||||
<div class="certificate-summary">
|
||||
<span>Issuers:</span>
|
||||
</div>
|
||||
<div class="array-display">
|
||||
`;
|
||||
certData.unique_issuers.forEach(issuer => {
|
||||
html += `<div class="array-display-item">${this.escapeHtml(String(issuer))}</div>`;
|
||||
});
|
||||
html += '</div></div>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
return html;
|
||||
return categories;
|
||||
}
|
||||
|
||||
formatAttributeValue(value) {
|
||||
/**
|
||||
* UPDATED: Format StandardAttribute value for display
|
||||
*/
|
||||
formatStandardAttributeValue(attr) {
|
||||
const value = attr.value;
|
||||
|
||||
if (value === null || value === undefined) {
|
||||
return '<em>None</em>';
|
||||
}
|
||||
@@ -1567,35 +1452,6 @@ class DNSReconApp {
|
||||
return this.escapeHtml(String(value));
|
||||
}
|
||||
|
||||
|
||||
categorizeAttributes(attributes) {
|
||||
const categories = {
|
||||
'DNS Records': {},
|
||||
'Certificates': {},
|
||||
'Network Info': {},
|
||||
'Provider Data': {},
|
||||
'Other': {}
|
||||
};
|
||||
|
||||
for (const [key, value] of Object.entries(attributes)) {
|
||||
const lowerKey = key.toLowerCase();
|
||||
|
||||
if (lowerKey.includes('dns') || lowerKey.includes('record') || key.endsWith('_record')) {
|
||||
categories['DNS Records'][key] = value;
|
||||
} else if (lowerKey.includes('cert') || lowerKey.includes('ssl') || lowerKey.includes('tls')) {
|
||||
categories['Certificates'][key] = value;
|
||||
} else if (lowerKey.includes('ip') || lowerKey.includes('asn') || lowerKey.includes('network')) {
|
||||
categories['Network Info'][key] = value;
|
||||
} else if (lowerKey.includes('shodan') || lowerKey.includes('crtsh') || lowerKey.includes('provider')) {
|
||||
categories['Provider Data'][key] = value;
|
||||
} else {
|
||||
categories['Other'][key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return categories;
|
||||
}
|
||||
|
||||
formatObjectCompact(obj) {
|
||||
if (!obj || typeof obj !== 'object') return '';
|
||||
|
||||
@@ -1625,7 +1481,7 @@ class DNSReconApp {
|
||||
return `
|
||||
<div class="section-card description-section">
|
||||
<div class="section-header">
|
||||
<h4><span class="section-icon">📝</span>Description</h4>
|
||||
<h4><span class="section-icon">📄</span>Description</h4>
|
||||
</div>
|
||||
<div class="description-content">
|
||||
${this.escapeHtml(node.description)}
|
||||
@@ -1827,7 +1683,7 @@ class DNSReconApp {
|
||||
getNodeTypeIcon(nodeType) {
|
||||
const icons = {
|
||||
'domain': '🌐',
|
||||
'ip': '📍',
|
||||
'ip': '🔍',
|
||||
'asn': '🏢',
|
||||
'large_entity': '📦',
|
||||
'correlation_object': '🔗'
|
||||
|
||||
Reference in New Issue
Block a user