Files
webclone/web-interface/public/app.js
T

442 lines
15 KiB
JavaScript

// 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 => `
<div class="stat-card">
<i class="${stat.icon}" style="font-size: 1.5rem; color: #667eea; margin-bottom: 0.5rem;"></i>
<span class="stat-value">${stat.value}</span>
<div class="stat-label">${stat.label}</div>
</div>
`).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.fileCount ? `${data.cloneInfo.fileCount} arquivos (${formatBytes(data.cloneInfo.totalSize || 0)})` : 'verificando...' },
{ label: 'Diretório Local', value: data.cloneInfo.directory }
);
}
overviewContent.innerHTML = overviewItems.map(item => `
<div class="overview-item">
<span class="label">${item.label}:</span>
<span class="value">${item.value}</span>
</div>
`).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 = `
<div style="margin-top: 1rem; padding: 1rem; background: #e8f5e8; border-radius: 8px; border-left: 4px solid #28a745;">
<h4 style="color: #155724; margin-bottom: 0.5rem;">🎉 Site Clonado com Sucesso!</h4>
<p style="color: #155724; margin-bottom: 1rem;">O site foi clonado e salvo localmente com todos os assets.</p>
<p style="color: #155724; font-size: 0.9rem; margin-bottom: 1rem;"><strong>Localização:</strong> ${data.cloneInfo.directory}</p>
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
<button class="btn btn-success btn-sm" onclick="openCloneFolder('${data.cloneInfo.directory}')">
📁 Abrir Pasta
</button>
<button class="btn btn-primary btn-sm" onclick="openClonedSite('${data.cloneInfo.directory}')">
🌐 Abrir Site Local
</button>
</div>
</div>
`;
overviewContent.appendChild(cloneButton);
}
}
// Populate links
function populateLinks(links) {
const linksList = document.querySelector('#links .links-list');
if (links.length === 0) {
linksList.innerHTML = '<p style="text-align: center; color: #666; padding: 2rem;">Nenhum link encontrado.</p>';
return;
}
linksList.innerHTML = links.map(link => `
<div class="link-item">
<i class="fas fa-external-link-alt"></i>
<a href="${link}" target="_blank" rel="noopener noreferrer">${link}</a>
</div>
`).join('');
}
// Populate images
function populateImages(images) {
const imagesGrid = document.querySelector('#images .images-grid');
if (images.length === 0) {
imagesGrid.innerHTML = '<p style="text-align: center; color: #666; padding: 2rem; grid-column: 1 / -1;">Nenhuma imagem encontrada.</p>';
return;
}
imagesGrid.innerHTML = images.map(imageUrl => `
<div class="image-item">
<img src="${imageUrl}" alt="Website Image" onerror="this.style.display='none'; this.nextElementSibling.style.display='block';">
<div style="display: none; padding: 2rem; color: #666;">
<i class="fas fa-image" style="font-size: 2rem; margin-bottom: 1rem;"></i>
<p>Imagem não pôde ser carregada</p>
</div>
<div class="image-url">${imageUrl}</div>
</div>
`).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) {
// Extract folder name and open site in browser via window.open
const folderName = directory.split('/').pop();
const siteUrl = window.location.protocol + '//' + window.location.host + '/' + folderName + '/index.html';
window.open(siteUrl, '_blank');
}