# 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](#vue-densemble) 2. [Objectifs et Portée](#objectifs-et-portée) 3. [Types de Périphériques](#types-de-périphériques) 4. [Caractéristiques des Données](#caractéristiques-des-données) 5. [Architecture Base de Données](#architecture-base-de-données) 6. [Architecture Backend](#architecture-backend) 7. [Architecture Frontend](#architecture-frontend) 8. [Fonctionnalités Principales](#fonctionnalités-principales) 9. [Système de Localisation](#système-de-localisation) 10. [Système de Prêt](#système-de-prêt) 11. [Appareils Complets](#appareils-complets) 12. [Configuration YAML](#configuration-yaml) 13. [Plan de Déploiement](#plan-de-déploiement) 14. [Prompt de Développement](#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**: ```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 ```json { "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 ```json { "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 ```json { "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 ```json { "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 ```json { "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` ```sql 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` ```sql 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` ```sql 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); ``` ### Table `peripheral_links` ```sql 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` ```sql 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` ```sql 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` ```sql 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`) ```python 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`) ```python 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**: ```python # /backend/app/db/base.py Base = declarative_base() # DB principale BasePeripherals = declarative_base() # DB périphériques ``` **Modèle Peripheral**: ```python # /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 ```python # /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`) ```python 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`) ```python 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`) ```python 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`) ```javascript 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 ```javascript 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 = `