Files
serv_benchmark/docs/PERIPHERALS_MODULE_SPECIFICATION.md
Gilles Soulier c67befc549 addon
2026-01-05 16:08:01 +01:00

75 KiB
Executable File
Raw Permalink Blame History

MODULE PÉRIPHÉRIQUES - Spécifications Complètes

Linux BenchTools - Extension Inventaire Matériel

Version: 1.0 Date: 2025-12-30 Auteur: Analyse collaborative Projet: serv_benchmark - Module Périphériques


📋 TABLE DES MATIÈRES

  1. Vue d'ensemble
  2. Objectifs et Portée
  3. Types de Périphériques
  4. Caractéristiques des Données
  5. Architecture Base de Données
  6. Architecture Backend
  7. Architecture Frontend
  8. Fonctionnalités Principales
  9. Système de Localisation
  10. Système de Prêt
  11. Appareils Complets
  12. Configuration YAML
  13. Plan de Déploiement
  14. Prompt de Développement

🎯 VUE D'ENSEMBLE

Contexte

Le projet Linux BenchTools est une application web existante permettant de :

  • Benchmarker des machines Linux (CPU, RAM, Disque, Réseau, GPU)
  • Stocker l'historique des benchmarks
  • Gérer un inventaire de machines (devices)

Besoin

Ajouter un module complet de gestion d'inventaire de périphériques pour :

  • Cataloguer tous les périphériques informatiques (USB, Bluetooth, PCIe, câbles, etc.)
  • Gérer leur localisation physique (pièces, placards, tiroirs)
  • Suivre les prêts/emprunts
  • Lier certains périphériques aux machines benchmarkées
  • Gérer stock, photos, documents, garanties

Architecture Existante

Backend:

  • FastAPI + SQLAlchemy ORM
  • Base de données: SQLite (data.db)
  • Structure: /backend/app/ avec models/, schemas/, api/, core/
  • Authentification par token

Frontend:

  • Vanilla JavaScript (pas de framework)
  • Thème: Monokai dark
  • Layout: Two-panel design
  • Pages: Dashboard, Devices, Settings

Base de données existante (data.db):

  • devices - Machines inventoriées
  • hardware_snapshots - Snapshots matériel
  • benchmarks - Résultats benchmarks
  • documents - Documents devices
  • manufacturer_links - Liens

🎯 OBJECTIFS ET PORTÉE

Objectifs Principaux

  1. Inventaire Complet

    • Cataloguer TOUS les périphériques informatiques possibles
    • USB, Bluetooth, Wi-Fi, PCIe, HDMI, Câbles, Visserie, etc.
    • Appareils complets (Laptops, Desktops, Smartphones, Tablettes, Serveurs, Consoles)
  2. Gestion Physique

    • Localisation précise (Pièce > Placard > Tiroir > Étagère)
    • Tracking des déplacements
    • QR codes pour identification rapide
  3. Gestion de Prêt

    • Prêter périphériques à des personnes
    • Suivre les retours
    • Gérer cautions
    • Alertes et rappels automatiques
  4. Intégration Benchmarks

    • Lier appareils complets aux devices benchmarkés
    • Afficher historique benchmarks
    • Sync automatique des specs
  5. Gestion Avancée

    • Stock et quantités
    • Photos et documents
    • Évaluation qualité (0-5 étoiles)
    • Configuration YAML flexible
    • Compression d'images automatique

Architecture

BASE DE DONNÉES SÉPARÉE: peripherals.db

  • Isolation des données
  • Performance optimale
  • Backups indépendants
  • Module activable/désactivable

📦 TYPES DE PÉRIPHÉRIQUES

1. CONNECTIVITÉ FILAIRE

USB (Universal Serial Bus)

  • Types: USB Type-A (2.0, 3.0, 3.1, 3.2), USB Type-C, Thunderbolt 3/4, Mini/Micro
  • Périphériques:
    • Clés USB / Disques externes
    • Souris / Claviers
    • Webcams
    • Imprimantes / Scanners
    • Adaptateurs réseau (Ethernet, Wi-Fi)
    • Cartes son externes / DAC
    • Dongles (Bluetooth, Wi-Fi, DVB-T)
    • Lecteurs de cartes
    • Contrôleurs de jeux
    • Microphones / Interfaces audio
    • Hubs USB

PCIe (Peripheral Component Interconnect Express)

  • Cartes graphiques (GPU)
  • Cartes son
  • Cartes réseau (Ethernet, Wi-Fi, Fibre)
  • Cartes d'acquisition vidéo
  • Contrôleurs RAID
  • Adaptateurs NVMe
  • Cartes USB/Thunderbolt d'extension
  • Cartes TV/DVB

SATA / M.2 / NVMe

  • Disques durs (HDD)
  • SSD SATA
  • SSD M.2 (SATA/NVMe)
  • Lecteurs optiques (CD/DVD/Blu-ray)

Audio / Vidéo

  • Audio: Jack 3.5mm/6.35mm, XLR, Optique S/PDIF, Coaxial
  • Vidéo: HDMI (1.4, 2.0, 2.1), DisplayPort, DVI, VGA, Mini DisplayPort
  • Périphériques: Écrans, Projecteurs, Casques, Enceintes, Microphones

2. CONNECTIVITÉ SANS-FIL

Wi-Fi

  • Normes: 802.11 a/b/g/n/ac/ax (Wi-Fi 4/5/6/6E/7)
  • Bandes: 2.4 GHz, 5 GHz, 6 GHz
  • Adaptateurs USB, Cartes PCIe/M.2, Points d'accès

Bluetooth

  • Versions: 2.0 à 5.3
  • Profils: A2DP, HFP, HSP, HID
  • Casques, Claviers, Souris, Enceintes, Adaptateurs

Autres

  • NFC, RF propriétaire, Infrarouge

3. RÉSEAU

Ethernet

  • Vitesses: 10/100/1000 Mbps, 2.5G, 5G, 10G, 25G, 40G
  • Cartes NIC, Switches, Routeurs, Modems

Fibre Optique

  • Cartes SFP/SFP+, Convertisseurs

4. CÂBLES

Types de Câbles

  • HDMI: Versions 1.4, 2.0, 2.1 (Standard, Premium, Ultra)
  • DisplayPort: Versions 1.2, 1.4, 2.0
  • USB: USB-A, USB-C, Mini, Micro (différentes longueurs)
  • Ethernet: Cat5e, Cat6, Cat6a, Cat7, Cat8 (blindé/non-blindé)
  • Audio: Jack 3.5mm, XLR, RCA, Optique
  • Alimentation: C13, C14, Secteur, USB-C PD
  • Vidéo: DVI, VGA, Composite

Caractéristiques spécifiques:

  • Longueur (mètres)
  • Certification (pour HDMI)
  • Blindage (pour Ethernet)
  • Puissance supportée (pour USB-C)

5. VISSERIE ET FIXATIONS

Types

  • Vis: M2, M3, M4 (différentes longueurs, têtes: plate, fraisée, bombée)
  • Entretoises (Standoffs): M2, M3, M4 (différentes hauteurs)
  • Écrous, Rondelles
  • Clips plastiques, Attaches câbles

Caractéristiques:

  • Diamètre/Norme
  • Longueur
  • Matériau (Acier, Inox, Laiton, Plastique)
  • Type de tête

6. BOÎTIERS PC

Formats

  • Mini-ITX
  • Micro-ATX
  • ATX
  • E-ATX
  • Full Tower

Caractéristiques:

  • Emplacements 3.5" / 2.5"
  • Ventilateurs inclus/supportés
  • Fenêtre latérale
  • RGB
  • Dimensions

7. STOCKAGE

  • Disques durs externes
  • SSD externes
  • NAS (Network Attached Storage)
  • Lecteurs de bandes
  • Stations d'accueil

8. ENTRÉE / SORTIE

Entrée

  • Claviers (USB, Bluetooth, PS/2)
  • Souris / Trackballs / Tablettes graphiques
  • Scanners
  • Lecteurs de codes-barres
  • Lecteurs biométriques
  • Webcams
  • Microphones

Sortie

  • Imprimantes (USB, réseau, sans-fil)
  • Imprimantes 3D
  • Enceintes
  • Projecteurs

9. APPAREILS COMPLETS

Desktop (PC de Bureau)

Caractéristiques:

  • CPU (marque, modèle, cores, threads, fréquence)
  • RAM (capacité, type DDR, fréquence)
  • GPU (intégré/dédié, modèle, VRAM)
  • Stockage (type, capacité)
  • Carte mère (marque, modèle, chipset)
  • Alimentation (wattage, certification 80+)
  • Boîtier (format, modèle)
  • Refroidissement (Air/AIO/Watercooling)
  • Système d'exploitation

Laptop (Ordinateur Portable)

Caractéristiques:

  • Écran (taille, résolution, tactile, taux rafraîchissement)
  • CPU (marque, modèle, génération)
  • RAM (capacité, extensible, max)
  • Stockage (type, capacité, slots M.2 libres)
  • GPU (intégré/dédié/hybrid)
  • Batterie (Wh, autonomie)
  • Connectique (USB-A, USB-C, Thunderbolt, HDMI, Ethernet, SD)
  • Webcam (résolution)
  • Poids

Tablette

Caractéristiques:

  • OS (iOS/iPadOS, Android, Windows)
  • Écran (taille, résolution, type: LCD/OLED)
  • Processeur
  • RAM, Stockage
  • Batterie (mAh)
  • Stylet (inclus, modèle)
  • Clavier (inclus)
  • 4G/5G
  • Poids

Smartphone

Caractéristiques:

  • OS (iOS, Android)
  • Écran (taille, résolution, type, fréquence Hz)
  • Processeur
  • RAM, Stockage
  • Caméras (principale MP, ultra-wide, téléphoto, frontale)
  • Batterie (mAh, charge rapide W, sans-fil)
  • 5G, Dual SIM, eSIM
  • Résistance (IP67/IP68)
  • Lecteur d'empreintes
  • IMEI

Serveur

