dnsrecon/static/js/main.js
overcuriousity ce0e11cf0b progress
2025-09-10 15:17:17 +02:00

1057 lines
37 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Main application logic for DNSRecon web interface
* Handles UI interactions, API communication, and data flow
* DEBUG VERSION WITH EXTRA LOGGING
*/
class DNSReconApp {
constructor() {
console.log('DNSReconApp constructor called');
this.graphManager = null;
this.scanStatus = 'idle';
this.pollInterval = null;
this.currentSessionId = null;
// UI Elements
this.elements = {};
// Application state
this.isScanning = false;
this.lastGraphUpdate = null;
this.init();
}
/**
* Initialize the application
*/
init() {
console.log('DNSReconApp init called');
document.addEventListener('DOMContentLoaded', () => {
console.log('DOM loaded, initializing application...');
try {
this.initializeElements();
this.setupEventHandlers();
this.initializeGraph();
this.updateStatus();
this.loadProviders();
console.log('DNSRecon application initialized successfully');
} catch (error) {
console.error('Failed to initialize DNSRecon application:', error);
this.showError(`Initialization failed: ${error.message}`);
}
});
}
/**
* Initialize DOM element references
*/
initializeElements() {
console.log('Initializing DOM elements...');
this.elements = {
// Form elements
targetDomain: document.getElementById('target-domain'),
maxDepth: document.getElementById('max-depth'),
startScan: document.getElementById('start-scan'),
stopScan: document.getElementById('stop-scan'),
exportResults: document.getElementById('export-results'),
// Status elements
scanStatus: document.getElementById('scan-status'),
targetDisplay: document.getElementById('target-display'),
depthDisplay: document.getElementById('depth-display'),
progressDisplay: document.getElementById('progress-display'),
indicatorsDisplay: document.getElementById('indicators-display'),
relationshipsDisplay: document.getElementById('relationships-display'),
progressFill: document.getElementById('progress-fill'),
// Provider elements
providerList: document.getElementById('provider-list'),
// Modal elements
nodeModal: document.getElementById('node-modal'),
modalTitle: document.getElementById('modal-title'),
modalDetails: document.getElementById('modal-details'),
modalClose: document.getElementById('modal-close'),
// Other elements
sessionId: document.getElementById('session-id'),
connectionStatus: document.getElementById('connection-status')
};
// Verify critical elements exist
const requiredElements = ['targetDomain', 'startScan', 'scanStatus'];
for (const elementName of requiredElements) {
if (!this.elements[elementName]) {
throw new Error(`Required element '${elementName}' not found in DOM`);
}
}
console.log('DOM elements initialized successfully');
this.createMessageContainer();
}
/**
* Create a message container for showing user feedback
*/
createMessageContainer() {
// Check if message container already exists
let messageContainer = document.getElementById('message-container');
if (!messageContainer) {
messageContainer = document.createElement('div');
messageContainer.id = 'message-container';
messageContainer.className = 'message-container';
messageContainer.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
max-width: 400px;
`;
document.body.appendChild(messageContainer);
console.log('Message container created');
}
}
/**
* Setup event handlers
*/
setupEventHandlers() {
console.log('Setting up event handlers...');
try {
// Form interactions
this.elements.startScan.addEventListener('click', (e) => {
console.log('Start scan button clicked');
e.preventDefault();
this.startScan();
});
this.elements.stopScan.addEventListener('click', (e) => {
console.log('Stop scan button clicked');
e.preventDefault();
this.stopScan();
});
this.elements.exportResults.addEventListener('click', (e) => {
console.log('Export results button clicked');
e.preventDefault();
this.exportResults();
});
// Enter key support for target domain input
this.elements.targetDomain.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !this.isScanning) {
console.log('Enter key pressed in domain input');
this.startScan();
}
});
// Modal interactions
if (this.elements.modalClose) {
this.elements.modalClose.addEventListener('click', () => this.hideModal());
}
if (this.elements.nodeModal) {
this.elements.nodeModal.addEventListener('click', (e) => {
if (e.target === this.elements.nodeModal) {
this.hideModal();
}
});
}
// Custom events
document.addEventListener('nodeSelected', (e) => {
this.showNodeModal(e.detail.nodeId, e.detail.node);
});
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
this.hideModal();
}
});
// Window events
window.addEventListener('beforeunload', () => {
if (this.isScanning) {
return 'A scan is currently in progress. Are you sure you want to leave?';
}
});
console.log('Event handlers set up successfully');
} catch (error) {
console.error('Failed to setup event handlers:', error);
throw error;
}
}
/**
* Initialize graph visualization
*/
initializeGraph() {
try {
console.log('Initializing graph manager...');
this.graphManager = new GraphManager('network-graph');
console.log('Graph manager initialized successfully');
} catch (error) {
console.error('Failed to initialize graph manager:', error);
this.showError('Failed to initialize graph visualization');
}
}
/**
* Start a reconnaissance scan
*/
async startScan() {
console.log('=== STARTING SCAN ===');
try {
const targetDomain = this.elements.targetDomain.value.trim();
const maxDepth = parseInt(this.elements.maxDepth.value);
console.log(`Target domain: "${targetDomain}", Max depth: ${maxDepth}`);
// Validation
if (!targetDomain) {
console.log('Validation failed: empty domain');
this.showError('Please enter a target domain');
this.elements.targetDomain.focus();
return;
}
if (!this.isValidDomain(targetDomain)) {
console.log(`Validation failed: invalid domain format for "${targetDomain}"`);
this.showError('Please enter a valid domain name (e.g., example.com)');
this.elements.targetDomain.focus();
return;
}
console.log('Validation passed, setting UI state to scanning...');
this.setUIState('scanning');
this.showInfo('Starting reconnaissance scan...');
console.log('Making API call to start scan...');
const requestData = {
target_domain: targetDomain,
max_depth: maxDepth
};
console.log('Request data:', requestData);
const response = await this.apiCall('/api/scan/start', 'POST', requestData);
console.log('API response received:', response);
if (response.success) {
this.currentSessionId = response.scan_id;
console.log('Starting polling with session ID:', this.currentSessionId);
this.startPolling();
this.showSuccess('Reconnaissance scan started successfully');
// Clear previous graph
this.graphManager.clear();
console.log(`Scan started for ${targetDomain} with depth ${maxDepth}`);
// Force an immediate status update
console.log('Forcing immediate status update...');
setTimeout(() => {
this.updateStatus();
this.updateGraph();
}, 100);
} else {
throw new Error(response.error || 'Failed to start scan');
}
} catch (error) {
console.error('Failed to start scan:', error);
this.showError(`Failed to start scan: ${error.message}`);
this.setUIState('idle');
}
}
/**
* Stop the current scan
*/
async stopScan() {
try {
console.log('Stopping scan...');
const response = await this.apiCall('/api/scan/stop', 'POST');
if (response.success) {
this.showSuccess('Scan stop requested');
console.log('Scan stop requested');
} else {
throw new Error(response.error || 'Failed to stop scan');
}
} catch (error) {
console.error('Failed to stop scan:', error);
this.showError(`Failed to stop scan: ${error.message}`);
}
}
/**
* Export scan results
*/
async exportResults() {
try {
console.log('Exporting results...');
// Create a temporary link to trigger download
const link = document.createElement('a');
link.href = '/api/export';
link.download = ''; // Let server determine filename
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
this.showSuccess('Results export initiated');
console.log('Results export initiated');
} catch (error) {
console.error('Failed to export results:', error);
this.showError(`Failed to export results: ${error.message}`);
}
}
/**
* Start polling for scan updates
*/
startPolling() {
console.log('=== STARTING POLLING ===');
if (this.pollInterval) {
console.log('Clearing existing poll interval');
clearInterval(this.pollInterval);
}
this.pollInterval = setInterval(() => {
console.log('--- Polling tick ---');
this.updateStatus();
this.updateGraph();
}, 1000); // Poll every 1 second for debugging
console.log('Polling started with 1 second interval');
}
/**
* Stop polling for updates
*/
stopPolling() {
console.log('=== STOPPING POLLING ===');
if (this.pollInterval) {
clearInterval(this.pollInterval);
this.pollInterval = null;
}
}
/**
* Update scan status from server
*/
async updateStatus() {
try {
console.log('Updating status...');
const response = await this.apiCall('/api/scan/status');
console.log('Status response:', response);
if (response.success) {
const status = response.status;
console.log('Current scan status:', status.status);
console.log('Current progress:', status.progress_percentage + '%');
console.log('Graph stats:', status.graph_statistics);
this.updateStatusDisplay(status);
// Handle status changes
if (status.status !== this.scanStatus) {
console.log(`*** STATUS CHANGED: ${this.scanStatus} -> ${status.status} ***`);
this.handleStatusChange(status.status);
}
this.scanStatus = status.status;
} else {
console.error('Status update failed:', response);
}
} catch (error) {
console.error('Failed to update status:', error);
this.showConnectionError();
}
}
/**
* Update graph from server
*/
async updateGraph() {
try {
console.log('Updating graph...');
const response = await this.apiCall('/api/graph');
console.log('Graph response:', response);
if (response.success) {
const graphData = response.graph;
console.log('Graph data received:');
console.log('- Nodes:', graphData.nodes ? graphData.nodes.length : 0);
console.log('- Edges:', graphData.edges ? graphData.edges.length : 0);
if (graphData.nodes) {
graphData.nodes.forEach(node => {
console.log(` Node: ${node.id} (${node.type})`);
});
}
if (graphData.edges) {
graphData.edges.forEach(edge => {
console.log(` Edge: ${edge.from} -> ${edge.to} (${edge.label})`);
});
}
// Only update if data has changed
if (this.hasGraphChanged(graphData)) {
console.log('*** GRAPH DATA CHANGED - UPDATING VISUALIZATION ***');
this.graphManager.updateGraph(graphData);
this.lastGraphUpdate = Date.now();
// Update relationship count in status
const edgeCount = graphData.edges ? graphData.edges.length : 0;
if (this.elements.relationshipsDisplay) {
this.elements.relationshipsDisplay.textContent = edgeCount;
}
} else {
console.log('Graph data unchanged, skipping update');
}
} else {
console.error('Graph update failed:', response);
}
} catch (error) {
console.error('Failed to update graph:', error);
// Don't show error for graph updates to avoid spam
}
}
/**
* Update status display elements
* @param {Object} status - Status object from server
*/
updateStatusDisplay(status) {
try {
console.log('Updating status display...');
// Update status text with animation
if (this.elements.scanStatus) {
const formattedStatus = this.formatStatus(status.status);
if (this.elements.scanStatus.textContent !== formattedStatus) {
this.elements.scanStatus.textContent = formattedStatus;
this.elements.scanStatus.classList.add('fade-in');
setTimeout(() => this.elements.scanStatus.classList.remove('fade-in'), 300);
}
// Add status-specific classes for styling
this.elements.scanStatus.className = `status-value status-${status.status}`;
}
if (this.elements.targetDisplay) {
this.elements.targetDisplay.textContent = status.target_domain || 'None';
}
if (this.elements.depthDisplay) {
this.elements.depthDisplay.textContent = `${status.current_depth}/${status.max_depth}`;
}
if (this.elements.progressDisplay) {
this.elements.progressDisplay.textContent = `${status.progress_percentage.toFixed(1)}%`;
}
if (this.elements.indicatorsDisplay) {
this.elements.indicatorsDisplay.textContent = status.indicators_processed || 0;
}
// Update progress bar with smooth animation
if (this.elements.progressFill) {
this.elements.progressFill.style.width = `${status.progress_percentage}%`;
// Add pulsing animation for active scans
if (status.status === 'running') {
this.elements.progressFill.parentElement.classList.add('scanning');
} else {
this.elements.progressFill.parentElement.classList.remove('scanning');
}
}
// Update session ID
if (this.currentSessionId && this.elements.sessionId) {
this.elements.sessionId.textContent = `Session: ${this.currentSessionId}`;
}
console.log('Status display updated successfully');
} catch (error) {
console.error('Error updating status display:', error);
}
}
/**
* Handle status changes
* @param {string} newStatus - New scan status
*/
handleStatusChange(newStatus) {
console.log(`=== STATUS CHANGE: ${this.scanStatus} -> ${newStatus} ===`);
switch (newStatus) {
case 'running':
this.setUIState('scanning');
this.showSuccess('Scan is running');
// Reset polling frequency for active scans
this.pollFrequency = 2000;
this.updateConnectionStatus('active');
break;
case 'completed':
this.setUIState('completed');
this.stopPolling();
this.showSuccess('Scan completed successfully');
this.updateConnectionStatus('completed');
// Force a final graph update
console.log('Scan completed - forcing final graph update');
setTimeout(() => this.updateGraph(), 100);
break;
case 'failed':
this.setUIState('failed');
this.stopPolling();
this.showError('Scan failed');
this.updateConnectionStatus('error');
break;
case 'stopped':
this.setUIState('stopped');
this.stopPolling();
this.showSuccess('Scan stopped');
this.updateConnectionStatus('stopped');
break;
case 'idle':
this.setUIState('idle');
this.stopPolling();
this.updateConnectionStatus('idle');
break;
}
}
/**
* Update connection status indicator
* @param {string} status - Connection status
*/
updateConnectionStatus(status) {
if (!this.elements.connectionStatus) return;
const statusColors = {
'idle': '#c7c7c7',
'active': '#00ff41',
'completed': '#00aa2e',
'stopped': '#ff9900',
'error': '#ff6b6b'
};
this.elements.connectionStatus.style.backgroundColor = statusColors[status] || '#c7c7c7';
const statusText = this.elements.connectionStatus.parentElement?.querySelector('.status-text');
if (statusText) {
const statusTexts = {
'idle': 'System Ready',
'active': 'Scanning Active',
'completed': 'Scan Complete',
'stopped': 'Scan Stopped',
'error': 'Connection Error'
};
statusText.textContent = statusTexts[status] || 'System Online';
}
}
/**
* Set UI state based on scan status
* @param {string} state - UI state
*/
setUIState(state) {
console.log(`Setting UI state to: ${state}`);
switch (state) {
case 'scanning':
this.isScanning = true;
if (this.elements.startScan) {
this.elements.startScan.disabled = true;
this.elements.startScan.classList.add('loading');
}
if (this.elements.stopScan) {
this.elements.stopScan.disabled = false;
this.elements.stopScan.classList.remove('loading');
}
if (this.elements.targetDomain) this.elements.targetDomain.disabled = true;
if (this.elements.maxDepth) this.elements.maxDepth.disabled = true;
if (this.elements.configureApiKeys) this.elements.configureApiKeys.disabled = true;
break;
case 'idle':
case 'completed':
case 'failed':
case 'stopped':
this.isScanning = false;
if (this.elements.startScan) {
this.elements.startScan.disabled = false;
this.elements.startScan.classList.remove('loading');
}
if (this.elements.stopScan) {
this.elements.stopScan.disabled = true;
this.elements.stopScan.classList.add('loading');
}
if (this.elements.targetDomain) this.elements.targetDomain.disabled = false;
if (this.elements.maxDepth) this.elements.maxDepth.disabled = false;
if (this.elements.configureApiKeys) this.elements.configureApiKeys.disabled = false;
break;
}
}
/**
* Load provider information
*/
async loadProviders() {
try {
console.log('Loading providers...');
const response = await this.apiCall('/api/providers');
if (response.success) {
this.updateProviderDisplay(response.providers);
console.log('Providers loaded successfully');
}
} catch (error) {
console.error('Failed to load providers:', error);
}
}
/**
* Update provider display
* @param {Object} providers - Provider information
*/
updateProviderDisplay(providers) {
if (!this.elements.providerList) return;
this.elements.providerList.innerHTML = '';
for (const [name, info] of Object.entries(providers)) {
const providerItem = document.createElement('div');
providerItem.className = 'provider-item fade-in';
let statusClass = 'disabled';
let statusText = 'Disabled';
if (info.enabled) {
statusClass = 'enabled';
statusText = 'Enabled';
} else if (info.requires_api_key) {
statusClass = 'api-key-required';
statusText = 'API Key Required';
}
providerItem.innerHTML = `
<div class="provider-header">
<div class="provider-name">${name.toUpperCase()}</div>
<div class="provider-status ${statusClass}">${statusText}</div>
</div>
<div class="provider-stats">
<div class="provider-stat">
<span class="provider-stat-label">Requests:</span>
<span class="provider-stat-value">${info.statistics.total_requests || 0}</span>
</div>
<div class="provider-stat">
<span class="provider-stat-label">Success Rate:</span>
<span class="provider-stat-value">${(info.statistics.success_rate || 0).toFixed(1)}%</span>
</div>
<div class="provider-stat">
<span class="provider-stat-label">Relationships:</span>
<span class="provider-stat-value">${info.statistics.relationships_found || 0}</span>
</div>
<div class="provider-stat">
<span class="provider-stat-label">Rate Limit:</span>
<span class="provider-stat-value">${info.rate_limit}/min</span>
</div>
</div>
`;
this.elements.providerList.appendChild(providerItem);
}
}
/**
* Show node details modal
* @param {string} nodeId - Node identifier
* @param {Object} node - Node data
*/
showNodeModal(nodeId, node) {
if (!this.elements.nodeModal) return;
if (this.elements.modalTitle) {
this.elements.modalTitle.textContent = `Node Details: ${nodeId}`;
}
let detailsHtml = '';
detailsHtml += `<div class="detail-row"><span class="detail-label">Identifier:</span><span class="detail-value">${nodeId}</span></div>`;
detailsHtml += `<div class="detail-row"><span class="detail-label">Type:</span><span class="detail-value">${node.metadata.type || node.type || 'Unknown'}</span></div>`;
if (node.metadata) {
for (const [key, value] of Object.entries(node.metadata)) {
if (key !== 'type') {
detailsHtml += `<div class="detail-row"><span class="detail-label">${this.formatLabel(key)}:</span><span class="detail-value">${this.formatValue(value)}</span></div>`;
}
}
}
// Add timestamps if available
if (node.added_timestamp) {
const addedDate = new Date(node.added_timestamp);
detailsHtml += `<div class="detail-row"><span class="detail-label">Added:</span><span class="detail-value">${addedDate.toLocaleString()}</span></div>`;
}
if (this.elements.modalDetails) {
this.elements.modalDetails.innerHTML = detailsHtml;
}
this.elements.nodeModal.style.display = 'block';
}
/**
* Hide the modal
*/
hideModal() {
if (this.elements.nodeModal) {
this.elements.nodeModal.style.display = 'none';
}
}
/**
* Check if graph data has changed
* @param {Object} graphData - New graph data
* @returns {boolean} True if data has changed
*/
hasGraphChanged(graphData) {
// Simple check based on node and edge counts and timestamps
const currentStats = this.graphManager.getStatistics();
const newNodeCount = graphData.nodes ? graphData.nodes.length : 0;
const newEdgeCount = graphData.edges ? graphData.edges.length : 0;
// Check if counts changed
const countsChanged = currentStats.nodeCount !== newNodeCount || currentStats.edgeCount !== newEdgeCount;
// Also check if we have new timestamp data
const hasNewTimestamp = graphData.statistics &&
graphData.statistics.last_modified &&
graphData.statistics.last_modified !== this.lastGraphTimestamp;
if (hasNewTimestamp) {
this.lastGraphTimestamp = graphData.statistics.last_modified;
}
const changed = countsChanged || hasNewTimestamp;
console.log(`Graph change check: Current(${currentStats.nodeCount}n, ${currentStats.edgeCount}e) vs New(${newNodeCount}n, ${newEdgeCount}e) = ${changed}`);
return changed;
}
/**
* Make API call to server
* @param {string} endpoint - API endpoint
* @param {string} method - HTTP method
* @param {Object} data - Request data
* @returns {Promise<Object>} Response data
*/
async apiCall(endpoint, method = 'GET', data = null) {
console.log(`Making API call: ${method} ${endpoint}`, data ? data : '(no data)');
try {
const options = {
method: method,
headers: {
'Content-Type': 'application/json'
}
};
if (data && method !== 'GET') {
options.body = JSON.stringify(data);
console.log('Request body:', options.body);
}
console.log('Fetch options:', options);
const response = await fetch(endpoint, options);
console.log('Response status:', response.status, response.statusText);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const result = await response.json();
console.log('Response data:', result);
return result;
} catch (error) {
console.error(`API call failed for ${method} ${endpoint}:`, error);
throw error;
}
}
/**
* Validate domain name - improved validation
* @param {string} domain - Domain to validate
* @returns {boolean} True if valid
*/
isValidDomain(domain) {
console.log(`Validating domain: "${domain}"`);
// Basic checks
if (!domain || typeof domain !== 'string') {
console.log('Validation failed: empty or non-string domain');
return false;
}
if (domain.length > 253) {
console.log('Validation failed: domain too long');
return false;
}
if (domain.startsWith('.') || domain.endsWith('.')) {
console.log('Validation failed: domain starts or ends with dot');
return false;
}
if (domain.includes('..')) {
console.log('Validation failed: domain contains double dots');
return false;
}
// Split into parts and validate each
const parts = domain.split('.');
if (parts.length < 2) {
console.log('Validation failed: domain has less than 2 parts');
return false;
}
// Check each part
for (const part of parts) {
if (!part || part.length > 63) {
console.log(`Validation failed: invalid part "${part}"`);
return false;
}
if (part.startsWith('-') || part.endsWith('-')) {
console.log(`Validation failed: part "${part}" starts or ends with hyphen`);
return false;
}
if (!/^[a-zA-Z0-9-]+$/.test(part)) {
console.log(`Validation failed: part "${part}" contains invalid characters`);
return false;
}
}
// Check TLD (last part) is alphabetic
const tld = parts[parts.length - 1];
if (!/^[a-zA-Z]{2,}$/.test(tld)) {
console.log(`Validation failed: invalid TLD "${tld}"`);
return false;
}
console.log('Domain validation passed');
return true;
}
/**
* Format status text for display
* @param {string} status - Raw status
* @returns {string} Formatted status
*/
formatStatus(status) {
const statusMap = {
'idle': 'Idle',
'running': 'Running',
'completed': 'Completed',
'failed': 'Failed',
'stopped': 'Stopped'
};
return statusMap[status] || status;
}
/**
* Format label for display
* @param {string} label - Raw label
* @returns {string} Formatted label
*/
formatLabel(label) {
return label.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
}
/**
* Format value for display
* @param {*} value - Raw value
* @returns {string} Formatted value
*/
formatValue(value) {
if (Array.isArray(value)) {
return value.join(', ');
} else if (typeof value === 'object') {
return JSON.stringify(value, null, 2);
} else {
return String(value);
}
}
/**
* Show success message
* @param {string} message - Success message
*/
showSuccess(message) {
this.showMessage(message, 'success');
}
/**
* Show info message
* @param {string} message - Info message
*/
showInfo(message) {
this.showMessage(message, 'info');
}
showWarning(message) {
this.showMessage(message, 'warning');
}
/**
* Show error message
* @param {string} message - Error message
*/
showError(message) {
this.showMessage(message, 'error');
}
/**
* Show connection error
*/
showConnectionError() {
this.updateConnectionStatus('error');
}
/**
* Show message with visual feedback
* @param {string} message - Message text
* @param {string} type - Message type (success, error, warning, info)
*/
showMessage(message, type = 'info') {
console.log(`${type.toUpperCase()}: ${message}`);
// Create message element
const messageElement = document.createElement('div');
messageElement.className = `message-toast message-${type}`;
messageElement.innerHTML = `
<div style="display: flex; justify-content: space-between; align-items: center; padding: 12px 20px;">
<span style="flex: 1;">${message}</span>
<button onclick="this.parentElement.parentElement.remove()"
style="background: none; border: none; color: #fff; cursor: pointer; font-size: 16px; margin-left: 10px; opacity: 0.7;">×</button>
</div>
`;
// Add to container
const container = document.getElementById('message-container');
if (container) {
container.appendChild(messageElement);
// Auto-remove after delay
setTimeout(() => {
if (messageElement.parentNode) {
messageElement.style.animation = 'slideOutRight 0.3s ease-out';
setTimeout(() => {
if (messageElement.parentNode) {
messageElement.remove();
}
}, 300);
}
}, type === 'error' ? 8000 : 5000); // Errors stay longer
}
// Update connection status to show activity
if (type === 'success' && this.consecutiveErrors === 0) {
this.updateConnectionStatus(this.isScanning ? 'active' : 'idle');
}
}
/**
* Get message background color based on type
* @param {string} type - Message type
* @returns {string} CSS color
*/
getMessageColor(type) {
const colors = {
'success': '#2c5c34',
'error': '#5c2c2c',
'warning': '#5c4c2c',
'info': '#2c3e5c'
};
return colors[type] || colors.info;
}
/**
* Get message border color based on type
* @param {string} type - Message type
* @returns {string} CSS color
*/
getMessageBorderColor(type) {
const colors = {
'success': '#00ff41',
'error': '#ff6b6b',
'warning': '#ff9900',
'info': '#00aaff'
};
return colors[type] || colors.info;
}
}
// Add CSS animations for message toasts
const style = document.createElement('style');
style.textContent = `
@keyframes slideInRight {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slideOutRight {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(100%);
opacity: 0;
}
}
.message-container {
pointer-events: auto;
}
.message-toast {
pointer-events: auto;
}
`;
document.head.appendChild(style);
// Initialize application when page loads
console.log('Creating DNSReconApp instance...');
const app = new DNSReconApp();