// 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 => `
${stat.value}
${stat.label}
`).join(''); } // Populate overview function populateOverview(data) { const overviewContent = document.querySelector('#overview .overview-content'); const overviewItems = [ { label: 'URL', value: data.url || 'N/A' }, { label: 'Título', value: data.title || 'N/A' }, { label: 'Status Code', value: data.statusCode || 'N/A' }, { label: 'Timestamp', value: formatDate(data.timestamp) }, { label: 'Tamanho do Conteúdo', value: formatBytes(data.contentLength || 0) }, { label: 'Total de Links', value: (data.links || []).length }, { label: 'Total de Imagens', value: (data.images || []).length } ]; // Adicionar informações do clone se disponível if (data.cloneInfo) { overviewItems.push( { label: 'Clone Status', value: data.cloneInfo.success ? '✅ Sucesso' : '❌ Falhou' }, { label: 'Assets Baixados', value: `${data.cloneInfo.assetsDownloaded}/${data.cloneInfo.totalAssets}` }, { label: 'Diretório Local', value: data.cloneInfo.directory } ); } overviewContent.innerHTML = overviewItems.map(item => `
${item.label}: ${item.value}
`).join(''); // Adicionar botão para abrir pasta do clone if (data.cloneInfo && data.cloneInfo.success) { const cloneButton = document.createElement('div'); cloneButton.className = 'clone-actions'; cloneButton.innerHTML = `

🎉 Site Clonado com Sucesso!

O site foi clonado e salvo localmente com todos os assets.

Localização: ${data.cloneInfo.directory}

`; overviewContent.appendChild(cloneButton); } } // Populate links function populateLinks(links) { const linksList = document.querySelector('#links .links-list'); if (links.length === 0) { linksList.innerHTML = '

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 => `
Website Image

Imagem não pôde ser carregada

${imageUrl}
`).join(''); } // Populate raw data function populateRawData(data) { const rawDataElement = document.getElementById('rawData'); rawDataElement.textContent = JSON.stringify(data, null, 2); } // Show error function showError(message) { hideAllSections(); errorSection.style.display = 'block'; document.getElementById('errorMessage').textContent = message; } // Hide all sections function hideAllSections() { loadingSection.style.display = 'none'; resultsSection.style.display = 'none'; errorSection.style.display = 'none'; } // Reset form function resetForm() { hideAllSections(); urlInput.value = ''; urlInput.focus(); currentResults = null; } // Export results function exportResults() { if (!currentResults) { alert('Nenhum resultado para exportar.'); return; } const dataStr = JSON.stringify(currentResults, null, 2); const dataBlob = new Blob([dataStr], { type: 'application/json' }); const url = URL.createObjectURL(dataBlob); const link = document.createElement('a'); link.href = url; link.download = `crawl-results-${new Date().toISOString().split('T')[0]}.json`; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); } // Utility functions function formatBytes(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } function formatDate(dateString) { if (!dateString) return 'N/A'; const date = new Date(dateString); return date.toLocaleString('pt-BR'); } // Add some sample URLs for quick testing document.addEventListener('DOMContentLoaded', function() { const urlInput = document.getElementById('urlInput'); // Add placeholder with sample URLs const sampleUrls = [ 'https://example.com', 'https://github.com', 'https://stackoverflow.com', 'https://www.uol.com.br' ]; let currentSample = 0; urlInput.placeholder = sampleUrls[currentSample]; // Cycle through sample URLs in placeholder setInterval(() => { currentSample = (currentSample + 1) % sampleUrls.length; if (!urlInput.value) { urlInput.placeholder = sampleUrls[currentSample]; } }, 3000); }); // Funções para gerenciar sites clonados function openCloneFolder(directory) { // Tentar abrir a pasta no explorador do Windows fetch('/api/open-folder', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ path: directory }) }).then(response => { if (!response.ok) { alert('Não foi possível abrir a pasta automaticamente.\nLocalização: ' + directory); } }).catch(error => { alert('Não foi possível abrir a pasta automaticamente.\nLocalização: ' + directory); }); } function openClonedSite(directory) { // Tentar abrir o index.html do site clonado fetch('/api/open-site', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ path: directory }) }).then(response => { if (!response.ok) { alert('Não foi possível abrir o site automaticamente.\nAbra manualmente: ' + directory + '\\index.html'); } }).catch(error => { alert('Não foi possível abrir o site automaticamente.\nAbra manualmente: ' + directory + '\\index.html'); }); }