11 KiB
Executable File
Fonctionnalité : Miniatures dans la liste des périphériques
🎯 Objectif
Afficher les miniatures (thumbnails) de 48px dans la liste des périphériques au lieu de l'icône générique.
Comportement :
- Si le périphérique a une photo principale → Afficher la miniature
- Si pas de photo → Afficher l'icône
<i class="fas fa-microchip"></i> - Si l'image ne charge pas (erreur) → Fallback vers l'icône
✅ Implémentation
1. Backend - Schéma API
Fichier : backend/app/schemas/peripheral.py (ligne 150)
Modification : Ajout du champ thumbnail_url au schéma PeripheralSummary
class PeripheralSummary(BaseModel):
"""Summary schema for peripheral lists"""
id: int
nom: str
type_principal: str
sous_type: Optional[str]
marque: Optional[str]
modele: Optional[str]
etat: str
rating: float
prix: Optional[float]
en_pret: bool
is_complete_device: bool
quantite_disponible: int
thumbnail_url: Optional[str] = None # ← Nouveau champ
class Config:
from_attributes = True
Résultat : L'API retourne maintenant l'URL du thumbnail pour chaque périphérique dans la liste.
2. Backend - Service
Fichier : backend/app/services/peripheral_service.py (lignes 179-210)
Modification : Récupération de la photo principale et construction de l'URL
# Import PeripheralPhoto here to avoid circular import
from app.models.peripheral import PeripheralPhoto
# Convert to summary
items = []
for p in peripherals:
# Get primary photo thumbnail
thumbnail_url = None
primary_photo = db.query(PeripheralPhoto).filter(
PeripheralPhoto.peripheral_id == p.id,
PeripheralPhoto.is_primary == True
).first()
if primary_photo and primary_photo.thumbnail_path:
# Convert file path to URL
thumbnail_url = primary_photo.thumbnail_path.replace('/app/uploads/', '/uploads/')
items.append(PeripheralSummary(
id=p.id,
nom=p.nom,
type_principal=p.type_principal,
sous_type=p.sous_type,
marque=p.marque,
modele=p.modele,
etat=p.etat or "Inconnu",
rating=p.rating or 0.0,
prix=p.prix,
en_pret=p.en_pret or False,
is_complete_device=p.is_complete_device or False,
quantite_disponible=p.quantite_disponible or 0,
thumbnail_url=thumbnail_url # ← Ajout du thumbnail
))
Logique :
- Pour chaque périphérique, requête SQL pour trouver la photo avec
is_primary = True - Si une photo principale existe et a un
thumbnail_path:- Convertir le chemin serveur (
/app/uploads/...) en URL web (/uploads/...)
- Convertir le chemin serveur (
- Sinon :
thumbnail_url = None
Exemple de résultat API :
{
"items": [
{
"id": 6,
"nom": "USB Receiver",
"thumbnail_url": "/uploads/peripherals/photos/6/thumbnail/logitechreceiver_thumb_20251231_101254.png"
},
{
"id": 5,
"nom": "Flash Card Reader/Writer",
"thumbnail_url": null
}
],
"total": 46,
"page": 1,
"page_size": 10,
"total_pages": 5
}
3. Frontend - JavaScript
Fichier : frontend/js/peripherals.js (lignes 325-329)
Modification : Affichage conditionnel de l'image ou de l'icône
<div class="peripheral-photo">
${p.thumbnail_url
? `<img src="${escapeHtml(p.thumbnail_url)}" alt="${escapeHtml(p.nom)}" onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
<i class="fas fa-microchip" style="display:none;"></i>`
: `<i class="fas fa-microchip"></i>`
}
</div>
Logique :
-
Si
thumbnail_urlexiste :- Afficher
<img>avec l'URL du thumbnail - Ajouter un
onerrorhandler : si l'image ne charge pas, cacher l'image et afficher l'icône - Icône en
display:nonepar défaut (visible seulement en cas d'erreur)
- Afficher
-
Si pas de
thumbnail_url:- Afficher directement l'icône
<i class="fas fa-microchip"></i>
- Afficher directement l'icône
Cas gérés :
- ✅ Périphérique avec photo → Image affichée
- ✅ Périphérique sans photo → Icône affichée
- ✅ Image qui ne charge pas (404, erreur réseau) → Fallback vers icône
4. Frontend - CSS
Fichier : frontend/css/peripherals.css (lignes 156-176)
Modification : Style pour conteneur et images
.peripheral-photo {
width: 50px;
height: 50px;
background: #232323;
border: 1px solid #3e3d32;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
color: #66d9ef;
font-size: 1.5rem;
overflow: hidden; /* ← Ajouté */
}
.peripheral-photo img {
max-width: 100%;
max-height: 100%;
width: auto;
height: auto;
object-fit: contain; /* ← Conserve ratio */
}
Caractéristiques :
- Conteneur : 50×50px, fond sombre, bordure, centré
- Image :
max-width/max-height: 100%→ Ne dépasse jamais le conteneurwidth/height: auto→ Préserve le ratio d'aspectobject-fit: contain→ L'image entière est visible sans déformation- Centré grâce au
display: flexdu parent
Rendu visuel :
┌─────────────────────────────────────────────────────┐
│ Nom │ Type │ Photo │
├─────────────────────────────────────────────────────┤
│ USB Receiver │ USB │ [🖼️ Thumbnail] │
│ Flash Card Reader │ Stockage│ [💾 Icône] │
│ ConBee II │ USB │ [🖼️ Thumbnail] │
│ CS9711Fingprint │ USB │ [🖼️ Thumbnail] │
│ TL-WN823N │ Réseau │ [💾 Icône] │
└─────────────────────────────────────────────────────┘
📊 Exemples
Exemple 1 : Périphérique avec photo
API Response :
{
"id": 6,
"nom": "USB Receiver",
"thumbnail_url": "/uploads/peripherals/photos/6/thumbnail/logitechreceiver_thumb_20251231_101254.png"
}
HTML généré :
<div class="peripheral-photo">
<img src="/uploads/peripherals/photos/6/thumbnail/logitechreceiver_thumb_20251231_101254.png"
alt="USB Receiver"
onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
<i class="fas fa-microchip" style="display:none;"></i>
</div>
Rendu : Image du thumbnail (48px de large, ratio conservé)
Exemple 2 : Périphérique sans photo
API Response :
{
"id": 5,
"nom": "Flash Card Reader/Writer",
"thumbnail_url": null
}
HTML généré :
<div class="peripheral-photo">
<i class="fas fa-microchip"></i>
</div>
Rendu : Icône générique de puce électronique
Exemple 3 : Image qui ne charge pas (erreur)
Scénario : Le fichier thumbnail est supprimé mais l'URL existe en base
API Response :
{
"id": 7,
"nom": "Peripheral Test",
"thumbnail_url": "/uploads/peripherals/photos/7/thumbnail/deleted.png"
}
HTML généré :
<div class="peripheral-photo">
<img src="/uploads/peripherals/photos/7/thumbnail/deleted.png"
alt="Peripheral Test"
onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
<i class="fas fa-microchip" style="display:none;"></i>
</div>
Comportement :
- Navigateur tente de charger l'image
- Image introuvable → Event
onerrordéclenché - JavaScript cache l'image (
display:none) - JavaScript affiche l'icône (
display:flex)
Rendu final : Icône générique (fallback automatique)
🔄 Flux complet
1. User charge page /peripherals.html
↓
2. JavaScript appelle GET /api/peripherals/?page=1&page_size=10
↓
3. Backend service list_peripherals()
│ ├─> Query périphériques (avec pagination)
│ └─> Pour chaque périphérique:
│ ├─> Query photo principale (is_primary=True)
│ └─> Si photo existe: thumbnail_url = "/uploads/..."
↓
4. API retourne JSON avec items[].thumbnail_url
↓
5. JavaScript génère HTML du tableau
│ ├─> Si thumbnail_url → <img>
│ └─> Sinon → <i class="fas fa-microchip">
↓
6. Navigateur affiche la liste
│ ├─> Charge les images (si URLs présentes)
│ └─> Affiche icônes (si pas d'URL ou erreur)
📝 Fichiers modifiés
Backend
| Fichier | Lignes | Modification |
|---|---|---|
backend/app/schemas/peripheral.py |
150 | Ajout thumbnail_url: Optional[str] = None |
backend/app/services/peripheral_service.py |
179-210 | Query photo principale + construction URL |
Frontend
| Fichier | Lignes | Modification |
|---|---|---|
frontend/js/peripherals.js |
325-329 | Affichage conditionnel image/icône |
frontend/css/peripherals.css |
170-176 | Style pour img dans .peripheral-photo |
🧪 Tests
Test 1 : API retourne thumbnail_url
curl -s "http://localhost:8007/api/peripherals/?page=1&page_size=2" | \
python3 -c "import sys, json; data=json.load(sys.stdin); print(data['items'][0].get('thumbnail_url'))"
Résultat attendu :
/uploads/peripherals/photos/6/thumbnail/logitechreceiver_thumb_20251231_101254.png
Test 2 : Plusieurs périphériques avec/sans photos
curl -s "http://localhost:8007/api/peripherals/?page=1&page_size=10" | \
python3 -c "
import sys, json
data = json.load(sys.stdin)
for item in data['items'][:5]:
thumb = item.get('thumbnail_url', 'None')
print(f'{item[\"nom\"][:30]:30} → {thumb}')
"
Résultat attendu :
USB Receiver → /uploads/peripherals/photos/6/thumbnail/logitechreceiver_thumb_20251231_101254.png
Flash Card Reader/Writer → None
ConBee II → /uploads/peripherals/photos/4/thumbnail/conbee2_thumb_20251231_101147.png
CS9711Fingprint → /uploads/peripherals/photos/3/thumbnail/csfingerprint_thumb_20251231_101537.png
TL-WN823N v2/v3 → None
Test 3 : Affichage visuel
- Ouvrir
http://10.0.0.50:8087/peripherals.html - Vérifier la colonne "Photo" :
- ✅ Les périphériques avec photos affichent la miniature
- ✅ Les périphériques sans photos affichent l'icône puce
- ✅ Les images sont bien dimensionnées (max 50×50px)
💡 Améliorations futures
- Cache des requêtes thumbnail (éviter N+1 queries)
- Lazy loading des images (
loading="lazy") - Preview au survol (hover) de la miniature
- Optimisation : JOIN au lieu de requête séparée par périphérique
- Placeholder animé pendant chargement de l'image
Date : 31 décembre 2025 Statut : ✅ Implémenté et testé Impact : Affichage des miniatures dans la liste des périphériques avec fallback automatique vers icône