Caractéristiques:

  • Format (Tour, Rack 1U/2U/4U, Blade)
  • OS (Windows Server, Linux, ESXi, Proxmox)
  • CPU (nombre, modèle, cores total)
  • RAM (capacité, type ECC)
  • RAID (contrôleur, niveau)
  • Baies (3.5", 2.5")
  • Alimentation (redondante, wattage)
  • IPMI/iLO/iDRAC

Console de Jeu

Caractéristiques:

  • Type: Console de salon, Portable, Hybrid
  • Marque: PlayStation, Xbox, Nintendo, Steam, Autre
  • Modèle: PS5, Xbox Series X, Switch OLED, Steam Deck, etc.
  • Génération: PS5 (9ème gen), PS4 (8ème gen), etc.
  • Stockage (capacité, type, extensible)
  • Résolution max (1080p, 4K, 8K)
  • FPS max
  • Ray tracing
  • VR compatible
  • Lecteur disque physique
  • Rétrocompatibilité
  • Manettes incluses
  • Connectique (HDMI version, USB, Ethernet)
  • Dimensions
  • Poids

Exemple de configuration YAML:

- id: console_gaming
  nom: "Console de Jeu"
  icon: "gamepad"
  is_complete_device: true

  champs_specifiques:
    - type_console:
        type: "select"
        options: ["Salon", "Portable", "Hybrid"]
        label: "Type de console"
    - marque:
        type: "select"
        options: ["PlayStation", "Xbox", "Nintendo", "Steam", "Autre"]
    - modele:
        type: "text"
        placeholder: "ex: PlayStation 5, Xbox Series X, Switch OLED"
    - generation:
        type: "text"
        label: "Génération"
    - stockage_go:
        type: "number"
        label: "Stockage (Go)"
    - stockage_type:
        type: "select"
        options: ["SSD", "HDD", "Cartouche"]
    - stockage_extensible:
        type: "boolean"
    - resolution_max:
        type: "select"
        options: ["720p", "1080p", "1440p", "4K", "8K"]
    - fps_max:
        type: "number"
        label: "FPS maximum"
    - ray_tracing:
        type: "boolean"
    - vr_compatible:
        type: "boolean"
        label: "Compatible VR"
    - lecteur_disque:
        type: "boolean"
        label: "Lecteur disque physique"
    - retrocompatibilite:
        type: "text"
        label: "Rétrocompatibilité"
        placeholder: "ex: PS4, Xbox One"
    - manettes_incluses:
        type: "number"
        label: "Manettes incluses"
    - hdmi_version:
        type: "select"
        options: ["1.4", "2.0", "2.1"]
    - ports_usb:
        type: "number"
    - ethernet_gbps:
        type: "select"
        options: ["1", "2.5", "10"]
    - wifi:
        type: "select"
        options: ["Wi-Fi 5", "Wi-Fi 6", "Wi-Fi 6E"]
    - bluetooth:
        type: "select"
        options: ["4.0", "5.0", "5.1", "5.2"]
    - poids_kg:
        type: "number"
        step: 0.1

10. AUTRES

  • Onduleurs (UPS)
  • Alimentations PC
  • Cartes d'acquisition (DAQ)
  • Dispositifs KVM
  • Contrôleurs MIDI
  • GPS USB
  • Dongles de sécurité

🔧 CARACTÉRISTIQUES DES DONNÉES

Caractéristiques GLOBALES (tous périphériques)

📝 IDENTIFICATION
├── id (auto-increment)
├── nom (user-defined name)
├── type_principal (USB, Bluetooth, PCIe, etc.)
├── sous_type (Clé USB, Souris, GPU, etc.)
├── marque (manufacturer)
├── modele (model number)
├── numero_serie (serial number)
└── ean_upc (barcode)

💰 ACHAT
├── boutique (store name)
├── date_achat (purchase date)
├── prix (price)
├── devise (currency, default: EUR)
├── garantie_duree_mois (warranty duration)
└── garantie_expiration (warranty expiration date)

⭐ ÉVALUATION
├── rating (0-5 étoiles, float)
└── notes (free text review)

📦 STOCK
├── quantite_totale (total quantity)
├── quantite_disponible (available)
├── seuil_alerte (alert threshold)
└── emplacement_stockage (storage location)

📷 MÉDIAS
├── photos[] (array of image paths)
├── pdf_manuels[] (manuals)
├── pdf_factures[] (invoices)
└── pdf_autres[] (other docs)

🔗 LIENS & RÉFÉRENCES
├── url_fabricant (manufacturer page)
├── url_support (support page)
├── url_drivers (drivers download)
├── url_documentation
└── liens_personnalises[] (custom links)

📋 MÉTADONNÉES
├── date_creation (record creation)
├── date_modification (last update)
├── etat (Neuf, Bon, Usagé, Défectueux, Retiré)
├── localisation (physical location)
├── proprietaire (owner)
├── tags[] (custom tags, JSON)
└── notes (free text)

🐧 IDENTIFICATION LINUX
├── device_path (e.g., /dev/sda, /dev/input/mouse0)
├── sysfs_path (e.g., /sys/devices/pci0000:00/...)
├── vendor_id (USB: VID, PCI: vendor ID)
├── product_id (USB: PID, PCI: device ID)
├── class_id (device class)
├── driver_utilise (kernel driver in use)
├── modules_kernel[] (required kernel modules, JSON)
├── udev_rules (custom udev rules)
└── identifiant_systeme (lsusb, lspci output)

⚙️ INSTALLATION & PROBLÈMES
├── installation_auto (auto-detected: bool)
├── driver_requis (required drivers)
├── firmware_requis (required firmware)
├── paquets_necessaires[] (required packages, JSON)
├── commandes_installation (installation commands)
├── problemes_connus (known issues)
├── solutions (solutions/workarounds)
└── compatibilite_noyau (kernel version compatibility)

🔌 CONNECTIVITÉ
├── interface_connexion (USB, PCIe, Bluetooth, etc.)
├── connecte_a (connected to which device/port)
└── consommation_electrique_w (power consumption)

📍 LOCALISATION PHYSIQUE
├── location_id (FK vers locations)
├── location_details (détails supplémentaires)
└── location_auto (bool: suit le device assigné)

