improving the display
This commit is contained in:
@@ -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
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user