// Display Results Module // This module handles the display of analysis results in the UI // Main namespace to avoid global pollution const displayResults = (function() { /** * Displays risk factors in the Key Risk Factors section * @param {Object} data - The analysis data from the API */ function displayRiskFactors(data) { const riskFactorsContainer = document.getElementById('topFeatures'); if (!riskFactorsContainer) { console.error("Risk factors container not found with ID 'topFeatures'"); return; } riskFactorsContainer.innerHTML = ''; console.log("Displaying risk factors from data:", data); let factors = []; // Try to get features from feature_table (primary source) if (data.feature_table && Array.isArray(data.feature_table)) { console.log("Found feature_table:", data.feature_table); factors = data.feature_table .filter(f => Math.abs(f.impact || 0) > 0.01) .sort((a, b) => Math.abs(b.impact || 0) - Math.abs(a.impact || 0)) .slice(0, 5) .map(f => ({ name: f.feature, value: f.value, percentage: f.impact || 0, color_class: f.color_class || 'success' })); } // Fallback to feature_contributions if available else if (data.feature_contributions && Array.isArray(data.feature_contributions)) { console.log("Using feature_contributions:", data.feature_contributions); factors = data.feature_contributions .filter(f => f.section === "Key Risk Factors" && Math.abs(f.percentage || 0) > 0.01) .sort((a, b) => Math.abs(b.percentage || 0) - Math.abs(a.percentage || 0)) .slice(0, 5); } if (factors.length > 0) { // Create and append risk factor elements factors.forEach(factor => { // Determine color class based on percentage const percentage = parseFloat(factor.percentage || 0); let colorClass = 'success'; if (percentage > 30) { colorClass = 'danger'; } else if (percentage > 15) { colorClass = 'warning'; } const factorEl = document.createElement('div'); factorEl.className = 'contribution-bar'; factorEl.style.marginBottom = '16px'; // Create header with name and value const headerEl = document.createElement('div'); headerEl.className = 'contribution-label'; headerEl.style.display = 'flex'; headerEl.style.justifyContent = 'space-between'; headerEl.style.marginBottom = '6px'; const nameEl = document.createElement('span'); nameEl.className = 'factor-name'; nameEl.textContent = formatFeatureName(factor.name || factor.feature_name); nameEl.style.fontWeight = '500'; const valueEl = document.createElement('span'); valueEl.className = `contribution-value ${colorClass}`; valueEl.textContent = `${percentage.toFixed(1)}%`; valueEl.style.fontWeight = '600'; if (colorClass === 'danger') { valueEl.style.color = '#ef4444'; } else if (colorClass === 'warning') { valueEl.style.color = '#f59e0b'; } else { valueEl.style.color = '#10b981'; } headerEl.appendChild(nameEl); headerEl.appendChild(valueEl); // Create enhanced bar chart visualization const barContainerEl = document.createElement('div'); barContainerEl.className = 'bar-container'; barContainerEl.style.position = 'relative'; barContainerEl.style.height = '12px'; barContainerEl.style.borderRadius = '6px'; barContainerEl.style.overflow = 'hidden'; barContainerEl.style.backgroundColor = 'rgba(30, 41, 59, 0.4)'; const barEl = document.createElement('div'); barEl.className = `bar-fill ${colorClass}`; barEl.style.height = '100%'; barEl.style.width = `${Math.min(Math.abs(percentage), 100)}%`; barEl.style.borderRadius = '6px'; barEl.style.transition = 'width 1s cubic-bezier(0.22, 1, 0.36, 1)'; // Add gradient to the bar if (colorClass === 'danger') { barEl.style.background = 'linear-gradient(90deg, #ef4444, #b91c1c)'; } else if (colorClass === 'warning') { barEl.style.background = 'linear-gradient(90deg, #f59e0b, #d97706)'; } else { barEl.style.background = 'linear-gradient(90deg, #10b981, #059669)'; } barContainerEl.appendChild(barEl); // Feature value display const valueDisplayEl = document.createElement('div'); valueDisplayEl.className = 'feature-value-display'; valueDisplayEl.style.display = 'flex'; valueDisplayEl.style.justifyContent = 'flex-end'; valueDisplayEl.style.marginTop = '4px'; valueDisplayEl.style.fontSize = '0.85rem'; valueDisplayEl.style.color = '#94a3b8'; // Format and display the feature value let displayValue = factor.value; if (typeof factor.value === 'boolean') { displayValue = factor.value ? 'Yes' : 'No'; } else if (factor.value === 0 || factor.value === 1) { displayValue = factor.value === 1 ? 'Yes' : 'No'; } valueDisplayEl.textContent = `Value: ${displayValue}`; // Assemble the components factorEl.appendChild(headerEl); factorEl.appendChild(barContainerEl); factorEl.appendChild(valueDisplayEl); // Add to container riskFactorsContainer.appendChild(factorEl); }); } else { // No risk factors found const noFactorsEl = document.createElement('p'); noFactorsEl.textContent = 'No significant risk factors detected.'; noFactorsEl.style.textAlign = 'center'; noFactorsEl.style.padding = '20px'; noFactorsEl.style.color = '#94a3b8'; riskFactorsContainer.appendChild(noFactorsEl); } } /** * Displays domain information in the Domain Information section * @param {Object} data - The analysis data from the API */ function displayDomainInfo(data) { const domainInfoContainer = document.getElementById('domainInfo'); if (!domainInfoContainer) { console.error("Domain info container not found with ID 'domainInfo'"); return; } domainInfoContainer.innerHTML = ''; // Extract domain info from data const domainInfo = data.domain_info || {}; // Get URL parts const url = data.url || ''; const parsed = new URL(url); const domain = parsed.hostname; const protocol = parsed.protocol.replace(':', ''); // Check if IP address could not be resolved const ipNotResolved = !domainInfo.ip_address || domainInfo.ip_address === 'Unknown' || domainInfo.ip_address === 'Could not resolve'; // If IP address couldn't be resolved, increase section risk if (ipNotResolved) { increaseIPResolutionRisk(data); } // Calculate domain age if available let domainAge = null; let domainAgeStatus = 'neutral'; if (domainInfo.created && domainInfo.created !== 'Unknown') { try { const createdDate = new Date(domainInfo.created); const now = new Date(); const ageInDays = Math.floor((now - createdDate) / (1000 * 60 * 60 * 24)); // Format the creation date const formattedDate = createdDate.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }); if (ageInDays < 30) { domainAge = `${ageInDays} days (Created: ${formattedDate})`; domainAgeStatus = 'danger'; } else if (ageInDays < 90) { domainAge = `${Math.floor(ageInDays / 30)} months (Created: ${formattedDate})`; domainAgeStatus = 'warning'; } else if (ageInDays < 365) { domainAge = `${Math.floor(ageInDays / 30)} months (Created: ${formattedDate})`; domainAgeStatus = 'neutral'; } else { const years = Math.floor(ageInDays / 365); domainAge = `${years} ${years === 1 ? 'year' : 'years'} (Created: ${formattedDate})`; domainAgeStatus = 'safe'; } } catch (e) { console.error('Error calculating domain age:', e); domainAge = domainInfo.created; } } // Check TLD type let tldType = null; let tldSuspicious = false; let tldDescription = ''; if (domain) { try { const tld = domain.split('.').pop().toLowerCase(); const commonTlds = ['com', 'org', 'net', 'edu', 'gov', 'io', 'co', 'me', 'app', 'dev']; const suspiciousTlds = ['tk', 'ml', 'ga', 'cf', 'gq', 'top', 'xyz', 'online', 'site', 'club', 'icu']; if (commonTlds.includes(tld)) { tldType = `Common TLD (.${tld})`; tldDescription = 'Well-established and trusted top-level domain'; } else if (suspiciousTlds.includes(tld)) { tldType = `Suspicious TLD (.${tld})`; tldSuspicious = true; tldDescription = 'This TLD is commonly associated with malicious websites'; } else if (tld.length === 2) { tldType = `Country Code (.${tld})`; tldDescription = 'A country-specific top-level domain'; } else { tldType = `Generic TLD (.${tld})`; tldDescription = 'A less common top-level domain'; } } catch (e) { console.error('Error determining TLD type:', e); } } // Determine if WHOIS privacy is enabled based on organization name const whoisPrivacy = domainInfo.organization && /privacy|protect|proxy|private|whois/i.test(domainInfo.organization) ? 'Enabled' : null; // Check if we have location data for the map const hasLocationData = (domainInfo.latitude && domainInfo.longitude && (domainInfo.latitude !== 0 || domainInfo.longitude !== 0)) || (domainInfo.country && domainInfo.country !== 'Unknown'); // Create info items list with actual data const infoItems = []; // Only add items with valid values if (domain) { infoItems.push({ label: 'Domain Name', value: domain, icon: 'fa-globe', description: 'The registered domain name of the website' }); } if (protocol) { infoItems.push({ label: 'Protocol', value: protocol.toUpperCase(), icon: protocol === 'https' ? 'fa-lock' : 'fa-unlock', isSecure: protocol === 'https', description: protocol === 'https' ? 'Secure connection with encryption' : 'Insecure connection without encryption' }); } if (domainInfo.organization && domainInfo.organization !== 'Unknown') { infoItems.push({ label: 'Organization', value: domainInfo.organization, icon: 'fa-building', description: 'The organization that owns this domain' }); } if (domainInfo.country && domainInfo.country !== 'Unknown') { infoItems.push({ label: 'Location', value: domainInfo.city && domainInfo.city !== 'Unknown' ? `${domainInfo.city}, ${domainInfo.country}` : domainInfo.country, icon: 'fa-location-dot', description: 'Geographic location of the hosting server' }); } if (domainAge) { infoItems.push({ label: 'Domain Age', value: domainAge, icon: 'fa-calendar-alt', isNew: domainAgeStatus === 'danger' || domainAgeStatus === 'warning', description: domainAgeStatus === 'danger' ? 'Very recently registered domain - high risk' : domainAgeStatus === 'warning' ? 'Recently registered domain - potential risk' : domainAgeStatus === 'safe' ? 'Well-established domain - lower risk' : 'How long the domain has been registered' }); } if (tldType) { infoItems.push({ label: 'TLD Type', value: tldType, icon: 'fa-tag', isSuspicious: tldSuspicious, description: tldDescription || 'The type of top-level domain used by this website' }); } if (whoisPrivacy) { infoItems.push({ label: 'WHOIS Privacy', value: whoisPrivacy, icon: 'fa-user-shield', description: 'Whether the domain uses privacy protection to hide owner information' }); } if (data.ssl_info) { infoItems.push({ label: 'SSL Certificate', value: data.ssl_info.has_ssl ? 'Valid' : 'Not found', icon: data.ssl_info.has_ssl ? 'fa-shield-check' : 'fa-shield-exclamation', isSecure: data.ssl_info.has_ssl, description: data.ssl_info.has_ssl ? 'SSL certificate is valid and trusted' : 'No SSL certificate found, connection is not encrypted' }); } // Only create the IP and location section if there's an IP to display if (domainInfo.ip_address && domainInfo.ip_address !== 'Unknown') { // Create a card style container for IP and location const ipLocationCard = document.createElement('div'); ipLocationCard.className = 'domain-info-card'; ipLocationCard.style.marginBottom = '15px'; ipLocationCard.style.padding = '15px'; ipLocationCard.style.backgroundColor = 'rgba(15, 23, 42, 0.3)'; ipLocationCard.style.borderRadius = '10px'; ipLocationCard.style.border = '1px solid var(--border-color)'; // Create IP Address header const ipHeader = document.createElement('div'); ipHeader.className = 'ip-header'; ipHeader.style.marginBottom = '10px'; ipHeader.style.display = 'flex'; ipHeader.style.alignItems = 'center'; ipHeader.style.justifyContent = 'space-between'; const ipLabel = document.createElement('div'); ipLabel.style.color = 'var(--text-muted)'; ipLabel.style.fontSize = '0.9rem'; ipLabel.style.fontWeight = '500'; ipLabel.innerHTML = ' IP Address'; const ipValue = document.createElement('div'); ipValue.style.fontWeight = 'bold'; ipValue.style.fontSize = '1.1rem'; // Apply red color if IP could not be resolved if (domainInfo.ip_address === 'Could not resolve') { ipValue.style.color = '#ef4444'; } ipValue.textContent = domainInfo.ip_address; ipHeader.appendChild(ipLabel); ipHeader.appendChild(ipValue); ipLocationCard.appendChild(ipHeader); // Only add the map if we have location data if (hasLocationData) { // Add a server location label with icon const serverLocationLabel = document.createElement('div'); serverLocationLabel.className = 'server-location-label'; serverLocationLabel.style.marginBottom = '10px'; serverLocationLabel.style.fontWeight = '500'; serverLocationLabel.style.display = 'flex'; serverLocationLabel.style.alignItems = 'center'; serverLocationLabel.innerHTML = ' Server Location'; ipLocationCard.appendChild(serverLocationLabel); // Add geolocation map const serverLocationDiv = document.createElement('div'); serverLocationDiv.id = 'server-location-map'; serverLocationDiv.style.width = '100%'; serverLocationDiv.style.height = '180px'; serverLocationDiv.style.borderRadius = '8px'; serverLocationDiv.style.overflow = 'hidden'; serverLocationDiv.style.border = '1px solid var(--border-color)'; ipLocationCard.appendChild(serverLocationDiv); // Add the IP location card to the container (only if it has content) domainInfoContainer.appendChild(ipLocationCard); // Initialize map with the server location try { setTimeout(() => { initMap('server-location-map', domainInfo, false); }, 100); } catch (e) { console.error('Error initializing map:', e); serverLocationDiv.innerHTML = '
Error loading map
'; } } else { // Add the IP location card without map if it has the IP but no location domainInfoContainer.appendChild(ipLocationCard); } } else if (!domainInfo.ip_address || domainInfo.ip_address === 'Unknown') { // Create a card showing that IP address could not be resolved const ipLocationCard = document.createElement('div'); ipLocationCard.className = 'domain-info-card'; ipLocationCard.style.marginBottom = '15px'; ipLocationCard.style.padding = '15px'; ipLocationCard.style.backgroundColor = 'rgba(15, 23, 42, 0.3)'; ipLocationCard.style.borderRadius = '10px'; ipLocationCard.style.border = '1px solid var(--border-color)'; // Create IP Address header const ipHeader = document.createElement('div'); ipHeader.className = 'ip-header'; ipHeader.style.marginBottom = '10px'; ipHeader.style.display = 'flex'; ipHeader.style.alignItems = 'center'; ipHeader.style.justifyContent = 'space-between'; const ipLabel = document.createElement('div'); ipLabel.style.color = 'var(--text-muted)'; ipLabel.style.fontSize = '0.9rem'; ipLabel.style.fontWeight = '500'; ipLabel.innerHTML = ' IP Address'; const ipValue = document.createElement('div'); ipValue.style.fontWeight = 'bold'; ipValue.style.fontSize = '1.1rem'; ipValue.style.color = '#ef4444'; // Red color to indicate error ipValue.textContent = 'Could not resolve'; // Add warning message about IP resolution failure const warningMessage = document.createElement('div'); warningMessage.style.marginTop = '10px'; warningMessage.style.padding = '8px 12px'; warningMessage.style.backgroundColor = 'rgba(239, 68, 68, 0.1)'; warningMessage.style.borderRadius = '6px'; warningMessage.style.border = '1px solid rgba(239, 68, 68, 0.3)'; warningMessage.style.color = '#ef4444'; warningMessage.style.fontSize = '0.85rem'; warningMessage.innerHTML = ' Unable to resolve IP address. This may indicate domain masking or a newly registered domain, which increases risk.'; ipHeader.appendChild(ipLabel); ipHeader.appendChild(ipValue); ipLocationCard.appendChild(ipHeader); ipLocationCard.appendChild(warningMessage); // Add the IP card to the container domainInfoContainer.appendChild(ipLocationCard); } // Only create domain details card if we have valid items to display if (infoItems.length > 0) { // Create domain details card const domainDetailsCard = document.createElement('div'); domainDetailsCard.className = 'domain-details-card'; domainDetailsCard.style.backgroundColor = 'rgba(15, 23, 42, 0.3)'; domainDetailsCard.style.borderRadius = '10px'; domainDetailsCard.style.border = '1px solid var(--border-color)'; domainDetailsCard.style.overflow = 'hidden'; // Add domain details header const domainDetailsHeader = document.createElement('div'); domainDetailsHeader.className = 'domain-details-header'; domainDetailsHeader.style.padding = '12px 15px'; domainDetailsHeader.style.borderBottom = '1px solid var(--border-color)'; domainDetailsHeader.style.backgroundColor = 'rgba(15, 23, 42, 0.5)'; domainDetailsHeader.style.fontWeight = 'bold'; domainDetailsHeader.innerHTML = ' Domain Details'; domainDetailsCard.appendChild(domainDetailsHeader); // Create domain details content const domainDetailsList = document.createElement('ul'); domainDetailsList.className = 'domain-details-list'; domainDetailsList.style.listStyle = 'none'; domainDetailsList.style.padding = '0'; domainDetailsList.style.margin = '0'; // Add each info item infoItems.forEach(item => { const listItem = document.createElement('li'); listItem.className = 'domain-info-item'; listItem.style.display = 'flex'; listItem.style.alignItems = 'center'; listItem.style.padding = '12px 15px'; listItem.style.borderBottom = '1px solid rgba(71, 85, 105, 0.2)'; // Add hover effect listItem.addEventListener('mouseover', () => { listItem.style.backgroundColor = 'rgba(30, 41, 59, 0.3)'; }); listItem.addEventListener('mouseout', () => { listItem.style.backgroundColor = ''; }); // Icon part const iconDiv = document.createElement('div'); iconDiv.className = 'info-icon'; iconDiv.style.marginRight = '12px'; iconDiv.style.width = '24px'; iconDiv.style.height = '24px'; iconDiv.style.display = 'flex'; iconDiv.style.alignItems = 'center'; iconDiv.style.justifyContent = 'center'; const iconElement = document.createElement('i'); iconElement.className = `fas ${item.icon}`; // Set icon color based on item properties if (item.isSecure) { iconElement.style.color = '#10b981'; // Green for secure items } else if (item.isSuspicious || item.isNew) { iconElement.style.color = '#f59e0b'; // Amber for suspicious or new items } else if (item.label === 'SSL Certificate' && item.value === 'Not found') { iconElement.style.color = '#ef4444'; // Red for missing SSL } else { iconElement.style.color = '#3b82f6'; // Default blue } iconDiv.appendChild(iconElement); listItem.appendChild(iconDiv); // Content part const contentDiv = document.createElement('div'); contentDiv.className = 'info-content'; contentDiv.style.flex = '1'; // Label const labelDiv = document.createElement('div'); labelDiv.className = 'info-label'; labelDiv.style.fontSize = '0.85rem'; labelDiv.style.color = 'var(--text-muted)'; labelDiv.textContent = item.label; contentDiv.appendChild(labelDiv); // Value const valueDiv = document.createElement('div'); valueDiv.className = 'info-value'; valueDiv.style.fontWeight = '500'; // Add visual indicators for certain values if (item.label === 'Protocol') { if (item.value === 'HTTPS') { valueDiv.innerHTML = `${item.value}`; } else { valueDiv.innerHTML = `${item.value}`; } } else if (item.label === 'TLD Type' && item.isSuspicious) { valueDiv.innerHTML = `${item.value} SUSPICIOUS`; } else if (item.label === 'Domain Age' && item.isNew) { valueDiv.innerHTML = `${item.value}`; } else if (item.label === 'SSL Certificate') { if (item.value === 'Valid') { valueDiv.innerHTML = `${item.value}`; } else { valueDiv.innerHTML = `${item.value}`; } } else { valueDiv.textContent = item.value; } contentDiv.appendChild(valueDiv); listItem.appendChild(contentDiv); domainDetailsList.appendChild(listItem); }); // Append the list to the card and the card to the container domainDetailsCard.appendChild(domainDetailsList); domainInfoContainer.appendChild(domainDetailsCard); } // If there's no content at all, add a no-info message if (domainInfoContainer.childNodes.length === 0) { const noInfoMsg = document.createElement('div'); noInfoMsg.className = 'no-domain-info'; noInfoMsg.style.padding = '20px'; noInfoMsg.style.textAlign = 'center'; noInfoMsg.style.color = 'var(--text-muted)'; noInfoMsg.style.backgroundColor = 'rgba(15, 23, 42, 0.3)'; noInfoMsg.style.borderRadius = '10px'; noInfoMsg.style.border = '1px solid var(--border-color)'; noInfoMsg.innerHTML = '
No domain information available.'; domainInfoContainer.appendChild(noInfoMsg); } } /** * Increases the section risk when IP address cannot be resolved * @param {Object} data - The analysis data to modify */ function increaseIPResolutionRisk(data) { // Only proceed if we have section_totals if (!data.section_totals) { data.section_totals = {}; } // Get the current domain information section risk or default to 0 const currentDomainRisk = data.section_totals["Domain Information"] || 0; // Increase the risk by a significant amount (5-15% range) const additionalRisk = 7.5; data.section_totals["Domain Information"] = Math.min(100, currentDomainRisk + additionalRisk); // Also add to total Section Risk if it exists if (data.section_totals["Section Risk"] !== undefined) { const currentSectionRisk = data.section_totals["Section Risk"] || 0; data.section_totals["Section Risk"] = Math.min(100, currentSectionRisk + additionalRisk / 2); } // If there's a feature_contributions array, add or update the IP resolution entry if (data.feature_contributions && Array.isArray(data.feature_contributions)) { // Check if IP resolution feature already exists const ipFeatureIndex = data.feature_contributions.findIndex( f => (f.name || "").toLowerCase().includes("ip_resolution") || (f.name || "").toLowerCase().includes("ip_address") ); if (ipFeatureIndex >= 0) { // Update existing feature data.feature_contributions[ipFeatureIndex].value = "Could not resolve"; data.feature_contributions[ipFeatureIndex].percentage = (parseFloat(data.feature_contributions[ipFeatureIndex].percentage) || 0) + additionalRisk; data.feature_contributions[ipFeatureIndex].color_class = "danger"; } else { // Add new feature data.feature_contributions.push({ name: "IP Resolution", value: "Could not resolve", percentage: additionalRisk, color_class: "danger" }); } // Sort by percentage (importance) data.feature_contributions.sort((a, b) => parseFloat(b.percentage || 0) - parseFloat(a.percentage || 0)); } // Update the overall score if (data.score !== undefined) { data.score = Math.min(100, data.score + additionalRisk / 3); } } /** * Initializes a map to show the geolocation of the domain * @param {string} containerId - The ID of the container element for the map * @param {Object} domainInfo - Domain information including location data * @param {boolean} hasExactCoords - Whether we have exact coordinates */ function initMap(containerId, domainInfo, hasExactCoords) { // Default coordinates (center of world map) let lat = 0; let lng = 0; let zoom = 1; if (hasExactCoords) { // Use exact coordinates if available lat = parseFloat(domainInfo.latitude); lng = parseFloat(domainInfo.longitude); zoom = 10; // Zoom in more for exact location } else if (domainInfo.country) { // Use approximate country location const countryCenters = { 'United States': [37.0902, -95.7129], 'Russia': [61.5240, 105.3188], 'China': [35.8617, 104.1954], 'India': [20.5937, 78.9629], 'Brazil': [-14.2350, -51.9253], 'Australia': [-25.2744, 133.7751], 'Canada': [56.1304, -106.3468], 'Germany': [51.1657, 10.4515], 'Japan': [36.2048, 138.2529], 'United Kingdom': [55.3781, -3.4360], 'France': [46.2276, 2.2137], 'Italy': [41.8719, 12.5674], 'South Korea': [35.9078, 127.7669], 'Spain': [40.4637, -3.7492], 'Mexico': [23.6345, -102.5528], 'Indonesia': [-0.7893, 113.9213], 'Netherlands': [52.1326, 5.2913], 'Switzerland': [46.8182, 8.2275], 'Saudi Arabia': [23.8859, 45.0792], 'Turkey': [38.9637, 35.2433] }; if (countryCenters[domainInfo.country]) { [lat, lng] = countryCenters[domainInfo.country]; zoom = 4; // Country level zoom } } // Remove the IP address label above the map - we'll show it directly on the map const mapContainer = document.getElementById(containerId); // Check if the map library is available if (typeof L !== 'undefined') { try { // Initialize the map const map = L.map(containerId, { center: [lat, lng], zoom: zoom, zoomControl: true, attributionControl: true }); // Add the tile layer (OpenStreetMap) L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors', maxZoom: 18 }).addTo(map); // Add a marker if we have a location if (lat !== 0 || lng !== 0) { // Create a marker with a popup const marker = L.marker([lat, lng]).addTo(map); // Prepare popup content let popupContent = '
'; if (domainInfo.ip_address && domainInfo.ip_address !== 'Unknown') { popupContent += `IP: ${domainInfo.ip_address}
`; } if (domainInfo.organization && domainInfo.organization !== 'Unknown') { popupContent += `Organization: ${domainInfo.organization}
`; } if (domainInfo.city && domainInfo.city !== 'Unknown') { popupContent += `City: ${domainInfo.city}
`; } if (domainInfo.country && domainInfo.country !== 'Unknown') { popupContent += `Country: ${domainInfo.country}`; } popupContent += '
'; // Add popup to marker marker.bindPopup(popupContent); // Open popup by default marker.openPopup(); // Add a better label with the IP address directly on the map if (domainInfo.ip_address && domainInfo.ip_address !== 'Unknown') { const customLabel = L.divIcon({ className: 'ip-address-map-label', html: `
IP: ${domainInfo.ip_address}
`, iconSize: [140, 30], iconAnchor: [70, 35] }); L.marker([lat, lng], { icon: customLabel }).addTo(map); } } // Force map to resize to container setTimeout(() => { map.invalidateSize(); }, 100); // Remove any unused space by setting a fixed height mapContainer.style.height = '180px'; mapContainer.style.marginBottom = '0'; } catch (e) { console.error('Error creating map:', e); document.getElementById(containerId).innerHTML = '
' + '
' + 'Map data not available
'; } } else { console.error('Leaflet library not available'); document.getElementById(containerId).innerHTML = '
' + '
' + 'Map library not available
'; } } /** * Displays suspicious patterns in the Suspicious Patterns section * @param {Object} data - The analysis data from the API */ function displaySuspiciousPatterns(data) { const suspiciousPatternsContainer = document.getElementById('suspiciousPatterns'); if (!suspiciousPatternsContainer) return; // Clear previous content suspiciousPatternsContainer.innerHTML = ''; // Get patterns from data const patterns = data.suspicious_patterns || []; // When no patterns are found, the section risk should always be 0% // This will override whatever might be in the data let sectionRiskValue = patterns && patterns.length > 0 ? (data.section_totals && data.section_totals["Suspicious Patterns"] || 0) : 0; // Create container const patternContainer = document.createElement('div'); patternContainer.className = 'suspicious-patterns-container'; const header = document.createElement('div'); header.className = 'section-header'; // Add icon and title const titleContainer = document.createElement('div'); titleContainer.className = 'section-title-container'; const icon = document.createElement('i'); icon.className = 'fas fa-exclamation-triangle'; icon.style.color = '#ff6b6b'; icon.style.marginRight = '10px'; const title = document.createElement('h3'); title.className = 'section-title'; title.textContent = 'Suspicious Patterns'; titleContainer.appendChild(icon); titleContainer.appendChild(title); header.appendChild(titleContainer); patternContainer.appendChild(header); // Create content container const contentContainer = document.createElement('div'); contentContainer.className = 'patterns-content'; if (patterns && patterns.length > 0) { // Calculate total risk score from patterns const totalPatternRiskScore = patterns.reduce((total, pattern) => total + (pattern.risk_score || 0), 0); // Add risk overview section const riskOverviewSection = document.createElement('div'); riskOverviewSection.className = 'risk-overview-section'; riskOverviewSection.style.marginBottom = '20px'; riskOverviewSection.style.padding = '15px'; riskOverviewSection.style.backgroundColor = 'rgba(30, 41, 59, 0.3)'; riskOverviewSection.style.borderRadius = '8px'; riskOverviewSection.style.border = '1px solid rgba(79, 99, 135, 0.2)'; riskOverviewSection.innerHTML = `
Risk Overview
Total patterns detected: ${patterns.length}
Combined pattern risk score: ${totalPatternRiskScore} points
Section risk contribution: ${sectionRiskValue.toFixed(1)}%
`; contentContainer.appendChild(riskOverviewSection); // Sort patterns by severity patterns.sort((a, b) => { const severityOrder = { high: 0, medium: 1, low: 2 }; return severityOrder[a.severity] - severityOrder[b.severity]; }); // Add count of patterns by severity const severityCounts = { high: patterns.filter(p => p.severity === 'high').length, medium: patterns.filter(p => p.severity === 'medium').length, low: patterns.filter(p => p.severity === 'low').length }; // Only add severity badges if we have patterns with that severity const severityBadgesContainer = document.createElement('div'); severityBadgesContainer.className = 'severity-badges'; severityBadgesContainer.style.display = 'flex'; severityBadgesContainer.style.marginBottom = '15px'; if (severityCounts.high > 0) { severityBadgesContainer.appendChild(createSeverityBadge(severityCounts.high, 'high', 'High')); } if (severityCounts.medium > 0) { severityBadgesContainer.appendChild(createSeverityBadge(severityCounts.medium, 'medium', 'Medium')); } if (severityCounts.low > 0) { severityBadgesContainer.appendChild(createSeverityBadge(severityCounts.low, 'low', 'Low')); } contentContainer.appendChild(severityBadgesContainer); // Create patterns list with details for each pattern const patternsList = document.createElement('div'); patternsList.className = 'patterns-list'; patterns.forEach(pattern => { const patternItem = document.createElement('div'); patternItem.className = `pattern-item ${getSeverityClass(pattern.severity)}`; patternItem.style.marginBottom = '12px'; patternItem.style.padding = '15px'; patternItem.style.borderRadius = '8px'; patternItem.style.backgroundColor = 'rgba(30, 41, 59, 0.5)'; patternItem.style.borderLeft = `4px solid ${pattern.severity === 'high' ? '#ff6b6b' : pattern.severity === 'medium' ? '#ffba08' : '#90be6d'}`; const patternHeader = document.createElement('div'); patternHeader.className = 'pattern-header'; patternHeader.style.display = 'flex'; patternHeader.style.alignItems = 'center'; patternHeader.style.justifyContent = 'space-between'; const patternTitleWrapper = document.createElement('div'); patternTitleWrapper.style.display = 'flex'; patternTitleWrapper.style.alignItems = 'center'; const patternIcon = document.createElement('i'); patternIcon.className = pattern.severity === 'high' ? 'fas fa-exclamation-circle' : pattern.severity === 'medium' ? 'fas fa-exclamation' : 'fas fa-info-circle'; patternIcon.style.marginRight = '8px'; patternIcon.style.color = pattern.severity === 'high' ? '#ff6b6b' : pattern.severity === 'medium' ? '#ffba08' : '#90be6d'; patternIcon.style.fontSize = '16px'; const patternTitle = document.createElement('div'); patternTitle.className = 'pattern-title'; patternTitle.style.fontWeight = 'bold'; patternTitle.style.fontSize = '1rem'; patternTitle.textContent = pattern.pattern; patternTitleWrapper.appendChild(patternIcon); patternTitleWrapper.appendChild(patternTitle); // Add risk score badge const riskScoreBadge = document.createElement('div'); riskScoreBadge.className = 'risk-score-badge'; riskScoreBadge.style.backgroundColor = pattern.severity === 'high' ? 'rgba(239, 68, 68, 0.2)' : pattern.severity === 'medium' ? 'rgba(245, 158, 11, 0.2)' : 'rgba(16, 185, 129, 0.2)'; riskScoreBadge.style.color = pattern.severity === 'high' ? '#f87171' : pattern.severity === 'medium' ? '#fbbf24' : '#34d399'; riskScoreBadge.style.fontWeight = 'bold'; riskScoreBadge.style.padding = '4px 8px'; riskScoreBadge.style.borderRadius = '12px'; riskScoreBadge.style.fontSize = '0.8rem'; riskScoreBadge.style.display = 'flex'; riskScoreBadge.style.alignItems = 'center'; riskScoreBadge.style.justifyContent = 'center'; riskScoreBadge.innerHTML = ` ${pattern.risk_score || 0} points`; patternHeader.appendChild(patternTitleWrapper); patternHeader.appendChild(riskScoreBadge); // Only add the pattern item header (no description or technical details) patternItem.appendChild(patternHeader); patternsList.appendChild(patternItem); }); contentContainer.appendChild(patternsList); // REMOVED: The duplicate section risk meter has been removed } else { // Display a message when no patterns are found with a green checkmark const safeContainer = document.createElement('div'); safeContainer.className = 'safe-container'; safeContainer.style.display = 'flex'; safeContainer.style.flexDirection = 'column'; safeContainer.style.alignItems = 'center'; safeContainer.style.justifyContent = 'center'; safeContainer.style.padding = '30px 20px'; safeContainer.style.backgroundColor = 'rgba(15, 30, 50, 0.3)'; safeContainer.style.borderRadius = '12px'; safeContainer.style.textAlign = 'center'; safeContainer.style.margin = '10px 0'; // Large checkmark icon const checkIcon = document.createElement('div'); checkIcon.innerHTML = ''; // No patterns message const noPatternTitle = document.createElement('h4'); noPatternTitle.textContent = 'No Suspicious Patterns Detected'; noPatternTitle.style.fontSize = '1.2rem'; noPatternTitle.style.fontWeight = '600'; noPatternTitle.style.marginBottom = '10px'; const noPatternMessage = document.createElement('p'); noPatternMessage.textContent = 'This website does not contain any known suspicious patterns or risky behaviors.'; noPatternMessage.style.opacity = '0.8'; noPatternMessage.style.fontSize = '0.95rem'; noPatternMessage.style.maxWidth = '380px'; noPatternMessage.style.lineHeight = '1.6'; safeContainer.appendChild(checkIcon); safeContainer.appendChild(noPatternTitle); safeContainer.appendChild(noPatternMessage); contentContainer.appendChild(safeContainer); } patternContainer.appendChild(contentContainer); suspiciousPatternsContainer.appendChild(patternContainer); } /** * Displays feature details in the Feature Details section * @param {Object} data - The analysis data from the API */ function displayFeatureDetails(data) { const featureDetailsContainer = document.getElementById('featureDetails'); if (!featureDetailsContainer) { console.error("Feature details container not found with ID 'featureDetails'"); return; } featureDetailsContainer.innerHTML = ''; console.log("Displaying feature details from data:", data); let features = []; // Try to get features from feature_table (primary source) if (data.feature_table && Array.isArray(data.feature_table)) { console.log("Found feature_table:", data.feature_table); features = data.feature_table; } // Fallback to feature_contributions if available else if (data.feature_contributions && Array.isArray(data.feature_contributions)) { console.log("Using feature_contributions as fallback:", data.feature_contributions); features = data.feature_contributions.map(f => ({ feature: f.name || f.feature_name, value: f.value, impact: f.percentage || 0, color_class: f.color_class || 'success' })); } // Fallback to feature_values as last resort else if (data.feature_values && typeof data.feature_values === 'object') { console.log("Using feature_values as fallback:", data.feature_values); features = Object.entries(data.feature_values).map(([key, value]) => ({ feature: key, value: value, impact: 0, color_class: 'success' })); } // Clear any existing description headers (to prevent duplication) const existingDescHeaders = document.querySelectorAll('.feature-description-header'); existingDescHeaders.forEach(header => header.remove()); // Create a description header with improved explanation const descriptionHeader = document.createElement('div'); descriptionHeader.className = 'feature-description-header'; descriptionHeader.style.marginBottom = '25px'; descriptionHeader.style.fontSize = '0.95rem'; descriptionHeader.style.color = 'var(--text-color)'; descriptionHeader.style.display = 'flex'; descriptionHeader.style.alignItems = 'flex-start'; descriptionHeader.style.gap = '15px'; descriptionHeader.style.backgroundColor = 'rgba(15, 30, 50, 0.4)'; descriptionHeader.style.padding = '18px 20px'; descriptionHeader.style.borderRadius = '12px'; descriptionHeader.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.1)'; descriptionHeader.style.border = '1px solid rgba(79, 99, 135, 0.2)'; const infoIcon = document.createElement('i'); infoIcon.className = 'fas fa-info-circle'; infoIcon.style.color = '#60a5fa'; infoIcon.style.marginTop = '3px'; infoIcon.style.fontSize = '20px'; const infoText = document.createElement('div'); infoText.style.flex = '1'; infoText.style.lineHeight = '1.6'; infoText.innerHTML = ` These are the URL and domain features analyzed to determine the risk score. Each feature contributes differently to the overall risk assessment and is categorized into one of three sections: `; descriptionHeader.appendChild(infoIcon); descriptionHeader.appendChild(infoText); // Insert the description at the beginning of the main section-content container const sectionContent = featureDetailsContainer.closest('.section-content'); if (sectionContent) { sectionContent.insertBefore(descriptionHeader, sectionContent.firstChild); } // Create explanations for common features const featureExplanations = { // Key Risk Factors "https_present": "Websites without HTTPS are insecure and allow data interception. Phishing sites often lack proper encryption.", "domain_length": "Unusually long domain names may be trying to impersonate legitimate sites or hide suspicious elements.", "domain_entropy": "Higher entropy (randomness) in domain names is associated with algorithmically generated phishing domains.", "special_characters": "An abnormal number of special characters is commonly seen in deceptive URLs trying to confuse users.", "tld_score": "Certain top-level domains are more commonly associated with malicious websites due to lower registration restrictions.", "url_length": "Excessively long URLs often attempt to hide the true destination or include suspicious parameters.", "subdomain_count": "Multiple subdomains can be used to make a URL appear legitimate while hiding the actual domain.", "numeric_path": "Paths consisting only of numbers are uncommon in legitimate websites and may indicate an automated attack.", "digit_percentage": "High percentage of digits in a URL is unusual and often associated with malicious sites.", "keyword_count": "Presence of terms like 'login', 'verify', 'account' may indicate phishing attempts.", "path_length": "Unusually long URL paths can be used to hide malicious destinations.", "query_length": "Excessively long query parameters may contain obfuscated malicious code.", "fragment_length": "Long URL fragments (#) may be used to evade security scanning.", // Domain Information "ip_resolution": "Domains that can't be resolved to an IP address may be newly registered for phishing or no longer active.", "rep_domain_age_category": "Newly registered domains have a higher likelihood of being used for malicious purposes.", "whois_recently_registered": "Domains registered in the past 30 days have a higher risk of being used for fraud.", "geo_suspicious_country": "Some countries have higher rates of hosting malicious websites due to lax cybercrime enforcement.", "ct_suspicious_cert_pattern": "Certificate Transparency logs show suspicious certificate issuance patterns for this domain.", "ip_blacklisted": "The IP address hosting this domain is on known malware or spam blacklists.", "domain_blacklisted": "This domain appears on reputation blacklists for previous malicious activity.", // Content Features "favicon_present": "Legitimate sites typically have a favicon (site icon). Phishing sites often neglect this detail.", "content_form_count": "Multiple forms may indicate attempts to collect sensitive information.", "content_password_field_count": "Password fields on unexpected pages may indicate credential harvesting.", "content_external_resources_count": "High numbers of external resources can indicate content loaded from malicious sources.", "content_js_to_html_ratio": "Excessive JavaScript relative to HTML content may indicate obfuscation techniques.", "content_title_brand_mismatch": "Mismatch between page title and domain name is common in impersonation attempts.", "content_similar_domain_redirect": "Redirects to similar but different domains may indicate a bait-and-switch attack.", "html_security_score": "Overall security analysis of the HTML content, including forms, scripts, and iframe usage.", "html_risk_factor_count": "Number of suspicious elements detected in the HTML content.", "html_has_password_field": "Presence of password fields may indicate an attempt to steal credentials.", "html_has_obfuscated_js": "Obfuscated JavaScript is often used to hide malicious code from detection." }; if (features.length > 0) { // Sort features by impact features.sort((a, b) => Math.abs(b.impact || 0) - Math.abs(a.impact || 0)); // Create feature categories const featureCategories = { url: ['url_length', 'path_length', 'query_length', 'fragment_length', 'special_char_count', 'numeric_path'], domain: ['domain_length', 'domain_entropy', 'subdomain_count', 'tld_score', 'ip_url'], security: ['https_present', 'ssl_valid', 'hsts_present'], content: ['keyword_count', 'digit_percentage', 'letter_percentage', 'suspicious_keywords'] }; // Create a direct display (non-accordion) for each category const createCategorySection = (title, icon, features, accentColor) => { if (features.length === 0) return null; const sectionContainer = document.createElement('div'); sectionContainer.className = 'feature-category-section'; sectionContainer.style.marginBottom = '30px'; sectionContainer.style.backgroundColor = 'rgba(15, 30, 50, 0.3)'; sectionContainer.style.borderRadius = '12px'; sectionContainer.style.overflow = 'hidden'; sectionContainer.style.boxShadow = '0 4px 15px rgba(0, 0, 0, 0.1)'; sectionContainer.style.border = '1px solid rgba(79, 99, 135, 0.15)'; // Create header const headerEl = document.createElement('div'); headerEl.className = 'category-header'; headerEl.style.padding = '16px 20px'; headerEl.style.backgroundColor = 'rgba(15, 30, 50, 0.6)'; headerEl.style.borderBottom = `1px solid rgba(79, 99, 135, 0.2)`; headerEl.style.display = 'flex'; headerEl.style.alignItems = 'center'; headerEl.style.gap = '12px'; const iconEl = document.createElement('i'); iconEl.className = `fas ${icon}`; iconEl.style.width = '24px'; iconEl.style.height = '24px'; iconEl.style.display = 'flex'; iconEl.style.alignItems = 'center'; iconEl.style.justifyContent = 'center'; iconEl.style.color = accentColor; iconEl.style.fontSize = '16px'; const titleEl = document.createElement('span'); titleEl.style.fontWeight = '600'; titleEl.style.fontSize = '1.05rem'; titleEl.textContent = title; const countEl = document.createElement('span'); countEl.style.fontSize = '0.85rem'; countEl.style.backgroundColor = 'rgba(79, 99, 135, 0.3)'; countEl.style.color = '#e2e8f0'; countEl.style.padding = '3px 8px'; countEl.style.borderRadius = '12px'; countEl.style.marginLeft = '8px'; countEl.textContent = `${features.length}`; headerEl.appendChild(iconEl); headerEl.appendChild(titleEl); headerEl.appendChild(countEl); // Create content container const contentEl = document.createElement('div'); contentEl.className = 'category-content'; contentEl.style.padding = '5px'; // Create table for features const tableEl = document.createElement('table'); tableEl.className = 'feature-table'; tableEl.style.width = '100%'; tableEl.style.borderCollapse = 'collapse'; tableEl.style.margin = '0'; // Add header row const theadEl = document.createElement('thead'); const headerRowEl = document.createElement('tr'); // Check if this is the Content Features section const isContentFeatures = title === 'Content Features'; // Define headers based on section type const headers = isContentFeatures ? ['Feature', 'Why It Matters'] : ['Feature', 'Value', 'Impact on Risk', 'Why It Matters']; headers.forEach((headerText, index) => { const th = document.createElement('th'); th.style.padding = '14px 16px'; th.style.textAlign = 'left'; th.style.color = '#94a3b8'; th.style.fontSize = '0.85rem'; th.style.fontWeight = '600'; th.style.letterSpacing = '0.025em'; th.style.textTransform = 'uppercase'; th.style.borderBottom = '1px solid rgba(79, 99, 135, 0.15)'; th.textContent = headerText; // Set column widths if (isContentFeatures) { // For Content Features section with only 2 columns if (index === 0) { th.style.width = '25%'; } else { th.style.width = '75%'; } } else { // For other sections with 4 columns if (index === 0) { th.style.width = '18%'; } else if (index === 1) { th.style.width = '10%'; } else if (index === 2) { th.style.width = '15%'; } else { th.style.width = '57%'; } } headerRowEl.appendChild(th); }); theadEl.appendChild(headerRowEl); tableEl.appendChild(theadEl); // Add body rows const tbodyEl = document.createElement('tbody'); features.forEach((feature, index) => { const row = document.createElement('tr'); row.style.transition = 'background-color 0.2s ease, transform 0.1s ease'; row.style.backgroundColor = index % 2 === 0 ? 'rgba(30, 41, 59, 0.2)' : 'transparent'; // Add hover effect row.addEventListener('mouseover', () => { row.style.backgroundColor = 'rgba(51, 65, 85, 0.4)'; row.style.transform = 'translateX(3px)'; }); row.addEventListener('mouseout', () => { row.style.backgroundColor = index % 2 === 0 ? 'rgba(30, 41, 59, 0.2)' : 'transparent'; row.style.transform = 'translateX(0)'; }); // Feature name cell const nameCell = document.createElement('td'); nameCell.style.padding = '12px 16px'; nameCell.style.borderBottom = '1px solid rgba(79, 99, 135, 0.1)'; nameCell.style.fontSize = '0.9rem'; const nameContent = document.createElement('div'); nameContent.style.display = 'flex'; nameContent.style.alignItems = 'center'; nameContent.style.gap = '10px'; // Add feature icon based on category const featureIconEl = document.createElement('div'); featureIconEl.style.width = '28px'; featureIconEl.style.height = '28px'; featureIconEl.style.minWidth = '28px'; featureIconEl.style.borderRadius = '6px'; featureIconEl.style.display = 'flex'; featureIconEl.style.alignItems = 'center'; featureIconEl.style.justifyContent = 'center'; const iconI = document.createElement('i'); if (title === 'URL Features') { featureIconEl.style.backgroundColor = 'rgba(59, 130, 246, 0.15)'; iconI.className = 'fas fa-link'; iconI.style.color = '#60a5fa'; } else if (title === 'Domain Features') { featureIconEl.style.backgroundColor = 'rgba(16, 185, 129, 0.15)'; iconI.className = 'fas fa-globe'; iconI.style.color = '#10b981'; } else if (title === 'Security Features') { featureIconEl.style.backgroundColor = 'rgba(245, 158, 11, 0.15)'; iconI.className = 'fas fa-shield-alt'; iconI.style.color = '#f59e0b'; } else { featureIconEl.style.backgroundColor = 'rgba(139, 92, 246, 0.15)'; iconI.className = 'fas fa-file-alt'; iconI.style.color = '#8b5cf6'; } featureIconEl.appendChild(iconI); const nameTextEl = document.createElement('span'); nameTextEl.style.fontWeight = '500'; nameTextEl.textContent = formatFeatureName(feature.feature); nameContent.appendChild(featureIconEl); nameContent.appendChild(nameTextEl); nameCell.appendChild(nameContent); row.appendChild(nameCell); // Only add Value and Impact on Risk columns for non-Content Features sections if (!isContentFeatures) { // Feature value cell const valueCell = document.createElement('td'); valueCell.style.padding = '12px 16px'; valueCell.style.borderBottom = '1px solid rgba(79, 99, 135, 0.1)'; valueCell.style.fontSize = '0.9rem'; // Format value based on feature type let formattedValue = feature.value; const featureName = feature.feature.toLowerCase(); if (featureName.includes('present') || featureName.includes('valid') || featureName === 'ip_url') { // Boolean features - show Yes/No const boolValue = parseFloat(feature.value) > 0; formattedValue = boolValue ? 'Yes' : 'No'; const valueSpan = document.createElement('span'); valueSpan.style.padding = '3px 8px'; valueSpan.style.borderRadius = '4px'; valueSpan.style.fontSize = '0.85rem'; valueSpan.style.fontWeight = '500'; if ((featureName.includes('present') || featureName.includes('valid')) && !boolValue) { // Missing security feature - show as risky valueSpan.style.backgroundColor = 'rgba(239, 68, 68, 0.15)'; valueSpan.style.color = '#f87171'; } else if (featureName === 'ip_url' && boolValue) { // IP as URL is risky valueSpan.style.backgroundColor = 'rgba(239, 68, 68, 0.15)'; valueSpan.style.color = '#f87171'; } else { // Normal state valueSpan.style.backgroundColor = 'rgba(16, 185, 129, 0.15)'; valueSpan.style.color = '#34d399'; } valueSpan.textContent = formattedValue; valueCell.appendChild(valueSpan); } else if (featureName.includes('percentage')) { // Percentage values const percentValue = parseFloat(feature.value); formattedValue = percentValue.toFixed(1) + '%'; valueCell.textContent = formattedValue; } else if (featureName === 'tld_score') { // TLD score is 0-1 const tldValue = parseFloat(feature.value); const tldSpan = document.createElement('span'); tldSpan.style.padding = '2px 6px'; tldSpan.style.borderRadius = '4px'; tldSpan.style.fontSize = '0.85rem'; // Color based on TLD risk if (tldValue > 0.6) { tldSpan.style.backgroundColor = 'rgba(239, 68, 68, 0.15)'; tldSpan.style.color = '#f87171'; } else if (tldValue > 0.3) { tldSpan.style.backgroundColor = 'rgba(245, 158, 11, 0.15)'; tldSpan.style.color = '#fbbf24'; } else { tldSpan.style.backgroundColor = 'rgba(16, 185, 129, 0.15)'; tldSpan.style.color = '#34d399'; } tldSpan.textContent = tldValue.toFixed(2); valueCell.appendChild(tldSpan); } else if (featureName === 'domain_entropy') { // Domain entropy (higher is riskier) const entropyValue = parseFloat(feature.value); const entropySpan = document.createElement('span'); entropySpan.style.padding = '2px 6px'; entropySpan.style.borderRadius = '4px'; entropySpan.style.fontSize = '0.85rem'; // Color based on entropy value if (entropyValue > 4) { entropySpan.style.backgroundColor = 'rgba(239, 68, 68, 0.15)'; entropySpan.style.color = '#f87171'; } else if (entropyValue > 3) { entropySpan.style.backgroundColor = 'rgba(245, 158, 11, 0.15)'; entropySpan.style.color = '#fbbf24'; } else { entropySpan.style.backgroundColor = 'rgba(16, 185, 129, 0.15)'; entropySpan.style.color = '#34d399'; } entropySpan.textContent = entropyValue.toFixed(2); valueCell.appendChild(entropySpan); } else { // Default formatting valueCell.textContent = formattedValue; } row.appendChild(valueCell); // Feature impact cell const impactCell = document.createElement('td'); impactCell.style.padding = '12px 16px'; impactCell.style.borderBottom = '1px solid rgba(79, 99, 135, 0.1)'; const impact = parseFloat(feature.impact || 0); if (!isNaN(impact) && impact > 0) { // Create impact container const impactContainer = document.createElement('div'); impactContainer.style.display = 'flex'; impactContainer.style.alignItems = 'center'; impactContainer.style.gap = '10px'; // Create impact value const impactValueEl = document.createElement('div'); impactValueEl.style.minWidth = '45px'; impactValueEl.style.fontWeight = '600'; impactValueEl.style.fontSize = '0.9rem'; impactValueEl.style.padding = '2px 6px'; impactValueEl.style.borderRadius = '4px'; impactValueEl.style.textAlign = 'center'; // Set color based on impact let colorClass = feature.color_class || 'success'; if (impact > 60) { colorClass = 'danger'; impactValueEl.style.backgroundColor = 'rgba(239, 68, 68, 0.15)'; impactValueEl.style.color = '#f87171'; } else if (impact > 20) { colorClass = 'warning'; impactValueEl.style.backgroundColor = 'rgba(245, 158, 11, 0.15)'; impactValueEl.style.color = '#fbbf24'; } else { impactValueEl.style.backgroundColor = 'rgba(16, 185, 129, 0.15)'; impactValueEl.style.color = '#34d399'; } impactValueEl.textContent = `${impact.toFixed(1)}%`; // Create impact bar container const barContainerEl = document.createElement('div'); barContainerEl.style.flex = '1'; barContainerEl.style.height = '8px'; barContainerEl.style.backgroundColor = 'rgba(30, 41, 59, 0.5)'; barContainerEl.style.borderRadius = '4px'; barContainerEl.style.overflow = 'hidden'; barContainerEl.style.position = 'relative'; barContainerEl.style.cursor = 'pointer'; // Create impact bar const barEl = document.createElement('div'); barEl.style.height = '100%'; barEl.style.width = `${Math.min(impact * 1.5, 100)}%`; barEl.style.borderRadius = '4px'; barEl.style.transition = 'width 1s cubic-bezier(0.34, 1.56, 0.64, 1), filter 0.3s ease'; // Set bar color with gradient if (colorClass === 'danger') { barEl.style.background = 'linear-gradient(90deg, #ef4444, #b91c1c)'; barEl.style.boxShadow = '0 0 8px rgba(239, 68, 68, 0.3)'; } else if (colorClass === 'warning') { barEl.style.background = 'linear-gradient(90deg, #f59e0b, #d97706)'; barEl.style.boxShadow = '0 0 8px rgba(245, 158, 11, 0.3)'; } else { barEl.style.background = 'linear-gradient(90deg, #10b981, #059669)'; barEl.style.boxShadow = '0 0 8px rgba(16, 185, 129, 0.3)'; } // Create tooltip for additional impact information const tooltip = document.createElement('div'); tooltip.style.position = 'absolute'; tooltip.style.bottom = '100%'; tooltip.style.left = '50%'; tooltip.style.transform = 'translateX(-50%)'; tooltip.style.backgroundColor = 'rgba(15, 23, 42, 0.95)'; tooltip.style.color = '#f8fafc'; tooltip.style.padding = '8px 12px'; tooltip.style.borderRadius = '6px'; tooltip.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.3)'; tooltip.style.fontSize = '0.85rem'; tooltip.style.whiteSpace = 'nowrap'; tooltip.style.zIndex = '10'; tooltip.style.marginBottom = '8px'; tooltip.style.border = '1px solid rgba(79, 99, 135, 0.3)'; tooltip.style.display = 'none'; tooltip.style.textAlign = 'center'; tooltip.style.backdropFilter = 'blur(4px)'; const featureName = formatFeatureName(feature.feature); let riskLevel = 'Low'; if (impact > 60) riskLevel = 'High'; else if (impact > 20) riskLevel = 'Medium'; tooltip.innerHTML = `
${featureName}
Impact: ${impact.toFixed(1)}%
Risk Level: ${riskLevel}
`; // Add arrow to tooltip const tooltipArrow = document.createElement('div'); tooltipArrow.style.position = 'absolute'; tooltipArrow.style.bottom = '-5px'; tooltipArrow.style.left = '50%'; tooltipArrow.style.transform = 'translateX(-50%) rotate(45deg)'; tooltipArrow.style.width = '10px'; tooltipArrow.style.height = '10px'; tooltipArrow.style.backgroundColor = 'rgba(15, 23, 42, 0.95)'; tooltipArrow.style.borderRight = '1px solid rgba(79, 99, 135, 0.3)'; tooltipArrow.style.borderBottom = '1px solid rgba(79, 99, 135, 0.3)'; tooltip.appendChild(tooltipArrow); // Toggle tooltip on hover barContainerEl.addEventListener('mouseover', () => { tooltip.style.display = 'block'; barEl.style.filter = 'brightness(1.2)'; }); barContainerEl.addEventListener('mouseout', () => { tooltip.style.display = 'none'; barEl.style.filter = 'brightness(1)'; }); barContainerEl.appendChild(barEl); barContainerEl.appendChild(tooltip); // Ensure proper assembly of elements impactContainer.appendChild(impactValueEl); impactContainer.appendChild(barContainerEl); impactCell.appendChild(impactContainer); } else { // No impact data - create empty bar instead of just text const impactContainer = document.createElement('div'); impactContainer.style.display = 'flex'; impactContainer.style.alignItems = 'center'; impactContainer.style.gap = '10px'; // Create impact value with "No impact" label const impactValueEl = document.createElement('div'); impactValueEl.style.minWidth = '70px'; impactValueEl.style.fontSize = '0.85rem'; impactValueEl.style.padding = '2px 6px'; impactValueEl.style.borderRadius = '4px'; impactValueEl.style.backgroundColor = 'rgba(100, 116, 139, 0.2)'; impactValueEl.style.color = '#94a3b8'; impactValueEl.style.textAlign = 'center'; impactValueEl.textContent = 'No impact'; // Create empty bar container const barContainerEl = document.createElement('div'); barContainerEl.style.flex = '1'; barContainerEl.style.height = '8px'; barContainerEl.style.backgroundColor = 'rgba(30, 41, 59, 0.5)'; barContainerEl.style.borderRadius = '4px'; barContainerEl.style.overflow = 'hidden'; impactContainer.appendChild(impactValueEl); impactContainer.appendChild(barContainerEl); impactCell.appendChild(impactContainer); } row.appendChild(impactCell); } // Add explanation cell const explanationCell = document.createElement('td'); explanationCell.style.padding = '12px 16px'; explanationCell.style.borderBottom = '1px solid rgba(79, 99, 135, 0.1)'; explanationCell.style.fontSize = '0.85rem'; explanationCell.style.lineHeight = '1.5'; explanationCell.style.color = '#e2e8f0'; // For Content Features, make the explanation cell larger if (isContentFeatures) { explanationCell.style.fontSize = '0.9rem'; explanationCell.style.lineHeight = '1.6'; } // Get explanation for this feature let featureKey = feature.feature.toLowerCase(); let explanation = ''; // Look for exact match first if (featureExplanations[featureKey]) { explanation = featureExplanations[featureKey]; } else { // Look for partial matches in the keys for (const [key, value] of Object.entries(featureExplanations)) { if (featureKey.includes(key) || key.includes(featureKey)) { explanation = value; break; } } // If still no match, provide generic explanations based on feature name if (!explanation) { if (featureKey.includes('https') || featureKey.includes('ssl') || featureKey.includes('security')) { explanation = "Security features help protect user data. Missing security features indicate higher risk."; } else if (featureKey.includes('domain')) { explanation = "Domain characteristics can indicate potential phishing or deception attempts."; } else if (featureKey.includes('content') || featureKey.includes('html')) { explanation = "Website content analysis can reveal suspicious elements used in phishing pages."; } else { explanation = "This feature is analyzed as part of the machine learning model's risk assessment."; } } } // Highlight key terms in the explanation const highlightTerms = ["phishing", "malicious", "suspicious", "risk", "fraud", "deceptive", "insecure"]; let highlightedExplanation = explanation; highlightTerms.forEach(term => { const regex = new RegExp(`\\b${term}\\b`, 'gi'); highlightedExplanation = highlightedExplanation.replace( regex, `$&` ); }); explanationCell.innerHTML = highlightedExplanation; row.appendChild(explanationCell); tbodyEl.appendChild(row); }); tableEl.appendChild(tbodyEl); contentEl.appendChild(tableEl); sectionContainer.appendChild(headerEl); sectionContainer.appendChild(contentEl); return sectionContainer; }; // Categorize features const categorizedFeatures = { url: [], domain: [], security: [], content: [] }; features.forEach(feature => { const featureName = feature.feature.toLowerCase(); // Check which category this feature belongs to for (const [category, featureList] of Object.entries(featureCategories)) { if (featureList.some(f => featureName.includes(f))) { categorizedFeatures[category].push(feature); return; } } // Default to content category if not found categorizedFeatures.content.push(feature); }); // Create sections for each category with specific accent colors const urlSection = createCategorySection('URL Features', 'fa-link', categorizedFeatures.url, '#60a5fa'); const domainSection = createCategorySection('Domain Features', 'fa-globe', categorizedFeatures.domain, '#10b981'); const securitySection = createCategorySection('Security Features', 'fa-shield-alt', categorizedFeatures.security, '#f59e0b'); const contentSection = createCategorySection('Content Features', 'fa-file-alt', categorizedFeatures.content, '#8b5cf6'); // Add animation style for staggered appearance const animationStyle = document.createElement('style'); animationStyle.textContent = ` @keyframes featureSectionFadeIn { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: translateY(0); } } `; document.head.appendChild(animationStyle); // Add sections to container with staggered animations if (urlSection) { urlSection.style.animation = 'featureSectionFadeIn 0.5s cubic-bezier(0.4, 0, 0.2, 1) forwards'; featureDetailsContainer.appendChild(urlSection); } if (domainSection) { domainSection.style.animation = 'featureSectionFadeIn 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.1s forwards'; domainSection.style.opacity = '0'; featureDetailsContainer.appendChild(domainSection); } if (securitySection) { securitySection.style.animation = 'featureSectionFadeIn 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.2s forwards'; securitySection.style.opacity = '0'; featureDetailsContainer.appendChild(securitySection); } if (contentSection) { contentSection.style.animation = 'featureSectionFadeIn 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.3s forwards'; contentSection.style.opacity = '0'; featureDetailsContainer.appendChild(contentSection); } } else { // Add a message if no features found const noFeaturesEl = document.createElement('div'); noFeaturesEl.style.padding = '40px 20px'; noFeaturesEl.style.textAlign = 'center'; noFeaturesEl.style.color = '#94a3b8'; noFeaturesEl.style.backgroundColor = 'rgba(15, 30, 50, 0.3)'; noFeaturesEl.style.borderRadius = '12px'; noFeaturesEl.style.border = '1px solid rgba(79, 99, 135, 0.15)'; noFeaturesEl.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.1)'; const icon = document.createElement('i'); icon.className = 'fas fa-info-circle'; icon.style.fontSize = '32px'; icon.style.marginBottom = '15px'; icon.style.color = '#60a5fa'; const text = document.createElement('div'); text.textContent = 'No feature details available for this analysis.'; text.style.fontSize = '1rem'; noFeaturesEl.appendChild(icon); noFeaturesEl.appendChild(text); featureDetailsContainer.appendChild(noFeaturesEl); } } /** * Displays HTML security information in the Content Security Analysis section * @param {Object} data - The analysis data from the API */ function displayHtmlSecurity(data) { const securityRisksContainer = document.getElementById('htmlSecurityRisks'); const securityChecksContainer = document.getElementById('htmlSecurityChecks'); const securityScoreElement = document.getElementById('htmlSecurityScore'); const scoreCircle = document.getElementById('htmlSecurityScoreCircle'); if (!securityRisksContainer || !securityChecksContainer || !securityScoreElement || !scoreCircle) { console.error("HTML Security containers not found"); return; } securityRisksContainer.innerHTML = ''; securityChecksContainer.innerHTML = ''; // Remove any existing info notes to avoid duplication const securityContentContainer = securityScoreElement.closest('.security-analysis-content'); if (securityContentContainer) { const existingInfoNotes = securityContentContainer.querySelectorAll('.content-security-info-note'); existingInfoNotes.forEach(note => note.remove()); } // Add modern informational card about this section const infoNote = document.createElement('div'); infoNote.className = 'content-security-info-note info-note'; infoNote.style.fontSize = '0.9rem'; infoNote.style.color = '#cbd5e1'; infoNote.style.marginTop = '15px'; infoNote.style.marginBottom = '20px'; infoNote.style.padding = '12px 16px'; infoNote.style.backgroundColor = 'rgba(30, 41, 59, 0.4)'; infoNote.style.borderRadius = '10px'; infoNote.style.border = '1px solid rgba(71, 85, 105, 0.2)'; infoNote.style.display = 'flex'; infoNote.style.alignItems = 'center'; infoNote.style.gap = '10px'; infoNote.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.08)'; infoNote.style.animation = 'fadeIn 0.5s ease-out'; // Create info icon const infoIcon = document.createElement('div'); infoIcon.style.display = 'flex'; infoIcon.style.alignItems = 'center'; infoIcon.style.justifyContent = 'center'; infoIcon.style.width = '32px'; infoIcon.style.height = '32px'; infoIcon.style.borderRadius = '50%'; infoIcon.style.backgroundColor = 'rgba(59, 130, 246, 0.2)'; infoIcon.style.color = '#60a5fa'; infoIcon.style.flexShrink = '0'; infoIcon.innerHTML = ''; const infoText = document.createElement('div'); infoText.style.flexGrow = '1'; infoText.style.lineHeight = '1.5'; infoText.textContent = 'This analysis examines the website\'s HTML content for security issues. This is shown for informational purposes only and is not included in the overall risk score.'; infoNote.appendChild(infoIcon); infoNote.appendChild(infoText); // Insert the info note after the score display if (securityContentContainer) { securityContentContainer.insertBefore(infoNote, securityContentContainer.children[1]); } // Case 1: HTML security data is not available if (!data.html_security) { const noDataContainer = document.createElement('div'); noDataContainer.className = 'no-data-container'; noDataContainer.style.textAlign = 'center'; noDataContainer.style.padding = '25px 20px'; noDataContainer.style.backgroundColor = 'rgba(15, 23, 42, 0.3)'; noDataContainer.style.borderRadius = '12px'; noDataContainer.style.margin = '15px 0'; const noDataIcon = document.createElement('div'); noDataIcon.innerHTML = ''; const noDataTitle = document.createElement('h4'); noDataTitle.textContent = 'No Content Analysis Available'; noDataTitle.style.fontSize = '1.1rem'; noDataTitle.style.fontWeight = '600'; noDataTitle.style.marginBottom = '8px'; const noDataText = document.createElement('p'); noDataText.textContent = 'The system was unable to analyze the website content.'; noDataText.style.fontSize = '0.9rem'; noDataText.style.opacity = '0.8'; noDataContainer.appendChild(noDataIcon); noDataContainer.appendChild(noDataTitle); noDataContainer.appendChild(noDataText); securityRisksContainer.appendChild(noDataContainer); // Set default score securityScoreElement.textContent = "N/A"; scoreCircle.className = 'score-circle'; return; } // Case 2: HTML security analysis encountered an error if (data.html_security.risk_factors && data.html_security.risk_factors.length > 0 && data.html_security.risk_factors[0].includes("Error analyzing content")) { // Display error message with better styling const errorContainer = document.createElement('div'); errorContainer.className = 'error-container'; errorContainer.style.display = 'flex'; errorContainer.style.padding = '16px'; errorContainer.style.backgroundColor = 'rgba(239, 68, 68, 0.1)'; errorContainer.style.borderRadius = '10px'; errorContainer.style.borderLeft = '4px solid #ef4444'; errorContainer.style.margin = '10px 0'; errorContainer.style.alignItems = 'flex-start'; errorContainer.style.gap = '12px'; const errorIcon = document.createElement('div'); errorIcon.className = 'error-icon'; errorIcon.innerHTML = ''; const errorContent = document.createElement('div'); errorContent.className = 'error-content'; errorContent.style.flexGrow = '1'; const errorTitle = document.createElement('div'); errorTitle.className = 'error-title'; errorTitle.textContent = 'Content Analysis Error'; errorTitle.style.fontWeight = '600'; errorTitle.style.marginBottom = '4px'; errorTitle.style.fontSize = '1rem'; const errorDetails = document.createElement('div'); errorDetails.className = 'error-details'; errorDetails.textContent = data.html_security.risk_factors[0]; errorDetails.style.fontSize = '0.9rem'; errorDetails.style.opacity = '0.9'; errorDetails.style.lineHeight = '1.5'; errorContent.appendChild(errorTitle); errorContent.appendChild(errorDetails); errorContainer.appendChild(errorIcon); errorContainer.appendChild(errorContent); securityRisksContainer.appendChild(errorContainer); // Update score to reflect the error risk const errorScore = data.html_security.content_score || 25; securityScoreElement.textContent = errorScore; // Update the progress circle updateScoreCircle(errorScore); return; } // Case 3: Normal operation - Update the score const contentScore = data.html_security.content_score || 0; securityScoreElement.textContent = contentScore; // Update the progress circle updateScoreCircle(contentScore); // Display risk factors with improved styles const riskFactors = data.html_security.risk_factors || []; if (riskFactors.length === 0) { // Create a nice "no issues found" card const safeContainer = document.createElement('div'); safeContainer.className = 'safe-container'; safeContainer.style.display = 'flex'; safeContainer.style.flexDirection = 'column'; safeContainer.style.alignItems = 'center'; safeContainer.style.justifyContent = 'center'; safeContainer.style.padding = '30px 20px'; safeContainer.style.backgroundColor = 'rgba(16, 185, 129, 0.1)'; safeContainer.style.borderRadius = '12px'; safeContainer.style.textAlign = 'center'; safeContainer.style.margin = '15px 0'; safeContainer.style.border = '1px solid rgba(16, 185, 129, 0.3)'; safeContainer.style.animation = 'fadeIn 0.6s ease-out'; const checkIcon = document.createElement('div'); checkIcon.innerHTML = ''; const safeTitle = document.createElement('h4'); safeTitle.textContent = 'No Content Security Issues Detected'; safeTitle.style.fontSize = '1.2rem'; safeTitle.style.fontWeight = '600'; safeTitle.style.marginBottom = '10px'; const safeDescription = document.createElement('p'); safeDescription.textContent = 'The website\'s HTML content appears to be safe with no suspicious elements found.'; safeDescription.style.fontSize = '0.95rem'; safeDescription.style.maxWidth = '380px'; safeDescription.style.lineHeight = '1.6'; safeDescription.style.opacity = '0.9'; safeContainer.appendChild(checkIcon); safeContainer.appendChild(safeTitle); safeContainer.appendChild(safeDescription); securityRisksContainer.appendChild(safeContainer); } else { // Create a styled container for risk factors const riskFactorsListContainer = document.createElement('div'); riskFactorsListContainer.className = 'risk-factors-list'; riskFactorsListContainer.style.animation = 'fadeIn 0.6s ease-out'; riskFactors.forEach((risk, index) => { const riskItem = document.createElement('div'); riskItem.className = 'risk-item'; riskItem.style.backgroundColor = 'rgba(30, 41, 59, 0.4)'; riskItem.style.borderRadius = '10px'; riskItem.style.padding = '16px'; riskItem.style.marginBottom = '12px'; riskItem.style.display = 'flex'; riskItem.style.alignItems = 'flex-start'; riskItem.style.gap = '14px'; riskItem.style.transition = 'transform 0.2s ease'; riskItem.style.cursor = 'default'; riskItem.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.05)'; riskItem.style.animation = `fadeIn 0.${6 + index}s ease-out`; // Add hover effect riskItem.onmouseover = function() { this.style.transform = 'translateY(-2px)'; this.style.boxShadow = '0 8px 16px rgba(0, 0, 0, 0.08)'; }; riskItem.onmouseout = function() { this.style.transform = 'translateY(0)'; this.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.05)'; }; // Determine severity based on content const isHighSeverity = risk.toLowerCase().includes("password") || risk.toLowerCase().includes("form") || risk.toLowerCase().includes("insecure") || index === 0; const isMediumSeverity = risk.toLowerCase().includes("script") || risk.toLowerCase().includes("obfuscated") || risk.toLowerCase().includes("iframe") || risk.toLowerCase().includes("external"); let severityColor, severityIconClass, severityIconBg; if (isHighSeverity) { severityColor = '#ef4444'; severityIconClass = 'fa-exclamation-circle'; severityIconBg = 'rgba(239, 68, 68, 0.2)'; riskItem.style.borderLeft = '4px solid #ef4444'; } else if (isMediumSeverity) { severityColor = '#f59e0b'; severityIconClass = 'fa-exclamation'; severityIconBg = 'rgba(245, 158, 11, 0.2)'; riskItem.style.borderLeft = '4px solid #f59e0b'; } else { severityColor = '#3b82f6'; severityIconClass = 'fa-info-circle'; severityIconBg = 'rgba(59, 130, 246, 0.2)'; riskItem.style.borderLeft = '4px solid #3b82f6'; } // Create icon container const iconContainer = document.createElement('div'); iconContainer.className = 'risk-icon'; iconContainer.style.width = '36px'; iconContainer.style.height = '36px'; iconContainer.style.borderRadius = '50%'; iconContainer.style.backgroundColor = severityIconBg; iconContainer.style.display = 'flex'; iconContainer.style.alignItems = 'center'; iconContainer.style.justifyContent = 'center'; iconContainer.style.flexShrink = '0'; iconContainer.innerHTML = ``; // Create content container const contentContainer = document.createElement('div'); contentContainer.className = 'risk-content'; contentContainer.style.flexGrow = '1'; // Create risk type label const riskTypeLabel = document.createElement('div'); riskTypeLabel.className = 'risk-type'; riskTypeLabel.textContent = isHighSeverity ? 'High Risk Factor' : isMediumSeverity ? 'Medium Risk Factor' : 'Low Risk Factor'; riskTypeLabel.style.fontSize = '0.8rem'; riskTypeLabel.style.color = severityColor; riskTypeLabel.style.fontWeight = '600'; riskTypeLabel.style.marginBottom = '6px'; riskTypeLabel.style.textTransform = 'uppercase'; riskTypeLabel.style.letterSpacing = '0.03em'; // Create risk description const riskDescription = document.createElement('div'); riskDescription.className = 'risk-description'; riskDescription.textContent = risk; riskDescription.style.fontSize = '0.95rem'; riskDescription.style.lineHeight = '1.5'; // Assemble the components contentContainer.appendChild(riskTypeLabel); contentContainer.appendChild(riskDescription); riskItem.appendChild(iconContainer); riskItem.appendChild(contentContainer); riskFactorsListContainer.appendChild(riskItem); }); securityRisksContainer.appendChild(riskFactorsListContainer); } // Add a style tag for animations const styleTag = document.createElement('style'); styleTag.textContent = ` @keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } } @keyframes slideIn { from { opacity: 0; transform: translateX(-10px); } to { opacity: 1; transform: translateX(0); } } `; document.head.appendChild(styleTag); // Display security checks with improved styles const securityChecks = data.html_security.security_checks || []; // Create a container for security checks const checksContainer = document.createElement('div'); checksContainer.className = 'security-checks-container'; checksContainer.style.display = 'flex'; checksContainer.style.flexDirection = 'column'; checksContainer.style.gap = '10px'; checksContainer.style.marginTop = '10px'; if (securityChecks.length > 0) { securityChecks.forEach((check, index) => { const checkItem = document.createElement('div'); checkItem.className = 'security-check-item'; checkItem.style.display = 'flex'; checkItem.style.alignItems = 'center'; checkItem.style.gap = '12px'; checkItem.style.padding = '10px 12px'; checkItem.style.backgroundColor = 'rgba(16, 185, 129, 0.1)'; checkItem.style.borderRadius = '8px'; checkItem.style.animation = `slideIn 0.${4 + index}s ease-out`; const checkIcon = document.createElement('div'); checkIcon.className = 'check-icon'; checkIcon.style.width = '28px'; checkIcon.style.height = '28px'; checkIcon.style.borderRadius = '50%'; checkIcon.style.backgroundColor = 'rgba(16, 185, 129, 0.2)'; checkIcon.style.display = 'flex'; checkIcon.style.alignItems = 'center'; checkIcon.style.justifyContent = 'center'; checkIcon.style.flexShrink = '0'; checkIcon.innerHTML = ''; const checkText = document.createElement('div'); checkText.className = 'check-text'; checkText.textContent = check; checkText.style.fontSize = '0.9rem'; checkText.style.lineHeight = '1.4'; checkItem.appendChild(checkIcon); checkItem.appendChild(checkText); checksContainer.appendChild(checkItem); }); } else { const noChecksItem = document.createElement('div'); noChecksItem.className = 'no-checks-item'; noChecksItem.style.display = 'flex'; noChecksItem.style.alignItems = 'center'; noChecksItem.style.gap = '12px'; noChecksItem.style.padding = '12px 14px'; noChecksItem.style.backgroundColor = 'rgba(71, 85, 105, 0.2)'; noChecksItem.style.borderRadius = '8px'; noChecksItem.style.animation = 'slideIn 0.4s ease-out'; const infoIcon = document.createElement('div'); infoIcon.className = 'info-icon'; infoIcon.style.width = '28px'; infoIcon.style.height = '28px'; infoIcon.style.borderRadius = '50%'; infoIcon.style.backgroundColor = 'rgba(71, 85, 105, 0.2)'; infoIcon.style.display = 'flex'; infoIcon.style.alignItems = 'center'; infoIcon.style.justifyContent = 'center'; infoIcon.style.flexShrink = '0'; infoIcon.innerHTML = ''; const infoText = document.createElement('div'); infoText.className = 'info-text'; infoText.textContent = 'No specific security checks passed'; infoText.style.fontSize = '0.9rem'; infoText.style.color = '#cbd5e1'; noChecksItem.appendChild(infoIcon); noChecksItem.appendChild(infoText); checksContainer.appendChild(noChecksItem); } securityChecksContainer.appendChild(checksContainer); } /** * Updates the score circle visualization for HTML security analysis * @param {number} score - The content risk score */ function updateScoreCircle(score) { const circle = document.getElementById('htmlSecurityScoreCircle'); if (!circle) return; // Ensure the progress value is properly set const progressValue = score + '%'; // Define risk level if (score < 30) { circle.className = 'score-circle low-risk'; circle.style.setProperty('--progress', progressValue); circle.style.setProperty('--color', 'var(--success-color)'); } else if (score < 70) { circle.className = 'score-circle medium-risk'; circle.style.setProperty('--progress', progressValue); circle.style.setProperty('--color', 'var(--warning-color)'); } else { circle.className = 'score-circle high-risk'; circle.style.setProperty('--progress', progressValue); circle.style.setProperty('--color', 'var(--danger-color)'); } // Force reflow to ensure the animation takes effect void circle.offsetWidth; // Make sure the score text is set and visible const scoreText = document.getElementById('htmlSecurityScore'); if (scoreText) { scoreText.textContent = score; scoreText.style.visibility = 'visible'; } } /** * Displays all results from the analysis * @param {Object} data - The analysis data from the API */ function displayAllResults(data) { console.log("Received data for display:", data); // Validate data before attempting to display if (!data || typeof data !== 'object') { console.error("Invalid data object received:", data); throw new Error("Invalid or missing data object"); } // Show the results section const resultsSection = document.getElementById('results'); if (resultsSection) { resultsSection.style.display = 'block'; } else { console.error("Results section not found with ID 'results'"); } // Ensure we have the necessary data structures to prevent errors data.domain_info = data.domain_info || {}; data.suspicious_patterns = data.suspicious_patterns || []; data.html_security = data.html_security || {}; data.section_totals = data.section_totals || {}; data.feature_table = data.feature_table || []; data.feature_contributions = data.feature_contributions || []; // Check for unresolvable IP before displaying results const domainInfo = data.domain_info; const ipNotResolved = !domainInfo.ip_address || domainInfo.ip_address === 'Unknown' || domainInfo.ip_address === 'Could not resolve'; if (ipNotResolved) { try { increaseIPResolutionRisk(data); } catch (error) { console.error("Error adjusting IP resolution risk:", error); } } // Calculate section scores and update the overall score first try { displaySectionRiskMeters(data); } catch (error) { console.error("Error displaying section risk meters:", error); } // Display all components with error handling for each section try { displayRiskFactors(data); } catch (error) { console.error("Error displaying risk factors:", error); } try { displayDomainInfo(data); } catch (error) { console.error("Error displaying domain info:", error); } try { displaySuspiciousPatterns(data); } catch (error) { console.error("Error displaying suspicious patterns:", error); } try { displayFeatureDetails(data); } catch (error) { console.error("Error displaying feature details:", error); } try { displayHtmlSecurity(data); } catch (error) { console.error("Error displaying HTML security:", error); } // Update the result URL const resultUrlElement = document.getElementById('resultUrl'); if (resultUrlElement && data.url) { resultUrlElement.textContent = data.url; } // Scroll to results if (resultsSection) { resultsSection.scrollIntoView({ behavior: 'smooth' }); } } /** * Displays section risk meters based on section totals * @param {Object} data - The analysis data from the API */ function displaySectionRiskMeters(data) { // Get section totals from the data const sectionTotals = data.section_totals || {}; // Get section scores const keyRiskScore = sectionTotals["Key Risk Factors (AI Predict)"] || sectionTotals["Key Risk Factors"] || 0; const domainInfoScore = sectionTotals["Domain Information"] || 0; // Check if there are any suspicious patterns const hasPatterns = data.suspicious_patterns && data.suspicious_patterns.length > 0; // Force suspicious patterns score to 0 if no patterns were detected const suspiciousPatternsScore = hasPatterns ? (sectionTotals["Suspicious Patterns"] || 0) : 0; // Calculate the total risk from all sections const totalRisk = keyRiskScore + domainInfoScore + suspiciousPatternsScore; // Update the overall fraud risk score to be the sum of the three sections data.score = totalRisk; console.log("Section scores:", { keyRiskScore, domainInfoScore, suspiciousPatternsScore, hasPatterns, totalRisk }); // Display each section's risk contribution updateSectionRiskMeter('keyRiskFactors', keyRiskScore, totalRisk); updateSectionRiskMeter('domainInfo', domainInfoScore, totalRisk); updateSectionRiskMeter('suspiciousPatterns', suspiciousPatternsScore, totalRisk); // Update the overall score display with the new calculated total const riskScoreElement = document.getElementById('riskScore'); if (riskScoreElement) { riskScoreElement.textContent = Math.round(totalRisk) + '%'; // Update risk level const riskTagElement = document.getElementById('riskTag'); let riskClass, riskText; if (totalRisk < 30) { riskClass = 'risk-low'; riskText = 'Low Risk'; } else if (totalRisk < 60) { riskClass = 'risk-medium'; riskText = 'Medium Risk'; } else { riskClass = 'risk-high'; riskText = 'High Risk'; } if (riskTagElement) { riskTagElement.textContent = riskText; riskTagElement.className = `risk-tag ${riskClass}`; } // Update score display container const scoreDisplayContainer = document.getElementById('scoreDisplayContainer'); if (scoreDisplayContainer) { scoreDisplayContainer.className = `score-display ${riskClass}`; } // Update meter const scoreMeter = document.getElementById('scoreMeter'); const meterLabel = document.getElementById('meterLabel'); if (scoreMeter) { scoreMeter.style.width = `${totalRisk}%`; scoreMeter.className = `meter-fill ${totalRisk < 30 ? 'low' : totalRisk < 60 ? 'medium' : 'high'}`; } if (meterLabel) { meterLabel.textContent = `${Math.round(totalRisk)}%`; } } // Add an explanation about the risk score breakdown const resultsSummary = document.querySelector('.results-summary'); if (resultsSummary) { // Remove any existing risk breakdown explanation const existingExplanation = resultsSummary.querySelector('.risk-breakdown-explanation'); if (existingExplanation) { resultsSummary.removeChild(existingExplanation); } const breakdownExplanation = document.createElement('div'); breakdownExplanation.className = 'risk-breakdown-explanation'; breakdownExplanation.style.fontSize = '0.9rem'; breakdownExplanation.style.backgroundColor = 'rgba(15, 23, 42, 0.3)'; breakdownExplanation.style.borderRadius = '8px'; breakdownExplanation.style.padding = '12px 16px'; breakdownExplanation.style.marginTop = '20px'; breakdownExplanation.style.color = '#cbd5e1'; breakdownExplanation.style.lineHeight = '1.6'; // Create table-like display for the calculation breakdownExplanation.innerHTML = `
Fraud Risk Score Calculation
URL Features (Key Risk Factors): ${keyRiskScore.toFixed(1)}%
Domain Information: ${domainInfoScore.toFixed(1)}%
Suspicious Patterns: ${suspiciousPatternsScore.toFixed(1)}%
`; // Add a divider and total const totalSection = document.createElement('div'); totalSection.style.borderTop = '1px solid rgba(100, 116, 139, 0.3)'; totalSection.style.marginTop = '10px'; totalSection.style.paddingTop = '10px'; totalSection.style.display = 'flex'; totalSection.style.justifyContent = 'space-between'; totalSection.style.fontWeight = '600'; totalSection.innerHTML = ` Total Fraud Risk Score: ${totalRisk.toFixed(1)}% `; breakdownExplanation.appendChild(totalSection); // Find any existing explanation and replace it resultsSummary.appendChild(breakdownExplanation); } } /** * Updates a section risk meter with the provided score * @param {string} sectionId - Base ID of the section * @param {number} sectionScore - Score for the section * @param {number} totalScore - Total risk score */ function updateSectionRiskMeter(sectionId, sectionScore, totalScore) { const scoreElement = document.getElementById(`${sectionId}Score`); const meterElement = document.getElementById(`${sectionId}Meter`); if (scoreElement && meterElement) { // Round to one decimal place const roundedScore = Math.round(sectionScore * 10) / 10; // Update the score text scoreElement.textContent = `${roundedScore}%`; // Update the meter width meterElement.style.width = `${roundedScore}%`; // Determine and set color based on contribution to total risk let colorClass = 'low'; // Calculate relative contribution const contribution = totalScore > 0 ? (sectionScore / totalScore) * 100 : 0; if (contribution >= 50) { colorClass = 'high'; } else if (contribution >= 25) { colorClass = 'medium'; } // Set color class meterElement.className = `section-meter-fill ${colorClass}`; } } // Helper functions function formatFeatureName(name) { if (!name) return 'Unknown Feature'; // Special case for Https Present if (name === 'https_present' || name === 'Https Present') { return 'Security Weights'; } return name .replace(/_/g, ' ') .replace(/\b\w/g, l => l.toUpperCase()); } function getSeverityClass(severity) { const severityLower = String(severity).toLowerCase(); if (severityLower === 'high') return 'high-severity'; if (severityLower === 'medium') return 'medium-severity'; return 'low-severity'; } function capitalizeFirstLetter(string) { return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase(); } /** * Gets risk level text based on a numeric score * @param {number} score - Risk score (0-100) * @returns {string} - Risk level text (low, medium, high) */ function get_risk_level(score) { const numericScore = parseFloat(score); if (isNaN(numericScore)) return "unknown"; if (numericScore < 30) { return "low"; } else if (numericScore < 60) { return "medium"; } else { return "high"; } } function extractDomainFromData(data) { // Try to get domain from various possible locations if (data.domain) return data.domain; if (data.url) { try { const url = new URL(data.url); return url.hostname; } catch (e) { return data.url; } } if (data.domain_info && data.domain_info.name) return data.domain_info.name; return 'Unknown Domain'; } function extractProtocolFromData(data) { // Try to get protocol from URL if (data.url) { try { const url = new URL(data.url); return url.protocol.replace(':', ''); } catch (e) { return data.url.startsWith('https') ? 'https' : 'http'; } } // Default to unknown return 'Unknown'; } function updateScoreDisplay(score) { const scoreElement = document.getElementById('risk-score'); if (!scoreElement) { console.error("Risk score element not found!"); return; } // Ensure score is a number and within valid range const numericScore = parseFloat(score); if (isNaN(numericScore)) { scoreElement.textContent = 'N/A'; return; } // Normalize to 0-100 if it's a decimal const normalizedScore = numericScore <= 1 ? Math.round(numericScore * 100) : Math.round(numericScore); // Update score text scoreElement.textContent = normalizedScore + '%'; // Update color and risk level let riskClass, riskText; if (normalizedScore < 30) { riskClass = 'risk-low'; riskText = 'Low Risk'; } else if (normalizedScore < 60) { riskClass = 'risk-medium'; riskText = 'Medium Risk'; } else { riskClass = 'risk-high'; riskText = 'High Risk'; } // Update score element class scoreElement.className = `risk-score ${riskClass}`; // Update risk level text const riskLevelElement = document.getElementById('risk-level'); if (riskLevelElement) { riskLevelElement.textContent = riskText; riskLevelElement.className = `risk-label ${riskClass}`; } // Update score display container const scoreDisplayElement = document.getElementById('score-display'); if (scoreDisplayElement) { scoreDisplayElement.className = `score-display ${riskClass}`; } // Update progress bar if it exists const progressBarElement = document.getElementById('score-progress-bar'); if (progressBarElement) { progressBarElement.style.width = `${normalizedScore}%`; // Update color class if (normalizedScore < 30) { progressBarElement.style.background = 'var(--success-color)'; } else if (normalizedScore < 60) { progressBarElement.style.background = 'linear-gradient(90deg, var(--warning-color), #f97316)'; } else { progressBarElement.style.background = 'var(--danger-color)'; } } // Update progress label const progressLabelElement = document.getElementById('score-progress-label'); if (progressLabelElement) { progressLabelElement.textContent = `${normalizedScore}%`; } } /** * Creates a severity badge for suspicious patterns * @param {number} count - Number of patterns with this severity * @param {string} severity - Severity level ('high', 'medium', 'low') * @param {string} label - Display label * @returns {HTMLElement} - Badge element */ function createSeverityBadge(count, severity, label) { const badge = document.createElement('div'); badge.className = `severity-badge ${severity}`; badge.style.padding = '5px 10px'; badge.style.borderRadius = '12px'; badge.style.marginRight = '8px'; badge.style.display = 'flex'; badge.style.alignItems = 'center'; badge.style.justifyContent = 'center'; badge.style.fontSize = '0.85rem'; badge.style.fontWeight = 'bold'; const colors = { high: { bg: 'rgba(239, 68, 68, 0.15)', text: '#f87171', icon: 'fa-exclamation-circle' }, medium: { bg: 'rgba(245, 158, 11, 0.15)', text: '#fbbf24', icon: 'fa-exclamation-triangle' }, low: { bg: 'rgba(16, 185, 129, 0.15)', text: '#34d399', icon: 'fa-info-circle' } }; badge.style.backgroundColor = colors[severity].bg; badge.style.color = colors[severity].text; // Add icon instead of any text that could be interpreted as an X const icon = document.createElement('i'); icon.className = `fas ${colors[severity].icon}`; icon.style.marginRight = '5px'; icon.style.fontSize = '0.9rem'; const countSpan = document.createElement('span'); countSpan.textContent = `${count} ${label}`; badge.appendChild(icon); badge.appendChild(countSpan); return badge; } // Public API return { displayAllResults, displayRiskFactors, displayDomainInfo, displaySuspiciousPatterns, displayFeatureDetails, displayHtmlSecurity, increaseIPResolutionRisk }; })(); // Make display functions globally available window.displayResults = displayResults;