This commit is contained in:
2025-11-12 13:35:59 +01:00
parent d59372d1d6
commit ce6c2f88bd
11 changed files with 1335 additions and 85 deletions

315
frontend/public/admin.js Normal file
View File

@@ -0,0 +1,315 @@
// 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>`;
}
}