// Linux BenchTools - Device Detail Logic
const { formatDate, formatRelativeTime, formatFileSize, createScoreBadge, getScoreBadgeText, escapeHtml, showError, showEmptyState, formatTags, initTabs, openModal, showToast, formatHardwareInfo } = window.BenchUtils;
const api = window.BenchAPI;
let currentDeviceId = null;
let currentDevice = null;
// Initialize page
document.addEventListener('DOMContentLoaded', async () => {
// Get device ID from URL
currentDeviceId = window.BenchUtils.getUrlParameter('id');
if (!currentDeviceId) {
document.getElementById('loadingState').innerHTML = '
Device ID manquant dans l\'URL
';
return;
}
// Initialize tabs
initTabs('.tabs-container');
// Load device data
await loadDeviceDetail();
});
// Load device detail
async function loadDeviceDetail() {
try {
currentDevice = await api.getDevice(currentDeviceId);
// Show content, hide loading
document.getElementById('loadingState').style.display = 'none';
document.getElementById('deviceContent').style.display = 'block';
// Render all sections
renderDeviceHeader();
renderMotherboardDetails();
renderCPUDetails();
renderMemoryDetails();
renderStorageDetails();
renderGPUDetails();
renderNetworkDetails();
renderOSDetails();
renderBenchmarkResults();
await loadBenchmarkHistory();
await loadDocuments();
await loadLinks();
} catch (error) {
console.error('Failed to load device:', error);
document.getElementById('loadingState').innerHTML =
`Erreur lors du chargement du device: ${escapeHtml(error.message)}
`;
}
}
// Render device header
function renderDeviceHeader() {
document.getElementById('deviceHostname').textContent = currentDevice.hostname;
document.getElementById('deviceDescription').textContent = currentDevice.description || 'Aucune description';
// Global score
const globalScore = currentDevice.last_benchmark?.global_score;
document.getElementById('globalScoreContainer').innerHTML =
globalScore !== null && globalScore !== undefined
? `${getScoreBadgeText(globalScore)}
`
: 'N/A';
// Meta information
const metaParts = [];
if (currentDevice.location) metaParts.push(`📍 ${escapeHtml(currentDevice.location)}`);
if (currentDevice.owner) metaParts.push(`👤 ${escapeHtml(currentDevice.owner)}`);
if (currentDevice.asset_tag) metaParts.push(`🏷️ ${escapeHtml(currentDevice.asset_tag)}`);
if (currentDevice.last_benchmark?.run_at) metaParts.push(`⏱️ ${formatRelativeTime(currentDevice.last_benchmark.run_at)}`);
document.getElementById('deviceMeta').innerHTML = metaParts.map(part =>
`${part}`
).join('');
// Tags
if (currentDevice.tags) {
document.getElementById('deviceTags').innerHTML = formatTags(currentDevice.tags);
}
}
// Render Motherboard Details
function renderMotherboardDetails() {
const snapshot = currentDevice.last_hardware_snapshot;
const container = document.getElementById('motherboardDetails');
if (!snapshot) {
container.innerHTML = 'Aucune information disponible
';
return;
}
// Helper to clean empty/whitespace-only strings
const cleanValue = (val) => {
if (!val || (typeof val === 'string' && val.trim() === '')) return 'N/A';
return val;
};
const items = [
{ label: 'Fabricant', value: cleanValue(snapshot.motherboard_vendor) },
{ label: 'Modèle', value: cleanValue(snapshot.motherboard_model) },
{ label: 'Version BIOS', value: cleanValue(snapshot.bios_version) },
{ label: 'Date BIOS', value: cleanValue(snapshot.bios_date) },
{ label: 'Slots RAM', value: `${snapshot.ram_slots_used || '?'} utilisés / ${snapshot.ram_slots_total || '?'} total` }
];
container.innerHTML = `
${items.map(item => `
${item.label}
${escapeHtml(String(item.value))}
`).join('')}
`;
}
// Render CPU Details
function renderCPUDetails() {
const snapshot = currentDevice.last_hardware_snapshot;
const container = document.getElementById('cpuDetails');
if (!snapshot) {
container.innerHTML = 'Aucune information disponible
';
return;
}
const items = [
{ label: 'Fabricant', value: snapshot.cpu_vendor || 'N/A' },
{ label: 'Modèle', value: snapshot.cpu_model || 'N/A' },
{ label: 'Microarchitecture', value: snapshot.cpu_microarchitecture || 'N/A' },
{ label: 'Cores', value: snapshot.cpu_cores != null ? snapshot.cpu_cores : 'N/A' },
{ label: 'Threads', value: snapshot.cpu_threads != null ? snapshot.cpu_threads : 'N/A' },
{ label: 'Fréquence de base', value: snapshot.cpu_base_freq_ghz ? `${snapshot.cpu_base_freq_ghz} GHz` : 'N/A' },
{ label: 'Fréquence max', value: snapshot.cpu_max_freq_ghz ? `${snapshot.cpu_max_freq_ghz} GHz` : 'N/A' },
{ label: 'TDP', value: snapshot.cpu_tdp_w ? `${snapshot.cpu_tdp_w} W` : 'N/A' },
{ label: 'Cache L1', value: snapshot.cpu_cache_l1_kb ? `${snapshot.cpu_cache_l1_kb} KB` : 'N/A' },
{ label: 'Cache L2', value: snapshot.cpu_cache_l2_kb ? `${snapshot.cpu_cache_l2_kb} KB` : 'N/A' },
{ label: 'Cache L3', value: snapshot.cpu_cache_l3_kb ? `${snapshot.cpu_cache_l3_kb} KB` : 'N/A' }
];
let html = `
${items.map(item => `
${item.label}
${escapeHtml(String(item.value))}
`).join('')}
`;
// CPU Flags
if (snapshot.cpu_flags) {
try {
const flags = typeof snapshot.cpu_flags === 'string' ? JSON.parse(snapshot.cpu_flags) : snapshot.cpu_flags;
if (Array.isArray(flags) && flags.length > 0) {
html += `
Instructions supportées :
${flags.slice(0, 50).map(flag => `${escapeHtml(flag)}`).join('')}
${flags.length > 50 ? `+${flags.length - 50} autres...` : ''}
`;
}
} catch (e) {
console.warn('Failed to parse CPU flags:', e);
}
}
container.innerHTML = html;
}
// Render Memory Details
function renderMemoryDetails() {
const snapshot = currentDevice.last_hardware_snapshot;
const container = document.getElementById('memoryDetails');
if (!snapshot) {
container.innerHTML = 'Aucune information disponible
';
return;
}
const ramTotalGB = Math.round((snapshot.ram_total_mb || 0) / 1024);
const ramUsedGB = snapshot.ram_used_mb != null ? Math.round(snapshot.ram_used_mb / 1024) : null;
const ramFreeGB = snapshot.ram_free_mb != null ? Math.round(snapshot.ram_free_mb / 1024) : null;
const ramSharedGB = snapshot.ram_shared_mb != null ? Math.round(snapshot.ram_shared_mb / 1024) : null;
const usagePercent = (ramTotalGB > 0 && snapshot.ram_used_mb != null) ? Math.round((snapshot.ram_used_mb / snapshot.ram_total_mb) * 100) : null;
const items = [
{ label: 'Capacité totale', value: `${ramTotalGB} GB` },
{ label: 'Mémoire utilisée', value: ramUsedGB != null ? `${ramUsedGB} GB${usagePercent != null ? ` (${usagePercent}%)` : ''}` : 'N/A' },
{ label: 'Mémoire libre', value: ramFreeGB != null ? `${ramFreeGB} GB` : 'N/A' },
{ label: 'Mémoire partagée', value: ramSharedGB != null ? `${ramSharedGB} GB` : 'N/A' },
{ label: 'Slots utilisés', value: `${snapshot.ram_slots_used || '?'} / ${snapshot.ram_slots_total || '?'}` },
{ label: 'ECC', value: snapshot.ram_ecc ? 'Oui' : 'Non' }
];
let html = `
${items.map(item => `
${item.label}
${escapeHtml(String(item.value))}
`).join('')}
`;
// RAM Layout (DIMM details)
if (snapshot.ram_layout_json) {
try {
const layout = typeof snapshot.ram_layout_json === 'string' ? JSON.parse(snapshot.ram_layout_json) : snapshot.ram_layout_json;
if (Array.isArray(layout) && layout.length > 0) {
html += `
Configuration des barrettes :
${layout.map(dimm => `
Slot ${escapeHtml(dimm.slot || 'N/A')}
${dimm.size_mb ? `${Math.round(dimm.size_mb / 1024)} GB` : 'N/A'}
${dimm.type ? `• ${escapeHtml(dimm.type)}` : ''}
${dimm.speed_mhz ? `• ${dimm.speed_mhz} MHz` : ''}
${dimm.vendor || dimm.manufacturer ? `
Fabricant
${escapeHtml(dimm.vendor || dimm.manufacturer)}
` : ''}
${dimm.part_number ? `
Part Number
${escapeHtml(dimm.part_number)}
` : ''}
`).join('')}
`;
}
} catch (e) {
console.warn('Failed to parse RAM layout:', e);
}
}
container.innerHTML = html;
}
// Render Storage Details
function renderStorageDetails() {
const snapshot = currentDevice.last_hardware_snapshot;
const container = document.getElementById('storageDetails');
if (!snapshot) {
container.innerHTML = 'Aucune information disponible
';
return;
}
let html = '';
// Parse storage devices
if (snapshot.storage_devices_json) {
try {
const devices = typeof snapshot.storage_devices_json === 'string'
? JSON.parse(snapshot.storage_devices_json)
: snapshot.storage_devices_json;
if (Array.isArray(devices) && devices.length > 0) {
html += '';
devices.forEach(disk => {
const typeIcon = disk.type === 'SSD' ? '💾' : '💿';
const healthColor = disk.smart_health === 'PASSED' ? 'var(--color-success)' :
disk.smart_health === 'FAILED' ? 'var(--color-danger)' :
'var(--text-secondary)';
html += `
${typeIcon} ${escapeHtml(disk.name || disk.device || 'N/A')}
${escapeHtml(disk.model || 'Unknown model')}
${disk.smart_health ? `
${escapeHtml(disk.smart_health)}
` : ''}
${disk.capacity_gb ? `
Capacité: ${disk.capacity_gb} GB
` : ''}
${disk.type ? `
Type: ${escapeHtml(disk.type)}
` : ''}
${disk.interface ? `
Interface: ${escapeHtml(disk.interface)}
` : ''}
${disk.temperature_c ? `
Température: ${disk.temperature_c}°C
` : ''}
`;
});
html += '
';
} else {
html = 'Aucun disque détecté
';
}
} catch (e) {
console.error('Failed to parse storage devices:', e);
html = 'Erreur lors du parsing des données de stockage
';
}
} else {
html = 'Aucune information de stockage disponible
';
}
container.innerHTML = html;
}
// Render GPU Details
function renderGPUDetails() {
const snapshot = currentDevice.last_hardware_snapshot;
const container = document.getElementById('gpuDetails');
if (!snapshot) {
container.innerHTML = 'Aucune information disponible
';
return;
}
if (!snapshot.gpu_vendor && !snapshot.gpu_model && !snapshot.gpu_summary) {
container.innerHTML = 'Aucun GPU détecté
';
return;
}
const items = [
{ label: 'Fabricant', value: snapshot.gpu_vendor || 'N/A' },
{ label: 'Modèle', value: snapshot.gpu_model || snapshot.gpu_summary || 'N/A' },
{ label: 'Driver', value: snapshot.gpu_driver_version || 'N/A' },
{ label: 'Mémoire dédiée', value: snapshot.gpu_memory_dedicated_mb ? `${snapshot.gpu_memory_dedicated_mb} MB` : 'N/A' },
{ label: 'Mémoire partagée', value: snapshot.gpu_memory_shared_mb ? `${snapshot.gpu_memory_shared_mb} MB` : 'N/A' }
];
let html = `
${items.map(item => `
${item.label}
${escapeHtml(String(item.value))}
`).join('')}
`;
// API Support
if (snapshot.gpu_api_support) {
try {
const apiSupport = typeof snapshot.gpu_api_support === 'string'
? JSON.parse(snapshot.gpu_api_support)
: snapshot.gpu_api_support;
if (Array.isArray(apiSupport) && apiSupport.length > 0) {
html += `
APIs supportées :
${apiSupport.map(api => `${escapeHtml(api)}`).join('')}
`;
}
} catch (e) {
console.warn('Failed to parse GPU API support:', e);
}
}
container.innerHTML = html;
}
// Render OS Details
function renderOSDetails() {
const snapshot = currentDevice.last_hardware_snapshot;
const container = document.getElementById('osDetails');
if (!snapshot) {
container.innerHTML = 'Aucune information disponible
';
return;
}
const items = [
{ label: 'Nom', value: snapshot.os_name || 'N/A' },
{ label: 'Version', value: snapshot.os_version || 'N/A' },
{ label: 'Kernel', value: snapshot.kernel_version || 'N/A' },
{ label: 'Architecture', value: snapshot.architecture || 'N/A' },
{ label: 'Virtualisation', value: snapshot.virtualization_type || 'none' }
];
container.innerHTML = `
${items.map(item => `
${item.label}
${escapeHtml(String(item.value))}
`).join('')}
`;
}
// Render Benchmark Results
function renderBenchmarkResults() {
const bench = currentDevice.last_benchmark;
const container = document.getElementById('benchmarkResults');
if (!bench) {
container.innerHTML = 'Aucun benchmark disponible
';
return;
}
container.innerHTML = `
Dernier benchmark:
${formatDate(bench.run_at)}
Version:
${escapeHtml(bench.bench_script_version || 'N/A')}
${createScoreBadge(bench.global_score, 'Score Global')}
${createScoreBadge(bench.cpu_score, 'CPU')}
${createScoreBadge(bench.memory_score, 'Mémoire')}
${createScoreBadge(bench.disk_score, 'Disque')}
${createScoreBadge(bench.network_score, 'Réseau')}
${createScoreBadge(bench.gpu_score, 'GPU')}
`;
}
// Render Network Details
function renderNetworkDetails() {
const container = document.getElementById('networkDetails');
const snapshot = currentDevice.last_hardware_snapshot;
if (!snapshot || !snapshot.network_interfaces_json) {
container.innerHTML = 'Aucune information réseau disponible
';
return;
}
try {
const interfaces = typeof snapshot.network_interfaces_json === 'string'
? JSON.parse(snapshot.network_interfaces_json)
: snapshot.network_interfaces_json;
if (!interfaces || interfaces.length === 0) {
container.innerHTML = 'Aucune interface réseau détectée
';
return;
}
let html = '';
// Interface details
interfaces.forEach(iface => {
const typeIcon = iface.type === 'ethernet' ? '🔌' : (iface.type === 'wifi' ? '📡' : '🌐');
const wol = iface.wake_on_lan;
const wolBadge = wol === true
? '
WoL ✓'
: (wol === false ? '
WoL ✗' : '');
html += `
${typeIcon} ${escapeHtml(iface.name)}
${escapeHtml(iface.type || 'unknown')}
${wolBadge}
${iface.ip ? `
Adresse IP:
${escapeHtml(iface.ip)}
` : ''}
${iface.mac ? `
MAC:
${escapeHtml(iface.mac)}
` : ''}
${iface.speed_mbps ? `
Vitesse:
${iface.speed_mbps} Mbps
` : ''}
${iface.driver ? `
Driver:
${escapeHtml(iface.driver)}
` : ''}
`;
});
html += '
';
container.innerHTML = html;
} catch (error) {
console.error('Failed to parse network interfaces:', error);
container.innerHTML = 'Erreur lors du parsing des données réseau
';
}
}
// Load benchmark history
async function loadBenchmarkHistory() {
const container = document.getElementById('benchmarkHistory');
try {
const data = await api.getDeviceBenchmarks(currentDeviceId, { limit: 20 });
if (!data.items || data.items.length === 0) {
showEmptyState(container, 'Aucun benchmark dans l\'historique', '📊');
return;
}
container.innerHTML = `
| Date |
Score Global |
CPU |
MEM |
DISK |
NET |
GPU |
Version |
Action |
${data.items.map(bench => `
| ${formatDate(bench.run_at)} |
${getScoreBadgeText(bench.global_score)} |
${getScoreBadgeText(bench.cpu_score)} |
${getScoreBadgeText(bench.memory_score)} |
${getScoreBadgeText(bench.disk_score)} |
${getScoreBadgeText(bench.network_score)} |
${getScoreBadgeText(bench.gpu_score)} |
${escapeHtml(bench.bench_script_version || 'N/A')} |
|
`).join('')}
`;
} catch (error) {
console.error('Failed to load benchmarks:', error);
showError(container, 'Erreur lors du chargement de l\'historique');
}
}
// View benchmark details
async function viewBenchmarkDetails(benchmarkId) {
const modalBody = document.getElementById('benchmarkModalBody');
openModal('benchmarkModal');
try {
const benchmark = await api.getBenchmark(benchmarkId);
modalBody.innerHTML = `
${JSON.stringify(benchmark.details || benchmark, null, 2)}
`;
} catch (error) {
console.error('Failed to load benchmark details:', error);
modalBody.innerHTML = `Erreur: ${escapeHtml(error.message)}
`;
}
}
// Load documents
async function loadDocuments() {
const container = document.getElementById('documentsList');
try {
// Use documents from currentDevice (already loaded)
const docs = currentDevice.documents || [];
if (!docs || docs.length === 0) {
showEmptyState(container, 'Aucun document uploadé', '📄');
return;
}
// Separate images from other documents
const images = docs.filter(doc => doc.doc_type === 'image');
const otherDocs = docs.filter(doc => doc.doc_type !== 'image');
let html = '';
// Display images with preview
if (images.length > 0) {
html += '🖼️ Images
';
html += '';
images.forEach(doc => {
const downloadUrl = api.getDocumentDownloadUrl(doc.id);
html += `
📎 ${escapeHtml(doc.filename)}
${formatFileSize(doc.size_bytes)} • ${formatDate(doc.uploaded_at)}
`;
});
html += '
';
}
// Display other documents (PDFs, manuals, etc.)
if (otherDocs.length > 0) {
html += '📄 Autres Documents
';
html += '';
otherDocs.forEach(doc => {
html += `
-
${getDocIcon(doc.doc_type)}
${escapeHtml(doc.filename)}
${doc.doc_type} • ${formatFileSize(doc.size_bytes)} • ${formatDate(doc.uploaded_at)}
`;
});
html += '
';
}
container.innerHTML = html;
} catch (error) {
console.error('Failed to load documents:', error);
showError(container, 'Erreur lors du chargement des documents');
}
}
// Get document icon
function getDocIcon(docType) {
const icons = {
image: '🖼️',
manual: '📘',
warranty: '📜',
invoice: '🧾',
photo: '📷',
other: '📄'
};
return icons[docType] || '📄';
}
// Upload document
async function uploadDocument() {
const fileInput = document.getElementById('fileInput');
const docTypeSelect = document.getElementById('docTypeSelect');
if (!fileInput.files || fileInput.files.length === 0) {
showToast('Veuillez sélectionner un fichier', 'error');
return;
}
const file = fileInput.files[0];
const docType = docTypeSelect.value;
try {
await api.uploadDocument(currentDeviceId, file, docType);
showToast('Document uploadé avec succès', 'success');
// Reset form
fileInput.value = '';
docTypeSelect.value = 'manual';
// Reload device to get updated documents
currentDevice = await api.getDevice(currentDeviceId);
await loadDocuments();
} catch (error) {
console.error('Failed to upload document:', error);
showToast('Erreur lors de l\'upload: ' + error.message, 'error');
}
}
// Delete document
async function deleteDocument(docId) {
if (!confirm('Êtes-vous sûr de vouloir supprimer ce document ?')) {
return;
}
try {
await api.deleteDocument(docId);
showToast('Document supprimé', 'success');
// Reload device to get updated documents
currentDevice = await api.getDevice(currentDeviceId);
await loadDocuments();
} catch (error) {
console.error('Failed to delete document:', error);
showToast('Erreur lors de la suppression: ' + error.message, 'error');
}
}
// Load links
async function loadLinks() {
const container = document.getElementById('linksList');
try {
const links = await api.getDeviceLinks(currentDeviceId);
if (!links || links.length === 0) {
showEmptyState(container, 'Aucun lien ajouté', '🔗');
return;
}
container.innerHTML = `
${links.map(link => `
-
`).join('')}
`;
} catch (error) {
console.error('Failed to load links:', error);
showError(container, 'Erreur lors du chargement des liens');
}
}
// Add link
async function addLink() {
const labelInput = document.getElementById('linkLabel');
const urlInput = document.getElementById('linkUrl');
const label = labelInput.value.trim();
const url = urlInput.value.trim();
if (!label || !url) {
showToast('Veuillez remplir tous les champs', 'error');
return;
}
try {
await api.addDeviceLink(currentDeviceId, { label, url });
showToast('Lien ajouté avec succès', 'success');
// Reset form
labelInput.value = '';
urlInput.value = '';
// Reload links
await loadLinks();
} catch (error) {
console.error('Failed to add link:', error);
showToast('Erreur lors de l\'ajout: ' + error.message, 'error');
}
}
// Delete link
async function deleteLink(linkId) {
if (!confirm('Êtes-vous sûr de vouloir supprimer ce lien ?')) {
return;
}
try {
await api.deleteLink(linkId);
showToast('Lien supprimé', 'success');
await loadLinks();
} catch (error) {
console.error('Failed to delete link:', error);
showToast('Erreur lors de la suppression: ' + error.message, 'error');
}
}
// Make functions available globally
window.viewBenchmarkDetails = viewBenchmarkDetails;
window.uploadDocument = uploadDocument;
window.deleteDocument = deleteDocument;
window.addLink = addLink;
window.deleteLink = deleteLink;