🤝 PRÊT / EMPRUNT
├── en_pret (bool)
├── pret_actuel_id (FK vers peripheral_loans)
└── prete_a (nom de l'emprunteur, pour affichage rapide)

💻 APPAREIL COMPLET
├── is_complete_device (bool)
├── device_type (desktop, laptop, tablet, smartphone, server, console)
├── linked_device_id (→ devices.id dans data.db, pour benchmarks)
└── device_id (→ devices.id dans data.db, assignation actuelle)

🎨 DONNÉES SPÉCIFIQUES
└── caracteristiques_specifiques (JSON flexible par type)

Caractéristiques SPÉCIFIQUES (exemples par type)

Stockage USB

{
  "capacite_go": 128,
  "type_memoire": "Flash NAND",
  "usb_version": "3.2 Gen 1",
  "vitesse_lecture_mbs": 150,
  "vitesse_ecriture_mbs": 50,
  "systeme_fichiers": "exFAT",
  "chiffrement": true,
  "chiffrement_type": "AES 256-bit"
}

Souris

{
  "connexion": "Bluetooth 5.0",
  "capteur_type": "Optique",
  "dpi_max": 16000,
  "dpi_reglable": true,
  "nombre_boutons": 8,
  "boutons_programmables": 6,
  "autonomie_heures": 200,
  "type_batterie": "Rechargeable Li-ion",
  "eclairage_rgb": true
}

Câble HDMI

{
  "longueur_m": 2,
  "version_hdmi": "2.1",
  "certification": "Premium",
  "debit_max_gbps": 48,
  "support_4k_120hz": true,
  "support_8k": true,
  "arc_earc": "eARC"
}

Desktop PC

{
  "systeme_exploitation": "Windows 11 Pro",
  "cpu_marque": "Intel",
  "cpu_modele": "Core i7-12700K",
  "cpu_cores": 12,
  "cpu_threads": 20,
  "ram_total_go": 32,
  "ram_type": "DDR5",
  "ram_freq_mhz": 5600,
  "gpu_type": "Dédié",
  "gpu_modele": "NVIDIA RTX 4070",
  "gpu_vram_go": 12,
  "stockage_principal": "1TB NVMe SSD",
  "carte_mere_marque": "ASUS",
  "carte_mere_modele": "ROG STRIX Z690-F",
  "alimentation_w": 850,
  "alimentation_certification": "80+ Gold",
  "boitier_format": "ATX",
  "refroidissement": "AIO 280mm"
}

Console de Jeu

{
  "type_console": "Salon",
  "marque": "PlayStation",
  "modele": "PlayStation 5",
  "generation": "9ème génération",
  "stockage_go": 825,
  "stockage_type": "SSD",
  "stockage_extensible": true,
  "resolution_max": "4K",
  "fps_max": 120,
  "ray_tracing": true,
  "vr_compatible": true,
  "lecteur_disque": true,
  "retrocompatibilite": "PS4",
  "manettes_incluses": 1,
  "hdmi_version": "2.1",
  "ports_usb": 4,
  "ethernet_gbps": "1",
  "wifi": "Wi-Fi 6",
  "bluetooth": "5.1",
  "poids_kg": 4.5
}

🗄️ ARCHITECTURE BASE DE DONNÉES

Stratégie: Deux Bases de Données Séparées

/backend/data/
├── data.db                    # DB PRINCIPALE (existante)
│   ├── devices                # Machines benchmarkées
│   ├── hardware_snapshots     # Snapshots hardware
│   ├── benchmarks             # Résultats benchmarks
│   ├── documents              # Documents devices
│   ├── manufacturer_links     # Liens devices
│   └── disk_smart             # Données SMART
│
└── peripherals.db             # DB PÉRIPHÉRIQUES (nouvelle)
    ├── peripherals            # Périphériques
    ├── peripheral_photos      # Photos
    ├── peripheral_documents   # Documents
    ├── peripheral_links       # Liens
    ├── peripheral_loans       # Prêts
    ├── peripheral_location_history  # Historique déplacements
    ├── peripheral_attachments # Relations périph ↔ périph
    ├── locations              # Emplacements physiques
    └── borrower_blacklist     # Liste noire emprunteurs

Pourquoi Séparé ?

Isolation - Données indépendantes Performance - Pas de contention Scalabilité - Peut grossir indépendamment Backups - Stratégies distinctes Migration - Plus facile Modularité - Activable/désactivable

Schéma Table peripherals

CREATE TABLE peripherals (
    -- IDENTIFICATION
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    nom VARCHAR(255) NOT NULL,
    type_principal VARCHAR(100) NOT NULL,
    sous_type VARCHAR(100),
    marque VARCHAR(100),
    modele VARCHAR(255),
    numero_serie VARCHAR(255),
    ean_upc VARCHAR(50),

    -- ACHAT
    boutique VARCHAR(255),
    date_achat DATE,
    prix DECIMAL(10, 2),
    devise VARCHAR(10) DEFAULT 'EUR',
    garantie_duree_mois INTEGER,
    garantie_expiration DATE,

    -- ÉVALUATION
    rating FLOAT DEFAULT 0,  -- 0-5 étoiles

    -- STOCK
    quantite_totale INTEGER DEFAULT 1,
    quantite_disponible INTEGER DEFAULT 1,
    seuil_alerte INTEGER DEFAULT 0,

    -- MÉTADONNÉES
    date_creation TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    date_modification TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    etat VARCHAR(50) DEFAULT 'Neuf',
    localisation VARCHAR(255),
    proprietaire VARCHAR(100),
    tags TEXT,  -- JSON array
    notes TEXT,

    -- LINUX IDENTIFICATION
    device_path VARCHAR(255),
    sysfs_path VARCHAR(500),
    vendor_id VARCHAR(20),
    product_id VARCHAR(20),
    class_id VARCHAR(20),
    driver_utilise VARCHAR(100),
    modules_kernel TEXT,  -- JSON
    udev_rules TEXT,
    identifiant_systeme TEXT,

    -- INSTALLATION
    installation_auto BOOLEAN DEFAULT FALSE,
    driver_requis TEXT,
    firmware_requis TEXT,
    paquets_necessaires TEXT,  -- JSON
    commandes_installation TEXT,
    problemes_connus TEXT,
    solutions TEXT,
    compatibilite_noyau VARCHAR(100),

    -- CONNECTIVITÉ
    interface_connexion VARCHAR(100),
    connecte_a VARCHAR(255),
    consommation_electrique_w DECIMAL(6, 2),

    -- LOCALISATION PHYSIQUE
    location_id INTEGER,
    location_details VARCHAR(500),
    location_auto BOOLEAN DEFAULT TRUE,

    -- PRÊT
    en_pret BOOLEAN DEFAULT FALSE,
    pret_actuel_id INTEGER,
    prete_a VARCHAR(255),

    -- APPAREIL COMPLET
    is_complete_device BOOLEAN DEFAULT FALSE,
    device_type VARCHAR(50),

    -- LIEN VERS DB PRINCIPALE (logique, pas FK SQL)
    linked_device_id INTEGER,  -- → devices.id dans data.db (benchmarks)
    device_id INTEGER,         -- → devices.id dans data.db (assignation)

    -- DONNÉES SPÉCIFIQUES
    caracteristiques_specifiques TEXT  -- JSON
);

CREATE INDEX idx_peripherals_type ON peripherals(type_principal);
CREATE INDEX idx_peripherals_sous_type ON peripherals(sous_type);
CREATE INDEX idx_peripherals_marque ON peripherals(marque);
CREATE INDEX idx_peripherals_device ON peripherals(device_id);
CREATE INDEX idx_peripherals_etat ON peripherals(etat);
CREATE INDEX idx_peripherals_complete_device ON peripherals(is_complete_device);
CREATE INDEX idx_peripherals_en_pret ON peripherals(en_pret);

Table peripheral_photos

CREATE TABLE peripheral_photos (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    peripheral_id INTEGER NOT NULL,
    filename VARCHAR(255) NOT NULL,
    stored_path VARCHAR(500) NOT NULL,
    mime_type VARCHAR(100),
    size_bytes INTEGER,
    uploaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    description TEXT,
    is_primary BOOLEAN DEFAULT FALSE,
    FOREIGN KEY (peripheral_id) REFERENCES peripherals(id) ON DELETE CASCADE
);

CREATE INDEX idx_peripheral_photos_pid ON peripheral_photos(peripheral_id);

Table peripheral_documents

CREATE TABLE peripheral_documents (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    peripheral_id INTEGER NOT NULL,
    doc_type VARCHAR(50) NOT NULL,  -- manual, warranty, invoice, datasheet, other
    filename VARCHAR(255) NOT NULL,
    stored_path VARCHAR(500) NOT NULL,
    mime_type VARCHAR(100),
    size_bytes INTEGER,
    uploaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    description TEXT,
    FOREIGN KEY (peripheral_id) REFERENCES peripherals(id) ON DELETE CASCADE
);

CREATE INDEX idx_peripheral_documents_pid ON peripheral_documents(peripheral_id);
CREATE INDEX idx_peripheral_documents_type ON peripheral_documents(doc_type);
CREATE TABLE peripheral_links (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    peripheral_id INTEGER NOT NULL,
    link_type VARCHAR(50) NOT NULL,  -- manufacturer, support, drivers, documentation, custom
    label VARCHAR(255) NOT NULL,
    url TEXT NOT NULL,
    FOREIGN KEY (peripheral_id) REFERENCES peripherals(id) ON DELETE CASCADE
);

CREATE INDEX idx_peripheral_links_pid ON peripheral_links(peripheral_id);

Table peripheral_loans

CREATE TABLE peripheral_loans (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    peripheral_id INTEGER NOT NULL,

    -- Emprunteur
    emprunte_par VARCHAR(255) NOT NULL,
    email_emprunteur VARCHAR(255),
    telephone VARCHAR(50),

    -- Dates
    date_pret DATE NOT NULL,
    date_retour_prevue DATE NOT NULL,
    date_retour_effectif DATE,

    -- Statut
    statut VARCHAR(50) NOT NULL DEFAULT 'en_cours',  -- en_cours, retourne, en_retard

    -- Caution
    caution_montant DECIMAL(10, 2),
    caution_rendue BOOLEAN DEFAULT FALSE,

    -- État
    etat_depart VARCHAR(50),
    etat_retour VARCHAR(50),
    problemes_retour TEXT,

    -- Informations
    raison_pret TEXT,
    notes TEXT,
    created_by VARCHAR(100),

    -- Rappels
    rappel_envoye BOOLEAN DEFAULT FALSE,
    date_rappel TIMESTAMP,

    FOREIGN KEY (peripheral_id) REFERENCES peripherals(id) ON DELETE CASCADE
);

CREATE INDEX idx_loans_peripheral ON peripheral_loans(peripheral_id);
CREATE INDEX idx_loans_statut ON peripheral_loans(statut);
CREATE INDEX idx_loans_emprunteur ON peripheral_loans(emprunte_par);
CREATE INDEX idx_loans_retour_prevue ON peripheral_loans(date_retour_prevue);

Table locations

CREATE TABLE locations (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    nom VARCHAR(255) NOT NULL UNIQUE,
    type VARCHAR(50) NOT NULL,  -- piece, placard, tiroir, etagere, meuble, boite
    parent_id INTEGER,  -- Hiérarchie
    description TEXT,
    image_path VARCHAR(500),
    qr_code_path VARCHAR(500),
    ordre_affichage INTEGER DEFAULT 0,
    FOREIGN KEY (parent_id) REFERENCES locations(id) ON DELETE CASCADE
);

CREATE INDEX idx_locations_parent ON locations(parent_id);
CREATE INDEX idx_locations_type ON locations(type);

Table peripheral_location_history

CREATE TABLE peripheral_location_history (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    peripheral_id INTEGER NOT NULL,
    from_location_id INTEGER,
    to_location_id INTEGER,
    from_device_id INTEGER,
    to_device_id INTEGER,
    action VARCHAR(50) NOT NULL,  -- moved, assigned, unassigned, stored
    timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    notes TEXT,
    user VARCHAR(100),
    FOREIGN KEY (peripheral_id) REFERENCES peripherals(id) ON DELETE CASCADE,
    FOREIGN KEY (from_location_id) REFERENCES locations(id) ON DELETE SET NULL,
    FOREIGN KEY (to_location_id) REFERENCES locations(id) ON DELETE SET NULL
);

CREATE INDEX idx_peripheral_history_pid ON peripheral_location_history(peripheral_id);

Organisation Fichiers Uploads

/uploads/peripherals/
├── photos/
│   ├── {hash16}_{peripheral_id}_1.jpg
│   ├── {hash16}_{peripheral_id}_2.png
│   └── ...
├── documents/
│   ├── manuals/
│   │   └── {hash16}_{peripheral_id}_manual.pdf
│   ├── invoices/
│   │   └── {hash16}_{peripheral_id}_invoice.pdf
│   ├── warranties/
│   │   └── {hash16}_{peripheral_id}_warranty.pdf
│   └── datasheets/
│       └── {hash16}_{peripheral_id}_datasheet.pdf
└── thumbnails/
    └── {hash16}_{peripheral_id}_thumb.webp

⚙️ ARCHITECTURE BACKEND

Configuration (/backend/app/core/config.py)

class Settings(BaseSettings):
    # ... config existante ...

    # DATABASE PRINCIPALE (benchmarks)
    DATABASE_URL: str = os.getenv(
        "DATABASE_URL",
        "sqlite:///./backend/data/data.db"
    )

    # DATABASE PÉRIPHÉRIQUES (nouvelle)
    PERIPHERALS_DB_URL: str = os.getenv(
        "PERIPHERALS_DB_URL",
        "sqlite:///./backend/data/peripherals.db"
    )

    # Upload directories
    UPLOAD_DIR: str = "./uploads"
    PERIPHERALS_UPLOAD_DIR: str = "./uploads/peripherals"

    # Module peripherals enabled/disabled
    PERIPHERALS_MODULE_ENABLED: bool = os.getenv(
        "PERIPHERALS_MODULE_ENABLED",
        "true"
    ).lower() == "true"

    # Image compression
    IMAGE_COMPRESSION_ENABLED: bool = True
    IMAGE_COMPRESSION_QUALITY: int = 85
    IMAGE_MAX_WIDTH: int = 1920
    IMAGE_MAX_HEIGHT: int = 1080
    THUMBNAIL_SIZE: int = 300
    THUMBNAIL_QUALITY: int = 75
    THUMBNAIL_FORMAT: str = "webp"

Sessions DB (/backend/app/db/session.py)

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session

# DB Principale
engine_main = create_engine(settings.DATABASE_URL, ...)
SessionLocalMain = sessionmaker(bind=engine_main)

# DB Périphériques
engine_peripherals = create_engine(settings.PERIPHERALS_DB_URL, ...)
SessionLocalPeripherals = sessionmaker(bind=engine_peripherals)

# Dependency Injection
def get_db() -> Session:
    """Session DB principale"""
    db = SessionLocalMain()
    try:
        yield db
    finally:
        db.close()

def get_peripherals_db() -> Session:
    """Session DB périphériques"""
    db = SessionLocalPeripherals()
    try:
        yield db
    finally:
        db.close()

Modèles SQLAlchemy

Base:

# /backend/app/db/base.py
Base = declarative_base()  # DB principale
BasePeripherals = declarative_base()  # DB périphériques

Modèle Peripheral:

# /backend/app/models/peripheral.py
from app.db.base import BasePeripherals

class Peripheral(BasePeripherals):
    __tablename__ = "peripherals"
    # ... tous les champs définis plus haut

Schémas Pydantic

# /backend/app/schemas/peripheral.py

class PeripheralBase(BaseModel):
    nom: str
    type_principal: str
    sous_type: Optional[str] = None
    marque: Optional[str] = None
    modele: Optional[str] = None
    # ... tous les champs

class PeripheralCreate(PeripheralBase):
    pass

class PeripheralUpdate(BaseModel):
    nom: Optional[str] = None
    # ... tous les champs optionnels

class PeripheralDetail(PeripheralBase):
    id: int
    date_creation: datetime
    photos: List[PeripheralPhotoSchema]
    documents: List[PeripheralDocumentSchema]
    links: List[PeripheralLinkSchema]
    loan: Optional[PeripheralLoanSchema]
    location: Optional[LocationSchema]

    class Config:
        from_attributes = True

class PeripheralSummary(BaseModel):
    id: int
    nom: str
    type_principal: str
    sous_type: Optional[str]
    marque: Optional[str]
    etat: str
    rating: float
    prix: Optional[float]
    photo_principale: Optional[str]
    en_pret: bool

    class Config:
        from_attributes = True

class PeripheralListResponse(BaseModel):
    items: List[PeripheralSummary]
    total: int
    page: int
    page_size: int
    total_pages: int

Routes API (/backend/app/api/peripherals.py)

from fastapi import APIRouter, Depends, HTTPException, UploadFile, File
from sqlalchemy.orm import Session
from app.db.session import get_db, get_peripherals_db
from app.services.peripheral_service import PeripheralService

router = APIRouter(prefix="/api/peripherals", tags=["peripherals"])

# CRUD Périphériques
@router.get("/", response_model=PeripheralListResponse)
async def list_peripherals(
    page: int = 1,
    page_size: int = 50,
    search: Optional[str] = None,
    type_principal: Optional[str] = None,
    marque: Optional[str] = None,
    etat: Optional[str] = None,
    en_pret: Optional[bool] = None,
    is_complete_device: Optional[bool] = None,
    db: Session = Depends(get_peripherals_db)
):
    """Liste périphériques avec pagination et filtres"""
    pass

@router.get("/{peripheral_id}", response_model=PeripheralDetail)
async def get_peripheral(
    peripheral_id: int,
    db_peripherals: Session = Depends(get_peripherals_db),
    db_main: Session = Depends(get_db)
):
    """Détail périphérique avec relations"""
    return PeripheralService.get_peripheral_with_device(
        peripheral_id, db_peripherals, db_main
    )

@router.post("/", response_model=PeripheralDetail)
async def create_peripheral(
    peripheral: PeripheralCreate,
    db: Session = Depends(get_peripherals_db)
):
    """Créer périphérique"""
    pass

@router.put("/{peripheral_id}")
async def update_peripheral(
    peripheral_id: int,
    data: PeripheralUpdate,
    db: Session = Depends(get_peripherals_db)
):
    """Mettre à jour périphérique"""
    pass

@router.delete("/{peripheral_id}")
async def delete_peripheral(
    peripheral_id: int,
    db: Session = Depends(get_peripherals_db)
):
    """Supprimer périphérique"""
    pass

# Photos
@router.post("/{peripheral_id}/photos")
async def upload_photo(
    peripheral_id: int,
    file: UploadFile = File(...),
    compress: bool = True,
    db: Session = Depends(get_peripherals_db)
):
    """Upload photo avec compression optionnelle"""
    pass

@router.get("/{peripheral_id}/photos")
async def get_photos(
    peripheral_id: int,
    db: Session = Depends(get_peripherals_db)
):
    """Liste photos"""
    pass

@router.delete("/photos/{photo_id}")
async def delete_photo(
    photo_id: int,
    db: Session = Depends(get_peripherals_db)
):
    """Supprimer photo"""
    pass

# Documents
@router.post("/{peripheral_id}/documents")
async def upload_document(
    peripheral_id: int,
    file: UploadFile = File(...),
    doc_type: str = "other",
    db: Session = Depends(get_peripherals_db)
):
    """Upload document"""
    pass

@router.get("/{peripheral_id}/documents")
async def get_documents(
    peripheral_id: int,
    db: Session = Depends(get_peripherals_db)
):
    """Liste documents"""
    pass

@router.get("/documents/{doc_id}/download")
async def download_document(doc_id: int, db: Session = Depends(get_peripherals_db)):
    """Télécharger document"""
    pass

# Liens
@router.post("/{peripheral_id}/links")
@router.get("/{peripheral_id}/links")
@router.put("/links/{link_id}")
@router.delete("/links/{link_id}")

# Prêts
@router.post("/{peripheral_id}/loan")
async def create_loan(
    peripheral_id: int,
    loan: LoanCreate,
    db: Session = Depends(get_peripherals_db)
):
    """Créer un prêt"""
    pass

@router.post("/loans/{loan_id}/return")
async def return_loan(
    loan_id: int,
    return_data: LoanReturn,
    db: Session = Depends(get_peripherals_db)
):
    """Enregistrer retour"""
    pass

@router.get("/loans/active")
@router.get("/loans/overdue")
@router.post("/loans/{loan_id}/reminder")

# Localisation
@router.post("/{peripheral_id}/assign")
@router.post("/{peripheral_id}/unassign")
@router.get("/{peripheral_id}/history")

# Appareils complets
@router.post("/{peripheral_id}/link-device")
@router.delete("/{peripheral_id}/unlink-device")
@router.get("/{peripheral_id}/benchmarks")

# Recherche avancée
@router.get("/search")
async def advanced_search(
    q: str,
    filters: dict,
    db: Session = Depends(get_peripherals_db)
):
    """Recherche multi-critères"""
    pass

# Config
@router.get("/types")
async def get_peripheral_types():
    """Liste types depuis YAML"""
    pass

Service Layer (/backend/app/services/peripheral_service.py)

class PeripheralService:
    @staticmethod
    def get_peripheral_with_device(
        peripheral_id: int,
        db_peripherals: Session,
        db_main: Session
    ):
        """Récupère périphérique avec device lié"""
        peripheral = db_peripherals.query(Peripheral).get(peripheral_id)

        result = {
            "peripheral": peripheral,
            "linked_device": None,
            "benchmarks": []
        }

        if peripheral.linked_device_id:
            device = db_main.query(Device).get(peripheral.linked_device_id)
            result["linked_device"] = device
            if device:
                result["benchmarks"] = device.benchmarks

        return result

    @staticmethod
    def compress_image(file_path: str, quality: int = 85):
        """Compresse une image"""
        from PIL import Image
        img = Image.open(file_path)
        # ... compression logic
        pass

    @staticmethod
    def generate_thumbnail(file_path: str, size: int = 300):
        """Génère thumbnail"""
        pass

Main App (/backend/app/main.py)

from app.api import peripherals, locations, loans

if settings.PERIPHERALS_MODULE_ENABLED:
    app.include_router(peripherals.router)
    app.include_router(locations.router)
    app.include_router(loans.router)

🎨 ARCHITECTURE FRONTEND

Pages HTML

/frontend/
├── peripherals.html        # Page principale périphériques
├── peripheral_detail.html  # Détail périphérique (optionnel)
├── locations.html          # Gestion emplacements
├── loans.html              # Gestion prêts
└── (pages existantes)

JavaScript

/frontend/js/
├── api.js                  # Extension BenchAPI.Peripherals.*
├── peripherals.js          # Logique page principale
├── peripheral_detail.js    # Logique détail
├── locations.js            # Gestion emplacements
├── loans.js                # Gestion prêts
├── components/
│   ├── PhotoGallery.js     # Galerie photos
│   ├── DocumentList.js     # Liste documents
│   ├── PeripheralFilters.js # Filtres avancés
│   ├── LocationSelector.js  # Sélecteur arborescence
│   ├── RatingStars.js      # Système étoiles
│   ├── TypeManager.js      # Gestion types YAML
│   ├── StockManager.js     # Gestion stock
│   └── ImageUploader.js    # Upload images
└── utils.js                # (existant, étendre si besoin)

Extension API Client (/frontend/js/api.js)

window.BenchAPI = window.BenchAPI || {};

window.BenchAPI.Peripherals = {
    // CRUD
    getPeripherals: async (page = 1, filters = {}) => {
        const params = new URLSearchParams({ page, ...filters });
        return await fetch(`${API_URL}/peripherals?${params}`).then(r => r.json());
    },

    getPeripheral: async (id) => {
        return await fetch(`${API_URL}/peripherals/${id}`).then(r => r.json());
    },

    createPeripheral: async (data) => {
        return await fetch(`${API_URL}/peripherals`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(data)
        }).then(r => r.json());
    },

    updatePeripheral: async (id, data) => {
        return await fetch(`${API_URL}/peripherals/${id}`, {
            method: 'PUT',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(data)
        }).then(r => r.json());
    },

    deletePeripheral: async (id) => {
        return await fetch(`${API_URL}/peripherals/${id}`, {
            method: 'DELETE'
        });
    },

    // Photos
    uploadPhoto: async (peripheralId, file, compress = true) => {
        const formData = new FormData();
        formData.append('file', file);
        formData.append('compress', compress);

        return await fetch(`${API_URL}/peripherals/${peripheralId}/photos`, {
            method: 'POST',
            body: formData
        }).then(r => r.json());
    },

    getPhotos: async (peripheralId) => {
        return await fetch(`${API_URL}/peripherals/${peripheralId}/photos`)
            .then(r => r.json());
    },

    // Documents
    uploadDocument: async (peripheralId, file, docType) => {
        const formData = new FormData();
        formData.append('file', file);
        formData.append('doc_type', docType);

        return await fetch(`${API_URL}/peripherals/${peripheralId}/documents`, {
            method: 'POST',
            body: formData
        }).then(r => r.json());
    },

    // Liens
    addLink: async (peripheralId, link) => {
        return await fetch(`${API_URL}/peripherals/${peripheralId}/links`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(link)
        }).then(r => r.json());
    },

    // Prêts
    createLoan: async (peripheralId, loanData) => {
        return await fetch(`${API_URL}/peripherals/${peripheralId}/loan`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(loanData)
        }).then(r => r.json());
    },

    returnLoan: async (loanId, returnData) => {
        return await fetch(`${API_URL}/peripherals/loans/${loanId}/return`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(returnData)
        }).then(r => r.json());
    },

    getActiveLoans: async () => {
        return await fetch(`${API_URL}/peripherals/loans/active`)
            .then(r => r.json());
    },

    // Benchmarks (appareils complets)
    linkDevice: async (peripheralId, deviceId) => {
        return await fetch(`${API_URL}/peripherals/${peripheralId}/link-device`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ device_id: deviceId })
        }).then(r => r.json());
    },

    getBenchmarks: async (peripheralId) => {
        return await fetch(`${API_URL}/peripherals/${peripheralId}/benchmarks`)
            .then(r => r.json());
    },

    // Recherche
    search: async (query, filters = {}) => {
        const params = new URLSearchParams({ q: query, ...filters });
        return await fetch(`${API_URL}/peripherals/search?${params}`)
            .then(r => r.json());
    },

    // Config
    getTypes: async () => {
        return await fetch(`${API_URL}/peripherals/types`)
            .then(r => r.json());
    }
};

