// Admin Dashboard JavaScript // Use relative URL to go through frontend proxy const API_BASE = window.location.origin; // Load all data on page load document.addEventListener('DOMContentLoaded', () => { loadSystemStats(); loadOllamaStatus(); loadGPUStatus(); loadPerformanceTest(); loadModels(); loadConfig(); loadClusteringStats(); }); // Refresh all data function refreshAll() { loadSystemStats(); loadOllamaStatus(); loadGPUStatus(); loadPerformanceTest(); loadModels(); loadConfig(); loadClusteringStats(); } // Load system statistics async function loadSystemStats() { try { const response = await fetch(`${API_BASE}/api/stats`); const data = await response.json(); const html = `
Total Articles ${data.articles || 0}
Crawled Articles ${data.crawled_articles || 0}
AI Summarized ${data.summarized_articles || 0}
Clustered Articles ${data.clustered_articles || 0}
Neutral Summaries ${data.neutral_summaries || 0}
Active Subscribers ${data.subscribers || 0}
`; document.getElementById('systemStats').innerHTML = html; } catch (error) { document.getElementById('systemStats').innerHTML = `
Error loading stats: ${error.message}
`; } } // Load Ollama status async function loadOllamaStatus() { try { const response = await fetch(`${API_BASE}/api/ollama/ping`); const data = await response.json(); const isActive = data.status === 'success'; const statusClass = isActive ? 'status-active' : 'status-inactive'; const html = `
Status ${isActive ? 'Active' : 'Inactive'}
Base URL ${data.ollama_config?.base_url || 'N/A'}
Current Model ${data.ollama_config?.model || 'N/A'}
Enabled ${data.ollama_config?.enabled ? 'Yes' : 'No'}
${isActive ? `
Response ${data.response?.substring(0, 50)}...
` : ''} `; document.getElementById('ollamaStatus').innerHTML = html; } catch (error) { document.getElementById('ollamaStatus').innerHTML = `
Error: ${error.message}
`; } } // Load GPU status async function loadGPUStatus() { try { const response = await fetch(`${API_BASE}/api/ollama/gpu-status`); const data = await response.json(); const gpuActive = data.gpu_in_use; const statusClass = gpuActive ? 'status-active' : 'status-warning'; const html = `
GPU Available ${data.gpu_available ? 'Yes' : 'No'}
GPU In Use ${gpuActive ? 'Yes' : 'No (CPU Mode)'}
Models Loaded ${data.models_loaded || 0}
${data.gpu_details ? `
GPU Model ${data.gpu_details.model}
GPU Layers ${data.gpu_details.gpu_layers}
` : ''} ${!gpuActive ? `
💡 Enable GPU for 5-10x faster processing
` : ''} `; document.getElementById('gpuStatus').innerHTML = html; } catch (error) { document.getElementById('gpuStatus').innerHTML = `
Error: ${error.message}
`; } } // Load performance test async function loadPerformanceTest() { document.getElementById('performanceTest').innerHTML = '
Click "Run Test" to check performance
'; } // Run performance test async function runPerformanceTest() { document.getElementById('performanceTest').innerHTML = '
Running test...
'; try { const response = await fetch(`${API_BASE}/api/ollama/test`); const data = await response.json(); let badgeClass = 'badge-fair'; if (data.duration_seconds < 5) badgeClass = 'badge-excellent'; else if (data.duration_seconds < 15) badgeClass = 'badge-good'; else if (data.duration_seconds > 30) badgeClass = 'badge-slow'; const html = `
Duration ${data.duration_seconds}s
Performance ${data.performance}
Model ${data.model}
${data.recommendation}
`; document.getElementById('performanceTest').innerHTML = html; } catch (error) { document.getElementById('performanceTest').innerHTML = `
Error: ${error.message}
`; } } // Load available models async function loadModels() { try { const response = await fetch(`${API_BASE}/api/ollama/models`); const data = await response.json(); if (data.models && data.models.length > 0) { const modelsList = data.models.map(model => { const isCurrent = model === data.current_model; return `
  • ${model} ${isCurrent ? '(current)' : ''}
  • `; }).join(''); const html = `
    Current: ${data.current_model}
    `; document.getElementById('modelsList').innerHTML = html; } else { document.getElementById('modelsList').innerHTML = '
    No models found
    '; } } catch (error) { document.getElementById('modelsList').innerHTML = `
    Error: ${error.message}
    `; } } // Load configuration async function loadConfig() { try { const response = await fetch(`${API_BASE}/api/ollama/config`); const data = await response.json(); const html = `
    Base URL ${data.ollama_config?.base_url || 'N/A'}
    Model ${data.ollama_config?.model || 'N/A'}
    Enabled ${data.ollama_config?.enabled ? 'Yes' : 'No'}
    Has API Key ${data.ollama_config?.has_api_key ? 'Yes' : 'No'}
    Config file: ${data.env_file_path || 'N/A'}
    Exists: ${data.env_file_exists ? 'Yes' : 'No'}
    `; document.getElementById('configInfo').innerHTML = html; } catch (error) { document.getElementById('configInfo').innerHTML = `
    Error: ${error.message}
    `; } } // Load clustering statistics async function loadClusteringStats() { try { const response = await fetch(`${API_BASE}/api/stats`); const data = await response.json(); const clusteringRate = data.clustered_articles > 0 ? ((data.neutral_summaries / data.clustered_articles) * 100).toFixed(1) : 0; const html = `
    Total Articles ${data.articles || 0}
    Clustered Articles ${data.clustered_articles || 0}
    Neutral Summaries ${data.neutral_summaries || 0}
    Clustering Rate ${clusteringRate}%
    Multi-Source Stories ${data.neutral_summaries || 0}
    AI Clustering: Automatically detects duplicate stories from different sources and generates neutral summaries.
    `; document.getElementById('clusteringStats').innerHTML = html; } catch (error) { document.getElementById('clusteringStats').innerHTML = `
    Error: ${error.message}
    `; } } // Subscriber Management async function viewSubscribers() { const listDiv = document.getElementById('subscriberList'); const messageDiv = document.getElementById('subscriberMessage'); listDiv.innerHTML = '

    Loading subscribers...

    '; messageDiv.innerHTML = ''; try { const response = await fetch('/api/subscribers'); const data = await response.json(); if (data.subscribers && data.subscribers.length > 0) { let html = '
    '; html += ''; html += ''; html += ''; data.subscribers.forEach(sub => { const categories = sub.categories ? sub.categories.join(', ') : 'All'; html += ``; }); html += '
    EmailCategoriesStatus
    ${sub.email} ${categories} ${sub.status}
    '; html += `

    Total: ${data.total} subscribers

    `; listDiv.innerHTML = html; } else { listDiv.innerHTML = '

    No subscribers found.

    '; } } catch (error) { listDiv.innerHTML = '

    Failed to load subscribers

    '; } } async function deleteAllSubscribers() { const messageDiv = document.getElementById('subscriberMessage'); if (!confirm('⚠️ Are you sure you want to delete ALL subscribers? This cannot be undone!')) { return; } if (!confirm('⚠️ FINAL WARNING: This will permanently delete all subscriber data. Continue?')) { return; } messageDiv.innerHTML = '

    Deleting all subscribers...

    '; try { const response = await fetch('/api/admin/subscribers/delete-all', { method: 'DELETE' }); const data = await response.json(); if (response.ok) { messageDiv.innerHTML = `

    ✓ ${data.message}

    `; document.getElementById('subscriberList').innerHTML = ''; // Refresh stats loadStats(); } else { messageDiv.innerHTML = `

    ✗ ${data.error || 'Failed to delete subscribers'}

    `; } } catch (error) { messageDiv.innerHTML = '

    ✗ Network error

    '; } } // RSS Feed Management async function exportRSSFeeds() { const messageDiv = document.getElementById('rssFeedMessage'); messageDiv.innerHTML = '

    Exporting RSS feeds...

    '; try { const response = await fetch('/api/rss-feeds/export'); const data = await response.json(); if (response.ok) { // Create download const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `rss-feeds-${new Date().toISOString().split('T')[0]}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); messageDiv.innerHTML = `

    ✓ Exported ${data.total} RSS feeds

    `; } else { messageDiv.innerHTML = `

    ✗ ${data.error || 'Export failed'}

    `; } } catch (error) { messageDiv.innerHTML = '

    ✗ Network error

    '; } } async function importRSSFeeds(event) { const messageDiv = document.getElementById('rssFeedMessage'); const file = event.target.files[0]; if (!file) return; messageDiv.innerHTML = '

    Importing RSS feeds...

    '; try { const text = await file.text(); const data = JSON.parse(text); const response = await fetch('/api/rss-feeds/import', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); const result = await response.json(); if (response.ok) { let html = `

    ✓ ${result.message}

    `; if (result.errors && result.errors.length > 0) { html += '
    Show details
    '; } messageDiv.innerHTML = html; // Refresh feed list if visible if (document.getElementById('rssFeedList').innerHTML) { viewRSSFeeds(); } } else { messageDiv.innerHTML = `

    ✗ ${result.error || 'Import failed'}

    `; } } catch (error) { messageDiv.innerHTML = `

    ✗ Error: ${error.message}

    `; } // Reset file input event.target.value = ''; } async function viewRSSFeeds() { const listDiv = document.getElementById('rssFeedList'); const messageDiv = document.getElementById('rssFeedMessage'); listDiv.innerHTML = '

    Loading RSS feeds...

    '; messageDiv.innerHTML = ''; try { const response = await fetch('/api/rss-feeds'); const data = await response.json(); if (data.feeds && data.feeds.length > 0) { let html = '
    '; html += ''; html += ''; html += ''; data.feeds.forEach(feed => { const statusColor = feed.active ? 'green' : 'gray'; const statusText = feed.active ? 'Active' : 'Inactive'; html += ``; }); html += '
    NameCategoryURLStatus
    ${feed.name} ${feed.category} ${feed.url} ${statusText}
    '; html += `

    Total: ${data.total} feeds

    `; listDiv.innerHTML = html; } else { listDiv.innerHTML = '

    No RSS feeds found.

    '; } } catch (error) { listDiv.innerHTML = '

    Failed to load RSS feeds

    '; } } // Recent Summarization Activity let autoRefreshInterval = null; async function loadRecentArticles() { const container = document.getElementById('recentArticles'); try { const response = await fetch('/api/admin/recent-articles'); const data = await response.json(); if (data.articles && data.articles.length > 0) { let html = '
    '; html += ''; html += ''; html += ''; data.articles.forEach(article => { const time = article.summarized_at ? new Date(article.summarized_at).toLocaleTimeString() : 'N/A'; const title = article.title_en || article.title; const categoryColors = { 'general': '#667eea', 'local': '#f59e0b', 'sports': '#10b981', 'science': '#8b5cf6' }; const categoryColor = categoryColors[article.category] || '#6b7280'; html += ``; }); html += '
    TimeTitleSourceCategoryWords
    ${time} ${title} ${article.source} ${article.category || 'N/A'} ${article.summary_word_count || 'N/A'}
    '; html += `

    Last updated: ${new Date().toLocaleTimeString()}

    `; container.innerHTML = html; } else { container.innerHTML = '

    No summarized articles found.

    '; } } catch (error) { container.innerHTML = '

    Failed to load recent articles

    '; } } function toggleAutoRefresh() { const checkbox = document.getElementById('autoRefresh'); if (checkbox.checked) { // Start auto-refresh every 10 seconds loadRecentArticles(); autoRefreshInterval = setInterval(loadRecentArticles, 10000); } else { // Stop auto-refresh if (autoRefreshInterval) { clearInterval(autoRefreshInterval); autoRefreshInterval = null; } } } // Load recent articles on page load document.addEventListener('DOMContentLoaded', () => { loadRecentArticles(); });