IA-Phishing / static /script.js
janiel01's picture
Upload 18 files
99e98ea verified
document.addEventListener('DOMContentLoaded', () => {
const urlForm = document.getElementById('url-form');
const urlInput = document.getElementById('url-input');
const textInput = document.getElementById('text-input');
const htmlInput = document.getElementById('html-input');
const imageUpload = document.getElementById('image-upload');
const imagePreview = document.getElementById('image-preview');
const previewImg = document.getElementById('preview-img');
const removeImageBtn = document.getElementById('remove-image');
const analyzeBtn = document.getElementById('analyze-btn');
const loadingSpinner = document.getElementById('loading');
const resultCard = document.getElementById('result-card');
const omniPlusBtn = document.getElementById('omni-plus-btn');
const omniMenu = document.getElementById('omni-menu');
const omniMenuItems = document.querySelectorAll('.omni-menu-item');
const sidebar = document.querySelector('.sidebar');
const sidebarToggle = document.getElementById('sidebar-toggle');
const navAnalyzer = document.getElementById('nav-analyzer');
let currentMode = 'url';
let selectedFile = null;
// --- Sidebar & Navigation ---
const overlay = document.createElement('div');
overlay.className = 'sidebar-overlay';
document.body.appendChild(overlay);
// All sidebar toggle buttons (analyzer page + stats page)
document.querySelectorAll('.sidebar-toggle-btn').forEach(btn => {
btn.addEventListener('click', () => {
const isActive = sidebar.classList.toggle('active');
overlay.style.display = isActive ? 'block' : 'none';
});
});
overlay.addEventListener('click', () => {
sidebar.classList.remove('active');
overlay.style.display = 'none';
});
// Close button inside sidebar
const sidebarClose = document.getElementById('sidebar-close');
if (sidebarClose) {
sidebarClose.addEventListener('click', () => {
sidebar.classList.remove('active');
overlay.style.display = 'none';
});
}
if (navAnalyzer) {
navAnalyzer.addEventListener('click', (e) => {
e.preventDefault();
showPage('analyzer');
if (window.innerWidth <= 1024) sidebar.classList.remove('active');
overlay.style.display = 'none';
});
}
// --- Page Navigation ---
const analyzerPage = document.querySelector('.main-content:not(#stats-page)');
const statsPage = document.getElementById('stats-page');
const navStats = document.getElementById('nav-stats');
const statsBackBtn = document.getElementById('stats-back-btn');
function showPage(page) {
if (page === 'stats') {
analyzerPage.classList.add('d-none');
statsPage.classList.remove('d-none');
navAnalyzer.classList.remove('active');
navStats.classList.add('active');
fetchStats();
} else {
statsPage.classList.add('d-none');
analyzerPage.classList.remove('d-none');
navStats.classList.remove('active');
navAnalyzer.classList.add('active');
}
window.scrollTo({ top: 0, behavior: 'smooth' });
}
if (navStats) {
navStats.addEventListener('click', (e) => {
e.preventDefault();
showPage('stats');
if (window.innerWidth <= 1024) sidebar.classList.remove('active');
overlay.style.display = 'none';
});
}
if (statsBackBtn) {
statsBackBtn.addEventListener('click', () => showPage('analyzer'));
}
// --- Omni-Menu Logic ---
omniPlusBtn.addEventListener('click', (e) => {
e.stopPropagation();
omniMenu.classList.toggle('active');
});
document.addEventListener('click', () => {
omniMenu.classList.remove('active');
});
omniMenuItems.forEach(item => {
item.addEventListener('click', () => {
const mode = item.dataset.mode;
if (mode) switchMode(mode);
omniMenu.classList.remove('active');
});
});
function switchMode(mode) {
currentMode = mode;
// Hide all
urlInput.classList.add('d-none');
textInput.classList.add('d-none');
htmlInput.classList.add('d-none');
// Remove 'required' from all
urlInput.removeAttribute('required');
textInput.removeAttribute('required');
htmlInput.removeAttribute('required');
// Show selected
if (mode === 'url') {
urlInput.classList.remove('d-none');
urlInput.setAttribute('required', '');
urlInput.focus();
} else if (mode === 'text') {
textInput.classList.remove('d-none');
textInput.setAttribute('required', '');
textInput.focus();
} else if (mode === 'html') {
htmlInput.classList.remove('d-none');
htmlInput.setAttribute('required', '');
htmlInput.focus();
}
}
// --- File/Image Handling ---
imageUpload.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file && file.type.startsWith('image/')) {
selectedFile = file;
const reader = new FileReader();
reader.onload = (e) => {
previewImg.src = e.target.result;
imagePreview.classList.remove('d-none');
};
reader.readAsDataURL(file);
}
});
removeImageBtn.addEventListener('click', () => {
selectedFile = null;
imageUpload.value = '';
imagePreview.classList.add('d-none');
});
// --- Analysis Submission ---
urlForm.addEventListener('submit', async (e) => {
e.preventDefault();
const url = urlInput.value.trim();
const text = textInput.value.trim();
const html = htmlInput.value.trim();
if (currentMode === 'url' && !url && !selectedFile) return;
if (currentMode === 'text' && !text) return;
if (currentMode === 'html' && !html) return;
// Reset UI
resultCard.style.display = 'none';
loadingSpinner.style.display = 'block';
analyzeBtn.disabled = true;
try {
const formData = new FormData();
if (currentMode === 'url' && url) formData.append('url', url);
if (currentMode === 'text' && text) formData.append('text', text);
if (currentMode === 'html' && html) formData.append('html', html);
if (selectedFile) formData.append('image', selectedFile);
const response = await fetch('/predict', {
method: 'POST',
body: formData // Fetch automatically sets content-type for FormData
});
const data = await response.json();
if (data.error) {
showError(data.error);
} else {
showResult(data);
fetchHistory();
}
} catch (error) {
showError('Erro ao conectar com o serviço de análise multi-modal.');
console.error(error);
} finally {
loadingSpinner.style.display = 'none';
analyzeBtn.disabled = false;
}
});
function showResult(data) {
const isPhishing = data.result === 'Phishing';
const isSuspect = data.result === 'Suspeito';
const colorClass = isPhishing ? 'is-phishing' : (isSuspect ? 'text-warning' : 'is-safe');
const borderColor = isPhishing ? 'var(--danger)' : (isSuspect ? 'orange' : 'var(--success)');
const icon = isPhishing ? 'fa-shield-virus' : (isSuspect ? 'fa-exclamation-triangle' : 'fa-shield-check');
const statusText = isPhishing ? 'Potencial Phishing' : (isSuspect ? 'Análise Suspeita' : 'URL Legítima');
let agentsHtml = '';
if (data.agent_details) {
agentsHtml = `
<div class="mt-4">
<h6 class="text-muted mb-3" style="font-size: 0.8rem; text-transform: uppercase; letter-spacing: 1px;">Insights dos Agentes</h6>
<div class="d-flex flex-column gap-2">
${data.agent_details.map(agent => `
<div class="glass p-2 px-3 rounded-3" style="border-left: 3px solid ${agent.result === 'Phishing' ? 'var(--danger)' : 'var(--success)'}; font-size: 0.85rem;">
<div class="d-flex justify-content-between align-items-center mb-1">
<strong>${agent.agent}</strong>
<span class="badge ${agent.result === 'Phishing' ? 'bg-danger' : (agent.result === 'Legítima' || agent.result === 'Safe' ? 'bg-success' : 'bg-secondary')}" style="font-size: 0.65rem;">${agent.result}</span>
</div>
${agent.findings && agent.findings.length > 0 ? `
<ul class="mb-0 ps-3 text-muted" style="font-size: 0.75rem;">
${agent.findings.map(f => `<li>${f}</li>`).join('')}
</ul>
` : ''}
</div>
`).join('')}
</div>
</div>
`;
}
resultCard.innerHTML = `
<div class="result-header">
<div class="status-icon glass ${colorClass}">
<i class="fas ${icon}"></i>
</div>
<div>
<h3 style="color: ${borderColor}">${statusText}</h3>
<p class="text-muted" style="font-size: 0.9rem; word-break: break-all;">${data.url}</p>
</div>
</div>
${data.confidence ? `
<div class="confidence-meter mt-3">
<div class="d-flex justify-content-between mb-2">
<span>Score de Risco Consolidado</span>
<span>${data.confidence}%</span>
</div>
<div style="height: 6px; background: rgba(255,255,255,0.1); border-radius: 10px; overflow: hidden;">
<div style="width: ${data.confidence}%; height: 100%; background: ${borderColor}; transition: width 1s ease-out;"></div>
</div>
</div>
` : ''}
<div class="mt-4 p-3 glass" style="border-radius: 12px; font-size: 0.85rem; color: var(--text-muted); border-left: 4px solid var(--primary);">
<i class="fas fa-info-circle me-2"></i>
Decisão consolidada: <strong>${data.description}</strong>
</div>
${agentsHtml}
`;
resultCard.style.display = 'block';
}
function showError(message) {
resultCard.innerHTML = `
<div class="alert alert-danger glass" style="border-color: var(--danger); color: var(--danger);">
<i class="fas fa-exclamation-triangle me-2"></i> ${message}
</div>
`;
resultCard.style.display = 'block';
}
// --- History Logic ---
const historyContainer = document.getElementById('history-container');
const viewMoreContainer = document.getElementById('view-more-container');
const viewMoreBtn = document.getElementById('view-more-btn');
let allHistoryItems = [];
let showingAll = false;
async function fetchHistory() {
try {
const response = await fetch('/history');
const data = await response.json();
if (!data.error) {
allHistoryItems = data;
renderHistory();
}
} catch (error) {
console.error('Erro ao buscar histórico:', error);
}
}
function renderHistory() {
if (!allHistoryItems || allHistoryItems.length === 0) {
historyContainer.innerHTML = `
<div class="text-center py-5 text-muted glass rounded-4">
<i class="fas fa-search mb-2 d-block fs-3"></i>
<p class="mb-0">Nenhuma busca recente encontrada.</p>
</div>
`;
viewMoreContainer.classList.add('d-none');
return;
}
const itemsToShow = showingAll ? allHistoryItems : allHistoryItems.slice(0, 2);
historyContainer.innerHTML = itemsToShow.map((item, index) => {
const isPhishing = item.result === 'Phishing';
const isSuspect = item.result === 'Suspeito';
const colorClass = isPhishing ? 'phishing' : (isSuspect ? 'suspect' : 'safe');
const confidenceText = item.confidence ? `${item.confidence}%` : 'N/A';
const desc = item.description || 'Sem detalhes disponíveis para esta análise.';
return `
<div class="history-card glass rounded-4 mb-3">
<div class="history-item" onclick="toggleHistoryDetail(${index})">
<div class="history-info">
<span class="history-url">${item.url}</span>
<div class="history-meta">
<span class="history-status-dot dot-${colorClass}"></span>
<span>${item.result}</span>
<span><i class="far fa-clock me-1"></i>${item.timestamp}</span>
</div>
</div>
<div class="d-flex align-items-center gap-2">
<div class="history-badge badge-${colorClass}">
${item.result}
</div>
<i class="fas fa-chevron-down history-chevron" id="chevron-${index}" style="color: var(--text-muted); font-size: 0.7rem; transition: transform 0.3s ease;"></i>
</div>
</div>
<div class="history-detail" id="detail-${index}" style="display: none;">
<div class="detail-divider"></div>
<div class="detail-content">
<div class="detail-row">
<div class="detail-label"><i class="fas fa-shield-alt me-2"></i>Resultado</div>
<div class="detail-value">
<span class="history-badge badge-${colorClass}">${item.result}</span>
<span class="ms-2" style="font-size: 0.85rem; color: var(--text-muted);">Confiança: <strong>${confidenceText}</strong></span>
</div>
</div>
<div class="detail-row">
<div class="detail-label"><i class="fas fa-brain me-2"></i>Análise da IA</div>
<div class="detail-description">${desc}</div>
</div>
</div>
</div>
</div>
`;
}).join('');
// Show/hide view more button
if (allHistoryItems.length > 2) {
viewMoreContainer.classList.remove('d-none');
viewMoreBtn.innerHTML = showingAll ?
'Ver Menos <i class="fas fa-chevron-up ms-1"></i>' :
`Ver Mais (${allHistoryItems.length - 2} extras) <i class="fas fa-chevron-down ms-1"></i>`;
} else {
viewMoreContainer.classList.add('d-none');
}
}
viewMoreBtn.addEventListener('click', () => {
showingAll = !showingAll;
renderHistory();
});
// --- Statistics Logic ---
async function fetchStats() {
try {
const response = await fetch('/stats');
const data = await response.json();
if (!data.error) renderStats(data);
} catch (error) {
console.error('Erro ao buscar estatísticas:', error);
}
}
function renderStats(data) {
// KPI Values
document.getElementById('stat-total').textContent = data.total;
document.getElementById('stat-phishing').textContent = data.phishing;
document.getElementById('stat-safe').textContent = data.safe;
document.getElementById('stat-rate').textContent = data.detection_rate + '%';
// Breakdown Bars
const total = data.total || 1;
const phishPct = (data.phishing / total * 100).toFixed(0);
const suspPct = (data.suspect / total * 100).toFixed(0);
const safePct = (data.safe / total * 100).toFixed(0);
setTimeout(() => {
document.getElementById('bar-phishing').style.width = phishPct + '%';
document.getElementById('bar-suspect').style.width = suspPct + '%';
document.getElementById('bar-safe').style.width = safePct + '%';
}, 100);
document.getElementById('bar-phishing-val').textContent = data.phishing;
document.getElementById('bar-suspect-val').textContent = data.suspect;
document.getElementById('bar-safe-val').textContent = data.safe;
// Timeline
const timelineEl = document.getElementById('stats-timeline');
if (data.timeline && data.timeline.length > 0) {
timelineEl.innerHTML = data.timeline.map(item => {
const isPhishing = item.result === 'Phishing';
const isSuspect = item.result === 'Suspeito';
const color = isPhishing ? 'var(--danger)' : (isSuspect ? '#f59e0b' : 'var(--success)');
const icon = isPhishing ? 'fa-exclamation-triangle' : (isSuspect ? 'fa-question-circle' : 'fa-check-circle');
return `
<div class="timeline-item" style="border-left-color: ${color};">
<i class="fas ${icon}" style="color: ${color};"></i>
<span class="text-muted">${item.date}</span>
<span style="color: ${color}; font-weight: 600;">${item.result}</span>
</div>
`;
}).join('');
} else {
timelineEl.innerHTML = '<p class="text-muted text-center mb-0">Nenhuma análise registrada ainda.</p>';
}
}
// --- Toggle History Detail ---
window.toggleHistoryDetail = function (index) {
const detail = document.getElementById(`detail-${index}`);
const chevron = document.getElementById(`chevron-${index}`);
if (!detail) return;
const isOpen = detail.style.display !== 'none';
detail.style.display = isOpen ? 'none' : 'block';
if (chevron) {
chevron.style.transform = isOpen ? 'rotate(0deg)' : 'rotate(180deg)';
}
};
// Initialize
fetchHistory();
});