// Locations
window.BenchAPI.Locations = {
    getLocations: async () => {
        return await fetch(`${API_URL}/locations`).then(r => r.json());
    },

    getLocationTree: async () => {
        return await fetch(`${API_URL}/locations/tree`).then(r => r.json());
    },

    createLocation: async (data) => {
        return await fetch(`${API_URL}/locations`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(data)
        }).then(r => r.json());
    }
};

Composants Réutilisables

PhotoGallery.js

class PhotoGallery {
    constructor(containerId, peripheralId) {
        this.container = document.getElementById(containerId);
        this.peripheralId = peripheralId;
        this.photos = [];
    }

    async load() {
        this.photos = await BenchAPI.Peripherals.getPhotos(this.peripheralId);
        this.render();
    }

    render() {
        // Grid layout avec lightbox
        this.container.innerHTML = `
            <div class="photo-grid">
                ${this.photos.map(p => `
                    <div class="photo-item" data-id="${p.id}">
                        <img src="${p.thumbnail_path || p.stored_path}"
                             alt="${p.description}"
                             onclick="PhotoGallery.openLightbox('${p.stored_path}')">
                        ${p.is_primary ? '<span class="badge-primary">Principal</span>' : ''}
                        <button onclick="PhotoGallery.delete(${p.id})">×</button>
                    </div>
                `).join('')}
                <div class="photo-upload">
                    <input type="file" id="upload-photo" accept="image/*" multiple>
                    <label for="upload-photo">+ Ajouter photos</label>
                </div>
            </div>
        `;
    }

