// 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:
- Key Risk Factors (AI Predict): Core URL and domain characteristics (40% of score)
- Domain Information: Registration and hosting details (10% of score)
- Suspicious Patterns: Security issues and content analysis (50% of score)
`;
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;