add go bench client

This commit is contained in:
Gilles Soulier
2026-01-11 23:41:30 +01:00
parent c67befc549
commit 6abc70cdfe
80 changed files with 13311 additions and 61 deletions

View File

@@ -14,33 +14,34 @@ let editingNotes = false;
let editingUpgradeNotes = false;
let editingPurchase = false;
const SECTION_ICON_PATHS = {
motherboard: 'icons/icons8-motherboard-94.png',
cpu: 'icons/icons8-processor-94.png',
ram: 'icons/icons8-memory-slot-94.png',
storage: 'icons/icons8-ssd-94.png',
gpu: 'icons/icons8-gpu-64.png',
network: 'icons/icons8-network-cable-94.png',
usb: 'icons/icons8-usb-memory-stick-94.png',
pci: 'icons/icons8-pcie-48.png',
os: 'icons/icons8-operating-system-64.png',
shares: 'icons/icons8-shared-folder-94.png',
benchmarks: 'icons/icons8-benchmark-64.png',
metadata: 'icons/icons8-hardware-64.png',
images: 'icons/icons8-picture-48.png',
pdf: 'icons/icons8-bios-94.png',
links: 'icons/icons8-server-94.png',
tags: 'icons/icons8-check-mark-48.png',
notes: 'icons/icons8-edit-pencil-48.png',
purchase: 'icons/icons8-laptop-50.png',
upgrade: 'icons/icons8-workstation-94.png'
// Section icon mapping - uses data-icon with IconManager
const SECTION_ICON_NAMES = {
motherboard: 'motherboard',
cpu: 'cpu',
ram: 'memory',
storage: 'hdd',
gpu: 'gpu',
network: 'network',
usb: 'usb',
pci: 'pci',
os: 'desktop',
shares: 'folder',
benchmarks: 'chart-line',
metadata: 'info-circle',
images: 'image',
pdf: 'file-pdf',
links: 'link',
tags: 'tag',
notes: 'edit',
purchase: 'shopping-cart',
upgrade: 'rocket'
};
function getSectionIcon(key, altText) {
const src = SECTION_ICON_PATHS[key];
if (!src) return '';
const iconName = SECTION_ICON_NAMES[key];
if (!iconName) return '';
const safeAlt = utils.escapeHtml(altText || key);
return `<img src="${src}" alt="${safeAlt}" class="section-icon" loading="lazy">`;
return `<span class="section-icon" data-icon="${iconName}" title="${safeAlt}"></span>`;
}
// Load devices
@@ -1083,6 +1084,117 @@ async function viewBenchmarkDetails(benchmarkId) {
}
}
// Render IP Display with edit capability
function renderIPDisplay(snapshot, device) {
// Extract non-loopback IPs
const networkInterfaces = snapshot?.network_interfaces_json ?
(typeof snapshot.network_interfaces_json === 'string' ? JSON.parse(snapshot.network_interfaces_json) : snapshot.network_interfaces_json) :
[];
const ips = networkInterfaces
.filter(iface => iface.ipv4 && iface.ipv4 !== '127.0.0.1' && iface.ipv4 !== 'N/A')
.map(iface => iface.ipv4);
const displayIP = ips.length > 0 ? ips.join(', ') : 'N/A';
const ipUrl = device.ip_url || (ips.length > 0 ? `http://${ips[0]}` : '');
return `
<div style="display: flex; flex-direction: column; gap: 0.5rem;">
<div style="display: flex; align-items: center; gap: 0.5rem;">
${ipUrl ? `<a href="${utils.escapeHtml(ipUrl)}" target="_blank" rel="noopener noreferrer" style="color: var(--color-info); text-decoration: none; font-weight: 600;" title="Ouvrir ${utils.escapeHtml(ipUrl)}">${utils.escapeHtml(displayIP)}</a>` : `<span>${utils.escapeHtml(displayIP)}</span>`}
<button id="btn-edit-ip-url" class="icon-btn" data-icon="edit" title="Éditer le lien IP" type="button" style="padding: 0.25rem; font-size: 0.75rem;">
<span data-icon="edit"></span>
</button>
</div>
<div id="ip-url-editor" style="display: none;">
<input type="text" id="ip-url-input" class="ip-url-input" placeholder="http://${ips[0] || '10.0.0.1'}" value="${utils.escapeHtml(ipUrl)}" style="width: 100%; padding: 0.5rem; border: 1px solid var(--border-color); border-radius: 4px; background: var(--bg-primary); color: var(--text-primary);">
<div style="display: flex; gap: 0.5rem; margin-top: 0.5rem;">
<button id="btn-save-ip-url" class="btn btn-success btn-sm" data-icon="check" type="button">
<span data-icon="check"></span> Sauvegarder
</button>
<button id="btn-cancel-ip-url" class="btn btn-secondary btn-sm" data-icon="times" type="button">
<span data-icon="times"></span> Annuler
</button>
</div>
</div>
</div>
`;
}
async function editIPUrl() {
const editor = document.getElementById('ip-url-editor');
const btnEdit = document.getElementById('btn-edit-ip-url');
if (!editor || !btnEdit) return;
editor.style.display = 'block';
btnEdit.style.display = 'none';
document.getElementById('ip-url-input')?.focus();
}
async function saveIPUrl() {
if (!currentDevice) return;
const input = document.getElementById('ip-url-input');
if (!input) return;
let url = input.value.trim();
// Auto-prefix http:// if not present and not empty
if (url && !url.match(/^https?:\/\//)) {
url = `http://${url}`;
}
try {
await apiClient.updateDevice(currentDevice.id, { ip_url: url || null });
utils.showToast('Lien IP sauvegardé', 'success');
await reloadCurrentDevice();
} catch (error) {
console.error('Failed to save IP URL:', error);
utils.showToast(error.message || 'Échec de la sauvegarde du lien IP', 'error');
}
}
async function cancelIPUrlEdit() {
if (!currentDevice) return;
const editor = document.getElementById('ip-url-editor');
const btnEdit = document.getElementById('btn-edit-ip-url');
if (!editor || !btnEdit) return;
editor.style.display = 'none';
btnEdit.style.display = 'inline-block';
// Reset input value
const input = document.getElementById('ip-url-input');
if (input) {
input.value = currentDevice.ip_url || '';
}
}
// Search model on web
function searchModelOnWeb() {
const btn = document.getElementById('btn-search-model');
if (!btn) return;
const model = btn.dataset.model;
if (!model || model === 'N/A') {
utils.showToast('Aucun modèle à rechercher', 'warning');
return;
}
// Get search engine from settings (default: Google)
const searchEngine = localStorage.getItem('searchEngine') || 'google';
const searchUrls = {
google: `https://www.google.com/search?q=${encodeURIComponent(model)}`,
duckduckgo: `https://duckduckgo.com/?q=${encodeURIComponent(model)}`,
bing: `https://www.bing.com/search?q=${encodeURIComponent(model)}`
};
const url = searchUrls[searchEngine] || searchUrls.google;
window.open(url, '_blank', 'noopener,noreferrer');
}
// Render device details (right panel)
function renderDeviceDetails(device) {
const previousDeviceId = currentDevice?.id;
@@ -1118,6 +1230,14 @@ function renderDeviceDetails(device) {
<div class="header-meta">${metaParts.length > 0 ? metaParts.join(' • ') : 'Aucune métadonnée'}</div>
</div>
</div>
<div class="header-row">
<div>
<div class="header-label">Adresse IP</div>
<div class="header-value" id="ip-display-container">
${renderIPDisplay(snapshot, device)}
</div>
</div>
</div>
<div class="header-row">
<div>
<div class="header-label">Marque</div>
@@ -1127,7 +1247,12 @@ function renderDeviceDetails(device) {
<div class="header-row">
<div>
<div class="header-label">Modèle</div>
<div class="header-value">${utils.escapeHtml(model)}</div>
<div class="header-value" style="display: flex; align-items: center; gap: 0.5rem;">
<span>${utils.escapeHtml(model)}</span>
<button id="btn-search-model" class="icon-btn" title="Recherche sur le Web" type="button" style="padding: 0.25rem; font-size: 0.75rem;" data-model="${utils.escapeHtml(model)}">
<span data-icon="globe"></span>
</button>
</div>
</div>
</div>
<div class="header-row">
@@ -1291,6 +1416,11 @@ function renderDeviceDetails(device) {
detailsContainer.innerHTML = headerHtml + orderedSections;
// Initialize icons using IconManager
if (window.IconManager) {
window.IconManager.inlineSvgIcons(detailsContainer);
}
bindDetailActions();
loadLinksSection(device.id);
loadBenchmarkHistorySection(device.id);
@@ -1305,6 +1435,14 @@ function bindDetailActions() {
const btnUploadPDF = document.getElementById('btn-upload-pdf');
const btnDelete = document.getElementById('btn-delete');
// IP URL editing
const btnEditIpUrl = document.getElementById('btn-edit-ip-url');
const btnSaveIpUrl = document.getElementById('btn-save-ip-url');
const btnCancelIpUrl = document.getElementById('btn-cancel-ip-url');
// Web search
const btnSearchModel = document.getElementById('btn-search-model');
if (btnEdit) btnEdit.addEventListener('click', toggleEditMode);
if (btnSave) btnSave.addEventListener('click', saveDevice);
if (btnCancel) {
@@ -1317,6 +1455,14 @@ function bindDetailActions() {
if (btnUploadImageHeader) btnUploadImageHeader.addEventListener('click', uploadImage);
if (btnUploadPDF) btnUploadPDF.addEventListener('click', uploadPDF);
if (btnDelete) btnDelete.addEventListener('click', deleteCurrentDevice);
// Bind IP URL actions
if (btnEditIpUrl) btnEditIpUrl.addEventListener('click', editIPUrl);
if (btnSaveIpUrl) btnSaveIpUrl.addEventListener('click', saveIPUrl);
if (btnCancelIpUrl) btnCancelIpUrl.addEventListener('click', cancelIPUrlEdit);
// Bind web search
if (btnSearchModel) btnSearchModel.addEventListener('click', searchModelOnWeb);
}
async function loadLinksSection(deviceId) {