    static openLightbox(imagePath) {
        // Lightbox overlay
    }
}

RatingStars.js

class RatingStars {
    constructor(containerId, currentRating = 0, editable = true) {
        this.container = document.getElementById(containerId);
        this.rating = currentRating;
        this.editable = editable;
        this.render();
    }

    render() {
        const stars = [];
        for (let i = 1; i <= 5; i++) {
            const filled = i <= Math.round(this.rating);
            stars.push(`
                <span class="star ${filled ? 'filled' : ''}"
                      data-rating="${i}"
                      ${this.editable ? `onclick="this.setRating(${i})"` : ''}>
                </span>
            `);
        }

        this.container.innerHTML = `
            <div class="rating-stars">
                ${stars.join('')}
                <span class="rating-value">${this.rating.toFixed(1)}/5</span>
            </div>
        `;
    }

    setRating(value) {
        this.rating = value;
        this.render();
        // Trigger event
        this.container.dispatchEvent(new CustomEvent('ratingChange', {
            detail: { rating: value }
        }));
    }
}

CSS Additions (/frontend/css/components.css)

/* Périphériques */
.peripheral-card {
    background: var(--card-bg);
    border-radius: 8px;
    padding: 1rem;
    margin-bottom: 1rem;
    transition: transform 0.2s;
}

.peripheral-card:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 8px rgba(0,0,0,0.3);
}

.peripheral-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.peripheral-type-badge {
    background: var(--color-info);
    color: #000;
    padding: 0.25rem 0.5rem;
    border-radius: 4px;
    font-size: 0.875rem;
}

.peripheral-status {
    display: flex;
    gap: 0.5rem;
}

.status-badge {
    padding: 0.25rem 0.5rem;
    border-radius: 4px;
    font-size: 0.75rem;
}

