it
This commit is contained in:
@@ -1440,6 +1440,50 @@ input[type="text"]:focus, select:focus {
|
||||
.certificate-status.invalid { background: #ff6b6b; color: #fff; }
|
||||
.certificate-status.expired { background: #ff9900; color: #000; }
|
||||
|
||||
.cert-summary-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.cert-stat-item {
|
||||
text-align: center;
|
||||
padding: 0.5rem;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
border: 1px solid #333;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.cert-stat-value {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
color: #00ff41;
|
||||
font-family: 'Roboto Mono', monospace;
|
||||
}
|
||||
|
||||
.cert-stat-label {
|
||||
font-size: 0.8rem;
|
||||
color: #999;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
/* Status badges - extends existing badge system */
|
||||
.cert-status.valid { background: #00ff41; color: #000; }
|
||||
.cert-status.invalid { background: #ff6b6b; color: #fff; }
|
||||
.cert-status.warning { background: #ff9900; color: #000; }
|
||||
|
||||
/* Certificate links */
|
||||
.cert-link {
|
||||
color: #00aaff;
|
||||
text-decoration: none;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.cert-link:hover {
|
||||
color: #00ff41;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* === CORRELATION OBJECT LAYOUT === */
|
||||
.correlation-grid {
|
||||
@@ -1529,6 +1573,23 @@ input[type="text"]:focus, select:focus {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.correlation-attr-name {
|
||||
color: #00ff41;
|
||||
font-weight: 600;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.correlation-hint {
|
||||
color: #999;
|
||||
cursor: help;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.correlation-hint:hover {
|
||||
opacity: 1;
|
||||
color: #00ff41;
|
||||
}
|
||||
|
||||
.node-list {
|
||||
padding: 1rem 1.5rem;
|
||||
display: flex;
|
||||
@@ -1935,46 +1996,6 @@ input[type="text"]:focus, select:focus {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
/* Loading and Error States */
|
||||
.loading {
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.loading-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(26, 26, 26, 0.8);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px solid #444;
|
||||
border-top: 3px solid #00ff41;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
margin-top: 1rem;
|
||||
color: #999;
|
||||
font-family: 'Roboto Mono', monospace;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #ff6b6b !important;
|
||||
border-color: #ff6b6b !important;
|
||||
@@ -2004,48 +2025,6 @@ input[type="text"]:focus, select:focus {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.text-success {
|
||||
color: #00ff41;
|
||||
}
|
||||
|
||||
.text-warning {
|
||||
color: #ff9900;
|
||||
}
|
||||
|
||||
.text-error {
|
||||
color: #ff6b6b;
|
||||
}
|
||||
|
||||
.glow {
|
||||
text-shadow: 0 0 5px rgba(0, 255, 65, 0.5);
|
||||
}
|
||||
|
||||
.glow-text {
|
||||
text-shadow: 0 0 10px currentColor;
|
||||
}
|
||||
|
||||
.amber {
|
||||
color: #ff9900;
|
||||
}
|
||||
|
||||
.glass-effect {
|
||||
background: rgba(42, 42, 42, 0.7);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.hover-lift {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.hover-lift:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* Graph specific styles */
|
||||
.vis-network {
|
||||
background-color: #1a1a1a !important;
|
||||
|
||||
@@ -32,6 +32,7 @@ class DNSReconApp {
|
||||
this.initializeGraph();
|
||||
this.updateStatus();
|
||||
this.loadProviders();
|
||||
this.initializeEnhancedModals();
|
||||
|
||||
console.log('DNSRecon application initialized successfully');
|
||||
} catch (error) {
|
||||
@@ -840,97 +841,241 @@ class DNSReconApp {
|
||||
let html = '';
|
||||
|
||||
// Relationships sections
|
||||
if (node.incoming_edges && node.incoming_edges.length > 0) {
|
||||
html += `
|
||||
<div class="modal-section">
|
||||
<details>
|
||||
<summary>⬅️ Incoming Relationships (${node.incoming_edges.length})</summary>
|
||||
<div class="modal-section-content">
|
||||
<div class="relationship-compact">
|
||||
`;
|
||||
|
||||
node.incoming_edges.forEach(edge => {
|
||||
const confidence = edge.data.confidence_score || 0;
|
||||
const confidenceClass = confidence >= 0.8 ? 'high' : confidence >= 0.6 ? 'medium' : 'low';
|
||||
|
||||
html += `
|
||||
<div class="relationship-compact-item">
|
||||
<span class="node-link-compact" data-node-id="${edge.from}">${edge.from}</span>
|
||||
<div>
|
||||
<span class="relation-label">${edge.data.relationship_type}</span>
|
||||
<span class="confidence-compact ${confidenceClass}">${Math.round(confidence * 100)}%</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
html += '</div></div></details></div>';
|
||||
}
|
||||
html += this.generateRelationshipsSection(node);
|
||||
|
||||
if (node.outgoing_edges && node.outgoing_edges.length > 0) {
|
||||
html += `
|
||||
<div class="modal-section">
|
||||
<details>
|
||||
<summary>➡️ Outgoing Relationships (${node.outgoing_edges.length})</summary>
|
||||
<div class="modal-section-content">
|
||||
<div class="relationship-compact">
|
||||
`;
|
||||
|
||||
node.outgoing_edges.forEach(edge => {
|
||||
const confidence = edge.data.confidence_score || 0;
|
||||
const confidenceClass = confidence >= 0.8 ? 'high' : confidence >= 0.6 ? 'medium' : 'low';
|
||||
|
||||
html += `
|
||||
<div class="relationship-compact-item">
|
||||
<span class="node-link-compact" data-node-id="${edge.to}">${edge.to}</span>
|
||||
<div>
|
||||
<span class="relation-label">${edge.data.relationship_type}</span>
|
||||
<span class="confidence-compact ${confidenceClass}">${Math.round(confidence * 100)}%</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
html += '</div></div></details></div>';
|
||||
}
|
||||
|
||||
// Attributes section
|
||||
// Enhanced attributes section with special certificate handling
|
||||
if (node.attributes && Object.keys(node.attributes).length > 0) {
|
||||
html += this.generateAttributesSection(node.attributes);
|
||||
const { certificates, ...otherAttributes } = node.attributes;
|
||||
|
||||
// Handle certificates separately with enhanced display
|
||||
if (certificates) {
|
||||
html += this.generateCertificateSection({ certificates });
|
||||
}
|
||||
|
||||
// Handle other attributes normally
|
||||
if (Object.keys(otherAttributes).length > 0) {
|
||||
html += this.generateAttributesSection(otherAttributes);
|
||||
}
|
||||
}
|
||||
|
||||
// Description section
|
||||
if (node.description) {
|
||||
html += this.generateDescriptionSection(node);
|
||||
|
||||
// Metadata section (collapsed by default)
|
||||
html += this.generateMetadataSection(node);
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhanced certificate section generation using existing styles
|
||||
*/
|
||||
generateCertificateSection(attributes) {
|
||||
const certificates = attributes.certificates;
|
||||
if (!certificates || typeof certificates !== 'object') {
|
||||
return '';
|
||||
}
|
||||
|
||||
let html = `
|
||||
<div class="modal-section">
|
||||
<details>
|
||||
<summary>🔒 SSL/TLS Certificates</summary>
|
||||
<div class="modal-section-content">
|
||||
`;
|
||||
|
||||
// Certificate summary using existing grid pattern
|
||||
html += this.generateCertificateSummary(certificates);
|
||||
|
||||
// Latest certificate info using existing attribute display
|
||||
if (certificates.latest_certificate) {
|
||||
html += this.generateLatestCertificateInfo(certificates.latest_certificate);
|
||||
}
|
||||
|
||||
// Detailed certificate list if available
|
||||
if (certificates.certificate_details && Array.isArray(certificates.certificate_details)) {
|
||||
html += this.generateCertificateList(certificates.certificate_details);
|
||||
}
|
||||
|
||||
html += '</div></details></div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate latest certificate info using existing attribute list
|
||||
*/
|
||||
generateLatestCertificateInfo(latest) {
|
||||
const isValid = latest.is_currently_valid;
|
||||
const statusText = isValid ? 'Valid' : 'Invalid/Expired';
|
||||
const statusColor = isValid ? '#00ff41' : '#ff6b6b';
|
||||
|
||||
let html = `
|
||||
<div style="margin-bottom: 1rem; padding: 0.75rem; background: rgba(255, 255, 255, 0.02); border-radius: 4px; border: 1px solid #333;">
|
||||
<h5 style="margin: 0 0 0.5rem 0; color: #00ff41; font-size: 0.9rem;">Most Recent Certificate</h5>
|
||||
<div class="attribute-list">
|
||||
<div class="attribute-item-compact">
|
||||
<span class="attribute-key-compact">Status:</span>
|
||||
<span class="attribute-value-compact" style="color: ${statusColor}; font-weight: 600;">${statusText}</span>
|
||||
</div>
|
||||
<div class="attribute-item-compact">
|
||||
<span class="attribute-key-compact">Issued:</span>
|
||||
<span class="attribute-value-compact">${latest.not_before || 'Unknown'}</span>
|
||||
</div>
|
||||
<div class="attribute-item-compact">
|
||||
<span class="attribute-key-compact">Expires:</span>
|
||||
<span class="attribute-value-compact">${latest.not_after || 'Unknown'}</span>
|
||||
</div>
|
||||
<div class="attribute-item-compact">
|
||||
<span class="attribute-key-compact">Issuer:</span>
|
||||
<span class="attribute-value-compact">${this.escapeHtml(latest.issuer_name || 'Unknown')}</span>
|
||||
</div>
|
||||
${latest.certificate_id ? `
|
||||
<div class="attribute-item-compact">
|
||||
<span class="attribute-key-compact">Certificate:</span>
|
||||
<span class="attribute-value-compact">
|
||||
<a href="https://crt.sh/?id=${latest.certificate_id}" target="_blank" class="cert-link">
|
||||
View on crt.sh ↗
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate certificate list using existing collapsible structure
|
||||
*/
|
||||
generateCertificateList(certificateDetails) {
|
||||
if (!certificateDetails || certificateDetails.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Limit display to prevent overwhelming the UI
|
||||
const maxDisplay = 8;
|
||||
const certificates = certificateDetails.slice(0, maxDisplay);
|
||||
const remaining = certificateDetails.length - maxDisplay;
|
||||
|
||||
let html = `
|
||||
<details style="margin-top: 1rem;">
|
||||
<summary>📋 Certificate Details (${certificates.length}${remaining > 0 ? ` of ${certificateDetails.length}` : ''})</summary>
|
||||
<div style="margin-top: 0.75rem;">
|
||||
`;
|
||||
|
||||
certificates.forEach((cert, index) => {
|
||||
const isValid = cert.is_currently_valid;
|
||||
let statusText = isValid ? '✅ Valid' : '❌ Invalid/Expired';
|
||||
let statusColor = isValid ? '#00ff41' : '#ff6b6b';
|
||||
|
||||
if (cert.expires_soon && isValid) {
|
||||
statusText = '⚠️ Valid (Expiring Soon)';
|
||||
statusColor = '#ff9900';
|
||||
}
|
||||
|
||||
html += `
|
||||
<div class="modal-section">
|
||||
<details>
|
||||
<summary>📝 Description</summary>
|
||||
<div class="modal-section-content">
|
||||
<p>${this.escapeHtml(node.description)}</p>
|
||||
<div style="margin-bottom: 0.75rem; padding: 0.75rem; background: rgba(255, 255, 255, 0.02); border: 1px solid #333; border-radius: 4px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem; border-bottom: 1px solid #333; padding-bottom: 0.5rem;">
|
||||
<span style="font-weight: 600; color: #999;">#${index + 1}</span>
|
||||
<span style="color: ${statusColor}; font-size: 0.85rem; font-weight: 500;">${statusText}</span>
|
||||
${cert.certificate_id ? `
|
||||
<a href="https://crt.sh/?id=${cert.certificate_id}" target="_blank" class="cert-link">crt.sh ↗</a>
|
||||
` : ''}
|
||||
</div>
|
||||
<div class="attribute-list">
|
||||
<div class="attribute-item-compact">
|
||||
<span class="attribute-key-compact">Common Name:</span>
|
||||
<span class="attribute-value-compact">${this.escapeHtml(cert.common_name || 'N/A')}</span>
|
||||
</div>
|
||||
</details>
|
||||
<div class="attribute-item-compact">
|
||||
<span class="attribute-key-compact">Issuer:</span>
|
||||
<span class="attribute-value-compact">${this.escapeHtml(cert.issuer_name || 'Unknown')}</span>
|
||||
</div>
|
||||
<div class="attribute-item-compact">
|
||||
<span class="attribute-key-compact">Valid From:</span>
|
||||
<span class="attribute-value-compact">${cert.not_before || 'Unknown'}</span>
|
||||
</div>
|
||||
<div class="attribute-item-compact">
|
||||
<span class="attribute-key-compact">Valid Until:</span>
|
||||
<span class="attribute-value-compact">${cert.not_after || 'Unknown'}</span>
|
||||
</div>
|
||||
${cert.validity_period_days ? `
|
||||
<div class="attribute-item-compact">
|
||||
<span class="attribute-key-compact">Period:</span>
|
||||
<span class="attribute-value-compact">${cert.validity_period_days} days</span>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
if (remaining > 0) {
|
||||
html += `
|
||||
<div style="text-align: center; padding: 1rem; color: #ff9900; background: rgba(255, 153, 0, 0.1); border: 1px solid #ff9900; border-radius: 4px;">
|
||||
📋 ${remaining} additional certificate${remaining > 1 ? 's' : ''} not shown.<br>
|
||||
<small style="color: #999;">Use the export function to see all certificates.</small>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Metadata section
|
||||
if (node.metadata && Object.keys(node.metadata).length > 0) {
|
||||
html += `
|
||||
<div class="modal-section">
|
||||
<details>
|
||||
<summary>🔧 Technical Metadata</summary>
|
||||
<div class="modal-section-content">
|
||||
${this.formatObjectToHtml(node.metadata)}
|
||||
</div>
|
||||
</details>
|
||||
html += '</div></details>';
|
||||
return html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate certificate summary using minimal new CSS
|
||||
*/
|
||||
generateCertificateSummary(certificates) {
|
||||
const total = certificates.total_certificates || 0;
|
||||
const valid = certificates.valid_certificates || 0;
|
||||
const expired = certificates.expired_certificates || 0;
|
||||
const expiringSoon = certificates.expires_soon_count || 0;
|
||||
const issuers = certificates.unique_issuers || [];
|
||||
|
||||
let html = `
|
||||
<div class="cert-summary-grid">
|
||||
<div class="cert-stat-item">
|
||||
<div class="cert-stat-value">${total}</div>
|
||||
<div class="cert-stat-label">Total</div>
|
||||
</div>
|
||||
<div class="cert-stat-item">
|
||||
<div class="cert-stat-value" style="color: #00ff41">${valid}</div>
|
||||
<div class="cert-stat-label">Valid</div>
|
||||
</div>
|
||||
<div class="cert-stat-item">
|
||||
<div class="cert-stat-value" style="color: #ff6b6b">${expired}</div>
|
||||
<div class="cert-stat-label">Expired</div>
|
||||
</div>
|
||||
<div class="cert-stat-item">
|
||||
<div class="cert-stat-value" style="color: #ff9900">${expiringSoon}</div>
|
||||
<div class="cert-stat-label">Expiring Soon</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Certificate authorities using existing array display
|
||||
if (issuers.length > 0) {
|
||||
html += `
|
||||
<div class="attribute-item-compact" style="margin-bottom: 1rem;">
|
||||
<span class="attribute-key-compact">Certificate Authorities:</span>
|
||||
<span class="attribute-value-compact">
|
||||
<div class="array-display">
|
||||
`;
|
||||
|
||||
issuers.forEach(issuer => {
|
||||
html += `<div class="array-display-item">${this.escapeHtml(issuer)}</div>`;
|
||||
});
|
||||
|
||||
html += '</div></span></div>';
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
|
||||
|
||||
generateLargeEntityDetails(node) {
|
||||
const attributes = node.attributes || {};
|
||||
const nodes = attributes.nodes || [];
|
||||
@@ -982,11 +1127,12 @@ class DNSReconApp {
|
||||
generateCorrelationDetails(node) {
|
||||
const metadata = node.metadata || {};
|
||||
const values = metadata.values || [];
|
||||
const sources = metadata.sources || [];
|
||||
const mergeCount = metadata.merge_count || 1;
|
||||
|
||||
let html = '';
|
||||
|
||||
// Correlation values section
|
||||
// Correlation values section with meaningful labels - reuses existing modal structure
|
||||
html += `
|
||||
<div class="modal-section">
|
||||
<details open>
|
||||
@@ -995,21 +1141,33 @@ class DNSReconApp {
|
||||
<span class="merge-badge">${mergeCount} value${mergeCount > 1 ? 's' : ''}</span>
|
||||
</summary>
|
||||
<div class="modal-section-content">
|
||||
<div class="array-display">
|
||||
<div class="attribute-list">
|
||||
`;
|
||||
|
||||
// Create a map of values to their source attributes for better labeling
|
||||
const valueSourceMap = this.createValueSourceMap(values, sources);
|
||||
|
||||
values.forEach((value, index) => {
|
||||
const sourceInfo = valueSourceMap[index] || {};
|
||||
const attributeName = sourceInfo.meaningfulName || `Value ${index + 1}`;
|
||||
const sourceDetails = sourceInfo.details || '';
|
||||
|
||||
html += `
|
||||
<div class="array-display-item">
|
||||
<strong>Value ${index + 1}:</strong><br>
|
||||
<code>${this.escapeHtml(String(value))}</code>
|
||||
<div class="attribute-item-compact">
|
||||
<span class="attribute-key-compact">
|
||||
<span class="correlation-attr-name">${this.escapeHtml(attributeName)}</span>
|
||||
${sourceDetails ? `<span class="correlation-hint" title="${this.escapeHtml(sourceDetails)}"> ℹ️</span>` : ''}
|
||||
</span>
|
||||
<span class="attribute-value-compact">
|
||||
<code>${this.escapeHtml(String(value))}</code>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
html += '</div></div></details></div>';
|
||||
|
||||
// Correlated nodes section
|
||||
// Correlated nodes section - reuses existing relationship display
|
||||
const correlatedNodes = metadata.correlated_nodes || [];
|
||||
if (correlatedNodes.length > 0) {
|
||||
html += `
|
||||
@@ -1034,6 +1192,44 @@ class DNSReconApp {
|
||||
return html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a mapping of values to their source attribute information
|
||||
*/
|
||||
createValueSourceMap(values, sources) {
|
||||
const valueSourceMap = {};
|
||||
|
||||
// Group sources by their meaningful attributes
|
||||
const attrGroups = {};
|
||||
sources.forEach(source => {
|
||||
const meaningfulAttr = source.meaningful_attr || source.parent_attr || 'correlation';
|
||||
|
||||
if (!attrGroups[meaningfulAttr]) {
|
||||
attrGroups[meaningfulAttr] = {
|
||||
nodeIds: [],
|
||||
paths: []
|
||||
};
|
||||
}
|
||||
attrGroups[meaningfulAttr].nodeIds.push(source.node_id);
|
||||
attrGroups[meaningfulAttr].paths.push(source.path || '');
|
||||
});
|
||||
|
||||
// Map values to their best attribute names
|
||||
values.forEach((value, index) => {
|
||||
// Find the most meaningful attribute name
|
||||
const attrNames = Object.keys(attrGroups);
|
||||
const bestAttr = attrNames.find(attr => attr !== 'correlation' && attr !== 'unknown') || attrNames[0] || 'correlation';
|
||||
|
||||
if (attrGroups[bestAttr]) {
|
||||
valueSourceMap[index] = {
|
||||
meaningfulName: bestAttr,
|
||||
details: `Found in: ${[...new Set(attrGroups[bestAttr].nodeIds)].join(', ')}`
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return valueSourceMap;
|
||||
}
|
||||
|
||||
generateCorrelationObjectLayout(node) {
|
||||
const metadata = node.metadata || {};
|
||||
const values = metadata.values || [];
|
||||
|
||||
Reference in New Issue
Block a user