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

1263 lines
46 KiB
JavaScript
Executable File

/**
* Linux BenchTools - Peripherals Management
*/
// Global state
let currentPage = 1;
let pageSize = 10; // Items per page
let totalPages = 1;
let sortBy = 'date_creation';
let sortOrder = 'desc';
let filterType = '';
let filterLocation = '';
let filterEtat = '';
let searchQuery = '';
let peripheralTypes = [];
let locations = [];
let devices = [];
let hosts = [];
// Initialize
document.addEventListener('DOMContentLoaded', () => {
loadStatistics();
loadPeripheralTypes();
loadLocations();
loadStockageLocations();
loadHosts();
loadBoutiques();
loadDevices();
loadPeripherals();
initStarRating();
const utilisationSelect = document.getElementById('utilisation');
if (utilisationSelect) {
utilisationSelect.addEventListener('change', updateUtilisationFields);
updateUtilisationFields();
}
const photoUrlInput = document.getElementById('photo-url-add');
if (photoUrlInput) {
photoUrlInput.addEventListener('input', updatePhotoUrlAddUI);
}
const photoFileInput = document.getElementById('photo-file-add');
if (photoFileInput) {
photoFileInput.addEventListener('change', onPhotoFileAddChange);
}
// Auto-refresh every 30 seconds
setInterval(() => {
loadStatistics();
loadPeripherals();
}, 30000);
});
// Initialize star rating system
function initStarRating() {
const starContainer = document.querySelector('.star-rating');
if (!starContainer) return;
const stars = starContainer.querySelectorAll('i[data-rating]');
const ratingInput = document.getElementById('rating');
// Click handler
stars.forEach(star => {
star.addEventListener('click', () => {
const rating = parseInt(star.dataset.rating);
ratingInput.value = rating;
updateStarDisplay(rating);
});
// Hover effect
star.addEventListener('mouseenter', () => {
const rating = parseInt(star.dataset.rating);
updateStarDisplay(rating, true);
});
});
// Reset to actual rating on mouse leave
starContainer.addEventListener('mouseleave', () => {
const currentRating = parseInt(ratingInput.value) || 0;
updateStarDisplay(currentRating);
});
}
// Update star display
function updateStarDisplay(rating, isHover = false) {
const stars = document.querySelectorAll('.star-rating i[data-rating]');
stars.forEach(star => {
const starRating = parseInt(star.dataset.rating);
if (starRating <= rating) {
star.classList.add('active');
} else {
star.classList.remove('active');
}
});
}
// Set rating value (used when loading peripheral data)
function setRating(value) {
const ratingInput = document.getElementById('rating');
if (ratingInput) {
ratingInput.value = value || 0;
updateStarDisplay(parseInt(value) || 0);
}
}
// Fill USB detailed information fields
function fillUSBDetails(caracteristiques) {
if (!caracteristiques) {
// Hide USB details section if no data
const section = document.getElementById('usb-details-section');
if (section) section.style.display = 'none';
return;
}
// Check if we have USB-specific fields
const hasUSBData = caracteristiques.vendor_id || caracteristiques.product_id ||
caracteristiques.usb_type || caracteristiques.interface_classes;
const section = document.getElementById('usb-details-section');
if (!hasUSBData) {
if (section) section.style.display = 'none';
return;
}
// Show section and fill fields
if (section) section.style.display = 'block';
// Helper function to set field value
const setField = (id, value) => {
const field = document.getElementById(id);
if (field && value !== null && value !== undefined) {
field.value = value;
}
};
// Fill all USB fields
setField('usb_vendor_id', caracteristiques.vendor_id);
setField('usb_product_id', caracteristiques.product_id);
setField('usb_fabricant', caracteristiques.fabricant);
setField('usb_type', caracteristiques.usb_type);
setField('usb_version_declared', caracteristiques.usb_version_declared);
setField('usb_negotiated_speed', caracteristiques.negotiated_speed);
setField('usb_max_power', caracteristiques.max_power_ma ? `${caracteristiques.max_power_ma} mA` : '');
// Power mode
let powerMode = '';
if (caracteristiques.is_bus_powered && caracteristiques.is_self_powered) {
powerMode = 'Bus Powered + Self Powered';
} else if (caracteristiques.is_bus_powered) {
powerMode = 'Bus Powered';
} else if (caracteristiques.is_self_powered) {
powerMode = 'Self Powered';
}
setField('usb_power_mode', powerMode);
// Power sufficient (Yes/No with color indicator)
const powerSufficient = caracteristiques.power_sufficient === true ? '✅ Oui' :
caracteristiques.power_sufficient === false ? '⚠️ Non' : 'N/A';
setField('usb_power_sufficient', powerSufficient);
// Firmware required
const firmwareReq = caracteristiques.requires_firmware === true ? '⚠️ Oui (classe 255)' :
caracteristiques.requires_firmware === false ? '✅ Non' : 'N/A';
setField('usb_requires_firmware', firmwareReq);
// Device class
const deviceClass = caracteristiques.device_class_nom ?
`${caracteristiques.device_class} (${caracteristiques.device_class_nom})` :
caracteristiques.device_class || 'N/A';
setField('usb_device_class', deviceClass);
// Interface classes (normative)
if (caracteristiques.interface_classes && caracteristiques.interface_classes.length > 0) {
const interfaceClassesStr = caracteristiques.interface_classes
.map(ic => `${ic.code} (${ic.name})`)
.join(', ');
setField('usb_interface_classes', interfaceClassesStr);
} else {
setField('usb_interface_classes', 'N/A');
}
}
// Load statistics
async function loadStatistics() {
try {
const stats = await apiRequest('/peripherals/statistics/summary');
document.getElementById('stat-total').textContent = stats.total_peripherals || 0;
document.getElementById('stat-disponible').textContent = stats.disponible || 0;
document.getElementById('stat-pret').textContent = stats.en_pret || 0;
document.getElementById('stat-low-stock').textContent = stats.low_stock_count || 0;
} catch (error) {
console.error('Error loading statistics:', error);
}
}
// Load peripheral types from YAML config
async function loadPeripheralTypes() {
try {
// For now, use hardcoded types (later can be loaded from API)
peripheralTypes = [
'USB', 'Bluetooth', 'Réseau', 'Stockage', 'Video', 'Audio',
'Câble', 'Quincaillerie', 'Console', 'Microcontrôleur'
];
const typeSelect = document.getElementById('filter-type');
const typeFormSelect = document.getElementById('type_principal');
peripheralTypes.forEach(type => {
const option1 = document.createElement('option');
option1.value = type;
option1.textContent = type;
typeSelect.appendChild(option1);
const option2 = document.createElement('option');
option2.value = type;
option2.textContent = type;
typeFormSelect.appendChild(option2);
});
} catch (error) {
console.error('Error loading peripheral types:', error);
}
}
// Load locations
async function loadLocations() {
try {
const response = await apiRequest('/locations');
locations = response;
const locationSelects = [
document.getElementById('filter-location'),
document.getElementById('location_id')
];
locationSelects.forEach(select => {
if (!select) return;
// Clear existing options except first
while (select.options.length > 1) {
select.remove(1);
}
locations.forEach(loc => {
const option = document.createElement('option');
option.value = loc.id;
option.textContent = loc.nom;
select.appendChild(option);
});
});
} catch (error) {
console.error('Error loading locations:', error);
}
}
// Load storage locations from YAML
async function loadStockageLocations() {
try {
const result = await apiRequest('/peripherals/config/stockage-locations');
if (!result.success) return;
const select = document.getElementById('stockage_location');
if (!select) return;
// Clear existing options except first
while (select.options.length > 1) {
select.remove(1);
}
result.locations.forEach(name => {
const option = document.createElement('option');
option.value = name;
option.textContent = name;
select.appendChild(option);
});
} catch (error) {
console.error('Error loading storage locations:', error);
}
}
// Load hosts from YAML
async function loadHosts() {
try {
const result = await apiRequest('/peripherals/config/hosts');
if (!result.success) return;
hosts = result.hosts || [];
const select = document.getElementById('connecte_a');
if (!select) return;
// Clear existing options except first
while (select.options.length > 1) {
select.remove(1);
}
hosts.forEach(host => {
const option = document.createElement('option');
option.value = host.nom;
option.textContent = host.localisation ? `${host.nom} (${host.localisation})` : host.nom;
select.appendChild(option);
});
} catch (error) {
console.error('Error loading hosts:', error);
}
}
function updateUtilisationFields() {
const utilisation = document.getElementById('utilisation')?.value || 'non_utilise';
const hostGroup = document.getElementById('group-connecte_a');
const locationGroup = document.getElementById('group-location_id');
const stockageGroup = document.getElementById('group-stockage_location');
if (utilisation === 'utilise') {
if (hostGroup) hostGroup.style.display = 'block';
if (locationGroup) locationGroup.style.display = 'none';
if (stockageGroup) stockageGroup.style.display = 'none';
} else {
if (hostGroup) hostGroup.style.display = 'none';
if (locationGroup) locationGroup.style.display = 'none';
if (stockageGroup) stockageGroup.style.display = 'block';
}
}
// Load devices (hosts) for dropdown
async function loadDevices() {
try {
const response = await apiRequest('/peripherals/config/devices');
if (response.success) {
devices = response.devices;
const deviceSelect = document.getElementById('device_id');
if (!deviceSelect) return;
// Clear existing options except first (En stock)
while (deviceSelect.options.length > 1) {
deviceSelect.remove(1);
}
// Add devices to dropdown
devices.forEach(device => {
const option = document.createElement('option');
option.value = device.id;
// Display format: "hostname (location)" or just "hostname" if no location
const displayText = device.location ?
`${device.hostname} (${device.location})` :
device.hostname;
option.textContent = displayText;
deviceSelect.appendChild(option);
});
}
} catch (error) {
console.error('Error loading devices:', error);
}
}
// Load boutiques
async function loadBoutiques() {
try {
const result = await apiRequest('/peripherals/config/boutiques');
if (!result.success) return;
const select = document.getElementById('boutique');
if (!select) return;
// Clear existing options except first
while (select.options.length > 1) {
select.remove(1);
}
result.boutiques.forEach(name => {
const option = document.createElement('option');
option.value = name;
option.textContent = name;
select.appendChild(option);
});
} catch (error) {
console.error('Error loading boutiques:', error);
}
}
// Get device display text by ID (for showing assignment status)
function getDeviceDisplayText(deviceId) {
if (!deviceId) {
return 'En stock';
}
const device = devices.find(d => d.id === deviceId);
if (!device) {
return 'En stock';
}
// Return "hostname + location" if location exists, otherwise just hostname
return device.location ?
`${device.hostname}${device.location}` :
device.hostname;
}
// Load peripherals
async function loadPeripherals() {
try {
const params = new URLSearchParams({
page: currentPage,
page_size: pageSize,
sort_by: sortBy,
sort_order: sortOrder
});
if (filterType) params.append('type_filter', filterType);
if (filterLocation) params.append('location_id', filterLocation);
if (filterEtat) params.append('etat', filterEtat);
if (searchQuery) params.append('search', searchQuery);
const data = await apiRequest(`/peripherals?${params}`);
totalPages = data.total_pages;
displayPeripherals(data.items);
updatePagination(data);
} catch (error) {
console.error('Error loading peripherals:', error);
showError('Erreur lors du chargement des périphériques');
}
}
// Display peripherals in table
function displayPeripherals(peripherals) {
const tbody = document.getElementById('peripherals-tbody');
if (peripherals.length === 0) {
tbody.innerHTML = '<tr><td colspan="10" class="no-data">Aucun périphérique trouvé</td></tr>';
return;
}
tbody.innerHTML = peripherals.map(p => `
<tr onclick="viewPeripheral(${p.id})">
<td>
<div class="peripheral-photo">
${p.thumbnail_url
? `<img src="${escapeHtml(p.thumbnail_url)}" alt="${escapeHtml(p.nom)}" onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
<i class="fas ${getTypeIcon(p.type_principal)}" style="display:none;"></i>`
: `<i class="fas ${getTypeIcon(p.type_principal)}"></i>`
}
</div>
</td>
<td>
<strong>${escapeHtml(p.nom)}</strong>
${p.en_pret ? '<span class="badge badge-warning">En prêt</span>' : ''}
${p.is_complete_device ? '<span class="badge badge-info">Appareil complet</span>' : ''}
</td>
<td>${escapeHtml(p.type_principal)}</td>
<td>${escapeHtml(p.marque || '-')}</td>
<td>${escapeHtml(p.modele || '-')}</td>
<td><span class="badge badge-${getEtatClass(p.etat)}">${escapeHtml(p.etat)}</span></td>
<td>${renderStars(p.rating)}</td>
<td>
${p.quantite_disponible}
${p.quantite_disponible === 0 ? '<i class="fas fa-exclamation-triangle text-danger"></i>' : ''}
</td>
<td>${p.prix ? `${p.prix.toFixed(2)}` : '-'}</td>
<td class="actions">
<button class="btn-icon" onclick="event.stopPropagation(); editPeripheral(${p.id})" title="Modifier">
<i class="fas fa-edit"></i>
</button>
<button class="btn-icon" onclick="event.stopPropagation(); deletePeripheral(${p.id})" title="Supprimer">
<i class="fas fa-trash"></i>
</button>
</td>
</tr>
`).join('');
}
// Update pagination
function updatePagination(data) {
document.getElementById('pagination-info').textContent =
`Page ${data.page} / ${data.total_pages} (${data.total} périphériques)`;
document.getElementById('btn-prev').disabled = data.page <= 1;
document.getElementById('btn-next').disabled = data.page >= data.total_pages;
}
// Pagination functions
function previousPage() {
if (currentPage > 1) {
currentPage--;
loadPeripherals();
}
}
function nextPage() {
if (currentPage < totalPages) {
currentPage++;
loadPeripherals();
}
}
// Sort table
function sortTable(column) {
if (sortBy === column) {
sortOrder = sortOrder === 'asc' ? 'desc' : 'asc';
} else {
sortBy = column;
sortOrder = 'asc';
}
loadPeripherals();
}
// Filter and search
function filterPeripherals() {
filterType = document.getElementById('filter-type').value;
filterLocation = document.getElementById('filter-location').value;
filterEtat = document.getElementById('filter-etat').value;
currentPage = 1;
loadPeripherals();
}
let searchTimeout;
function searchPeripherals() {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
searchQuery = document.getElementById('search').value;
currentPage = 1;
loadPeripherals();
}, 300);
}
// View peripheral details
function viewPeripheral(id) {
window.location.href = `peripheral-detail.html?id=${id}`;
}
// Show add modal
function showAddModal() {
document.getElementById('form-add-peripheral').reset();
document.getElementById('modal-add').style.display = 'block';
updateUtilisationFields();
updatePhotoUrlAddUI();
}
// Add peripheral
async function addPeripheral(event) {
event.preventDefault();
const formData = new FormData(event.target);
const data = {};
const photoFile = document.getElementById('photo-file-add')?.files?.[0] || null;
const photoUrl = (document.getElementById('photo-url-add')?.value || '').trim();
const photoDescription = document.getElementById('photo-description-add')?.value || '';
const photoPrimary = document.getElementById('photo-primary-add')?.checked || false;
for (let [key, value] of formData.entries()) {
if (value === '') continue;
// Convert numeric fields
if (['photo_file', 'photo_url', 'photo_description', 'photo_primary'].includes(key)) {
continue;
} else if (key === 'utilisation') {
continue;
} else if (['prix', 'rating', 'quantite_totale', 'quantite_disponible', 'garantie_duree_mois'].includes(key)) {
data[key] = parseFloat(value);
} else if (['location_id', 'device_id'].includes(key)) {
data[key] = parseInt(value);
} else {
data[key] = value;
}
}
const utilisation = document.getElementById('utilisation')?.value || 'non_utilise';
if (utilisation === 'utilise') {
const selectedHost = document.getElementById('connecte_a')?.value || null;
data.connecte_a = selectedHost;
data.location_id = null;
const host = hosts.find(h => h.nom === selectedHost);
if (host && host.localisation) {
data.location_details = host.localisation;
}
} else {
data.connecte_a = null;
data.location_id = null;
const stockage = document.getElementById('stockage_location')?.value || null;
data.location_details = stockage;
}
try {
const created = await apiRequest('/peripherals', {
method: 'POST',
body: JSON.stringify(data)
});
if (created && (photoFile || photoUrl)) {
if (photoFile) {
const photoData = new FormData();
photoData.append('file', photoFile);
photoData.append('description', photoDescription);
photoData.append('is_primary', photoPrimary ? 'true' : 'false');
await apiRequest(`/peripherals/${created.id}/photos`, {
method: 'POST',
body: photoData
});
} else if (/^https?:\/\//i.test(photoUrl)) {
const urlData = new FormData();
urlData.append('image_url', photoUrl);
urlData.append('description', photoDescription);
urlData.append('is_primary', photoPrimary ? 'true' : 'false');
await apiRequest(`/peripherals/${created.id}/photos/from-url`, {
method: 'POST',
body: urlData
});
} else if (photoUrl) {
showError('URL invalide (http/https requis)');
}
}
closeModal('modal-add');
showSuccess('Périphérique ajouté avec succès');
loadPeripherals();
loadStatistics();
} catch (error) {
console.error('Error adding peripheral:', error);
showError('Erreur lors de l\'ajout du périphérique');
}
}
function updatePhotoUrlAddUI() {
const url = (document.getElementById('photo-url-add')?.value || '').trim();
const button = document.getElementById('btn-photo-url-add');
const previewWrap = document.getElementById('photo-url-preview-wrap');
const previewImg = document.getElementById('photo-url-preview');
const isValid = /^https?:\/\//i.test(url);
if (button) button.disabled = !isValid;
if (!previewWrap || !previewImg) return;
if (isValid) {
previewImg.src = url;
previewWrap.style.display = 'flex';
} else {
previewImg.removeAttribute('src');
previewWrap.style.display = 'none';
}
}
function onPhotoFileAddChange() {
const photoFile = document.getElementById('photo-file-add')?.files?.[0] || null;
if (photoFile) {
const urlInput = document.getElementById('photo-url-add');
if (urlInput) urlInput.value = '';
updatePhotoUrlAddUI();
}
}
function submitPhotoUrlAdd() {
const url = (document.getElementById('photo-url-add')?.value || '').trim();
if (!/^https?:\/\//i.test(url)) {
showError('URL invalide (http/https requis)');
return;
}
const form = document.getElementById('form-add-peripheral');
if (form) {
form.requestSubmit();
}
}
// Edit peripheral
async function editPeripheral(id) {
// Redirect to detail page in edit mode
window.location.href = `peripheral-detail.html?id=${id}&edit=true`;
}
// Delete peripheral
async function deletePeripheral(id) {
if (!confirm('Êtes-vous sûr de vouloir supprimer ce périphérique ?')) {
return;
}
try {
await apiRequest(`/peripherals/${id}`, {
method: 'DELETE'
});
showSuccess('Périphérique supprimé avec succès');
loadPeripherals();
loadStatistics();
} catch (error) {
console.error('Error deleting peripheral:', error);
showError('Erreur lors de la suppression du périphérique');
}
}
// Show import USB modal
function showImportUSBModal() {
// Reset form and steps
document.getElementById('form-detect-usb').reset();
document.getElementById('usb-import-step-1').style.display = 'block';
document.getElementById('usb-import-step-2').style.display = 'none';
document.getElementById('modal-import-usb').style.display = 'block';
}
// Show import USB structured modal
function showImportUSBStructuredModal() {
document.getElementById('form-import-usb-structured').reset();
document.getElementById('modal-import-usb-structured').style.display = 'block';
}
// Copy USB command to clipboard
function copyUSBCommand() {
const command = document.getElementById('usb-command').textContent;
const button = document.getElementById('btn-copy-usb');
const tooltip = button.querySelector('.tooltip-copied');
navigator.clipboard.writeText(command).then(() => {
// Show tooltip
tooltip.classList.add('show');
// Hide tooltip after 2 seconds
setTimeout(() => {
tooltip.classList.remove('show');
}, 2000);
}).catch((error) => {
console.error('Erreur lors de la copie:', error);
showError('Erreur lors de la copie de la commande');
});
}
// Import USB - Step 1: Detect devices
let detectedUSBDevices = [];
let fullUSBOutput = '';
async function detectUSBDevices(event) {
event.preventDefault();
const formData = new FormData(event.target);
fullUSBOutput = formData.get('lsusb_output');
try {
const result = await apiRequest('/peripherals/import/usb-cli/detect', {
method: 'POST',
body: formData
});
if (result.success && result.devices && result.devices.length > 0) {
detectedUSBDevices = result.devices;
// Build device list HTML
const deviceListHTML = result.devices.map((device, index) => `
<div class="usb-device-item" onclick="selectUSBDevice(${index})">
<input type="radio" name="selected_usb_device" value="${index}" id="usb-device-${index}">
<div class="usb-device-info">
<div class="usb-device-bus">${device.bus_line}</div>
<div class="usb-device-id">ID: ${device.id} • Bus ${device.bus} Device ${device.device}</div>
</div>
</div>
`).join('');
document.getElementById('usb-devices-list').innerHTML = deviceListHTML;
// Show step 2, hide step 1
document.getElementById('usb-import-step-1').style.display = 'none';
document.getElementById('usb-import-step-2').style.display = 'block';
showSuccess(`${result.total_devices} périphérique(s) USB détecté(s)`);
}
} catch (error) {
console.error('Error detecting USB devices:', error);
showError(error.message || 'Erreur lors de la détection des périphériques USB');
}
}
// Select USB device (radio button click)
function selectUSBDevice(index) {
const radio = document.getElementById(`usb-device-${index}`);
if (radio) {
radio.checked = true;
// Enable Finaliser button when a device is selected
document.getElementById('btn-finalize-usb').disabled = false;
}
}
// Go back to step 1
function backToUSBStep1() {
document.getElementById('usb-import-step-2').style.display = 'none';
document.getElementById('usb-import-step-1').style.display = 'block';
document.getElementById('btn-finalize-usb').disabled = true;
detectedUSBDevices = [];
}
// Import selected USB device
async function importSelectedUSBDevice() {
const selectedRadio = document.querySelector('input[name="selected_usb_device"]:checked');
if (!selectedRadio) {
showError('Veuillez sélectionner un périphérique');
return;
}
const selectedIndex = parseInt(selectedRadio.value);
const selectedDevice = detectedUSBDevices[selectedIndex];
if (!selectedDevice) {
showError('Périphérique sélectionné invalide');
return;
}
// Extract the device section
const formData = new FormData();
formData.append('lsusb_output', fullUSBOutput);
formData.append('bus', selectedDevice.bus);
formData.append('device', selectedDevice.device);
try {
const result = await apiRequest('/peripherals/import/usb-cli/extract', {
method: 'POST',
body: formData
});
if (result.success) {
if (result.already_exists) {
// Device already exists
const existing = result.existing_peripheral;
const message = `Ce périphérique existe déjà dans la base de données:\n\n` +
`Nom: ${existing.nom}\n` +
`Marque: ${existing.marque || 'N/A'}\n` +
`Modèle: ${existing.modele || 'N/A'}\n` +
`Fabricant: ${existing.fabricant || 'N/A'}\n` +
`Produit: ${existing.produit || 'N/A'}\n` +
`Quantité disponible: ${existing.quantite_disponible}\n\n` +
`Voulez-vous voir sa fiche ?`;
if (confirm(message)) {
window.location.href = `peripheral-detail.html?id=${existing.id}`;
}
} else {
// New device - pre-fill form
closeModal('modal-import-usb');
showAddModal();
const suggested = result.suggested_peripheral;
// Fill form fields
if (suggested.nom) document.getElementById('nom').value = suggested.nom;
if (suggested.marque) document.getElementById('marque').value = suggested.marque;
if (suggested.modele) document.getElementById('modele').value = suggested.modele;
if (suggested.fabricant) document.getElementById('fabricant').value = suggested.fabricant;
if (suggested.produit) document.getElementById('produit').value = suggested.produit;
if (suggested.numero_serie) document.getElementById('numero_serie').value = suggested.numero_serie;
// Fill documentation fields
if (suggested.cli_yaml) {
const cliYamlField = document.getElementById('cli_yaml');
if (cliYamlField) {
cliYamlField.value = suggested.cli_yaml;
}
}
if (suggested.cli_raw) {
const cliRawField = document.getElementById('cli_raw');
if (cliRawField) {
cliRawField.value = suggested.cli_raw;
}
}
if (suggested.synthese) {
const syntheseField = document.getElementById('synthese');
if (syntheseField) {
syntheseField.value = suggested.synthese;
}
}
// Fill USB detailed information
fillUSBDetails(suggested.caracteristiques_specifiques);
// Set type_principal and wait for subtypes to load before setting sous_type
if (suggested.type_principal) {
document.getElementById('type_principal').value = suggested.type_principal;
// Trigger the change event to load subtypes
const typePrincipalSelect = document.getElementById('type_principal');
const changeEvent = new Event('change');
typePrincipalSelect.dispatchEvent(changeEvent);
// Wait for subtypes to load, then set sous_type
if (suggested.sous_type) {
// Use a Promise-based approach with retry logic
const setSousType = async () => {
for (let i = 0; i < 10; i++) {
await new Promise(resolve => setTimeout(resolve, 100));
const sousTypeSelect = document.getElementById('sous_type');
if (sousTypeSelect && sousTypeSelect.options.length > 1) {
// Options are loaded
sousTypeSelect.value = suggested.sous_type;
break;
}
}
};
setSousType();
}
}
showSuccess('Données USB importées avec succès. Vérifiez et complétez les informations.');
// Reset USB import state
detectedUSBDevices = [];
fullUSBOutput = '';
document.getElementById('usb-import-step-2').style.display = 'none';
document.getElementById('usb-import-step-1').style.display = 'block';
document.getElementById('form-detect-usb').reset();
}
}
} catch (error) {
console.error('Error extracting USB device:', error);
showError(error.message || 'Erreur lors de l\'extraction du périphérique USB');
}
}
// Import USB structured information
async function importUSBStructured(event) {
event.preventDefault();
const formData = new FormData(event.target);
try {
const result = await apiRequest('/peripherals/import/usb-structured', {
method: 'POST',
body: formData
});
if (result.success) {
if (result.already_exists) {
// Device already exists
const existing = result.existing_peripheral;
const message = `Ce périphérique existe déjà dans la base de données:\n\n` +
`Nom: ${existing.nom}\n` +
`Marque: ${existing.marque || 'N/A'}\n` +
`Modèle: ${existing.modele || 'N/A'}\n` +
`Fabricant: ${existing.fabricant || 'N/A'}\n` +
`Produit: ${existing.produit || 'N/A'}\n` +
`Quantité disponible: ${existing.quantite_disponible}\n\n` +
`Voulez-vous voir sa fiche ?`;
if (confirm(message)) {
window.location.href = `peripheral-detail.html?id=${existing.id}`;
}
} else {
// New device - pre-fill form
closeModal('modal-import-usb-structured');
showAddModal();
const suggested = result.suggested_peripheral;
// Fill form fields
if (suggested.nom) document.getElementById('nom').value = suggested.nom;
if (suggested.marque) document.getElementById('marque').value = suggested.marque;
if (suggested.modele) document.getElementById('modele').value = suggested.modele;
if (suggested.fabricant) document.getElementById('fabricant').value = suggested.fabricant;
if (suggested.produit) document.getElementById('produit').value = suggested.produit;
if (suggested.numero_serie) document.getElementById('numero_serie').value = suggested.numero_serie;
// Fill documentation fields
if (suggested.cli_yaml) {
const cliYamlField = document.getElementById('cli_yaml');
if (cliYamlField) {
cliYamlField.value = suggested.cli_yaml;
}
}
if (suggested.cli_raw) {
const cliRawField = document.getElementById('cli_raw');
if (cliRawField) {
cliRawField.value = suggested.cli_raw;
}
}
// Fill USB detailed information
fillUSBDetails(suggested.caracteristiques_specifiques);
// Set type_principal and wait for subtypes to load before setting sous_type
if (suggested.type_principal) {
document.getElementById('type_principal').value = suggested.type_principal;
// Trigger the change event to load subtypes
const typePrincipalSelect = document.getElementById('type_principal');
const changeEvent = new Event('change');
typePrincipalSelect.dispatchEvent(changeEvent);
// Wait for subtypes to load, then set sous_type
if (suggested.sous_type) {
const setSousType = async () => {
for (let i = 0; i < 10; i++) {
await new Promise(resolve => setTimeout(resolve, 100));
const sousTypeSelect = document.getElementById('sous_type');
if (sousTypeSelect && sousTypeSelect.options.length > 1) {
sousTypeSelect.value = suggested.sous_type;
break;
}
}
};
setSousType();
}
}
showSuccess('Informations USB importées avec succès. Vérifiez et complétez les informations.');
// Reset form
document.getElementById('form-import-usb-structured').reset();
}
}
} catch (error) {
console.error('Error importing USB structured info:', error);
showError(error.message || 'Erreur lors de l\'import des informations USB');
}
}
// Show import markdown modal
function showImportMDModal() {
document.getElementById('form-import-md').reset();
document.getElementById('md-file-preview').innerHTML = '';
document.getElementById('modal-import-md').style.display = 'block';
// Add file change listener for preview
const fileInput = document.getElementById('md-file');
fileInput.addEventListener('change', function() {
const file = fileInput.files[0];
if (file) {
document.getElementById('md-file-preview').innerHTML = `
<div class="file-info">
<i class="fas fa-file-alt"></i>
<span>${file.name}</span>
<span class="file-size">(${(file.size / 1024).toFixed(2)} KB)</span>
</div>
`;
}
});
}
// Import Markdown
async function importMarkdown(event) {
event.preventDefault();
const formData = new FormData(event.target);
const file = document.getElementById('md-file').files[0];
if (!file) {
showError('Veuillez sélectionner un fichier .md');
return;
}
if (!file.name.endsWith('.md')) {
showError('Seuls les fichiers .md sont acceptés');
return;
}
try {
const result = await apiRequest('/peripherals/import/markdown', {
method: 'POST',
body: formData
});
if (result.success) {
// Check if peripheral already exists
if (result.already_exists) {
closeModal('modal-import-md');
const existing = result.existing_peripheral;
const message = `Ce périphérique existe déjà dans la base de données:\n\n` +
`Nom: ${existing.nom}\n` +
`Marque: ${existing.marque || 'N/A'}\n` +
`Modèle: ${existing.modele || 'N/A'}\n` +
`Fabricant: ${existing.fabricant || 'N/A'}\n` +
`Produit: ${existing.produit || 'N/A'}\n` +
`Quantité: ${existing.quantite_totale}\n\n` +
`Voulez-vous voir ce périphérique?`;
if (confirm(message)) {
// Redirect to detail page
window.location.href = `peripheral-detail.html?id=${existing.id}`;
} else {
showInfo(`Import annulé - le périphérique "${existing.nom}" existe déjà.`);
}
} else if (result.suggested_peripheral) {
// New peripheral - pre-fill form
closeModal('modal-import-md');
showAddModal();
const suggested = result.suggested_peripheral;
// Fill basic form fields
if (suggested.nom) document.getElementById('nom').value = suggested.nom;
if (suggested.type_principal) {
document.getElementById('type_principal').value = suggested.type_principal;
loadPeripheralSubtypes(); // Reload subtypes
}
// Wait for subtypes to load before setting sous_type
setTimeout(() => {
if (suggested.sous_type) document.getElementById('sous_type').value = suggested.sous_type;
}, 100);
if (suggested.marque) document.getElementById('marque').value = suggested.marque;
if (suggested.modele) document.getElementById('modele').value = suggested.modele;
if (suggested.fabricant) document.getElementById('fabricant').value = suggested.fabricant;
if (suggested.produit) document.getElementById('produit').value = suggested.produit;
if (suggested.numero_serie) document.getElementById('numero_serie').value = suggested.numero_serie;
if (suggested.description) document.getElementById('description').value = suggested.description;
if (suggested.notes) document.getElementById('notes').value = suggested.notes;
if (suggested.etat) document.getElementById('etat').value = suggested.etat;
if (suggested.quantite_totale) document.getElementById('quantite_totale').value = suggested.quantite_totale;
if (suggested.quantite_disponible) document.getElementById('quantite_disponible').value = suggested.quantite_disponible;
// Show success message with filename
showSuccess(`Fichier ${result.filename} importé avec succès. Vérifiez et complétez les informations.`);
}
}
} catch (error) {
console.error('Error importing markdown:', error);
showError('Erreur lors de l\'import du fichier .md. Vérifiez le format du fichier.');
}
}
// Cache for peripheral types from API
let peripheralTypesCache = null;
// Load peripheral types from API
async function loadPeripheralTypesFromAPI() {
if (peripheralTypesCache) {
return peripheralTypesCache;
}
try {
const result = await apiRequest('/peripherals/config/types');
if (result.success && result.types) {
peripheralTypesCache = result.types;
return result.types;
}
} catch (error) {
console.error('Failed to load peripheral types from API:', error);
}
// Fallback to hardcoded types if API fails
return {
'USB': ['Clavier', 'Souris', 'Hub', 'Clé USB', 'Webcam', 'Adaptateur WiFi', 'Autre'],
'Bluetooth': ['Clavier', 'Souris', 'Audio', 'Autre'],
'Réseau': ['Wi-Fi', 'Ethernet', 'Autre'],
'Stockage': ['SSD', 'HDD', 'Clé USB', 'Carte SD', 'Autre'],
'Video': ['GPU', 'Écran', 'Webcam', 'Autre'],
'Audio': ['Haut-parleur', 'Microphone', 'Casque', 'Carte son', 'Autre'],
'Câble': ['USB', 'HDMI', 'DisplayPort', 'Ethernet', 'Audio', 'Alimentation', 'Autre'],
'Quincaillerie': ['Vis', 'Écrou', 'Entretoise', 'Autre'],
'Console': ['PlayStation', 'Xbox', 'Nintendo', 'Autre'],
'Microcontrôleur': ['Raspberry Pi', 'Arduino', 'ESP32', 'Autre']
};
}
// Load peripheral subtypes based on type
async function loadPeripheralSubtypes() {
const type = document.getElementById('type_principal').value;
const subtypeSelect = document.getElementById('sous_type');
// Clear current options
subtypeSelect.innerHTML = '<option value="">Sélectionner...</option>';
// Load types from API
const subtypes = await loadPeripheralTypesFromAPI();
if (subtypes[type]) {
// Add "Autre" at the end if not present
const typeSubtypes = [...subtypes[type]];
if (!typeSubtypes.includes('Autre')) {
typeSubtypes.push('Autre');
}
typeSubtypes.forEach(subtype => {
const option = document.createElement('option');
option.value = subtype;
option.textContent = subtype;
subtypeSelect.appendChild(option);
});
}
}
// Helper: Get CSS class for état
function getEtatClass(etat) {
const classes = {
'Neuf': 'success',
'Bon': 'info',
'Usagé': 'warning',
'Défectueux': 'danger',
'Retiré': 'secondary'
};
return classes[etat] || 'secondary';
}
// Get icon based on peripheral type
function getTypeIcon(type) {
if (!type) return 'fa-microchip';
const typeUpper = type.toUpperCase();
// USB devices
if (typeUpper.includes('USB')) return 'fa-plug';
// Storage devices
if (typeUpper.includes('STOCKAGE') || typeUpper.includes('DISK') ||
typeUpper.includes('SSD') || typeUpper.includes('HDD') ||
typeUpper.includes('FLASH')) return 'fa-hard-drive';
// Network devices
if (typeUpper.includes('RÉSEAU') || typeUpper.includes('RESEAU') ||
typeUpper.includes('NETWORK') || typeUpper.includes('WIFI') ||
typeUpper.includes('ETHERNET')) return 'fa-network-wired';
// Audio devices
if (typeUpper.includes('AUDIO') || typeUpper.includes('SOUND') ||
typeUpper.includes('SPEAKER') || typeUpper.includes('HEADPHONE')) return 'fa-volume-up';
// Video devices
if (typeUpper.includes('VIDEO') || typeUpper.includes('VIDÉO') ||
typeUpper.includes('WEBCAM') || typeUpper.includes('CAMERA')) return 'fa-video';
// Input devices
if (typeUpper.includes('CLAVIER') || typeUpper.includes('KEYBOARD')) return 'fa-keyboard';
if (typeUpper.includes('SOURIS') || typeUpper.includes('MOUSE')) return 'fa-mouse';
// Other devices
if (typeUpper.includes('BLUETOOTH')) return 'fa-bluetooth';
if (typeUpper.includes('HUB')) return 'fa-sitemap';
if (typeUpper.includes('ADAPTATEUR') || typeUpper.includes('ADAPTER')) return 'fa-plug';
if (typeUpper.includes('CÂBLE') || typeUpper.includes('CABLE')) return 'fa-link';
// Default
return 'fa-microchip';
}
// Helper: Render star rating
function renderStars(rating) {
const fullStars = Math.floor(rating);
const hasHalfStar = rating % 1 >= 0.5;
const emptyStars = 5 - fullStars - (hasHalfStar ? 1 : 0);
let html = '';
for (let i = 0; i < fullStars; i++) {
html += '<i class="fas fa-star text-warning"></i>';
}
if (hasHalfStar) {
html += '<i class="fas fa-star-half-alt text-warning"></i>';
}
for (let i = 0; i < emptyStars; i++) {
html += '<i class="far fa-star text-muted"></i>';
}
return html;
}
// Close modal
function closeModal(modalId) {
document.getElementById(modalId).style.display = 'none';
}
// Close modal when clicking outside
window.onclick = function(event) {
if (event.target.classList.contains('modal')) {
event.target.style.display = 'none';
}
}