maj
This commit is contained in:
@@ -34,9 +34,14 @@ async function loadDeviceDetail() {
|
||||
|
||||
// Render all sections
|
||||
renderDeviceHeader();
|
||||
renderHardwareSummary();
|
||||
renderLastBenchmark();
|
||||
renderMotherboardDetails();
|
||||
renderCPUDetails();
|
||||
renderMemoryDetails();
|
||||
renderStorageDetails();
|
||||
renderGPUDetails();
|
||||
renderNetworkDetails();
|
||||
renderOSDetails();
|
||||
renderBenchmarkResults();
|
||||
await loadBenchmarkHistory();
|
||||
await loadDocuments();
|
||||
await loadLinks();
|
||||
@@ -77,74 +82,371 @@ function renderDeviceHeader() {
|
||||
}
|
||||
}
|
||||
|
||||
// Render hardware summary
|
||||
function renderHardwareSummary() {
|
||||
// Render Motherboard Details
|
||||
function renderMotherboardDetails() {
|
||||
const snapshot = currentDevice.last_hardware_snapshot;
|
||||
const container = document.getElementById('motherboardDetails');
|
||||
|
||||
if (!snapshot) {
|
||||
document.getElementById('hardwareSummary').innerHTML =
|
||||
'<p style="color: var(--text-muted);">Aucune information hardware disponible</p>';
|
||||
container.innerHTML = '<p style="color: var(--text-muted);">Aucune information disponible</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
// RAM usage info
|
||||
const ramTotalGB = Math.round((snapshot.ram_total_mb || 0) / 1024);
|
||||
const ramUsedMB = snapshot.ram_used_mb || 0;
|
||||
const ramFreeMB = snapshot.ram_free_mb || 0;
|
||||
const ramSharedMB = snapshot.ram_shared_mb || 0;
|
||||
// Helper to clean empty/whitespace-only strings
|
||||
const cleanValue = (val) => {
|
||||
if (!val || (typeof val === 'string' && val.trim() === '')) return 'N/A';
|
||||
return val;
|
||||
};
|
||||
|
||||
let ramValue = `${ramTotalGB} GB`;
|
||||
if (ramUsedMB > 0 || ramFreeMB > 0) {
|
||||
const usagePercent = ramTotalGB > 0 ? Math.round((ramUsedMB / (snapshot.ram_total_mb || 1)) * 100) : 0;
|
||||
ramValue = `${ramTotalGB} GB (${usagePercent}% utilisé)<br><small>Utilisée: ${Math.round(ramUsedMB / 1024)}GB • Libre: ${Math.round(ramFreeMB / 1024)}GB`;
|
||||
if (ramSharedMB > 0) {
|
||||
ramValue += ` • Partagée: ${Math.round(ramSharedMB / 1024)}GB`;
|
||||
}
|
||||
ramValue += `<br>${snapshot.ram_slots_used || '?'} / ${snapshot.ram_slots_total || '?'} slots</small>`;
|
||||
} else {
|
||||
ramValue += `<br><small>${snapshot.ram_slots_used || '?'} / ${snapshot.ram_slots_total || '?'} slots</small>`;
|
||||
}
|
||||
|
||||
const hardwareItems = [
|
||||
{ label: 'CPU', icon: '🔲', value: `${snapshot.cpu_model || 'N/A'}<br><small>${snapshot.cpu_cores || 0}C / ${snapshot.cpu_threads || 0}T @ ${snapshot.cpu_max_freq_ghz || snapshot.cpu_base_freq_ghz || '?'} GHz</small>` },
|
||||
{ label: 'RAM', icon: '💾', value: ramValue },
|
||||
{ label: 'GPU', icon: '🎮', value: snapshot.gpu_model || snapshot.gpu_summary || 'N/A' },
|
||||
{ label: 'Stockage', icon: '💿', value: snapshot.storage_summary || 'N/A' },
|
||||
{ label: 'Réseau', icon: '🌐', value: snapshot.network_interfaces_json ? `${JSON.parse(snapshot.network_interfaces_json).length} interface(s)` : 'N/A' },
|
||||
{ label: 'Carte mère', icon: '⚡', value: `${snapshot.motherboard_vendor || ''} ${snapshot.motherboard_model || 'N/A'}` },
|
||||
{ label: 'OS', icon: '🐧', value: `${snapshot.os_name || 'N/A'} ${snapshot.os_version || ''}<br><small>Kernel ${snapshot.kernel_version || 'N/A'}</small>` },
|
||||
{ label: 'Architecture', icon: '🏗️', value: snapshot.architecture || 'N/A' },
|
||||
{ label: 'Virtualisation', icon: '📦', value: snapshot.virtualization_type || 'none' }
|
||||
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` }
|
||||
];
|
||||
|
||||
document.getElementById('hardwareSummary').innerHTML = hardwareItems.map(item => `
|
||||
<div class="hardware-item">
|
||||
<div class="hardware-item-label">${item.icon} ${item.label}</div>
|
||||
<div class="hardware-item-value">${item.value}</div>
|
||||
container.innerHTML = `
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1rem;">
|
||||
${items.map(item => `
|
||||
<div class="hardware-item">
|
||||
<div class="hardware-item-label">${item.label}</div>
|
||||
<div class="hardware-item-value">${escapeHtml(String(item.value))}</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
`).join('');
|
||||
`;
|
||||
}
|
||||
|
||||
// Render last benchmark scores
|
||||
function renderLastBenchmark() {
|
||||
const bench = currentDevice.last_benchmark;
|
||||
// Render CPU Details
|
||||
function renderCPUDetails() {
|
||||
const snapshot = currentDevice.last_hardware_snapshot;
|
||||
const container = document.getElementById('cpuDetails');
|
||||
|
||||
if (!bench) {
|
||||
document.getElementById('lastBenchmark').innerHTML =
|
||||
'<p style="color: var(--text-muted);">Aucun benchmark disponible</p>';
|
||||
if (!snapshot) {
|
||||
container.innerHTML = '<p style="color: var(--text-muted);">Aucune information disponible</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('lastBenchmark').innerHTML = `
|
||||
<div style="margin-bottom: 1rem;">
|
||||
<span style="color: var(--text-secondary);">Date: </span>
|
||||
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 = `
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem;">
|
||||
${items.map(item => `
|
||||
<div class="hardware-item">
|
||||
<div class="hardware-item-label">${item.label}</div>
|
||||
<div class="hardware-item-value">${escapeHtml(String(item.value))}</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 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 += `
|
||||
<div style="margin-top: 1.5rem;">
|
||||
<div style="font-weight: 600; margin-bottom: 0.5rem; color: var(--text-secondary);">Instructions supportées :</div>
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 0.5rem;">
|
||||
${flags.slice(0, 50).map(flag => `<span class="badge badge-muted">${escapeHtml(flag)}</span>`).join('')}
|
||||
${flags.length > 50 ? `<span class="badge">+${flags.length - 50} autres...</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
} 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 = '<p style="color: var(--text-muted);">Aucune information disponible</p>';
|
||||
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 = `
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 1.5rem;">
|
||||
${items.map(item => `
|
||||
<div class="hardware-item">
|
||||
<div class="hardware-item-label">${item.label}</div>
|
||||
<div class="hardware-item-value">${escapeHtml(String(item.value))}</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 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 += `
|
||||
<div style="margin-top: 1rem;">
|
||||
<div style="font-weight: 600; margin-bottom: 0.75rem; color: var(--text-secondary);">Configuration des barrettes :</div>
|
||||
<div style="display: grid; gap: 0.75rem;">
|
||||
${layout.map(dimm => `
|
||||
<div style="border: 1px solid var(--border-color); border-radius: 8px; padding: 1rem; display: grid; grid-template-columns: auto 1fr; gap: 0.5rem 1rem; align-items: center;">
|
||||
<strong style="color: var(--color-primary);">Slot ${escapeHtml(dimm.slot || 'N/A')}</strong>
|
||||
<div>
|
||||
${dimm.size_mb ? `${Math.round(dimm.size_mb / 1024)} GB` : 'N/A'}
|
||||
${dimm.type ? `• ${escapeHtml(dimm.type)}` : ''}
|
||||
${dimm.speed_mhz ? `• ${dimm.speed_mhz} MHz` : ''}
|
||||
</div>
|
||||
${dimm.vendor || dimm.manufacturer ? `
|
||||
<span style="color: var(--text-secondary);">Fabricant</span>
|
||||
<span>${escapeHtml(dimm.vendor || dimm.manufacturer)}</span>
|
||||
` : ''}
|
||||
${dimm.part_number ? `
|
||||
<span style="color: var(--text-secondary);">Part Number</span>
|
||||
<span><code style="font-size: 0.85rem;">${escapeHtml(dimm.part_number)}</code></span>
|
||||
` : ''}
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
} 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 = '<p style="color: var(--text-muted);">Aucune information disponible</p>';
|
||||
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 += '<div style="display: grid; gap: 1rem;">';
|
||||
|
||||
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 += `
|
||||
<div style="border: 1px solid var(--border-color); border-radius: 8px; padding: 1rem;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 0.75rem;">
|
||||
<div>
|
||||
<div style="font-weight: 600; color: var(--color-primary); font-size: 1.1rem;">
|
||||
${typeIcon} ${escapeHtml(disk.name || disk.device || 'N/A')}
|
||||
</div>
|
||||
<div style="color: var(--text-secondary); font-size: 0.9rem; margin-top: 0.25rem;">
|
||||
${escapeHtml(disk.model || 'Unknown model')}
|
||||
</div>
|
||||
</div>
|
||||
${disk.smart_health ? `
|
||||
<span class="badge" style="background: ${healthColor}; color: white;">
|
||||
${escapeHtml(disk.smart_health)}
|
||||
</span>
|
||||
` : ''}
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 0.75rem; font-size: 0.9rem;">
|
||||
${disk.capacity_gb ? `
|
||||
<div>
|
||||
<strong>Capacité:</strong> ${disk.capacity_gb} GB
|
||||
</div>
|
||||
` : ''}
|
||||
${disk.type ? `
|
||||
<div>
|
||||
<strong>Type:</strong> ${escapeHtml(disk.type)}
|
||||
</div>
|
||||
` : ''}
|
||||
${disk.interface ? `
|
||||
<div>
|
||||
<strong>Interface:</strong> ${escapeHtml(disk.interface)}
|
||||
</div>
|
||||
` : ''}
|
||||
${disk.temperature_c ? `
|
||||
<div>
|
||||
<strong>Température:</strong> ${disk.temperature_c}°C
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
html += '</div>';
|
||||
} else {
|
||||
html = '<p style="color: var(--text-muted);">Aucun disque détecté</p>';
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to parse storage devices:', e);
|
||||
html = '<p style="color: var(--text-danger);">Erreur lors du parsing des données de stockage</p>';
|
||||
}
|
||||
} else {
|
||||
html = '<p style="color: var(--text-muted);">Aucune information de stockage disponible</p>';
|
||||
}
|
||||
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
// Render GPU Details
|
||||
function renderGPUDetails() {
|
||||
const snapshot = currentDevice.last_hardware_snapshot;
|
||||
const container = document.getElementById('gpuDetails');
|
||||
|
||||
if (!snapshot) {
|
||||
container.innerHTML = '<p style="color: var(--text-muted);">Aucune information disponible</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!snapshot.gpu_vendor && !snapshot.gpu_model && !snapshot.gpu_summary) {
|
||||
container.innerHTML = '<p style="color: var(--text-muted);">Aucun GPU détecté</p>';
|
||||
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 = `
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem;">
|
||||
${items.map(item => `
|
||||
<div class="hardware-item">
|
||||
<div class="hardware-item-label">${item.label}</div>
|
||||
<div class="hardware-item-value">${escapeHtml(String(item.value))}</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 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 += `
|
||||
<div style="margin-top: 1rem;">
|
||||
<div style="font-weight: 600; margin-bottom: 0.5rem; color: var(--text-secondary);">APIs supportées :</div>
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 0.5rem;">
|
||||
${apiSupport.map(api => `<span class="badge badge-success">${escapeHtml(api)}</span>`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
} 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 = '<p style="color: var(--text-muted);">Aucune information disponible</p>';
|
||||
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 = `
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem;">
|
||||
${items.map(item => `
|
||||
<div class="hardware-item">
|
||||
<div class="hardware-item-label">${item.label}</div>
|
||||
<div class="hardware-item-value">${escapeHtml(String(item.value))}</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Render Benchmark Results
|
||||
function renderBenchmarkResults() {
|
||||
const bench = currentDevice.last_benchmark;
|
||||
const container = document.getElementById('benchmarkResults');
|
||||
|
||||
if (!bench) {
|
||||
container.innerHTML = '<p style="color: var(--text-muted);">Aucun benchmark disponible</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = `
|
||||
<div style="margin-bottom: 1.5rem;">
|
||||
<span style="color: var(--text-secondary);">Dernier benchmark: </span>
|
||||
<strong>${formatDate(bench.run_at)}</strong>
|
||||
<span style="margin-left: 1rem; color: var(--text-secondary);">Version: </span>
|
||||
<strong>${escapeHtml(bench.bench_script_version || 'N/A')}</strong>
|
||||
</div>
|
||||
|
||||
<div class="score-grid">
|
||||
${createScoreBadge(bench.global_score, 'Global')}
|
||||
${createScoreBadge(bench.global_score, 'Score Global')}
|
||||
${createScoreBadge(bench.cpu_score, 'CPU')}
|
||||
${createScoreBadge(bench.memory_score, 'Mémoire')}
|
||||
${createScoreBadge(bench.disk_score, 'Disque')}
|
||||
@@ -152,19 +454,18 @@ function renderLastBenchmark() {
|
||||
${createScoreBadge(bench.gpu_score, 'GPU')}
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 1rem;">
|
||||
<div style="margin-top: 1.5rem;">
|
||||
<button class="btn btn-secondary btn-sm" onclick="viewBenchmarkDetails(${bench.id})">
|
||||
Voir les détails complets (JSON)
|
||||
📋 Voir les détails complets (JSON)
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Render network details
|
||||
// Render Network Details
|
||||
function renderNetworkDetails() {
|
||||
const container = document.getElementById('networkDetails');
|
||||
const snapshot = currentDevice.last_hardware_snapshot;
|
||||
const bench = currentDevice.last_benchmark;
|
||||
|
||||
if (!snapshot || !snapshot.network_interfaces_json) {
|
||||
container.innerHTML = '<p style="color: var(--text-muted);">Aucune information réseau disponible</p>';
|
||||
@@ -172,7 +473,9 @@ function renderNetworkDetails() {
|
||||
}
|
||||
|
||||
try {
|
||||
const interfaces = JSON.parse(snapshot.network_interfaces_json);
|
||||
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 = '<p style="color: var(--text-muted);">Aucune interface réseau détectée</p>';
|
||||
@@ -190,87 +493,45 @@ function renderNetworkDetails() {
|
||||
: (wol === false ? '<span class="badge badge-muted" style="margin-left: 0.5rem;">WoL ✗</span>' : '');
|
||||
|
||||
html += `
|
||||
<div class="hardware-item" style="border: 1px solid var(--border-color); border-radius: 8px; padding: 1rem;">
|
||||
<div style="border: 1px solid var(--border-color); border-radius: 8px; padding: 1rem;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 0.75rem;">
|
||||
<div>
|
||||
<div style="font-weight: 600; color: var(--color-primary);">${typeIcon} ${escapeHtml(iface.name)}</div>
|
||||
<div style="font-weight: 600; color: var(--color-primary); font-size: 1.05rem;">${typeIcon} ${escapeHtml(iface.name)}</div>
|
||||
<div style="color: var(--text-secondary); font-size: 0.9rem; margin-top: 0.25rem;">${escapeHtml(iface.type || 'unknown')}</div>
|
||||
</div>
|
||||
<div>${wolBadge}</div>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; gap: 0.5rem; font-size: 0.9rem;">
|
||||
${iface.ip ? `<div><strong>IP:</strong> ${escapeHtml(iface.ip)}</div>` : ''}
|
||||
${iface.mac ? `<div><strong>MAC:</strong> <code>${escapeHtml(iface.mac)}</code></div>` : ''}
|
||||
${iface.speed_mbps ? `<div><strong>Vitesse:</strong> ${iface.speed_mbps} Mbps</div>` : ''}
|
||||
${iface.driver ? `<div><strong>Driver:</strong> ${escapeHtml(iface.driver)}</div>` : ''}
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 0.75rem; font-size: 0.9rem;">
|
||||
${iface.ip ? `
|
||||
<div>
|
||||
<strong style="color: var(--text-secondary);">Adresse IP:</strong><br>
|
||||
<code>${escapeHtml(iface.ip)}</code>
|
||||
</div>
|
||||
` : ''}
|
||||
${iface.mac ? `
|
||||
<div>
|
||||
<strong style="color: var(--text-secondary);">MAC:</strong><br>
|
||||
<code>${escapeHtml(iface.mac)}</code>
|
||||
</div>
|
||||
` : ''}
|
||||
${iface.speed_mbps ? `
|
||||
<div>
|
||||
<strong style="color: var(--text-secondary);">Vitesse:</strong><br>
|
||||
${iface.speed_mbps} Mbps
|
||||
</div>
|
||||
` : ''}
|
||||
${iface.driver ? `
|
||||
<div>
|
||||
<strong style="color: var(--text-secondary);">Driver:</strong><br>
|
||||
${escapeHtml(iface.driver)}
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
// Network benchmark results (iperf3)
|
||||
if (bench && bench.network_score !== null && bench.network_score !== undefined) {
|
||||
let netBenchHtml = '<div style="border: 2px solid var(--color-info); border-radius: 8px; padding: 1rem; margin-top: 1rem;">';
|
||||
netBenchHtml += '<div style="font-weight: 600; color: var(--color-info); margin-bottom: 0.75rem;">📈 Résultats Benchmark Réseau (iperf3)</div>';
|
||||
netBenchHtml += '<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1rem;">';
|
||||
|
||||
// Try to parse network_results_json if available
|
||||
let uploadMbps = null;
|
||||
let downloadMbps = null;
|
||||
let pingMs = null;
|
||||
|
||||
if (bench.network_results_json) {
|
||||
try {
|
||||
const netResults = typeof bench.network_results_json === 'string'
|
||||
? JSON.parse(bench.network_results_json)
|
||||
: bench.network_results_json;
|
||||
uploadMbps = netResults.upload_mbps;
|
||||
downloadMbps = netResults.download_mbps;
|
||||
pingMs = netResults.ping_ms;
|
||||
} catch (e) {
|
||||
console.warn('Failed to parse network_results_json:', e);
|
||||
}
|
||||
}
|
||||
|
||||
if (uploadMbps !== null && uploadMbps !== undefined) {
|
||||
netBenchHtml += `
|
||||
<div style="text-align: center;">
|
||||
<div style="font-size: 1.5rem; font-weight: 600; color: var(--color-success);">↑ ${uploadMbps.toFixed(2)}</div>
|
||||
<div style="color: var(--text-secondary); font-size: 0.85rem;">Upload Mbps</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
if (downloadMbps !== null && downloadMbps !== undefined) {
|
||||
netBenchHtml += `
|
||||
<div style="text-align: center;">
|
||||
<div style="font-size: 1.5rem; font-weight: 600; color: var(--color-info);">↓ ${downloadMbps.toFixed(2)}</div>
|
||||
<div style="color: var(--text-secondary); font-size: 0.85rem;">Download Mbps</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
if (pingMs !== null && pingMs !== undefined) {
|
||||
netBenchHtml += `
|
||||
<div style="text-align: center;">
|
||||
<div style="font-size: 1.5rem; font-weight: 600; color: var(--color-warning);">${typeof pingMs === 'number' ? pingMs.toFixed(2) : pingMs}</div>
|
||||
<div style="color: var(--text-secondary); font-size: 0.85rem;">Ping ms</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
netBenchHtml += `
|
||||
<div style="text-align: center;">
|
||||
<div style="font-size: 1.5rem; font-weight: 600; color: var(--color-primary);">${bench.network_score.toFixed(2)}</div>
|
||||
<div style="color: var(--text-secondary); font-size: 0.85rem;">Score</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
netBenchHtml += '</div></div>';
|
||||
html += netBenchHtml;
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
container.innerHTML = html;
|
||||
|
||||
@@ -360,16 +621,58 @@ async function loadDocuments() {
|
||||
const container = document.getElementById('documentsList');
|
||||
|
||||
try {
|
||||
const docs = await api.getDeviceDocs(currentDeviceId);
|
||||
// Use documents from currentDevice (already loaded)
|
||||
const docs = currentDevice.documents || [];
|
||||
|
||||
if (!docs || docs.length === 0) {
|
||||
showEmptyState(container, 'Aucun document uploadé', '📄');
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = `
|
||||
<ul class="document-list">
|
||||
${docs.map(doc => `
|
||||
// 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 += '<h4 style="margin-bottom: 1rem; color: var(--color-primary);">🖼️ Images</h4>';
|
||||
html += '<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 1rem; margin-bottom: 2rem;">';
|
||||
|
||||
images.forEach(doc => {
|
||||
const downloadUrl = api.getDocumentDownloadUrl(doc.id);
|
||||
html += `
|
||||
<div style="border: 1px solid var(--border-color); border-radius: 8px; overflow: hidden; background: var(--bg-secondary);">
|
||||
<div style="width: 100%; height: 200px; background: var(--bg-primary); display: flex; align-items: center; justify-content: center; overflow: hidden;">
|
||||
<img src="${downloadUrl}" alt="${escapeHtml(doc.filename)}" style="max-width: 100%; max-height: 100%; object-fit: contain; cursor: pointer;" onclick="window.open('${downloadUrl}', '_blank')">
|
||||
</div>
|
||||
<div style="padding: 0.75rem;">
|
||||
<div style="font-size: 0.9rem; font-weight: 500; color: var(--text-primary); margin-bottom: 0.5rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" title="${escapeHtml(doc.filename)}">
|
||||
📎 ${escapeHtml(doc.filename)}
|
||||
</div>
|
||||
<div style="font-size: 0.8rem; color: var(--text-secondary); margin-bottom: 0.75rem;">
|
||||
${formatFileSize(doc.size_bytes)} • ${formatDate(doc.uploaded_at)}
|
||||
</div>
|
||||
<div style="display: flex; gap: 0.5rem;">
|
||||
<a href="${downloadUrl}" class="btn btn-sm btn-secondary" download style="flex: 1; text-align: center;">⬇️ Télécharger</a>
|
||||
<button class="btn btn-sm btn-danger" onclick="deleteDocument(${doc.id})" style="flex: 1;">🗑️ Supprimer</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
// Display other documents (PDFs, manuals, etc.)
|
||||
if (otherDocs.length > 0) {
|
||||
html += '<h4 style="margin-bottom: 1rem; color: var(--color-primary);">📄 Autres Documents</h4>';
|
||||
html += '<ul class="document-list">';
|
||||
|
||||
otherDocs.forEach(doc => {
|
||||
html += `
|
||||
<li class="document-item">
|
||||
<div class="document-info">
|
||||
<span class="document-icon">${getDocIcon(doc.doc_type)}</span>
|
||||
@@ -381,13 +684,17 @@ async function loadDocuments() {
|
||||
</div>
|
||||
</div>
|
||||
<div class="document-actions">
|
||||
<a href="${api.getDocumentDownloadUrl(doc.id)}" class="btn btn-sm btn-secondary" download>Télécharger</a>
|
||||
<button class="btn btn-sm btn-danger" onclick="deleteDocument(${doc.id})">Supprimer</button>
|
||||
<a href="${api.getDocumentDownloadUrl(doc.id)}" class="btn btn-sm btn-secondary" download>⬇️ Télécharger</a>
|
||||
<button class="btn btn-sm btn-danger" onclick="deleteDocument(${doc.id})">🗑️ Supprimer</button>
|
||||
</div>
|
||||
</li>
|
||||
`).join('')}
|
||||
</ul>
|
||||
`;
|
||||
`;
|
||||
});
|
||||
|
||||
html += '</ul>';
|
||||
}
|
||||
|
||||
container.innerHTML = html;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to load documents:', error);
|
||||
@@ -398,6 +705,7 @@ async function loadDocuments() {
|
||||
// Get document icon
|
||||
function getDocIcon(docType) {
|
||||
const icons = {
|
||||
image: '🖼️',
|
||||
manual: '📘',
|
||||
warranty: '📜',
|
||||
invoice: '🧾',
|
||||
@@ -428,7 +736,8 @@ async function uploadDocument() {
|
||||
fileInput.value = '';
|
||||
docTypeSelect.value = 'manual';
|
||||
|
||||
// Reload documents
|
||||
// Reload device to get updated documents
|
||||
currentDevice = await api.getDevice(currentDeviceId);
|
||||
await loadDocuments();
|
||||
|
||||
} catch (error) {
|
||||
@@ -446,6 +755,9 @@ async function deleteDocument(docId) {
|
||||
try {
|
||||
await api.deleteDocument(docId);
|
||||
showToast('Document supprimé', 'success');
|
||||
|
||||
// Reload device to get updated documents
|
||||
currentDevice = await api.getDevice(currentDeviceId);
|
||||
await loadDocuments();
|
||||
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user