graph
This commit is contained in:
525
static/script.js
525
static/script.js
@@ -1,4 +1,4 @@
|
||||
// DNS Reconnaissance Tool - Enhanced Frontend JavaScript with Debug Output
|
||||
// DNS Reconnaissance Tool - Enhanced Frontend JavaScript with Forensic Data Support
|
||||
|
||||
class ReconTool {
|
||||
constructor() {
|
||||
@@ -6,7 +6,8 @@ class ReconTool {
|
||||
this.pollInterval = null;
|
||||
this.liveDataInterval = null;
|
||||
this.currentReport = null;
|
||||
this.debugMode = true; // Enable debug logging
|
||||
this.debugMode = true;
|
||||
this.graphVisualization = null; // Add this line
|
||||
this.init();
|
||||
}
|
||||
|
||||
@@ -23,6 +24,13 @@ class ReconTool {
|
||||
init() {
|
||||
this.bindEvents();
|
||||
this.setupRealtimeElements();
|
||||
|
||||
// Handle window resize for graph
|
||||
window.addEventListener('resize', () => {
|
||||
if (this.graphVisualization) {
|
||||
this.graphVisualization.handleResize();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setupRealtimeElements() {
|
||||
@@ -43,6 +51,14 @@ class ReconTool {
|
||||
<span class="stat-label">IP Addresses:</span>
|
||||
<span id="liveIPs" class="stat-value">0</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Discovery Edges:</span>
|
||||
<span id="liveEdges" class="stat-value">0</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">Operations:</span>
|
||||
<span id="liveOperations" class="stat-value">0</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">DNS Records:</span>
|
||||
<span id="liveDNS" class="stat-value">0</span>
|
||||
@@ -117,6 +133,27 @@ class ReconTool {
|
||||
this.startScan();
|
||||
}
|
||||
});
|
||||
document.getElementById('showGraphView').addEventListener('click', () => {
|
||||
this.showGraphView();
|
||||
});
|
||||
}
|
||||
|
||||
showGraphView() {
|
||||
if (this.graphVisualization && this.currentScanId) {
|
||||
document.getElementById('graphSection').style.display = 'block';
|
||||
|
||||
// Update button states
|
||||
document.getElementById('showGraphView').classList.add('active');
|
||||
document.getElementById('showJson').classList.remove('active');
|
||||
document.getElementById('showText').classList.remove('active');
|
||||
|
||||
// Scroll to graph section
|
||||
document.getElementById('graphSection').scrollIntoView({
|
||||
behavior: 'smooth'
|
||||
});
|
||||
} else {
|
||||
alert('Graph data not available yet. Please wait for scan completion.');
|
||||
}
|
||||
}
|
||||
|
||||
async startScan() {
|
||||
@@ -137,7 +174,7 @@ class ReconTool {
|
||||
try {
|
||||
// Show progress section
|
||||
this.showProgressSection();
|
||||
this.updateProgress(0, 'Starting scan...');
|
||||
this.updateProgress(0, 'Starting forensic scan...');
|
||||
|
||||
this.debug('Starting scan with data:', scanData);
|
||||
|
||||
@@ -223,7 +260,7 @@ class ReconTool {
|
||||
// Update progress
|
||||
this.updateProgress(status.progress, status.message);
|
||||
|
||||
// Update live stats
|
||||
// Update live stats (handle new forensic format)
|
||||
if (status.live_stats) {
|
||||
this.debug('Received live stats:', status.live_stats);
|
||||
this.updateLiveStats(status.live_stats);
|
||||
@@ -270,7 +307,7 @@ class ReconTool {
|
||||
|
||||
this.debug('Received live data:', data);
|
||||
|
||||
// Update live discoveries
|
||||
// Update live discoveries (handle new forensic format)
|
||||
this.updateLiveDiscoveries(data);
|
||||
|
||||
} catch (error) {
|
||||
@@ -282,17 +319,19 @@ class ReconTool {
|
||||
updateLiveStats(stats) {
|
||||
this.debug('Updating live stats:', stats);
|
||||
|
||||
// Update the live statistics counters
|
||||
const statElements = {
|
||||
'liveHostnames': stats.hostnames || 0,
|
||||
'liveIPs': stats.ip_addresses || 0,
|
||||
// Handle both old and new stat formats for compatibility
|
||||
const statMappings = {
|
||||
'liveHostnames': stats.hostnames || stats.hostnames || 0,
|
||||
'liveIPs': stats.ip_addresses || stats.ips || 0,
|
||||
'liveEdges': stats.discovery_edges || 0, // New forensic field
|
||||
'liveOperations': stats.operations_performed || 0, // New forensic field
|
||||
'liveDNS': stats.dns_records || 0,
|
||||
'liveCerts': stats.certificates || 0,
|
||||
'liveCerts': stats.certificates_total || stats.certificates || 0,
|
||||
'liveShodan': stats.shodan_results || 0,
|
||||
'liveVT': stats.virustotal_results || 0
|
||||
};
|
||||
|
||||
Object.entries(statElements).forEach(([elementId, value]) => {
|
||||
Object.entries(statMappings).forEach(([elementId, value]) => {
|
||||
const element = document.getElementById(elementId);
|
||||
if (element) {
|
||||
const currentValue = element.textContent;
|
||||
@@ -315,43 +354,92 @@ class ReconTool {
|
||||
updateLiveDiscoveries(data) {
|
||||
this.debug('Updating live discoveries with data:', data);
|
||||
|
||||
// Handle new forensic data format
|
||||
let hostnames = [];
|
||||
let ipAddresses = [];
|
||||
let activities = [];
|
||||
|
||||
// Extract data from forensic format or fallback to old format
|
||||
if (data.hostnames && Array.isArray(data.hostnames)) {
|
||||
hostnames = data.hostnames;
|
||||
} else if (data.stats && data.stats.hostnames) {
|
||||
// If we only have stats, create a placeholder list
|
||||
hostnames = [`${data.stats.hostnames} discovered`];
|
||||
}
|
||||
|
||||
if (data.ip_addresses && Array.isArray(data.ip_addresses)) {
|
||||
ipAddresses = data.ip_addresses;
|
||||
} else if (data.stats && data.stats.ip_addresses) {
|
||||
// If we only have stats, create a placeholder list
|
||||
ipAddresses = [`${data.stats.ip_addresses} discovered`];
|
||||
}
|
||||
|
||||
// Handle activity log from forensic format
|
||||
if (data.latest_discoveries && Array.isArray(data.latest_discoveries)) {
|
||||
activities = data.latest_discoveries;
|
||||
}
|
||||
|
||||
// Update hostnames list
|
||||
const hostnameList = document.querySelector('#recentHostnames .hostname-list');
|
||||
if (hostnameList && data.hostnames && data.hostnames.length > 0) {
|
||||
if (hostnameList && hostnames.length > 0) {
|
||||
// Show last 10 hostnames
|
||||
const recentHostnames = data.hostnames;
|
||||
const recentHostnames = hostnames.slice(-10);
|
||||
hostnameList.innerHTML = recentHostnames.map(hostname =>
|
||||
`<span class="discovery-item">${hostname}</span>`
|
||||
).join('');
|
||||
this.debug(`Updated hostname list with ${recentHostnames.length} items`);
|
||||
} else if (hostnameList) {
|
||||
this.debug(`No hostnames to display (${data.hostnames ? data.hostnames.length : 0} total)`);
|
||||
this.debug(`No hostnames to display (${hostnames.length} total)`);
|
||||
}
|
||||
|
||||
// Update IP addresses list
|
||||
const ipList = document.querySelector('#recentIPs .ip-list');
|
||||
if (ipList && data.ip_addresses && data.ip_addresses.length > 0) {
|
||||
if (ipList && ipAddresses.length > 0) {
|
||||
// Show last 10 IPs
|
||||
const recentIPs = data.ip_addresses;
|
||||
const recentIPs = ipAddresses.slice(-10);
|
||||
ipList.innerHTML = recentIPs.map(ip =>
|
||||
`<span class="discovery-item">${ip}</span>`
|
||||
).join('');
|
||||
this.debug(`Updated IP list with ${recentIPs.length} items`);
|
||||
} else if (ipList) {
|
||||
this.debug(`No IPs to display (${data.ip_addresses ? data.ip_addresses.length : 0} total)`);
|
||||
this.debug(`No IPs to display (${ipAddresses.length} total)`);
|
||||
}
|
||||
|
||||
// Update activity log
|
||||
const activityList = document.querySelector('#activityLog .activity-list');
|
||||
if (activityList && data.latest_discoveries && data.latest_discoveries.length > 0) {
|
||||
const activities = data.latest_discoveries.slice(-5); // Last 5 activities
|
||||
activityList.innerHTML = activities.map(activity => {
|
||||
const time = new Date(activity.timestamp * 1000).toLocaleTimeString();
|
||||
return `<div class="activity-item">[${time}] ${activity.message}</div>`;
|
||||
}).join('');
|
||||
this.debug(`Updated activity log with ${activities.length} items`);
|
||||
} else if (activityList) {
|
||||
this.debug(`No activities to display (${data.latest_discoveries ? data.latest_discoveries.length : 0} total)`);
|
||||
if (activityList) {
|
||||
if (activities.length > 0) {
|
||||
const recentActivities = activities.slice(-5); // Last 5 activities
|
||||
activityList.innerHTML = recentActivities.map(activity => {
|
||||
const time = new Date(activity.timestamp * 1000).toLocaleTimeString();
|
||||
return `<div class="activity-item">[${time}] ${activity.message}</div>`;
|
||||
}).join('');
|
||||
this.debug(`Updated activity log with ${recentActivities.length} items`);
|
||||
} else {
|
||||
// Fallback: show generic activity based on stats
|
||||
const stats = data.stats || {};
|
||||
const genericActivities = [];
|
||||
|
||||
if (stats.operations_performed > 0) {
|
||||
genericActivities.push(`${stats.operations_performed} operations performed`);
|
||||
}
|
||||
if (stats.hostnames > 0) {
|
||||
genericActivities.push(`${stats.hostnames} hostnames discovered`);
|
||||
}
|
||||
if (stats.dns_records > 0) {
|
||||
genericActivities.push(`${stats.dns_records} DNS records collected`);
|
||||
}
|
||||
|
||||
if (genericActivities.length > 0) {
|
||||
const now = new Date().toLocaleTimeString();
|
||||
activityList.innerHTML = genericActivities.map(activity =>
|
||||
`<div class="activity-item">[${now}] ${activity}</div>`
|
||||
).join('');
|
||||
this.debug(`Updated activity log with ${genericActivities.length} generic items`);
|
||||
} else {
|
||||
this.debug('No activities to display');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,9 +462,15 @@ class ReconTool {
|
||||
this.debug('Report loaded successfully');
|
||||
this.showResultsSection();
|
||||
this.showReport('text'); // Default to text view
|
||||
|
||||
// Load and show graph
|
||||
if (!this.graphVisualization) {
|
||||
this.graphVisualization = new GraphVisualization();
|
||||
}
|
||||
await this.graphVisualization.loadAndShowGraph(this.currentScanId);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error loading report:', error);
|
||||
console.error('⚠️ Error loading report:', error);
|
||||
this.showError(`Error loading report: ${error.message}`);
|
||||
}
|
||||
}
|
||||
@@ -410,7 +504,7 @@ class ReconTool {
|
||||
if (liveSection) {
|
||||
const title = liveSection.querySelector('h3');
|
||||
if (title) {
|
||||
title.textContent = '📊 Final Discovery Summary';
|
||||
title.textContent = '📊 Final Forensic Summary';
|
||||
}
|
||||
liveSection.style.display = 'block';
|
||||
}
|
||||
@@ -424,7 +518,7 @@ class ReconTool {
|
||||
if (progressMessage) progressMessage.style.display = 'none';
|
||||
if (scanControls) scanControls.style.display = 'none';
|
||||
|
||||
this.debug('Showing results section with live discoveries');
|
||||
this.debug('Showing results section with forensic discoveries');
|
||||
}
|
||||
|
||||
resetToForm() {
|
||||
@@ -527,11 +621,11 @@ class ReconTool {
|
||||
content = typeof this.currentReport.json_report === 'string'
|
||||
? this.currentReport.json_report
|
||||
: JSON.stringify(this.currentReport.json_report, null, 2);
|
||||
filename = `recon-report-${this.currentScanId}.json`;
|
||||
filename = `forensic-recon-report-${this.currentScanId}.json`;
|
||||
mimeType = 'application/json';
|
||||
} else {
|
||||
content = this.currentReport.text_report;
|
||||
filename = `recon-report-${this.currentScanId}.txt`;
|
||||
filename = `forensic-recon-report-${this.currentScanId}.txt`;
|
||||
mimeType = 'text/plain';
|
||||
}
|
||||
|
||||
@@ -548,8 +642,375 @@ class ReconTool {
|
||||
}
|
||||
}
|
||||
|
||||
class GraphVisualization {
|
||||
constructor() {
|
||||
this.svg = null;
|
||||
this.simulation = null;
|
||||
this.graphData = null;
|
||||
this.showLabels = false;
|
||||
this.selectedNode = null;
|
||||
this.zoom = null;
|
||||
this.container = null;
|
||||
}
|
||||
|
||||
async loadAndShowGraph(scanId) {
|
||||
try {
|
||||
console.log('🕸️ Loading graph data...');
|
||||
const response = await fetch(`/api/scan/${scanId}/graph`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const graphData = await response.json();
|
||||
|
||||
if (graphData.error) {
|
||||
throw new Error(graphData.error);
|
||||
}
|
||||
|
||||
this.graphData = graphData;
|
||||
this.showGraphSection();
|
||||
this.initializeGraph();
|
||||
this.updateGraphStats(graphData.stats);
|
||||
|
||||
console.log('✅ Graph loaded successfully', graphData.stats);
|
||||
|
||||
} catch (error) {
|
||||
console.error('⚠️ Error loading graph:', error);
|
||||
alert(`Failed to load graph: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
showGraphSection() {
|
||||
document.getElementById('graphSection').style.display = 'block';
|
||||
this.bindGraphEvents();
|
||||
}
|
||||
|
||||
bindGraphEvents() {
|
||||
document.getElementById('showGraph').addEventListener('click', () => {
|
||||
this.showGraph();
|
||||
});
|
||||
|
||||
document.getElementById('hideGraph').addEventListener('click', () => {
|
||||
this.hideGraph();
|
||||
});
|
||||
|
||||
document.getElementById('resetZoom').addEventListener('click', () => {
|
||||
this.resetZoom();
|
||||
});
|
||||
|
||||
document.getElementById('toggleLabels').addEventListener('click', () => {
|
||||
this.toggleLabels();
|
||||
});
|
||||
}
|
||||
|
||||
initializeGraph() {
|
||||
if (!this.graphData) return;
|
||||
|
||||
// Clear existing graph
|
||||
d3.select('#discoveryGraph').selectAll('*').remove();
|
||||
|
||||
// Set up SVG
|
||||
const container = d3.select('#discoveryGraph');
|
||||
const containerNode = container.node();
|
||||
const width = containerNode.clientWidth;
|
||||
const height = containerNode.clientHeight;
|
||||
|
||||
this.svg = container
|
||||
.attr('width', width)
|
||||
.attr('height', height);
|
||||
|
||||
// Set up zoom behavior
|
||||
this.zoom = d3.zoom()
|
||||
.scaleExtent([0.1, 3])
|
||||
.on('zoom', (event) => {
|
||||
this.container.attr('transform', event.transform);
|
||||
});
|
||||
|
||||
this.svg.call(this.zoom);
|
||||
|
||||
// Create container for graph elements
|
||||
this.container = this.svg.append('g');
|
||||
|
||||
// Set up force simulation
|
||||
this.simulation = d3.forceSimulation(this.graphData.nodes)
|
||||
.force('link', d3.forceLink(this.graphData.edges)
|
||||
.id(d => d.id)
|
||||
.distance(80)
|
||||
.strength(0.5))
|
||||
.force('charge', d3.forceManyBody()
|
||||
.strength(-300)
|
||||
.distanceMax(400))
|
||||
.force('center', d3.forceCenter(width / 2, height / 2))
|
||||
.force('collision', d3.forceCollide()
|
||||
.radius(d => d.size + 5));
|
||||
|
||||
this.drawGraph();
|
||||
this.startSimulation();
|
||||
}
|
||||
|
||||
drawGraph() {
|
||||
// Draw links
|
||||
const links = this.container.append('g')
|
||||
.selectAll('line')
|
||||
.data(this.graphData.edges)
|
||||
.enter().append('line')
|
||||
.attr('class', 'graph-link')
|
||||
.attr('stroke', d => d.color)
|
||||
.on('mouseover', (event, d) => {
|
||||
this.showTooltip(event, `Discovery: ${d.method}<br>From: ${d.source}<br>To: ${d.target}`);
|
||||
})
|
||||
.on('mouseout', () => {
|
||||
this.hideTooltip();
|
||||
});
|
||||
|
||||
// Draw nodes
|
||||
const nodes = this.container.append('g')
|
||||
.selectAll('circle')
|
||||
.data(this.graphData.nodes)
|
||||
.enter().append('circle')
|
||||
.attr('class', 'graph-node')
|
||||
.attr('r', d => d.size)
|
||||
.attr('fill', d => d.color)
|
||||
.style('opacity', 0.8)
|
||||
.on('mouseover', (event, d) => {
|
||||
this.showNodeTooltip(event, d);
|
||||
this.highlightConnections(d);
|
||||
})
|
||||
.on('mouseout', (event, d) => {
|
||||
this.hideTooltip();
|
||||
this.unhighlightConnections();
|
||||
})
|
||||
.on('click', (event, d) => {
|
||||
this.selectNode(d);
|
||||
})
|
||||
.call(d3.drag()
|
||||
.on('start', (event, d) => {
|
||||
if (!event.active) this.simulation.alphaTarget(0.3).restart();
|
||||
d.fx = d.x;
|
||||
d.fy = d.y;
|
||||
})
|
||||
.on('drag', (event, d) => {
|
||||
d.fx = event.x;
|
||||
d.fy = event.y;
|
||||
})
|
||||
.on('end', (event, d) => {
|
||||
if (!event.active) this.simulation.alphaTarget(0);
|
||||
d.fx = null;
|
||||
d.fy = null;
|
||||
}));
|
||||
|
||||
// Draw labels (initially hidden)
|
||||
const labels = this.container.append('g')
|
||||
.selectAll('text')
|
||||
.data(this.graphData.nodes)
|
||||
.enter().append('text')
|
||||
.attr('class', 'graph-label')
|
||||
.attr('dy', '.35em')
|
||||
.style('opacity', this.showLabels ? 1 : 0)
|
||||
.text(d => d.label);
|
||||
|
||||
// Store references
|
||||
this.links = links;
|
||||
this.nodes = nodes;
|
||||
this.labels = labels;
|
||||
}
|
||||
|
||||
startSimulation() {
|
||||
this.simulation.on('tick', () => {
|
||||
this.links
|
||||
.attr('x1', d => d.source.x)
|
||||
.attr('y1', d => d.source.y)
|
||||
.attr('x2', d => d.target.x)
|
||||
.attr('y2', d => d.target.y);
|
||||
|
||||
this.nodes
|
||||
.attr('cx', d => d.x)
|
||||
.attr('cy', d => d.y);
|
||||
|
||||
this.labels
|
||||
.attr('x', d => d.x)
|
||||
.attr('y', d => d.y + d.size + 12);
|
||||
});
|
||||
}
|
||||
|
||||
showNodeTooltip(event, node) {
|
||||
const tooltip = `
|
||||
<strong>${node.label}</strong><br>
|
||||
Depth: ${node.depth}<br>
|
||||
DNS Records: ${node.dns_records}<br>
|
||||
Certificates: ${node.certificates}<br>
|
||||
IPs: ${node.ip_addresses.length}<br>
|
||||
Discovery: ${node.discovery_methods.join(', ')}<br>
|
||||
First Seen: ${node.first_seen ? new Date(node.first_seen).toLocaleString() : 'Unknown'}
|
||||
`;
|
||||
this.showTooltip(event, tooltip);
|
||||
}
|
||||
|
||||
showTooltip(event, content) {
|
||||
const tooltip = document.getElementById('graphTooltip');
|
||||
tooltip.innerHTML = content;
|
||||
tooltip.className = 'graph-tooltip visible';
|
||||
|
||||
const rect = tooltip.getBoundingClientRect();
|
||||
const containerRect = document.getElementById('discoveryGraph').getBoundingClientRect();
|
||||
|
||||
tooltip.style.left = `${event.clientX - containerRect.left + 10}px`;
|
||||
tooltip.style.top = `${event.clientY - containerRect.top - 10}px`;
|
||||
|
||||
// Adjust position if tooltip goes off screen
|
||||
if (event.clientX + rect.width > window.innerWidth) {
|
||||
tooltip.style.left = `${event.clientX - containerRect.left - rect.width - 10}px`;
|
||||
}
|
||||
|
||||
if (event.clientY - rect.height < 0) {
|
||||
tooltip.style.top = `${event.clientY - containerRect.top + 20}px`;
|
||||
}
|
||||
}
|
||||
|
||||
hideTooltip() {
|
||||
const tooltip = document.getElementById('graphTooltip');
|
||||
tooltip.className = 'graph-tooltip';
|
||||
}
|
||||
|
||||
highlightConnections(node) {
|
||||
// Highlight connected links
|
||||
this.links
|
||||
.style('opacity', d => (d.source.id === node.id || d.target.id === node.id) ? 1 : 0.2)
|
||||
.classed('highlighted', d => d.source.id === node.id || d.target.id === node.id);
|
||||
|
||||
// Highlight connected nodes
|
||||
this.nodes
|
||||
.style('opacity', d => {
|
||||
if (d.id === node.id) return 1;
|
||||
const connected = this.graphData.edges.some(edge =>
|
||||
(edge.source.id === node.id && edge.target.id === d.id) ||
|
||||
(edge.target.id === node.id && edge.source.id === d.id)
|
||||
);
|
||||
return connected ? 0.8 : 0.3;
|
||||
});
|
||||
}
|
||||
|
||||
unhighlightConnections() {
|
||||
this.links
|
||||
.style('opacity', 0.6)
|
||||
.classed('highlighted', false);
|
||||
|
||||
this.nodes
|
||||
.style('opacity', 0.8);
|
||||
}
|
||||
|
||||
selectNode(node) {
|
||||
// Update selected node styling
|
||||
this.nodes
|
||||
.classed('selected', d => d.id === node.id);
|
||||
|
||||
// Show node details
|
||||
this.showNodeDetails(node);
|
||||
this.selectedNode = node;
|
||||
}
|
||||
|
||||
showNodeDetails(node) {
|
||||
const detailsContainer = document.getElementById('nodeDetails');
|
||||
const selectedInfo = document.getElementById('selectedNodeInfo');
|
||||
|
||||
const details = `
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Hostname:</span>
|
||||
<span class="detail-value">${node.label}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Discovery Depth:</span>
|
||||
<span class="detail-value">${node.depth}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">DNS Records:</span>
|
||||
<span class="detail-value">${node.dns_records}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Certificates:</span>
|
||||
<span class="detail-value">${node.certificates}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">IP Addresses:</span>
|
||||
<span class="detail-value">${node.ip_addresses.join(', ') || 'None'}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Discovery Methods:</span>
|
||||
<span class="detail-value">${node.discovery_methods.join(', ')}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">First Seen:</span>
|
||||
<span class="detail-value">${node.first_seen ? new Date(node.first_seen).toLocaleString() : 'Unknown'}</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
detailsContainer.innerHTML = details;
|
||||
selectedInfo.style.display = 'block';
|
||||
}
|
||||
|
||||
toggleLabels() {
|
||||
this.showLabels = !this.showLabels;
|
||||
if (this.labels) {
|
||||
this.labels.transition()
|
||||
.duration(300)
|
||||
.style('opacity', this.showLabels ? 1 : 0);
|
||||
}
|
||||
|
||||
const button = document.getElementById('toggleLabels');
|
||||
button.textContent = this.showLabels ? 'Hide Labels' : 'Show Labels';
|
||||
}
|
||||
|
||||
resetZoom() {
|
||||
if (this.svg && this.zoom) {
|
||||
this.svg.transition()
|
||||
.duration(750)
|
||||
.call(this.zoom.transform, d3.zoomIdentity);
|
||||
}
|
||||
}
|
||||
|
||||
showGraph() {
|
||||
if (this.container) {
|
||||
this.container.style('display', 'block');
|
||||
}
|
||||
document.getElementById('showGraph').classList.add('active');
|
||||
document.getElementById('hideGraph').classList.remove('active');
|
||||
}
|
||||
|
||||
hideGraph() {
|
||||
if (this.container) {
|
||||
this.container.style('display', 'none');
|
||||
}
|
||||
document.getElementById('hideGraph').classList.add('active');
|
||||
document.getElementById('showGraph').classList.remove('active');
|
||||
}
|
||||
|
||||
updateGraphStats(stats) {
|
||||
document.getElementById('graphNodes').textContent = stats.node_count || 0;
|
||||
document.getElementById('graphEdges').textContent = stats.edge_count || 0;
|
||||
document.getElementById('graphDepth').textContent = stats.max_depth || 0;
|
||||
}
|
||||
|
||||
// Handle window resize
|
||||
handleResize() {
|
||||
if (this.svg && this.graphData) {
|
||||
const containerNode = document.getElementById('discoveryGraph');
|
||||
const width = containerNode.clientWidth;
|
||||
const height = containerNode.clientHeight;
|
||||
|
||||
this.svg
|
||||
.attr('width', width)
|
||||
.attr('height', height);
|
||||
|
||||
this.simulation
|
||||
.force('center', d3.forceCenter(width / 2, height / 2))
|
||||
.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the application when DOM is loaded
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
console.log('🌐 DNS Reconnaissance Tool initialized with debug mode');
|
||||
console.log('🌐 Forensic DNS Reconnaissance Tool initialized with debug mode');
|
||||
new ReconTool();
|
||||
});
|
||||
249
static/style.css
249
static/style.css
@@ -436,4 +436,253 @@ header p {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Add this CSS to the existing style.css file */
|
||||
|
||||
/* Graph Section */
|
||||
.graph-section {
|
||||
background: #2a2a2a;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #444;
|
||||
box-shadow: inset 0 0 15px rgba(0,0,0,0.5);
|
||||
padding: 30px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.graph-controls {
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.graph-controls button {
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.graph-legend {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
background: rgba(0,0,0,0.3);
|
||||
border-radius: 4px;
|
||||
border: 1px solid #444;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.graph-legend h4, .graph-legend h5 {
|
||||
color: #e0e0e0;
|
||||
margin-bottom: 10px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.legend-items, .legend-methods {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 0.9rem;
|
||||
color: #c7c7c7;
|
||||
}
|
||||
|
||||
.legend-color {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid #666;
|
||||
}
|
||||
|
||||
.method-item {
|
||||
font-size: 0.9rem;
|
||||
color: #a0a0a0;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.graph-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
background: #0a0a0a;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #333;
|
||||
margin-bottom: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#discoveryGraph {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
#discoveryGraph:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.graph-tooltip {
|
||||
position: absolute;
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
color: #00ff41;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #00ff41;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.8rem;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
z-index: 1000;
|
||||
max-width: 300px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.graph-tooltip.visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.graph-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.graph-stats {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.selected-node-info {
|
||||
background: rgba(0, 40, 0, 0.8);
|
||||
border: 1px solid #00ff41;
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
min-width: 250px;
|
||||
}
|
||||
|
||||
.selected-node-info h4 {
|
||||
color: #00ff41;
|
||||
margin-bottom: 10px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
#nodeDetails {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.4;
|
||||
color: #c7c7c7;
|
||||
}
|
||||
|
||||
#nodeDetails .detail-item {
|
||||
margin-bottom: 8px;
|
||||
border-bottom: 1px solid #333;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
#nodeDetails .detail-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
#nodeDetails .detail-label {
|
||||
color: #00ff41;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#nodeDetails .detail-value {
|
||||
color: #c7c7c7;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
/* Graph Nodes and Links Styling (applied via D3) */
|
||||
.graph-node {
|
||||
stroke: #333;
|
||||
stroke-width: 2px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.graph-node:hover {
|
||||
stroke: #fff;
|
||||
stroke-width: 3px;
|
||||
}
|
||||
|
||||
.graph-node.selected {
|
||||
stroke: #ff9900;
|
||||
stroke-width: 4px;
|
||||
}
|
||||
|
||||
.graph-link {
|
||||
stroke-opacity: 0.6;
|
||||
stroke-width: 2px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.graph-link:hover {
|
||||
stroke-opacity: 1;
|
||||
stroke-width: 3px;
|
||||
}
|
||||
|
||||
.graph-link.highlighted {
|
||||
stroke-opacity: 1;
|
||||
stroke-width: 3px;
|
||||
}
|
||||
|
||||
.graph-label {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 10px;
|
||||
fill: #c7c7c7;
|
||||
text-anchor: middle;
|
||||
pointer-events: none;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.graph-label.visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Responsive adjustments for graph */
|
||||
@media (max-width: 768px) {
|
||||
.graph-container {
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.graph-legend {
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.legend-items, .legend-methods {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.graph-info {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.graph-stats {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.selected-node-info {
|
||||
min-width: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.graph-tooltip {
|
||||
font-size: 0.7rem;
|
||||
max-width: 250px;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user