// Global state let currentResults = null; // DOM Elements const crawlerForm = document.getElementById('crawlerForm'); const urlInput = document.getElementById('urlInput'); const crawlBtn = document.getElementById('crawlBtn'); const loadingSection = document.getElementById('loadingSection'); const resultsSection = document.getElementById('resultsSection'); const errorSection = document.getElementById('errorSection'); const crawlerStatus = document.getElementById('crawlerStatus'); const exportBtn = document.getElementById('exportBtn'); // Initialize app document.addEventListener('DOMContentLoaded', function() { checkCrawlerStatus(); setupEventListeners(); setupTabs(); }); // Check crawler service status async function checkCrawlerStatus() { try { const response = await fetch('/api/health'); const data = await response.json(); if (data.status === 'ok') { updateStatusIndicator('online', 'Crawler Online'); } else { updateStatusIndicator('offline', 'Crawler Offline'); } } catch (error) { updateStatusIndicator('offline', 'Crawler Offline'); } } // Update status indicator function updateStatusIndicator(status, text) { crawlerStatus.className = `status-indicator ${status}`; crawlerStatus.querySelector('span').textContent = text; } // Setup event listeners function setupEventListeners() { crawlerForm.addEventListener('submit', handleCrawlSubmit); exportBtn.addEventListener('click', exportResults); // Auto-check status every 30 seconds setInterval(checkCrawlerStatus, 30000); } // Setup tabs functionality function setupTabs() { const tabButtons = document.querySelectorAll('.tab-btn'); const tabPanes = document.querySelectorAll('.tab-pane'); tabButtons.forEach(button => { button.addEventListener('click', () => { const targetTab = button.getAttribute('data-tab'); // Remove active class from all buttons and panes tabButtons.forEach(btn => btn.classList.remove('active')); tabPanes.forEach(pane => pane.classList.remove('active')); // Add active class to clicked button and corresponding pane button.classList.add('active'); document.getElementById(targetTab).classList.add('active'); }); }); } // Handle crawl form submission async function handleCrawlSubmit(e) { e.preventDefault(); const url = urlInput.value.trim(); if (!url) { showError('Por favor, insira uma URL válida.'); return; } // Validate URL format try { new URL(url); } catch { showError('Por favor, insira uma URL válida (ex: https://example.com).'); return; } await startCrawling(url); } // Start crawling process async function startCrawling(url) { showLoading(); updateLoadingMessage('🚀 Iniciando clonagem profissional com WGET...'); try { // SEM TIMEOUT - permite clonagens longas // Removido AbortController para permitir clonagens que demoram mais de 10 minutos // Atualizar mensagem periodicamente const messages = [ '🔍 Analisando estrutura do site...', '📄 Baixando páginas HTML...', '📦 Baixando imagens e assets...', '🎨 Baixando CSS e fontes...', '⚡ Baixando JavaScript...', '🔗 Convertendo links para locais...', '⏳ Aguarde, clonagem profissional em andamento...' ]; let messageIndex = 0; const messageInterval = setInterval(() => { messageIndex = (messageIndex + 1) % messages.length; updateLoadingMessage(messages[messageIndex]); }, 4000); // Tentar usar wget primeiro let response; try { updateLoadingMessage('🔧 Usando WGET para clonagem profissional...'); response = await fetch('/api/wget-clone', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url }) // SEM signal/timeout - permite clonagens longas }); } catch (wgetError) { // Se wget falhar, usar crawler normal console.log('WGET não disponível, usando crawler padrão'); updateLoadingMessage('🔄 Usando crawler alternativo...'); response = await fetch('/api/crawl', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url }) // SEM signal/timeout - permite clonagens longas }); } // Removido clearTimeout pois não há mais timeout clearInterval(messageInterval); const data = await response.json(); if (data.success) { updateLoadingMessage('✅ Processando resultados...'); setTimeout(() => { currentResults = data.data; showResults(data.data); }, 1000); } else { throw new Error(data.error || 'Erro desconhecido no crawling'); } } catch (error) { console.error('Crawling error:', error); if (error.name === 'AbortError') { showError('⏱️ Timeout: A clonagem demorou mais de 10 minutos. Tente novamente com um site menor.'); } else { showError(error.message || 'Erro ao conectar com o serviço de crawling'); } } } // Show loading state function showLoading() { hideAllSections(); loadingSection.style.display = 'block'; } // Update loading message function updateLoadingMessage(message) { const loadingMessage = document.getElementById('loadingMessage'); if (loadingMessage) { loadingMessage.textContent = message; } } // Show results function showResults(data) { hideAllSections(); resultsSection.style.display = 'block'; // Populate stats populateStats(data); // Populate overview populateOverview(data); // Populate links populateLinks(data.links || []); // Populate images populateImages(data.images || []); // Populate raw data populateRawData(data); } // Populate statistics function populateStats(data) { const statsGrid = document.getElementById('statsGrid'); const stats = [ { label: 'Status Code', value: data.statusCode || 'N/A', icon: 'fas fa-check-circle' }, { label: 'Links Encontrados', value: (data.links || []).length, icon: 'fas fa-link' }, { label: 'Imagens Encontradas', value: (data.images || []).length, icon: 'fas fa-image' }, { label: 'Tamanho do Conteúdo', value: formatBytes(data.contentLength || 0), icon: 'fas fa-file-alt' } ]; statsGrid.innerHTML = stats.map(stat => `
O site foi clonado e salvo localmente com todos os assets.
Localização: ${data.cloneInfo.directory}
Nenhum link encontrado.
'; return; } linksList.innerHTML = links.map(link => ` `).join(''); } // Populate images function populateImages(images) { const imagesGrid = document.querySelector('#images .images-grid'); if (images.length === 0) { imagesGrid.innerHTML = 'Nenhuma imagem encontrada.
'; return; } imagesGrid.innerHTML = images.map(imageUrl => `