diff --git a/core/graph_manager.py b/core/graph_manager.py
index 03d769d..8d4e743 100644
--- a/core/graph_manager.py
+++ b/core/graph_manager.py
@@ -282,6 +282,12 @@ class GraphManager:
attributes = node_data['attributes']
if node_type == 'domain' and attributes.get('certificates', {}).get('has_valid_cert') is False:
node_data['color'] = {'background': '#c7c7c7', 'border': '#999'} # Gray for invalid cert
+
+ # Add incoming and outgoing edges to node data
+ if self.graph.has_node(node_id):
+ node_data['incoming_edges'] = [{'from': u, 'data': d} for u, _, d in self.graph.in_edges(node_id, data=True)]
+ node_data['outgoing_edges'] = [{'to': v, 'data': d} for _, v, d in self.graph.out_edges(node_id, data=True)]
+
nodes.append(node_data)
edges = []
diff --git a/static/js/graph.js b/static/js/graph.js
index b522a9d..99867f3 100644
--- a/static/js/graph.js
+++ b/static/js/graph.js
@@ -196,8 +196,11 @@ class GraphManager {
if (this.network.isCluster(nodeId)) {
this.network.openCluster(nodeId);
} else {
- this.showNodeDetails(nodeId);
- this.highlightNodeConnections(nodeId);
+ const node = this.nodes.get(nodeId);
+ if (node) {
+ this.showNodeDetails(node);
+ this.highlightNodeConnections(nodeId);
+ }
}
} else {
this.clearHighlights();
@@ -346,7 +349,9 @@ class GraphManager {
attributes: node.attributes || {},
description: node.description || '',
metadata: node.metadata || {},
- type: node.type
+ type: node.type,
+ incoming_edges: node.incoming_edges || [],
+ outgoing_edges: node.outgoing_edges || []
};
// Add confidence-based styling
@@ -556,12 +561,9 @@ class GraphManager {
/**
* Show node details in modal
- * @param {string} nodeId - Node identifier
+ * @param {Object} node - Node object
*/
- showNodeDetails(nodeId) {
- const node = this.nodes.get(nodeId);
- if (!node) return;
-
+ showNodeDetails(node) {
// Trigger custom event for main application to handle
const event = new CustomEvent('nodeSelected', {
detail: { node }
diff --git a/static/js/main.js b/static/js/main.js
index a498df4..27f0095 100644
--- a/static/js/main.js
+++ b/static/js/main.js
@@ -635,7 +635,7 @@ class DNSReconApp {
break;
}
}
-
+
/**
* Update connection status indicator
* @param {string} status - Connection status
@@ -793,39 +793,61 @@ class DNSReconApp {
}
/**
- * **FIX**: Generates the HTML for the node details view using the new data model.
+ * Generates the HTML for the node details view using the new data model.
* @param {Object} node - The node object.
* @returns {string} The HTML string for the node details.
*/
generateNodeDetailsHtml(node) {
- if(!node) return '
Details not available.
';
-
+ if (!node) return 'Details not available.
';
+
let detailsHtml = '';
+ // Section for Incoming Edges (Source Nodes)
+ if (node.incoming_edges && node.incoming_edges.length > 0) {
+ detailsHtml += '
';
+ detailsHtml += '
Source Nodes (Incoming)
';
+ detailsHtml += '
';
+ node.incoming_edges.forEach(edge => {
+ detailsHtml += `- ${edge.from} (${edge.data.relationship_type})
`;
+ });
+ detailsHtml += '
';
+ }
+
+ // Section for Outgoing Edges (Destination Nodes)
+ if (node.outgoing_edges && node.outgoing_edges.length > 0) {
+ detailsHtml += '
';
+ detailsHtml += '
Destination Nodes (Outgoing)
';
+ detailsHtml += '
';
+ node.outgoing_edges.forEach(edge => {
+ detailsHtml += `- ${edge.to} (${edge.data.relationship_type})
`;
+ });
+ detailsHtml += '
';
+ }
+
// Section for Attributes
detailsHtml += '
';
detailsHtml += '
Attributes
';
detailsHtml += this.formatObjectToHtml(node.attributes);
detailsHtml += '';
-
+
// Section for Description
detailsHtml += '
';
detailsHtml += '
Description
';
detailsHtml += `
${node.description || 'No description available.'}
`;
detailsHtml += '
';
-
+
// Section for Metadata
detailsHtml += '
';
detailsHtml += '
Metadata
';
detailsHtml += this.formatObjectToHtml(node.metadata);
detailsHtml += '';
-
+
detailsHtml += '
';
return detailsHtml;
}
/**
- * Recursively formats a JavaScript object into an HTML unordered list.
+ * Recursively formats a JavaScript object into an HTML unordered list with collapsible sections.
* @param {Object} obj - The object to format.
* @returns {string} - An HTML string representing the object.
*/
@@ -833,22 +855,20 @@ class DNSReconApp {
if (!obj || Object.keys(obj).length === 0) {
return 'No data available.
';
}
-
+
let html = '';
for (const key in obj) {
if (Object.hasOwnProperty.call(obj, key)) {
const value = obj[key];
const formattedKey = key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
- html += `- ${formattedKey}:`;
-
- if (Array.isArray(value)) {
- html += `
${value.map(item => `- ${this.formatValue(item)}
`).join('')}
`;
- } else if (typeof value === 'object' && value !== null) {
+
+ if (typeof value === 'object' && value !== null) {
+ html += ` ${formattedKey}
`;
html += this.formatObjectToHtml(value);
+ html += ` `;
} else {
- html += ` ${this.formatValue(value)}`;
+ html += `- ${formattedKey}: ${this.formatValue(value)}
`;
}
- html += '';
}
}
html += '
';
@@ -889,6 +909,17 @@ class DNSReconApp {
if (this.elements.modalDetails) {
this.elements.modalDetails.innerHTML = detailsHtml;
+ this.elements.modalDetails.querySelectorAll('.node-link').forEach(link => {
+ link.addEventListener('click', (e) => {
+ e.preventDefault();
+ const nodeId = e.target.dataset.nodeId;
+ const nextNode = this.graphManager.nodes.get(nodeId);
+ if (nextNode) {
+ this.hideModal();
+ this.showNodeModal(nextNode);
+ }
+ });
+ });
}
this.elements.nodeModal.style.display = 'block';
}