316 lines
12 KiB
JavaScript
316 lines
12 KiB
JavaScript
// 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 = `
|
|
<div class="stat-row">
|
|
<span class="stat-label">Total Articles</span>
|
|
<span class="stat-value">${data.articles || 0}</span>
|
|
</div>
|
|
<div class="stat-row">
|
|
<span class="stat-label">Crawled Articles</span>
|
|
<span class="stat-value">${data.crawled_articles || 0}</span>
|
|
</div>
|
|
<div class="stat-row">
|
|
<span class="stat-label">AI Summarized</span>
|
|
<span class="stat-value">${data.summarized_articles || 0}</span>
|
|
</div>
|
|
<div class="stat-row">
|
|
<span class="stat-label">Clustered Articles</span>
|
|
<span class="stat-value">${data.clustered_articles || 0}</span>
|
|
</div>
|
|
<div class="stat-row">
|
|
<span class="stat-label">Neutral Summaries</span>
|
|
<span class="stat-value">${data.neutral_summaries || 0}</span>
|
|
</div>
|
|
<div class="stat-row">
|
|
<span class="stat-label">Active Subscribers</span>
|
|
<span class="stat-value">${data.subscribers || 0}</span>
|
|
</div>
|
|
`;
|
|
|
|
document.getElementById('systemStats').innerHTML = html;
|
|
} catch (error) {
|
|
document.getElementById('systemStats').innerHTML = `<div class="error">Error loading stats: ${error.message}</div>`;
|
|
}
|
|
}
|
|
|
|
// 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 = `
|
|
<div class="stat-row">
|
|
<span class="stat-label">Status</span>
|
|
<span class="stat-value">
|
|
<span class="status-indicator ${statusClass}"></span>
|
|
${isActive ? 'Active' : 'Inactive'}
|
|
</span>
|
|
</div>
|
|
<div class="stat-row">
|
|
<span class="stat-label">Base URL</span>
|
|
<span class="stat-value">${data.ollama_config?.base_url || 'N/A'}</span>
|
|
</div>
|
|
<div class="stat-row">
|
|
<span class="stat-label">Current Model</span>
|
|
<span class="stat-value">${data.ollama_config?.model || 'N/A'}</span>
|
|
</div>
|
|
<div class="stat-row">
|
|
<span class="stat-label">Enabled</span>
|
|
<span class="stat-value">${data.ollama_config?.enabled ? 'Yes' : 'No'}</span>
|
|
</div>
|
|
${isActive ? `
|
|
<div class="stat-row">
|
|
<span class="stat-label">Response</span>
|
|
<span class="stat-value" style="font-size: 12px;">${data.response?.substring(0, 50)}...</span>
|
|
</div>
|
|
` : ''}
|
|
`;
|
|
|
|
document.getElementById('ollamaStatus').innerHTML = html;
|
|
} catch (error) {
|
|
document.getElementById('ollamaStatus').innerHTML = `<div class="error">Error: ${error.message}</div>`;
|
|
}
|
|
}
|
|
|
|
// 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 = `
|
|
<div class="stat-row">
|
|
<span class="stat-label">GPU Available</span>
|
|
<span class="stat-value">
|
|
<span class="status-indicator ${data.gpu_available ? 'status-active' : 'status-inactive'}"></span>
|
|
${data.gpu_available ? 'Yes' : 'No'}
|
|
</span>
|
|
</div>
|
|
<div class="stat-row">
|
|
<span class="stat-label">GPU In Use</span>
|
|
<span class="stat-value">
|
|
<span class="status-indicator ${statusClass}"></span>
|
|
${gpuActive ? 'Yes' : 'No (CPU Mode)'}
|
|
</span>
|
|
</div>
|
|
<div class="stat-row">
|
|
<span class="stat-label">Models Loaded</span>
|
|
<span class="stat-value">${data.models_loaded || 0}</span>
|
|
</div>
|
|
${data.gpu_details ? `
|
|
<div class="stat-row">
|
|
<span class="stat-label">GPU Model</span>
|
|
<span class="stat-value">${data.gpu_details.model}</span>
|
|
</div>
|
|
<div class="stat-row">
|
|
<span class="stat-label">GPU Layers</span>
|
|
<span class="stat-value">${data.gpu_details.gpu_layers}</span>
|
|
</div>
|
|
` : ''}
|
|
${!gpuActive ? `
|
|
<div style="margin-top: 10px; padding: 10px; background: #fef3c7; border-radius: 5px; font-size: 12px;">
|
|
💡 Enable GPU for 5-10x faster processing
|
|
</div>
|
|
` : ''}
|
|
`;
|
|
|
|
document.getElementById('gpuStatus').innerHTML = html;
|
|
} catch (error) {
|
|
document.getElementById('gpuStatus').innerHTML = `<div class="error">Error: ${error.message}</div>`;
|
|
}
|
|
}
|
|
|
|
// Load performance test
|
|
async function loadPerformanceTest() {
|
|
document.getElementById('performanceTest').innerHTML = '<div class="loading">Click "Run Test" to check performance</div>';
|
|
}
|
|
|
|
// Run performance test
|
|
async function runPerformanceTest() {
|
|
document.getElementById('performanceTest').innerHTML = '<div class="loading">Running test...</div>';
|
|
|
|
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 = `
|
|
<div class="stat-row">
|
|
<span class="stat-label">Duration</span>
|
|
<span class="stat-value">${data.duration_seconds}s</span>
|
|
</div>
|
|
<div class="stat-row">
|
|
<span class="stat-label">Performance</span>
|
|
<span class="stat-value">
|
|
<span class="performance-badge ${badgeClass}">${data.performance}</span>
|
|
</span>
|
|
</div>
|
|
<div class="stat-row">
|
|
<span class="stat-label">Model</span>
|
|
<span class="stat-value">${data.model}</span>
|
|
</div>
|
|
<div style="margin-top: 10px; padding: 10px; background: #f3f4f6; border-radius: 5px; font-size: 12px;">
|
|
${data.recommendation}
|
|
</div>
|
|
`;
|
|
|
|
document.getElementById('performanceTest').innerHTML = html;
|
|
} catch (error) {
|
|
document.getElementById('performanceTest').innerHTML = `<div class="error">Error: ${error.message}</div>`;
|
|
}
|
|
}
|
|
|
|
// 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 `<li class="${isCurrent ? 'current-model' : ''}">${model} ${isCurrent ? '(current)' : ''}</li>`;
|
|
}).join('');
|
|
|
|
const html = `
|
|
<ul class="model-list">
|
|
${modelsList}
|
|
</ul>
|
|
<div style="margin-top: 10px; font-size: 12px; color: #666;">
|
|
Current: ${data.current_model}
|
|
</div>
|
|
`;
|
|
|
|
document.getElementById('modelsList').innerHTML = html;
|
|
} else {
|
|
document.getElementById('modelsList').innerHTML = '<div>No models found</div>';
|
|
}
|
|
} catch (error) {
|
|
document.getElementById('modelsList').innerHTML = `<div class="error">Error: ${error.message}</div>`;
|
|
}
|
|
}
|
|
|
|
// Load configuration
|
|
async function loadConfig() {
|
|
try {
|
|
const response = await fetch(`${API_BASE}/api/ollama/config`);
|
|
const data = await response.json();
|
|
|
|
const html = `
|
|
<div class="stat-row">
|
|
<span class="stat-label">Base URL</span>
|
|
<span class="stat-value" style="font-size: 12px;">${data.ollama_config?.base_url || 'N/A'}</span>
|
|
</div>
|
|
<div class="stat-row">
|
|
<span class="stat-label">Model</span>
|
|
<span class="stat-value">${data.ollama_config?.model || 'N/A'}</span>
|
|
</div>
|
|
<div class="stat-row">
|
|
<span class="stat-label">Enabled</span>
|
|
<span class="stat-value">${data.ollama_config?.enabled ? 'Yes' : 'No'}</span>
|
|
</div>
|
|
<div class="stat-row">
|
|
<span class="stat-label">Has API Key</span>
|
|
<span class="stat-value">${data.ollama_config?.has_api_key ? 'Yes' : 'No'}</span>
|
|
</div>
|
|
<div style="margin-top: 10px; padding: 10px; background: #f3f4f6; border-radius: 5px; font-size: 11px;">
|
|
<strong>Config file:</strong> ${data.env_file_path || 'N/A'}<br>
|
|
<strong>Exists:</strong> ${data.env_file_exists ? 'Yes' : 'No'}
|
|
</div>
|
|
`;
|
|
|
|
document.getElementById('configInfo').innerHTML = html;
|
|
} catch (error) {
|
|
document.getElementById('configInfo').innerHTML = `<div class="error">Error: ${error.message}</div>`;
|
|
}
|
|
}
|
|
|
|
// 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 = `
|
|
<div class="dashboard-grid">
|
|
<div>
|
|
<div class="stat-row">
|
|
<span class="stat-label">Total Articles</span>
|
|
<span class="stat-value">${data.articles || 0}</span>
|
|
</div>
|
|
<div class="stat-row">
|
|
<span class="stat-label">Clustered Articles</span>
|
|
<span class="stat-value">${data.clustered_articles || 0}</span>
|
|
</div>
|
|
<div class="stat-row">
|
|
<span class="stat-label">Neutral Summaries</span>
|
|
<span class="stat-value">${data.neutral_summaries || 0}</span>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div class="stat-row">
|
|
<span class="stat-label">Clustering Rate</span>
|
|
<span class="stat-value">${clusteringRate}%</span>
|
|
</div>
|
|
<div class="stat-row">
|
|
<span class="stat-label">Multi-Source Stories</span>
|
|
<span class="stat-value">${data.neutral_summaries || 0}</span>
|
|
</div>
|
|
<div style="margin-top: 10px; padding: 10px; background: #dbeafe; border-radius: 5px; font-size: 12px;">
|
|
<strong>AI Clustering:</strong> Automatically detects duplicate stories from different sources and generates neutral summaries.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
document.getElementById('clusteringStats').innerHTML = html;
|
|
} catch (error) {
|
|
document.getElementById('clusteringStats').innerHTML = `<div class="error">Error: ${error.message}</div>`;
|
|
}
|
|
}
|