// Global state let currentPage = 'dashboard'; let cloneHistory = []; let settings = { outputDir: 'M:\\CLONEWEB\\cloned-sites', timeout: 30, retries: 3, excludedDomains: 'facebook.com\ntwitter.com\ninstagram.com\ngoogle-analytics.com\ngoogletagmanager.com' }; // Initialize app document.addEventListener('DOMContentLoaded', function () { initializeApp(); }); function initializeApp() { setupNavigation(); setupThemeToggle(); setupForms(); checkServiceStatus(); loadDashboard(); loadHistory(); loadSettings(); // Auto-refresh status every 30 seconds setInterval(checkServiceStatus, 30000); } // Navigation function setupNavigation() { const navItems = document.querySelectorAll('.nav-item'); navItems.forEach(item => { item.addEventListener('click', (e) => { e.preventDefault(); const page = item.getAttribute('data-page'); navigateToPage(page); }); }); } function navigateToPage(page) { // Update nav items document.querySelectorAll('.nav-item').forEach(item => { item.classList.remove('active'); if (item.getAttribute('data-page') === page) { item.classList.add('active'); } }); // Update pages document.querySelectorAll('.page').forEach(p => { p.classList.remove('active'); }); document.getElementById(page).classList.add('active'); // Update page title const titles = { 'dashboard': 'Dashboard', 'new-clone': 'Nova Clonagem', 'history': 'Histórico', 'settings': 'Configurações' }; document.getElementById('pageTitle').textContent = titles[page] || page; currentPage = page; // Refresh data when navigating if (page === 'dashboard') { loadDashboard(); } else if (page === 'history') { loadHistory(); } } // Theme Toggle function setupThemeToggle() { const themeToggle = document.getElementById('themeToggle'); // Load saved theme const savedTheme = localStorage.getItem('theme') || 'light'; if (savedTheme === 'dark') { document.body.classList.add('dark-theme'); themeToggle.checked = true; } themeToggle.addEventListener('change', () => { if (themeToggle.checked) { document.body.classList.add('dark-theme'); localStorage.setItem('theme', 'dark'); } else { document.body.classList.remove('dark-theme'); localStorage.setItem('theme', 'light'); } }); } // Service Status - verifica o serviço WGET na porta 3002 async function checkServiceStatus() { const statusIndicator = document.getElementById('serviceStatus'); try { // Verificar o serviço WGET via proxy do servidor (mesma origem) const response = await fetch('/api/health', { method: 'GET' }); if (response.ok) { statusIndicator.className = 'status-indicator online'; statusIndicator.querySelector('.status-text').textContent = 'Serviço Online'; } else { statusIndicator.className = 'status-indicator offline'; statusIndicator.querySelector('.status-text').textContent = 'Serviço Offline'; } } catch (error) { statusIndicator.className = 'status-indicator offline'; statusIndicator.querySelector('.status-text').textContent = 'Serviço Offline'; } } // Dashboard function loadDashboard() { // Load from localStorage const history = JSON.parse(localStorage.getItem('cloneHistory') || '[]'); cloneHistory = history; // Calculate stats const totalSites = history.length; const totalSize = history.reduce((sum, item) => sum + (item.size || 0), 0); const lastClone = history.length > 0 ? history[0].timestamp : null; const successCount = history.filter(item => item.success).length; const successRate = totalSites > 0 ? Math.round((successCount / totalSites) * 100) : 100; // Update stats document.getElementById('totalSites').textContent = totalSites; document.getElementById('totalSize').textContent = formatBytes(totalSize); document.getElementById('lastClone').textContent = lastClone ? formatRelativeTime(lastClone) : 'Nunca'; document.getElementById('successRate').textContent = successRate + '%'; // Update recent clones list const recentClonesList = document.getElementById('recentClonesList'); if (history.length === 0) { recentClonesList.innerHTML = `

Nenhuma clonagem ainda

Comece clonando seu primeiro site!

