Files
serv_benchmark/frontend/js/dashboard.js
Gilles Soulier c67befc549 addon
2026-01-05 16:08:01 +01:00

376 lines
11 KiB
JavaScript
Executable File

// Linux BenchTools - Dashboard Logic
// Access utilities and API
const utils = window.BenchUtils;
const apiClient = window.BenchAPI;
// Global state
let allDevices = [];
let isLoading = false;
let apiToken = null;
let iperfServer = null;
// Load backend config (API token, etc.)
async function loadBackendConfig() {
try {
const response = await fetch(`${window.BenchConfig.backendApiUrl}/config`);
if (response.ok) {
const config = await response.json();
apiToken = config.api_token;
iperfServer = config.iperf_server || '10.0.0.50';
updateBenchCommandDisplay();
} else {
console.error('Failed to load backend config - HTTP', response.status);
// Set default values to allow the page to render
apiToken = 'LOADING_ERROR';
iperfServer = '10.0.0.50';
updateBenchCommandDisplay();
}
} catch (error) {
console.error('Failed to load backend config:', error);
// Set default values to allow the page to render
apiToken = 'LOADING_ERROR';
iperfServer = '10.0.0.50';
updateBenchCommandDisplay();
}
}
// Load dashboard data
async function loadDashboard() {
if (isLoading) return;
isLoading = true;
updateRefreshButton(true);
try {
await Promise.all([
loadStats(),
loadTopDevices()
]);
// Update last refresh time
updateLastRefreshTime();
} catch (error) {
console.error('Failed to load dashboard:', error);
utils.showToast('Erreur lors du chargement des données', 'error');
} finally {
isLoading = false;
updateRefreshButton(false);
}
}
// Update refresh button state
function updateRefreshButton(loading) {
const btn = document.getElementById('refreshBtn');
if (!btn) return;
if (loading) {
btn.disabled = true;
btn.innerHTML = '⏳ Chargement...';
} else {
btn.disabled = false;
btn.innerHTML = '🔄 Actualiser';
}
}
async function backupDatabase() {
const btn = document.getElementById('backupBtn');
if (btn) {
btn.disabled = true;
btn.textContent = '💾 Backup...';
}
try {
const result = await apiClient.backupDatabase();
const files = (result.backups || []).map(b => b.filename).join(', ');
utils.showToast(`Backup créé${files ? `: ${files}` : ''}`, 'success');
} catch (error) {
console.error('Backup failed:', error);
utils.showToast(`Backup échoué: ${error.message}`, 'error');
} finally {
if (btn) {
btn.disabled = false;
btn.textContent = '💾 Backup DB';
}
}
}
// Update last refresh time
function updateLastRefreshTime() {
const element = document.getElementById('lastUpdate');
if (!element) return;
const now = new Date();
element.textContent = `Mis à jour: ${now.toLocaleTimeString('fr-FR')}`;
}
// Load statistics
async function loadStats() {
try {
const devices = await apiClient.getDevices({ page_size: 100 });
const totalDevices = devices.total || 0;
let totalBenchmarks = 0;
let scoreSum = 0;
let scoreCount = 0;
let lastBenchDate = null;
// Calculate stats from devices
devices.items.forEach(device => {
if (device.last_benchmark) {
totalBenchmarks++;
if (device.last_benchmark.global_score !== null) {
scoreSum += device.last_benchmark.global_score;
scoreCount++;
}
const benchDate = new Date(device.last_benchmark.run_at);
if (!lastBenchDate || benchDate > lastBenchDate) {
lastBenchDate = benchDate;
}
}
});
const avgScore = scoreCount > 0 ? Math.ceil(scoreSum / scoreCount) : 0;
// Update UI
document.getElementById('totalDevices').textContent = totalDevices;
document.getElementById('totalBenchmarks').textContent = totalBenchmarks;
document.getElementById('avgScore').textContent = avgScore;
document.getElementById('lastBench').textContent = lastBenchDate
? utils.formatRelativeTime(lastBenchDate.toISOString())
: 'Aucun';
} catch (error) {
console.error('Failed to load stats:', error);
// Set default values on error
document.getElementById('totalDevices').textContent = '0';
document.getElementById('totalBenchmarks').textContent = '0';
document.getElementById('avgScore').textContent = '0';
document.getElementById('lastBench').textContent = 'N/A';
}
}
// Load top devices
async function loadTopDevices() {
const container = document.getElementById('devicesTable');
try {
const data = await apiClient.getDevices({ page_size: 50 });
if (!data.items || data.items.length === 0) {
utils.showEmptyState(container, 'Aucun device trouvé. Exécutez un benchmark sur une machine pour commencer.', '📊');
allDevices = [];
return;
}
// Store all devices for filtering
allDevices = data.items;
// Sort by global_score descending
const sortedDevices = allDevices.sort((a, b) => {
const scoreA = a.last_benchmark?.global_score ?? -1;
const scoreB = b.last_benchmark?.global_score ?? -1;
return scoreB - scoreA;
});
// Render devices
renderDevicesTable(sortedDevices);
} catch (error) {
console.error('Failed to load devices:', error);
container.innerHTML = `
<div class="error" style="text-align: center;">
<p style="margin-bottom: 1rem;">❌ Impossible de charger les devices</p>
<p style="font-size: 0.9rem; margin-bottom: 1rem;">${utils.escapeHtml(error.message)}</p>
<button class="btn btn-primary btn-sm" onclick="loadTopDevices()">🔄 Réessayer</button>
</div>
`;
}
}
// Render devices table
function renderDevicesTable(devices) {
const container = document.getElementById('devicesTable');
if (devices.length === 0) {
container.innerHTML = `
<div style="text-align: center; padding: 2rem; color: var(--text-secondary);">
<p>Aucun device trouvé avec ces critères de recherche.</p>
</div>
`;
return;
}
container.innerHTML = `
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>#</th>
<th>Hostname</th>
<th>Description</th>
<th>Score Global</th>
<th>CPU</th>
<th>MEM</th>
<th>DISK</th>
<th>NET</th>
<th>GPU</th>
<th>Dernier Bench</th>
<th>Action</th>
</tr>
</thead>
<tbody>
${devices.map((device, index) => createDeviceRow(device, index + 1)).join('')}
</tbody>
</table>
</div>
`;
}
// Create device row HTML
function createDeviceRow(device, rank) {
const bench = device.last_benchmark;
const globalScore = bench?.global_score;
const cpuScore = bench?.cpu_score;
const memScore = bench?.memory_score;
const diskScore = bench?.disk_score;
const netScore = bench?.network_score;
const gpuScore = bench?.gpu_score;
const runAt = bench?.run_at;
const globalScoreHtml = globalScore !== null && globalScore !== undefined
? `<span class="${utils.getScoreBadgeClass(globalScore)}">${utils.getScoreBadgeText(globalScore)}</span>`
: '<span class="badge">N/A</span>';
return `
<tr onclick="window.location.href='device_detail.html?id=${device.id}'">
<td><strong>${rank}</strong></td>
<td>
<strong style="color: var(--color-success);">${utils.escapeHtml(device.hostname)}</strong>
</td>
<td style="color: var(--text-secondary);">
${utils.escapeHtml(device.description || 'Aucune description')}
</td>
<td>${globalScoreHtml}</td>
<td><span class="${utils.getScoreBadgeClass(cpuScore)}">${utils.getScoreBadgeText(cpuScore)}</span></td>
<td><span class="${utils.getScoreBadgeClass(memScore)}">${utils.getScoreBadgeText(memScore)}</span></td>
<td><span class="${utils.getScoreBadgeClass(diskScore)}">${utils.getScoreBadgeText(diskScore)}</span></td>
<td><span class="${utils.getScoreBadgeClass(netScore)}">${utils.getScoreBadgeText(netScore)}</span></td>
<td><span class="${utils.getScoreBadgeClass(gpuScore)}">${utils.getScoreBadgeText(gpuScore)}</span></td>
<td style="color: var(--text-secondary); font-size: 0.85rem;">
${runAt ? utils.formatRelativeTime(runAt) : 'Jamais'}
</td>
<td>
<a href="device_detail.html?id=${device.id}" class="btn btn-sm btn-primary">Voir</a>
</td>
</tr>
`;
}
function buildBenchCommand() {
const cfg = window.BenchConfig || {};
const frontendBase = (cfg.frontendBaseUrl || window.location.origin).replace(/\/$/, '');
const scriptPath = cfg.benchScriptPath || '/scripts/bench.sh';
const backendBase = (cfg.backendApiUrl || `${window.location.protocol}//${window.location.hostname}:8007/api`).replace(/\/$/, '');
const token = apiToken || 'LOADING...';
const iperf = iperfServer || '10.0.0.50';
// Extract backend URL without /api suffix
const backendUrl = backendBase.replace(/\/api$/, '');
return `curl -fsSL ${frontendBase}${scriptPath} | sudo bash -s -- --server ${backendUrl} --token "${token}" --iperf-server ${iperf}`;
}
function updateBenchCommandDisplay() {
const element = document.getElementById('benchCommand');
if (!element) return;
element.textContent = buildBenchCommand();
}
// Copy bench command to clipboard
async function copyBenchCommand() {
const command = document.getElementById('benchCommand').textContent;
const success = await utils.copyToClipboard(command);
if (success) {
utils.showToast('Commande copiée dans le presse-papier !', 'success');
} else {
utils.showToast('Erreur lors de la copie', 'error');
}
}
// Filter devices based on search query
function filterDevices(query) {
if (!query || query.trim() === '') {
renderDevicesTable(allDevices);
return;
}
const lowerQuery = query.toLowerCase();
const filtered = allDevices.filter(device => {
const hostname = (device.hostname || '').toLowerCase();
const description = (device.description || '').toLowerCase();
const location = (device.location || '').toLowerCase();
return hostname.includes(lowerQuery) ||
description.includes(lowerQuery) ||
location.includes(lowerQuery);
});
renderDevicesTable(filtered);
}
// Debounced search
const debouncedSearch = utils.debounce((query) => {
filterDevices(query);
}, 300);
// Handle search input
function handleSearch(event) {
const query = event.target.value;
debouncedSearch(query);
}
// Clear search
function clearSearch() {
const searchInput = document.getElementById('searchInput');
if (searchInput) {
searchInput.value = '';
filterDevices('');
}
}
// Refresh dashboard manually
function refreshDashboard() {
if (!isLoading) {
loadDashboard();
}
}
// Initialize dashboard on page load
document.addEventListener('DOMContentLoaded', async () => {
// Load backend config first to get API token
await loadBackendConfig();
// Then load dashboard data
loadDashboard();
// Setup search input listener
const searchInput = document.getElementById('searchInput');
if (searchInput) {
searchInput.addEventListener('input', handleSearch);
}
// Refresh every 30 seconds
setInterval(loadDashboard, 30000);
});
// Make functions available globally
window.copyBenchCommand = copyBenchCommand;
window.clearSearch = clearSearch;
window.refreshDashboard = refreshDashboard;