2820 lines
75 KiB
Markdown
Executable File
2820 lines
75 KiB
Markdown
Executable File
# 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 = `
|
||
<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
|
||
```javascript
|
||
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`)
|
||
|
||
```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`):
|
||
```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`)
|
||
|
||
```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`):
|
||
```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_id` → `devices.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:
|
||
|
||
```yaml
|
||
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
|
||
- [x] Mettre à jour `/backend/app/core/config.py`
|
||
- Ajouter `PERIPHERALS_DB_URL`
|
||
- Ajouter paramètres compression images
|
||
- Ajouter `PERIPHERALS_MODULE_ENABLED`
|
||
- [x] Créer `/backend/app/db/session.py` (deux sessions)
|
||
- `get_db()` → DB principale
|
||
- `get_peripherals_db()` → DB périphériques
|
||
- [x] Créer `BasePeripherals` dans `/backend/app/db/base.py`
|
||
|
||
#### 1.2 Modèles SQLAlchemy
|
||
- [x] Créer `/backend/app/models/peripheral.py`
|
||
- `Peripheral` (table principale)
|
||
- `PeripheralPhoto`
|
||
- `PeripheralDocument`
|
||
- `PeripheralLink`
|
||
- `PeripheralLoan`
|
||
- [x] Créer `/backend/app/models/location.py`
|
||
- `Location` (hiérarchie emplacements)
|
||
- [x] Créer `/backend/app/models/peripheral_history.py`
|
||
- `PeripheralLocationHistory`
|
||
|
||
#### 1.3 Schémas Pydantic
|
||
- [x] Créer `/backend/app/schemas/peripheral.py`
|
||
- `PeripheralBase`, `PeripheralCreate`, `PeripheralUpdate`
|
||
- `PeripheralDetail`, `PeripheralSummary`, `PeripheralListResponse`
|
||
- [x] Créer `/backend/app/schemas/location.py`
|
||
- [x] Créer `/backend/app/schemas/loan.py`
|
||
|
||
#### 1.4 Services
|
||
- [x] 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()`
|
||
- [x] Créer `/backend/app/services/location_service.py`
|
||
- `get_location_tree()` (hiérarchie)
|
||
- `get_full_location_path()`
|
||
- [x] Créer `/backend/app/services/loan_service.py`
|
||
- `check_and_send_reminders()` (CRON)
|
||
- `send_loan_reminder()`
|
||
|
||
#### 1.5 Routes API
|
||
- [x] 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)
|
||
- [x] Créer `/backend/app/api/locations.py`
|
||
- CRUD emplacements
|
||
- Arborescence
|
||
- QR codes
|
||
- [x] Créer `/backend/app/api/loans.py`
|
||
- CRUD prêts
|
||
- Retours
|
||
- Rappels
|
||
- Statistiques
|
||
|
||
#### 1.6 Configuration YAML
|
||
- [x] 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
|
||
- [x] Créer `/backend/config/locations.yaml`
|
||
- Structure initiale emplacements
|
||
- [x] Créer `/backend/config/image_processing.yaml`
|
||
- Paramètres compression
|
||
- [x] Créer `/backend/config/notifications.yaml`
|
||
- Templates emails
|
||
- Configuration SMTP
|
||
|
||
#### 1.7 Utilitaires
|
||
- [x] Créer `/backend/app/utils/yaml_loader.py`
|
||
- Charger/sauvegarder YAML
|
||
- Parser configurations
|
||
- [x] Créer `/backend/app/utils/image_processor.py`
|
||
- Compression images (Pillow)
|
||
- Génération thumbnails
|
||
- Validation formats
|
||
- [x] Créer `/backend/app/utils/qr_generator.py`
|
||
- Génération QR codes
|
||
- [x] Créer `/backend/app/tasks/daily_loan_reminders.py`
|
||
- Tâche CRON quotidienne
|
||
|
||
#### 1.8 Initialisation
|
||
- [x] Mettre à jour `/backend/app/db/init_db.py`
|
||
- Créer tables `peripherals.db`
|
||
- Charger YAML initial
|
||
- Créer emplacements par défaut
|
||
- [x] 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
|
||
- [x] Créer `/frontend/peripherals.html`
|
||
- Layout deux panneaux
|
||
- Liste + Détails/Formulaire
|
||
- Navigation onglets
|
||
- [x] Créer `/frontend/peripheral_detail.html` (optionnel)
|
||
- Vue complète périphérique
|
||
- Onglets: Infos, Photos, Docs, Liens, Historique, Benchmarks
|
||
- [x] Créer `/frontend/locations.html`
|
||
- Gestion emplacements
|
||
- Arborescence interactive
|
||
- [x] Créer `/frontend/loans.html`
|
||
- Liste prêts en cours
|
||
- Historique
|
||
- Statistiques
|
||
|
||
#### 2.2 JavaScript - API Client
|
||
- [x] Étendre `/frontend/js/api.js`
|
||
- `BenchAPI.Peripherals.*` (CRUD, photos, docs, liens, prêts)
|
||
- `BenchAPI.Locations.*`
|
||
- `BenchAPI.Loans.*`
|
||
|
||
#### 2.3 JavaScript - Logique Pages
|
||
- [x] Créer `/frontend/js/peripherals.js`
|
||
- Initialisation page
|
||
- Chargement liste
|
||
- Recherche/filtres temps réel
|
||
- Formulaire dynamique (selon type)
|
||
- Upload médias
|
||
- [x] Créer `/frontend/js/peripheral_detail.js`
|
||
- Gestion onglets
|
||
- Affichage benchmarks
|
||
- Galerie photos
|
||
- [x] Créer `/frontend/js/locations.js`
|
||
- TreeView emplacements
|
||
- CRUD emplacements
|
||
- QR codes
|
||
- [x] Créer `/frontend/js/loans.js`
|
||
- Formulaire prêt
|
||
- Formulaire retour
|
||
- Liste prêts
|
||
- Rappels
|
||
|
||
#### 2.4 Composants Réutilisables
|
||
- [x] Créer `/frontend/js/components/PhotoGallery.js`
|
||
- Grid photos
|
||
- Upload drag & drop
|
||
- Lightbox
|
||
- Compression option
|
||
- [x] Créer `/frontend/js/components/DocumentList.js`
|
||
- Liste documents avec icônes
|
||
- Upload
|
||
- Preview PDF
|
||
- [x] Créer `/frontend/js/components/PeripheralFilters.js`
|
||
- Filtres latéraux
|
||
- Recherche avancée
|
||
- Tags
|
||
- [x] Créer `/frontend/js/components/LocationSelector.js`
|
||
- TreeView sélection
|
||
- Breadcrumb
|
||
- [x] Créer `/frontend/js/components/RatingStars.js`
|
||
- Système étoiles clickable
|
||
- Affichage/édition
|
||
- [x] Créer `/frontend/js/components/TypeManager.js`
|
||
- Modal gestion types YAML
|
||
- CRUD types/catégories
|
||
- [x] Créer `/frontend/js/components/StockManager.js`
|
||
- Gestion quantités
|
||
- Alertes stock
|
||
- [x] Créer `/frontend/js/components/ImageUploader.js`
|
||
- Upload multiple
|
||
- Preview
|
||
- Crop/rotate basique
|
||
|
||
#### 2.5 CSS
|
||
- [x] É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
|
||
- [x] 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
|
||
- [x] Interface recherche multi-critères
|
||
- [x] Résultats temps réel
|
||
- [x] Filtres combinables
|
||
- [x] Tri multi-colonnes
|
||
|
||
#### 3.2 Import/Export
|
||
- [x] Export CSV/JSON
|
||
- [x] Import CSV en masse
|
||
- [x] Export PDF fiche périphérique
|
||
- [x] Impression étiquettes
|
||
|
||
#### 3.3 QR Codes
|
||
- [x] Génération QR par périphérique
|
||
- [x] Génération QR par emplacement
|
||
- [x] Page scan → fiche
|
||
- [x] Impression batch
|
||
|
||
#### 3.4 Statistiques
|
||
- [x] Dashboard périphériques
|
||
- Total par type (Pie chart)
|
||
- Valeur inventaire
|
||
- Top marques (Bar chart)
|
||
- État (Donut chart)
|
||
- [x] Dashboard prêts
|
||
- Prêts en cours
|
||
- Valeur prêtée
|
||
- Alertes retards
|
||
- [x] Dashboard emplacements
|
||
- Inventaire par emplacement
|
||
- Valeur par pièce
|
||
|
||
---
|
||
|
||
### PHASE 4: Intégration et Optimisation (Semaine 6)
|
||
|
||
#### 4.1 Intégration Devices
|
||
- [x] Dans `device_detail.html`, ajouter onglet "Périphériques connectés"
|
||
- [x] Bouton "Créer fiche périphérique" depuis device
|
||
- [x] Auto-link lors création
|
||
- [x] Sync specs après benchmark
|
||
|
||
#### 4.2 Détection Automatique
|
||
- [x] Parser `lsusb` output
|
||
- [x] Parser `lspci` output
|
||
- [x] Suggérer ajout auto nouveaux périphériques
|
||
- [x] Pré-remplir vendor/product ID
|
||
|
||
#### 4.3 Notifications
|
||
- [x] Setup CRON pour rappels prêts
|
||
- [x] Templates emails
|
||
- [x] Configuration SMTP
|
||
- [x] Alertes stock bas
|
||
- [x] Alertes garantie expirée
|
||
|
||
#### 4.4 Performance
|
||
- [x] Optimisation queries SQL (eager loading)
|
||
- [x] Index appropriés
|
||
- [x] Compression images
|
||
- [x] Cache recherches fréquentes
|
||
- [x] Pagination efficace
|
||
|
||
#### 4.5 Sécurité
|
||
- [x] Validation uploads (MIME, taille)
|
||
- [x] Sanitization noms fichiers
|
||
- [x] Protection CSRF
|
||
- [x] Rate limiting API
|
||
|
||
---
|
||
|
||
### PHASE 5: Documentation et Tests (Semaine 7)
|
||
|
||
#### 5.1 Documentation
|
||
- [x] `/docs/PERIPHERALS_USER_GUIDE.md`
|
||
- Guide utilisateur
|
||
- Exemples d'utilisation
|
||
- FAQ
|
||
- [x] `/docs/PERIPHERALS_API.md`
|
||
- Documentation API (Swagger)
|
||
- Exemples requêtes
|
||
- [x] `/docs/PERIPHERALS_ADMIN.md`
|
||
- Configuration YAML
|
||
- Gestion types
|
||
- Maintenance
|
||
|
||
#### 5.2 Exemples et Fixtures
|
||
- [x] Script seed données test
|
||
- [x] Templates JSON par type
|
||
- [x] Données d'exemple
|
||
|
||
#### 5.3 Tests
|
||
- [x] Tests unitaires backend (pytest)
|
||
- Models
|
||
- Services
|
||
- Utils
|
||
- [x] Tests intégration API
|
||
- Endpoints CRUD
|
||
- Cross-DB queries
|
||
- [x] 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:
|
||
|
||
```yaml
|
||
# /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
|
||
|
||
```python
|
||
# /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
|
||
|
||
```python
|
||
# /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)
|
||
|
||
```python
|
||
# /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)
|
||
|
||
```python
|
||
# /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
|
||
|
||
```javascript
|
||
// /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**:
|
||
```javascript
|
||
class PhotoGallery {
|
||
constructor(containerId, peripheralId) { /* ... */ }
|
||
async load() { /* Charger photos */ }
|
||
render() { /* Grid + lightbox */ }
|
||
async upload(files) { /* Upload avec compression */ }
|
||
}
|
||
```
|
||
|
||
**RatingStars**:
|
||
```javascript
|
||
class RatingStars {
|
||
constructor(containerId, currentRating = 0, editable = true) { /* ... */ }
|
||
render() { /* ★★★★☆ */ }
|
||
setRating(value) { /* Clickable */ }
|
||
}
|
||
```
|
||
|
||
**LocationSelector**:
|
||
```javascript
|
||
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
|
||
|
||
```python
|
||
# /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
|
||
|
||
```python
|
||
# /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
|
||
|
||
```python
|
||
# /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 ✓
|
||
- [x] Deux bases de données (sessions séparées)
|
||
- [x] Modèles SQLAlchemy (BasePeripherals)
|
||
- [x] Schémas Pydantic (CRUD + relations)
|
||
- [x] Service layer (cross-DB queries)
|
||
- [x] Routes API (20+ endpoints)
|
||
- [x] Configuration YAML (types, locations, images, notifications)
|
||
- [x] Compression images (Pillow)
|
||
- [x] QR codes (qrcode lib)
|
||
- [x] CRON notifications (rappels prêts)
|
||
- [x] Init DB (création tables + seed data)
|
||
|
||
#### Frontend ✓
|
||
- [x] Pages HTML (peripherals, locations, loans)
|
||
- [x] Extension API client (BenchAPI.Peripherals.*)
|
||
- [x] Logique pages (peripherals.js, locations.js, loans.js)
|
||
- [x] Composants réutilisables (PhotoGallery, RatingStars, LocationSelector, TypeManager)
|
||
- [x] CSS cohérent (thème Monokai)
|
||
- [x] Navigation mise à jour (toutes les pages)
|
||
|
||
#### Fonctionnalités ✓
|
||
- [x] CRUD périphériques
|
||
- [x] Upload photos/documents
|
||
- [x] Gestion prêts (création, retour, rappels)
|
||
- [x] Localisation (arborescence, historique)
|
||
- [x] Appareils complets (lien benchmarks)
|
||
- [x] Recherche avancée
|
||
- [x] Stock management
|
||
- [x] Rating 0-5 étoiles
|
||
- [x] QR codes
|
||
- [x] Import/Export CSV/JSON
|
||
|
||
#### Tests & Documentation ✓
|
||
- [x] Tests unitaires backend
|
||
- [x] Tests intégration API
|
||
- [x] Documentation utilisateur
|
||
- [x] Documentation API (Swagger)
|
||
- [x] Exemples et fixtures
|
||
|
||
### Commandes Utiles
|
||
|
||
```bash
|
||
# 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 !** 🚀
|