`; return; } const recentClones = history.slice(0, 5); recentClonesList.innerHTML = recentClones.map(clone => `

${clone.url}

${formatRelativeTime(clone.timestamp)} • ${formatBytes(clone.size || 0)} • ${clone.files || 0} arquivos

${clone.success ? ` ` : ''}
`).join(''); } function refreshDashboard() { loadDashboard(); showToast('Dashboard atualizado!', 'success'); } // Clone Form function setupForms() { // Clone form const cloneForm = document.getElementById('cloneForm'); cloneForm.addEventListener('submit', handleCloneSubmit); // Depth slider const depthSlider = document.getElementById('depthLevel'); const depthValue = document.getElementById('depthValue'); depthSlider.addEventListener('input', () => { depthValue.textContent = depthSlider.value + ' níveis'; }); } async function handleCloneSubmit(e) { e.preventDefault(); const cloneUrlEl = document.getElementById('cloneUrl'); const depthLevelEl = document.getElementById('depthLevel'); const includeImagesEl = document.getElementById('includeImages'); const includeCSSEl = document.getElementById('includeCSS'); const includeJSEl = document.getElementById('includeJS'); const convertLinksEl = document.getElementById('convertLinks'); if (!cloneUrlEl || !depthLevelEl || !includeImagesEl || !includeCSSEl || !includeJSEl || !convertLinksEl) { console.error('Form elements not found'); showToast('Erro: Elementos do formulário não encontrados', 'error'); return; } const url = cloneUrlEl.value.trim(); const depth = parseInt(depthLevelEl.value); const includeImages = includeImagesEl.checked; const includeCSS = includeCSSEl.checked; const includeJS = includeJSEl.checked; const convertLinks = convertLinksEl.checked; const cloneMethod = 'wget'; if (!url) { showToast('Por favor, insira uma URL válida', 'error'); return; } // Validate URL try { new URL(url); } catch { showToast('URL inválida. Use o formato: https://exemplo.com', 'error'); return; } await startCloning(url, { depth, includeImages, includeCSS, includeJS, convertLinks, method: cloneMethod }); } async function startCloning(url, options) { const progressCard = document.getElementById('progressCard'); const progressBar = document.getElementById('progressBar'); const progressMessage = document.getElementById('progressMessage'); const filesDownloaded = document.getElementById('filesDownloaded'); const sizeDownloaded = document.getElementById('sizeDownloaded'); const timeElapsed = document.getElementById('timeElapsed'); // Show progress card progressCard.style.display = 'block'; progressBar.style.width = '0%'; const startTime = Date.now(); let progress = 0; // Simulate progress const progressInterval = setInterval(() => { progress = Math.min(progress + Math.random() * 10, 90); progressBar.style.width = progress + '%'; const elapsed = Math.floor((Date.now() - startTime) / 1000); timeElapsed.textContent = elapsed + 's'; }, 1000); const messages = options.method === 'smart' ? [ '🚀 Iniciando Smart Clone (Puppeteer)...', '⏳ Renderizando página completa...', '🖱️ Simulando interação do usuário...', '📜 Carregando conteúdo sob demanda...', '📦 Capturando recursos da rede...', '🔄 Reescrevendo links dinâmicos...', '✨ Otimizando clone final...' ] : [ '🚀 Iniciando clonagem com WGET...', '⏳ Sem limite de tempo - pode demorar vários minutos...', '🔍 Analisando estrutura do site...', '📄 Baixando páginas HTML...', '🎨 Baixando CSS e estilos...', '⚡ Baixando JavaScript...', '📦 Baixando imagens e assets...', '🔗 Convertendo links para locais...', '💪 Clonagem profissional em andamento...', '✨ Finalizando clonagem...' ]; let messageIndex = 0; progressMessage.textContent = messages[0]; const messageInterval = setInterval(() => { messageIndex = (messageIndex + 1) % messages.length; progressMessage.textContent = messages[messageIndex]; }, 4000); try { let response; if (options.method === 'smart') { response = await fetch('/api/smart-clone', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url }) }); } else { // WGET logic response = await fetch('/api/wget-clone', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url, depth: options.depth }) }); } clearInterval(progressInterval); clearInterval(messageInterval); const data = await response.json(); if (data.success) { // Complete progress progressBar.style.width = '100%'; progressMessage.textContent = '✅ Clonagem concluída com sucesso!'; // Update stats const files = data.data?.files || data.data?.cloneInfo?.fileCount || data.data?.resources || data.files || 0; const size = data.data?.size || data.data?.cloneInfo?.totalSize || data.size || 0; filesDownloaded.textContent = files; sizeDownloaded.textContent = formatBytes(size); // Save to history const cloneRecord = { url, timestamp: new Date().toISOString(), success: true, files: files, size: size, directory: data.data.path || data.data.directory, depth: options.depth, method: options.method }; saveToHistory(cloneRecord); // Show success message setTimeout(() => { showToast('Site clonado com sucesso!', 'success'); progressCard.style.display = 'none'; // Show success dialog showSuccessDialog(cloneRecord); }, 2000); } else { throw new Error(data.error || 'Erro desconhecido'); } } catch (error) { clearInterval(progressInterval); clearInterval(messageInterval); console.error('Clone error:', error); progressCard.style.display = 'none'; // Save failed attempt to history saveToHistory({ url, timestamp: new Date().toISOString(), success: false, error: error.message }); showToast('Erro ao clonar site: ' + error.message, 'error'); } } function showSuccessDialog(cloneRecord) { const dialog = document.createElement('div'); dialog.className = 'success-dialog'; dialog.innerHTML = `