.status-neuf { background: #a6e22e; color: #000; }
.status-bon { background: #66d9ef; color: #000; }
.status-use { background: #fd971f; color: #000; }
.status-defectueux { background: #f92672; color: #fff; }

/* Rating Stars */
.rating-stars {
    display: flex;
    align-items: center;
    gap: 0.25rem;
}

.star {
    font-size: 1.5rem;
    color: #666;
    cursor: pointer;
    transition: color 0.2s;
}

.star.filled {
    color: #ffd700;
}

.star:hover {
    color: #ffd700;
}

/* Photo Gallery */
.photo-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 1rem;
}

.photo-item {
    position: relative;
    aspect-ratio: 1;
    border-radius: 8px;
    overflow: hidden;
    cursor: pointer;
}

.photo-item img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.photo-upload {
    border: 2px dashed var(--border-color);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: border-color 0.2s;
}

.photo-upload:hover {
    border-color: var(--color-info);
}

/* Location Tree */
.location-tree {
    list-style: none;
    padding-left: 0;
}

.location-tree li {
    padding-left: 1.5rem;
    position: relative;
}

.location-tree li:before {
    content: '';
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 1px;
    background: var(--border-color);
}

.location-item {
    padding: 0.5rem;
    cursor: pointer;
    border-radius: 4px;
}

.location-item:hover {
    background: rgba(255,255,255,0.1);
}

.location-item.selected {
    background: var(--color-info);
    color: #000;
}

⚙️ FONCTIONNALITÉS PRINCIPALES

1. GESTION STOCK

  • Quantités: Totale, Disponible, Seuil d'alerte
  • Alertes: Notification si stock bas
  • Mouvements: Tracking ajouts/retraits
  • Emplacement: Stockage physique

2. COMPRESSION D'IMAGES

Configuration (image_processing.yaml):

compression:
  enabled: true
  original:
    keep: true
    path: "originals/"
  optimized:
    format: "webp"
    quality: 85
    max_width: 1920
    max_height: 1080
  thumbnail:
    enabled: true
    format: "webp"
    quality: 75
    width: 300
    height: 300
    fit: "cover"
  max_upload_size_mb: 10

3. RATING SYSTÈME (0-5 ÉTOILES)

  • Interface clickable
  • Affichage visuel (★★★★☆)
  • Stockage float (précision 0.1)
  • Filtrage par rating

4. RECHERCHE AVANCÉE

Critères de recherche:

  • Texte libre (nom, marque, modèle)
  • Type principal / Sous-type
  • Marque
  • État (Neuf, Bon, Usagé, Défectueux)
  • Prix (range)
  • Date d'achat (range)
  • Tags
  • Localisation
  • En prêt / Disponible
  • Rating minimum
  • Vendor ID / Product ID (Linux)

Résultats:

  • Temps réel (<200ms)
  • Pagination
  • Tri multi-colonnes
  • Export CSV/JSON

5. IMPORT/EXPORT

Export:

  • CSV (Excel compatible)
  • JSON (backup complet)
  • PDF (fiche périphérique)
  • Labels (impression étiquettes)

Import:

  • CSV en masse
  • Auto-détection colonnes
  • Validation données
  • Preview avant import

6. QR CODES

  • Génération automatique par périphérique
  • Lien vers fiche web
  • Impression étiquettes
  • Scan mobile → accès direct

📍 SYSTÈME DE LOCALISATION

Hiérarchie

📍 Racine
├── 🏠 Pièce (piece)
│   ├── 🗄️ Placard (placard)
│   │   ├── 🗃️ Tiroir (tiroir)
│   │   └── 📦 Étagère (etagere)
│   └── 🖥️ Meuble (meuble)
└── 🏠 Pièce 2

Logique

Si périphérique assigné à un device:

  • location_auto = TRUE → suit le device
  • Localisation = celle du device

Si périphérique stocké:

  • location_auto = FALSE
  • location_id → emplacement de stockage précis
  • location_details → précisions (ex: "2ème rangée, sachet antistatique")

Fonctionnalités

  • Arborescence interactive (TreeView)
  • Breadcrumb (Maison > Bureau > Placard A > Tiroir 2)
  • Historique mouvements (table peripheral_location_history)
  • QR code par emplacement (scan → voir contenu)
  • Photos emplacements (aide visuelle)
  • Rapport non-localisés (alerte périphériques sans emplacement)

Configuration YAML (locations.yaml)

locations:
  - nom: "Maison"
    type: "root"
    children:
      - nom: "Bureau"
        type: "piece"
        children:
          - nom: "Bureau principal"
            type: "meuble"
          - nom: "Placard A"
            type: "placard"
            children:
              - nom: "Tiroir 1"
                type: "tiroir"
                description: "Câbles et adaptateurs"
              - nom: "Tiroir 2"
                type: "tiroir"
                description: "Périphériques USB"

🤝 SYSTÈME DE PRÊT

Workflow

  1. Créer prêt

    • Emprunteur (nom, email, tél)
    • Dates (prêt, retour prévu)
    • Caution (montant, type)
    • État départ + photos
    • Raison du prêt
  2. Suivi automatique

    • Rappel 2j avant échéance
    • Rappel à l'échéance
    • Rappel retard (tous les 2j)
    • Statut: en_cours, en_retard
  3. Retour

    • Date retour effectif
    • État au retour + photos
    • Problèmes constatés
    • Caution rendue/retenue

Notifications (CRON)

Tâche quotidienne (check_and_send_reminders()):

  • Vérifier prêts à échéance J-2
  • Vérifier prêts à échéance J
  • Vérifier prêts en retard
  • Envoyer emails automatiques

Configuration (notifications.yaml):

loan_notifications:
  enabled: true
  rappels:
    avant_echeance:
      enabled: true
      jours_avant: 2
    retard:
      enabled: true
      frequence_jours: 2
      max_rappels: 3
  email:
    enabled: true
    smtp_server: "smtp.gmail.com"
    from_email: "inventory@example.com"
  templates:
    rappel_avant:
      sujet: "Rappel: Retour de {peripheral_name} prévu le {date_retour}"
      corps: |
        Bonjour {emprunteur_name},

        Ce message pour vous rappeler que le périphérique suivant
        doit être retourné le {date_retour} :

        • {peripheral_name}
        • Caution: {caution_montant}€

        Merci.

Statistiques

  • Total prêts en cours
  • Valeur totale prêtée
  • Prêts en retard
  • Cautions en attente
  • Top emprunteurs
  • Score fiabilité emprunteur
  • Blacklist (optionnel)

💻 APPAREILS COMPLETS

Concept

Les appareils complets (Desktop, Laptop, Tablet, Smartphone, Serveur, Console) sont des périphériques spéciaux qui:

  1. Peuvent avoir des benchmarks (linked_device_iddevices.id)
  2. Peuvent avoir des périphériques attachés (souris, clavier, écran)
  3. Ont des specs complètes (CPU, RAM, GPU, etc.)
  4. Peuvent être prêtés comme tout périphérique

Lien avec Benchmarks

Peripheral (Laptop Dell XPS 15)
  ├─ is_complete_device = TRUE
  ├─ device_type = "laptop"
  ├─ linked_device_id = 42 → devices.id (dans data.db)
  │
  └─ → Accès aux benchmarks du device 42
      ├─ Historique benchmarks
      ├─ Dernier score global
      ├─ Évolution performances
      └─ Hardware snapshots

Interface

Onglet "Benchmarks" dans fiche périphérique:

  • Dernier benchmark (score global, détails)
  • Historique (tableau, graphique évolution)
  • Lien vers page device detail
  • Bouton "Nouveau benchmark"

Sync automatique specs:

  • Après chaque benchmark
  • Mise à jour auto des specs (CPU, RAM, GPU, etc.)
  • Depuis hardware_snapshot

Périphériques Attachés

Exemple Desktop Gaming:

Desktop Gaming PC
├─ Linked device → Benchmarks
├─ Périphériques attachés:
│  ├─ Souris Logitech G Pro
│  ├─ Clavier Corsair K95
│  ├─ Casque SteelSeries
│  └─ Écran ASUS ROG 27"
└─ Localisation: Bureau > Bureau gaming

Gestion:

  • Bouton "Attacher périphérique"
  • Liste avec possibilité détacher
  • Tracking historique

📝 CONFIGURATION YAML

Fichiers de Configuration

/backend/config/
├── peripheral_types.yaml       # Types & catégories
├── locations.yaml              # Emplacements
├── image_processing.yaml       # Compression images
└── notifications.yaml          # Alertes & emails

peripheral_types.yaml

Structure extensible pour définir les types de périphériques:

categories:
  - id: connectivity
    nom: "Connectivité"
    icon: "plug"
    types:
      - id: usb_storage
        nom: "Stockage USB"
        icon: "usb-drive"
        champs_specifiques:
          - capacite_go:
              type: "number"
              required: true
              label: "Capacité (Go)"
          - usb_version:
              type: "select"
              options: ["2.0", "3.0", "3.1", "3.2"]
              label: "Version USB"
          - vitesse_lecture_mbs:
              type: "number"
              label: "Vitesse lecture (MB/s)"

  - id: cables
    nom: "Câbles"
    icon: "cable"
    types:
      - id: cable_hdmi
        nom: "Câble HDMI"
        champs_specifiques:
          - longueur_m:
              type: "number"
              required: true
              label: "Longueur (m)"
          - version_hdmi:
              type: "select"
              options: ["1.4", "2.0", "2.1"]
          - certification:
              type: "select"
              options: ["Standard", "Premium", "Ultra"]

  - id: complete_devices
    nom: "Appareils Complets"
    icon: "devices"
    is_special: true
    types:
      - id: desktop
        nom: "PC de Bureau (Desktop)"
        is_complete_device: true
        can_have_benchmarks: true
        champs_specifiques:
          - systeme_exploitation:
              type: "select"
              options: ["Windows 11", "Windows 10", "Linux", "macOS"]
          - cpu_modele:
              type: "text"
              placeholder: "ex: Core i7-12700K"
          # ... (voir section Types de Périphériques pour specs complètes)

      - id: console_gaming
        nom: "Console de Jeu"
        icon: "gamepad"
        is_complete_device: true
        champs_specifiques:
          - type_console:
              type: "select"
              options: ["Salon", "Portable", "Hybrid"]
          - marque:
              type: "select"
              options: ["PlayStation", "Xbox", "Nintendo", "Steam", "Autre"]
          # ... (voir section Types de Périphériques pour specs complètes)

# Types personnalisés ajoutés dynamiquement depuis l'interface
custom_types: []

Gestion Dynamique des Types

Interface admin (TypeManager.js):

  • Modal gestion types
  • Ajout/édition/suppression catégories
  • Ajout/édition/suppression types
  • Configuration champs spécifiques
  • Sauvegarde YAML automatique

Avantages:

  • Pas besoin modifier code pour nouveau type
  • Interface [+] dans formulaires
  • Persistance YAML
  • Sync base de données automatique

📐 PLAN DE DÉPLOIEMENT

PHASE 1: Backend - Base de Données et API (Semaine 1-2)

1.1 Configuration et Sessions

  • Mettre à jour /backend/app/core/config.py
    • Ajouter PERIPHERALS_DB_URL
    • Ajouter paramètres compression images
    • Ajouter PERIPHERALS_MODULE_ENABLED
  • Créer /backend/app/db/session.py (deux sessions)
    • get_db() → DB principale
    • get_peripherals_db() → DB périphériques
  • Créer BasePeripherals dans /backend/app/db/base.py

1.2 Modèles SQLAlchemy

  • Créer /backend/app/models/peripheral.py
    • Peripheral (table principale)
    • PeripheralPhoto
    • PeripheralDocument
    • PeripheralLink
    • PeripheralLoan
  • Créer /backend/app/models/location.py
    • Location (hiérarchie emplacements)
  • Créer /backend/app/models/peripheral_history.py
    • PeripheralLocationHistory

1.3 Schémas Pydantic

  • Créer /backend/app/schemas/peripheral.py
    • PeripheralBase, PeripheralCreate, PeripheralUpdate
    • PeripheralDetail, PeripheralSummary, PeripheralListResponse
  • Créer /backend/app/schemas/location.py
  • Créer /backend/app/schemas/loan.py

1.4 Services

  • Créer /backend/app/services/peripheral_service.py
    • get_peripheral_with_device() (cross-DB)
    • link_peripheral_to_device()
    • compress_image(), generate_thumbnail()
    • sync_peripheral_from_benchmark()
  • Créer /backend/app/services/location_service.py
    • get_location_tree() (hiérarchie)
    • get_full_location_path()
  • Créer /backend/app/services/loan_service.py
    • check_and_send_reminders() (CRON)
    • send_loan_reminder()

1.5 Routes API

  • Créer /backend/app/api/peripherals.py (20+ endpoints)
    • CRUD périphériques
    • Photos, Documents, Liens
    • Prêts, Localisation, Historique
    • Recherche avancée
    • Appareils complets (link device, benchmarks)
  • Créer /backend/app/api/locations.py
    • CRUD emplacements
    • Arborescence
    • QR codes
  • Créer /backend/app/api/loans.py
    • CRUD prêts
    • Retours
    • Rappels
    • Statistiques

1.6 Configuration YAML

  • Créer /backend/config/peripheral_types.yaml
    • Définir toutes les catégories
    • Définir tous les types (USB, Câbles, Desktop, Console, etc.)
    • Champs spécifiques par type
  • Créer /backend/config/locations.yaml
    • Structure initiale emplacements
  • Créer /backend/config/image_processing.yaml
    • Paramètres compression
  • Créer /backend/config/notifications.yaml
    • Templates emails
    • Configuration SMTP

1.7 Utilitaires

  • Créer /backend/app/utils/yaml_loader.py
    • Charger/sauvegarder YAML
    • Parser configurations
  • Créer /backend/app/utils/image_processor.py
    • Compression images (Pillow)
    • Génération thumbnails
    • Validation formats
  • Créer /backend/app/utils/qr_generator.py
    • Génération QR codes
  • Créer /backend/app/tasks/daily_loan_reminders.py
    • Tâche CRON quotidienne

1.8 Initialisation

  • Mettre à jour /backend/app/db/init_db.py
    • Créer tables peripherals.db
    • Charger YAML initial
    • Créer emplacements par défaut
  • Mettre à jour /backend/app/main.py
    • Include routers peripherals
    • Check PERIPHERALS_MODULE_ENABLED

PHASE 2: Frontend - Interface Utilisateur (Semaine 3-4)

2.1 Pages HTML

  • Créer /frontend/peripherals.html
    • Layout deux panneaux
    • Liste + Détails/Formulaire
    • Navigation onglets
  • Créer /frontend/peripheral_detail.html (optionnel)
    • Vue complète périphérique
    • Onglets: Infos, Photos, Docs, Liens, Historique, Benchmarks
  • Créer /frontend/locations.html
    • Gestion emplacements
    • Arborescence interactive
  • Créer /frontend/loans.html
    • Liste prêts en cours
    • Historique
    • Statistiques

2.2 JavaScript - API Client

  • Étendre /frontend/js/api.js
    • BenchAPI.Peripherals.* (CRUD, photos, docs, liens, prêts)
    • BenchAPI.Locations.*
    • BenchAPI.Loans.*

2.3 JavaScript - Logique Pages

  • Créer /frontend/js/peripherals.js
    • Initialisation page
    • Chargement liste
    • Recherche/filtres temps réel
    • Formulaire dynamique (selon type)
    • Upload médias
  • Créer /frontend/js/peripheral_detail.js
    • Gestion onglets
    • Affichage benchmarks
    • Galerie photos
  • Créer /frontend/js/locations.js
    • TreeView emplacements
    • CRUD emplacements
    • QR codes
  • Créer /frontend/js/loans.js
    • Formulaire prêt
    • Formulaire retour
    • Liste prêts
    • Rappels

2.4 Composants Réutilisables

  • Créer /frontend/js/components/PhotoGallery.js
    • Grid photos
    • Upload drag & drop
    • Lightbox
    • Compression option
  • Créer /frontend/js/components/DocumentList.js
    • Liste documents avec icônes
    • Upload
    • Preview PDF
  • Créer /frontend/js/components/PeripheralFilters.js
    • Filtres latéraux
    • Recherche avancée
    • Tags
  • Créer /frontend/js/components/LocationSelector.js
    • TreeView sélection
    • Breadcrumb
  • Créer /frontend/js/components/RatingStars.js
    • Système étoiles clickable
    • Affichage/édition
  • Créer /frontend/js/components/TypeManager.js
    • Modal gestion types YAML
    • CRUD types/catégories
  • Créer /frontend/js/components/StockManager.js
    • Gestion quantités
    • Alertes stock
  • Créer /frontend/js/components/ImageUploader.js
    • Upload multiple
    • Preview
    • Crop/rotate basique

2.5 CSS

  • Étendre /frontend/css/components.css
    • Styles périphériques
    • Cards
    • Badges (type, état, prêt)
    • Photo gallery
    • Rating stars
    • Location tree
    • Modals
    • Forms dynamiques

2.6 Navigation

  • Mettre à jour TOUTES les pages HTML
    • Ajouter lien "Périphériques" dans nav
    • Ordre: Dashboard | Devices | Périphériques | Prêts | Emplacements | Settings

PHASE 3: Fonctionnalités Avancées (Semaine 5)

3.1 Recherche Avancée

  • Interface recherche multi-critères
  • Résultats temps réel
  • Filtres combinables
  • Tri multi-colonnes

3.2 Import/Export

  • Export CSV/JSON
  • Import CSV en masse
  • Export PDF fiche périphérique
  • Impression étiquettes

3.3 QR Codes

  • Génération QR par périphérique
  • Génération QR par emplacement
  • Page scan → fiche
  • Impression batch

3.4 Statistiques

  • Dashboard périphériques
    • Total par type (Pie chart)
    • Valeur inventaire
    • Top marques (Bar chart)
    • État (Donut chart)
  • Dashboard prêts
    • Prêts en cours
    • Valeur prêtée
    • Alertes retards
  • Dashboard emplacements
    • Inventaire par emplacement
    • Valeur par pièce

PHASE 4: Intégration et Optimisation (Semaine 6)

4.1 Intégration Devices

  • Dans device_detail.html, ajouter onglet "Périphériques connectés"
  • Bouton "Créer fiche périphérique" depuis device
  • Auto-link lors création
  • Sync specs après benchmark

4.2 Détection Automatique

  • Parser lsusb output
  • Parser lspci output
  • Suggérer ajout auto nouveaux périphériques
  • Pré-remplir vendor/product ID

4.3 Notifications

  • Setup CRON pour rappels prêts
  • Templates emails
  • Configuration SMTP
  • Alertes stock bas
  • Alertes garantie expirée

4.4 Performance

  • Optimisation queries SQL (eager loading)
  • Index appropriés
  • Compression images
  • Cache recherches fréquentes
  • Pagination efficace

4.5 Sécurité

  • Validation uploads (MIME, taille)
  • Sanitization noms fichiers
  • Protection CSRF
  • Rate limiting API

PHASE 5: Documentation et Tests (Semaine 7)

5.1 Documentation

  • /docs/PERIPHERALS_USER_GUIDE.md
    • Guide utilisateur
    • Exemples d'utilisation
    • FAQ
  • /docs/PERIPHERALS_API.md
    • Documentation API (Swagger)
    • Exemples requêtes
  • /docs/PERIPHERALS_ADMIN.md
    • Configuration YAML
    • Gestion types
    • Maintenance

5.2 Exemples et Fixtures

  • Script seed données test
  • Templates JSON par type
  • Données d'exemple

5.3 Tests

  • Tests unitaires backend (pytest)
    • Models
    • Services
    • Utils
  • Tests intégration API
    • Endpoints CRUD
    • Cross-DB queries
  • Tests frontend
    • Validation formulaires
    • Composants

🚀 PROMPT DE DÉVELOPPEMENT

Contexte du Projet

Vous êtes chargé d'implémenter le Module Périphériques pour l'application Linux BenchTools existante. Cette application permet actuellement de benchmarker des machines Linux et de stocker leurs résultats.

Architecture Existante

Backend:

  • FastAPI + SQLAlchemy
  • Base de données: SQLite data.db
  • Structure: /backend/app/ avec models/, schemas/, api/, core/
  • Authentification par token

Frontend:

  • Vanilla JavaScript (pas de framework)
  • Thème: Monokai dark avec CSS variables
  • Layout: Two-panel design
  • Navigation: Dashboard, Devices, Settings

Objectif du Module

Créer un système complet de gestion d'inventaire de périphériques avec:

  1. Catalogue exhaustif de périphériques (USB, Bluetooth, Câbles, Visserie, Appareils complets, Consoles)
  2. Localisation physique (Pièces → Placards → Tiroirs)
  3. Gestion de prêts (avec rappels automatiques)
  4. Intégration benchmarks (pour Desktop, Laptop, Serveur)
  5. Fonctionnalités avancées (Stock, Rating, Photos, Documents, QR codes)

Décisions Architecturales Clés

1. Base de Données Séparée

IMPORTANT: Créer une nouvelle base de données peripherals.db séparée de data.db.

Pourquoi:

  • Isolation des données
  • Performance (pas de contention)
  • Backups indépendants
  • Module activable/désactivable

Lien entre DB:

  • peripherals.linked_device_id → référence logique vers devices.id (pas FK SQL)
  • Gérer relations manuellement côté application via service layer

2. Configuration YAML Dynamique

Les types de périphériques sont définis en YAML, modifiables depuis l'interface:

# /backend/config/peripheral_types.yaml
categories:
  - id: cables
    nom: "Câbles"
    types:
      - id: cable_hdmi
        nom: "Câble HDMI"
        champs_specifiques:
          - longueur_m: { type: "number", required: true }
          - version_hdmi: { type: "select", options: ["1.4", "2.0", "2.1"] }

Avantages:

  • Pas de modification code pour nouveau type
  • Interface admin [+] pour ajouter types
  • Persistance automatique

3. Appareils Complets avec Benchmarks

Les Desktop, Laptop, Serveur, Console sont des périphériques spéciaux:

  • is_complete_device = TRUE
  • linked_device_id → lien vers devices.id pour benchmarks
  • Affichage historique benchmarks intégré
  • Sync automatique specs depuis hardware_snapshot

4. Système de Localisation Hiérarchique

Arborescence emplacements:

Maison
├─ Bureau (piece)
│  ├─ Placard A (placard)
│  │  ├─ Tiroir 1 (tiroir)
│  │  └─ Tiroir 2 (tiroir)
│  └─ Bureau principal (meuble)
└─ Salon (piece)

Logique:

  • Si device_id != NULL et location_auto = TRUE → localisation = celle du device
  • Sinon → location_id (emplacement de stockage)

5. Compression d'Images Automatique

Configuration dans image_processing.yaml:

  • Format optimisé: WebP
  • Qualité: 85%
  • Thumbnails: 300x300px
  • Conserver original optionnel

Structure des Données

Table Principale: peripherals

Champs globaux (tous périphériques):

  • Identification (nom, type, marque, modèle, S/N)
  • Achat (boutique, date, prix, garantie)
  • Évaluation (rating 0-5 étoiles)
  • Stock (quantité totale, disponible, seuil alerte)
  • Métadonnées (dates, état, tags, notes)
  • Linux (device_path, vendor_id, product_id, driver)
  • Installation (auto, driver requis, problèmes connus)
  • Connectivité (interface, consommation)
  • Localisation (location_id, location_auto)
  • Prêt (en_pret, pret_actuel_id, prete_a)
  • Appareil complet (is_complete_device, linked_device_id)
  • Données spécifiques (JSON flexible par type)

Relations:

  • peripheral_photos (1:N, photos avec principale)
  • peripheral_documents (1:N, manuels/factures/datasheets)
  • peripheral_links (1:N, liens fabricant/support/drivers)
  • peripheral_loans (1:N, historique prêts)
  • peripheral_location_history (1:N, mouvements)
  • locations (hiérarchie N niveaux)

Implémentation Backend

Sessions DB

# /backend/app/db/session.py
engine_main = create_engine(settings.DATABASE_URL)  # data.db
engine_peripherals = create_engine(settings.PERIPHERALS_DB_URL)  # peripherals.db

def get_db() -> Session:
    """Session DB principale (benchmarks)"""
    ...

def get_peripherals_db() -> Session:
    """Session DB périphériques"""
    ...

Modèles

# /backend/app/models/peripheral.py
from app.db.base import BasePeripherals  # ← Pas Base !

class Peripheral(BasePeripherals):
    __tablename__ = "peripherals"
    # ... champs

    # PAS de relationship SQLAlchemy vers data.db
    # Gérer manuellement via service layer

Service Layer (Cross-DB)

# /backend/app/services/peripheral_service.py
class PeripheralService:
    @staticmethod
    def get_peripheral_with_device(
        peripheral_id: int,
        db_peripherals: Session,
        db_main: Session
    ):
        """Récupère périphérique + device lié (cross-DB)"""
        peripheral = db_peripherals.query(Peripheral).get(peripheral_id)

        if peripheral.linked_device_id:
            device = db_main.query(Device).get(peripheral.linked_device_id)
            benchmarks = device.benchmarks if device else []
            return {
                "peripheral": peripheral,
                "linked_device": device,
                "benchmarks": benchmarks
            }

        return {"peripheral": peripheral}

Routes API (Deux Sessions)

# /backend/app/api/peripherals.py
@router.get("/{peripheral_id}")
async def get_peripheral(
    peripheral_id: int,
    db_peripherals: Session = Depends(get_peripherals_db),  # ← DB périphériques
    db_main: Session = Depends(get_db)                       # ← DB principale
):
    """Détail avec benchmarks si appareil complet"""
    return PeripheralService.get_peripheral_with_device(
        peripheral_id, db_peripherals, db_main
    )

Implémentation Frontend

Structure

/frontend/
├── peripherals.html         # Page principale (two-panel)
├── peripheral_detail.html   # Détail (optionnel)
├── locations.html           # Gestion emplacements
├── loans.html               # Gestion prêts
├── js/
│   ├── api.js               # Extension BenchAPI.Peripherals.*
│   ├── peripherals.js       # Logique page
│   ├── components/
│   │   ├── PhotoGallery.js  # Galerie + upload
│   │   ├── RatingStars.js   # Système étoiles
│   │   ├── LocationSelector.js  # TreeView
│   │   └── TypeManager.js   # Gestion types YAML
│   └── ...
└── css/
    └── components.css       # Styles périphériques

API Client Extension

// /frontend/js/api.js
window.BenchAPI.Peripherals = {
    getPeripherals: async (page, filters) => { /* ... */ },
    getPeripheral: async (id) => { /* ... */ },
    createPeripheral: async (data) => { /* ... */ },
    uploadPhoto: async (peripheralId, file, compress = true) => { /* ... */ },
    createLoan: async (peripheralId, loanData) => { /* ... */ },
    linkDevice: async (peripheralId, deviceId) => { /* ... */ },
    getBenchmarks: async (peripheralId) => { /* ... */ },
    // ... 20+ méthodes
};

Composants Réutilisables

PhotoGallery:

class PhotoGallery {
    constructor(containerId, peripheralId) { /* ... */ }
    async load() { /* Charger photos */ }
    render() { /* Grid + lightbox */ }
    async upload(files) { /* Upload avec compression */ }
}

RatingStars:

class RatingStars {
    constructor(containerId, currentRating = 0, editable = true) { /* ... */ }
    render() { /* ★★★★☆ */ }
    setRating(value) { /* Clickable */ }
}

LocationSelector:

class LocationSelector {
    constructor(containerId) { /* ... */ }
    async loadTree() { /* Arborescence depuis API */ }
    render() { /* TreeView avec expand/collapse */ }
    getSelectedPath() { /* Breadcrumb */ }
}

Fonctionnalités Clés à Implémenter

1. Gestion de Prêt avec Notifications

Backend:

  • CRON quotidien (check_and_send_reminders())
  • Rappels 2j avant, à échéance, en retard
  • Templates emails configurables (YAML)

Frontend:

  • Modal prêt (emprunteur, dates, caution, photos)
  • Modal retour (état, problèmes, caution rendue)
  • Liste prêts actifs
  • Alertes retards

2. Compression d'Images

# /backend/app/utils/image_processor.py
def compress_image(file_path: str, quality: int = 85):
    from PIL import Image
    img = Image.open(file_path)

    # Resize si trop grand
    if img.width > settings.IMAGE_MAX_WIDTH:
        ratio = settings.IMAGE_MAX_WIDTH / img.width
        new_size = (int(img.width * ratio), int(img.height * ratio))
        img = img.resize(new_size, Image.LANCZOS)

    # Convertir en WebP
    output_path = file_path.replace(ext, '.webp')
    img.save(output_path, 'WEBP', quality=quality)

    return output_path

def generate_thumbnail(file_path: str, size: int = 300):
    # Crop/resize 300x300
    ...

3. QR Codes

# /backend/app/utils/qr_generator.py
import qrcode

def generate_peripheral_qr(peripheral_id: int):
    url = f"{settings.FRONTEND_URL}/peripherals/{peripheral_id}"
    img = qrcode.make(url)
    path = f"/uploads/peripherals/qr_{peripheral_id}.png"
    img.save(path)
    return path

4. Détection Auto Linux

# /backend/app/services/detection_service.py
def parse_lsusb_output(output: str) -> List[Dict]:
    """Parse lsusb et suggère périphériques"""
    devices = []
    for line in output.split('\n'):
        # Parse: Bus 001 Device 003: ID 046d:c52b Logitech, Inc. Unifying Receiver
        if match := re.match(r'Bus (\d+) Device (\d+): ID ([0-9a-f]{4}):([0-9a-f]{4}) (.+)', line):
            devices.append({
                'vendor_id': match.group(3),
                'product_id': match.group(4),
                'description': match.group(5),
                'suggested_name': parse_device_name(match.group(5))
            })
    return devices

Considérations Importantes

Thème et UX

  • Cohérence visuelle: Utiliser le même thème Monokai que l'existant
  • Two-panel layout: Liste gauche (20%) + Détails/Formulaire droite (80%)
  • Icons: Icônes SVG par type de périphérique
  • Badges: Colorés selon état (Neuf=vert, Usagé=orange, Défectueux=rouge)
  • Responsive: Grid layout adaptatif

Performance

  • Pagination: 50 items par page
  • Lazy loading: Images chargées à la demande
  • Cache: Recherches fréquentes (types, marques)
  • Index DB: Sur tous les champs de recherche

Sécurité

  • Validation uploads: MIME type, taille max (10MB)
  • Sanitization: Noms fichiers (supprimer caractères spéciaux)
  • CORS: Restreindre origines
  • Rate limiting: 100 req/min par IP

Extensibilité

  • YAML dynamique: Nouveaux types sans modifier code
  • JSON flexible: caracteristiques_specifiques adapté à chaque type
  • Plugins: Architecture permettant ajout de modules (future)

Checklist d'Implémentation

Backend ✓

  • Deux bases de données (sessions séparées)
  • Modèles SQLAlchemy (BasePeripherals)
  • Schémas Pydantic (CRUD + relations)
  • Service layer (cross-DB queries)
  • Routes API (20+ endpoints)
  • Configuration YAML (types, locations, images, notifications)
  • Compression images (Pillow)
  • QR codes (qrcode lib)
  • CRON notifications (rappels prêts)
  • Init DB (création tables + seed data)

Frontend ✓

  • Pages HTML (peripherals, locations, loans)
  • Extension API client (BenchAPI.Peripherals.*)
  • Logique pages (peripherals.js, locations.js, loans.js)
  • Composants réutilisables (PhotoGallery, RatingStars, LocationSelector, TypeManager)
  • CSS cohérent (thème Monokai)
  • Navigation mise à jour (toutes les pages)

Fonctionnalités ✓

  • CRUD périphériques
  • Upload photos/documents
  • Gestion prêts (création, retour, rappels)
  • Localisation (arborescence, historique)
  • Appareils complets (lien benchmarks)
  • Recherche avancée
  • Stock management
  • Rating 0-5 étoiles
  • QR codes
  • Import/Export CSV/JSON

Tests & Documentation ✓

  • Tests unitaires backend
  • Tests intégration API
  • Documentation utilisateur
  • Documentation API (Swagger)
  • Exemples et fixtures

Commandes Utiles

# Créer les bases de données
cd /backend
python -c "from app.db.init_db import init_db; init_db()"

# Lancer backend
cd /backend
uvicorn app.main:app --reload --host 0.0.0.0 --port 8007

# Tests
pytest tests/

# Restart Docker
docker-compose restart backend

Points d'Attention

  1. Ne JAMAIS créer de FK SQL entre data.db et peripherals.db (gérer manuellement)
  2. Toujours utiliser deux sessions dans les routes avec cross-DB queries
  3. Valider YAML lors du chargement (schéma pydantic)
  4. Compresser images par défaut (économie espace)
  5. Logger toutes les actions (historique, audit)
  6. Tester avec charge (1000+ périphériques)

Résultat Attendu

Un module périphériques complet, professionnel et extensible qui:

Permet d'inventorier TOUT le matériel informatique Localise précisément chaque élément Gère les prêts avec rappels automatiques S'intègre aux benchmarks existants Offre une recherche rapide et puissante Est facile à étendre (types YAML) Respecte le design existant (Monokai, two-panel) Fonctionne en production (sécurisé, performant)

Bonne implémentation ! 🚀