Site Clonado com Sucesso!

${cloneRecord.url}

${cloneRecord.files} arquivos ${formatBytes(cloneRecord.size)}
`; document.body.appendChild(dialog); // Add styles const style = document.createElement('style'); style.textContent = ` .success-dialog { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 10000; animation: fadeIn 0.3s; } .success-dialog-content { background: white; border-radius: 16px; padding: 2rem; max-width: 500px; text-align: center; animation: slideUp 0.3s; } .success-icon { font-size: 4rem; color: var(--success-color); margin-bottom: 1rem; } .success-dialog-content h3 { font-size: 1.5rem; margin-bottom: 0.5rem; } .success-dialog-content p { color: var(--text-secondary); margin-bottom: 1.5rem; } .success-stats { display: flex; justify-content: center; gap: 2rem; margin-bottom: 2rem; padding: 1rem; background: var(--bg-primary); border-radius: 8px; } .success-stats span { display: flex; align-items: center; gap: 0.5rem; } .success-actions { display: flex; gap: 0.5rem; justify-content: center; flex-wrap: wrap; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes slideUp { from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } `; document.head.appendChild(style); } function closeSuccessDialog() { const dialog = document.querySelector('.success-dialog'); if (dialog) { dialog.remove(); } } // History function loadHistory() { const history = JSON.parse(localStorage.getItem('cloneHistory') || '[]'); cloneHistory = history; const historyList = document.getElementById('historyList'); if (history.length === 0) { historyList.innerHTML = `

Nenhum histórico

Seus sites clonados aparecerão aqui

`; return; } historyList.innerHTML = history.map((item, index) => `

${item.url}

${formatDate(item.timestamp)} ${item.success ? 'Sucesso' : 'Falhou'} ${item.files ? ` ${item.files} arquivos` : ''} ${item.size ? ` ${formatBytes(item.size)}` : ''}
${item.success ? ` ` : ''}
`).join(''); } function refreshHistory() { loadHistory(); showToast('Histórico atualizado!', 'success'); } function clearHistory() { if (confirm('Tem certeza que deseja limpar todo o histórico? Esta ação não pode ser desfeita.')) { localStorage.removeItem('cloneHistory'); cloneHistory = []; loadHistory(); loadDashboard(); showToast('Histórico limpo com sucesso!', 'success'); } } function deleteHistoryItem(index) { if (confirm('Remover este item do histórico?')) { cloneHistory.splice(index, 1); localStorage.setItem('cloneHistory', JSON.stringify(cloneHistory)); loadHistory(); loadDashboard(); showToast('Item removido!', 'success'); } } function recloneSite(url) { document.getElementById('cloneUrl').value = url; navigateToPage('new-clone'); showToast('URL carregada! Configure as opções e clique em "Iniciar Clonagem"', 'info'); } function saveToHistory(record) { cloneHistory.unshift(record); // Keep only last 100 records if (cloneHistory.length > 100) { cloneHistory = cloneHistory.slice(0, 100); } localStorage.setItem('cloneHistory', JSON.stringify(cloneHistory)); } // Settings function loadSettings() { const savedSettings = localStorage.getItem('cloneSettings'); if (savedSettings) { settings = JSON.parse(savedSettings); } // Populate settings form (if needed) } function saveSettings() { localStorage.setItem('cloneSettings', JSON.stringify(settings)); showToast('Configurações salvas!', 'success'); } // Utility Functions function openClonedSite(directory) { // Try to open via server endpoint fetch('/api/health', { method: 'GET' }).then(response => { if (response.ok) { // Server is running, open the site via same origin const siteName = directory.replace(/\\/g, '/').split('/').pop(); window.open(`/${siteName}/index.html`, '_blank'); } else { showToast('Servidor não está respondendo.', 'error'); } }).catch(error => { showToast('Servidor não está respondendo.', 'error'); }); } function openCloneFolder(directory) { showToast('Abrindo pasta no sistema da VPS: ' + directory, 'info'); fetch('/api/open-folder', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ path: directory }) }).then(res => res.json()) .then(data => { if (!data.success) showToast('Erro ao abrir pasta: ' + (data.error || ''), 'error'); }) .catch(err => console.error('Erro ao chamar open-folder:', err)); } function showToast(message, type = 'info') { const toast = document.createElement('div'); toast.className = `toast toast-${type}`; toast.innerHTML = ` ${message} `; document.body.appendChild(toast); // Add styles if not already added if (!document.getElementById('toast-styles')) { const style = document.createElement('style'); style.id = 'toast-styles'; style.textContent = ` .toast { position: fixed; bottom: 2rem; right: 2rem; background: white; padding: 1rem 1.5rem; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); display: flex; align-items: center; gap: 0.75rem; z-index: 10000; animation: slideInRight 0.3s, slideOutRight 0.3s 2.7s; } .toast-success { border-left: 4px solid var(--success-color); } .toast-error { border-left: 4px solid var(--danger-color); } .toast-info { border-left: 4px solid var(--info-color); } .toast i { font-size: 1.2rem; } .toast-success i { color: var(--success-color); } .toast-error i { color: var(--danger-color); } .toast-info i { color: var(--info-color); } @keyframes slideInRight { from { transform: translateX(400px); opacity: 0; } to { transform: translateX(0); opacity: 1; } } @keyframes slideOutRight { from { transform: translateX(0); opacity: 1; } to { transform: translateX(400px); opacity: 0; } } `; document.head.appendChild(style); } setTimeout(() => { toast.remove(); }, 3000); } function formatBytes(bytes) { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', '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) { const date = new Date(dateString); return date.toLocaleString('pt-BR', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }); } function formatRelativeTime(dateString) { const date = new Date(dateString); const now = new Date(); const diff = now - date; const seconds = Math.floor(diff / 1000); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); const days = Math.floor(hours / 24); if (days > 0) return days === 1 ? 'Há 1 dia' : `Há ${days} dias`; if (hours > 0) return hours === 1 ? 'Há 1 hora' : `Há ${hours} horas`; if (minutes > 0) return minutes === 1 ? 'Há 1 minuto' : `Há ${minutes} minutos`; return 'Agora mesmo'; }