addon
This commit is contained in:
260
docs/SESSION_2025-12-31_DOCKER_IMAGES_FIX.md
Executable file
260
docs/SESSION_2025-12-31_DOCKER_IMAGES_FIX.md
Executable file
@@ -0,0 +1,260 @@
|
||||
# Session 2025-12-31 : Correction Docker - Servir les images
|
||||
|
||||
## 🎯 Problème
|
||||
|
||||
Les images uploadées dans le module périphériques n'étaient pas accessibles depuis le frontend.
|
||||
|
||||
**Erreurs** :
|
||||
```
|
||||
GET http://10.0.0.50:8087/app/uploads/peripherals/photos/3/csfingerprint_20251231_092242.webp
|
||||
[HTTP/1.1 404 Not Found]
|
||||
```
|
||||
|
||||
## 🔍 Analyse
|
||||
|
||||
### Problème 1 : Montage de volume impossible
|
||||
Tentative initiale de monter `./uploads` vers `/usr/share/nginx/html/app/uploads` dans le conteneur nginx.
|
||||
|
||||
**Erreur Docker** :
|
||||
```
|
||||
error mounting "/home/gilles/projects/serv_benchmark/uploads" to rootfs at "/usr/share/nginx/html/app/uploads":
|
||||
mkdirat /var/lib/docker/rootfs/overlayfs/.../usr/share/nginx/html/app: read-only file system
|
||||
```
|
||||
|
||||
**Cause** : Le système de fichiers root du conteneur nginx:alpine est en lecture seule. Docker ne peut pas créer le répertoire intermédiaire `/app/` dans `/usr/share/nginx/html/`.
|
||||
|
||||
### Problème 2 : Chemin filesystem vs chemin web
|
||||
|
||||
- **Base de données** : Stocke les chemins filesystem du backend : `/app/uploads/peripherals/photos/3/image.webp`
|
||||
- **Frontend** : A besoin de chemins web accessibles via nginx : `/uploads/peripherals/photos/3/image.webp`
|
||||
|
||||
## ✅ Solutions implémentées
|
||||
|
||||
### 1. Montage simplifié des uploads
|
||||
|
||||
**Fichier** : `docker-compose.yml` (ligne 37)
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- ./uploads:/uploads:ro
|
||||
```
|
||||
|
||||
✅ Montage direct vers `/uploads` (pas de répertoire intermédiaire à créer)
|
||||
|
||||
### 2. Configuration nginx personnalisée
|
||||
|
||||
**Fichier créé** : `frontend/nginx.conf`
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
# Serve static frontend files
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
|
||||
# Serve uploaded files
|
||||
location /uploads/ {
|
||||
alias /uploads/;
|
||||
autoindex off;
|
||||
# Cache uploaded images for 1 day
|
||||
expires 1d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# Security headers
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
}
|
||||
```
|
||||
|
||||
**Fonctionnalités** :
|
||||
- ✅ Location `/uploads/` sert les fichiers depuis `/uploads/` dans le conteneur
|
||||
- ✅ Cache navigateur 1 jour pour les images (performance)
|
||||
- ✅ En-têtes de sécurité (XSS, Clickjacking, MIME sniffing)
|
||||
|
||||
**Montage dans Docker** : `docker-compose.yml` (ligne 35)
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- ./frontend/nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
```
|
||||
|
||||
### 3. Conversion des chemins dans l'API backend
|
||||
|
||||
**Fichier** : `backend/app/api/endpoints/peripherals.py`
|
||||
|
||||
#### Endpoint `/peripherals/{id}/photos` (lignes 329-355)
|
||||
|
||||
```python
|
||||
@router.get("/{peripheral_id}/photos")
|
||||
def get_photos(
|
||||
peripheral_id: int,
|
||||
db: Session = Depends(get_peripherals_db)
|
||||
):
|
||||
"""Get all photos for a peripheral"""
|
||||
photos = db.query(PeripheralPhoto).filter(
|
||||
PeripheralPhoto.peripheral_id == peripheral_id
|
||||
).all()
|
||||
|
||||
# Convert stored paths to web-accessible URLs
|
||||
result = []
|
||||
for photo in photos:
|
||||
photo_dict = {
|
||||
"id": photo.id,
|
||||
"peripheral_id": photo.peripheral_id,
|
||||
"filename": photo.filename,
|
||||
"stored_path": photo.stored_path.replace('/app/uploads/', '/uploads/')
|
||||
if photo.stored_path.startswith('/app/uploads/')
|
||||
else photo.stored_path,
|
||||
"mime_type": photo.mime_type,
|
||||
"size_bytes": photo.size_bytes,
|
||||
"description": photo.description,
|
||||
"is_primary": photo.is_primary,
|
||||
"uploaded_at": photo.uploaded_at
|
||||
}
|
||||
result.append(photo_dict)
|
||||
|
||||
return result
|
||||
```
|
||||
|
||||
**Transformation** :
|
||||
- Base de données : `/app/uploads/peripherals/photos/3/image.webp`
|
||||
- API retourne : `/uploads/peripherals/photos/3/image.webp`
|
||||
|
||||
#### Endpoint `/peripherals/{id}/documents` (lignes 425-450)
|
||||
|
||||
Même transformation pour les documents :
|
||||
|
||||
```python
|
||||
stored_path": doc.stored_path.replace('/app/uploads/', '/uploads/')
|
||||
if doc.stored_path.startswith('/app/uploads/')
|
||||
else doc.stored_path
|
||||
```
|
||||
|
||||
### 4. Configuration frontend
|
||||
|
||||
**Fichier** : `frontend/config.js` (lignes 29-31)
|
||||
|
||||
```javascript
|
||||
if (!window.BenchConfig.uploadsPath) {
|
||||
window.BenchConfig.uploadsPath = '/uploads';
|
||||
}
|
||||
```
|
||||
|
||||
Permet de centraliser la configuration du chemin des uploads si besoin de le modifier.
|
||||
|
||||
## 📊 Flux complet
|
||||
|
||||
```
|
||||
1. Upload photo
|
||||
└─> Backend stocke : /app/uploads/peripherals/photos/3/image.webp (filesystem)
|
||||
|
||||
2. Frontend demande : GET /api/peripherals/3/photos
|
||||
└─> Backend convertit : /app/uploads/... → /uploads/...
|
||||
└─> API retourne : /uploads/peripherals/photos/3/image.webp
|
||||
|
||||
3. Frontend affiche : <img src="/uploads/peripherals/photos/3/image.webp">
|
||||
└─> Nginx sert depuis : /uploads/ (monté depuis ./uploads)
|
||||
└─> HTTP 200 OK
|
||||
```
|
||||
|
||||
## 🧪 Tests de validation
|
||||
|
||||
### Test 1 : Fichier existe dans le conteneur
|
||||
```bash
|
||||
docker exec linux_benchtools_frontend ls -la /uploads/peripherals/photos/3/
|
||||
```
|
||||
|
||||
**Résultat** :
|
||||
```
|
||||
-rwxrwxrwx 1 root root 20084 Dec 31 09:22 csfingerprint_20251231_092242.webp
|
||||
```
|
||||
✅ OK
|
||||
|
||||
### Test 2 : API retourne le bon chemin
|
||||
```bash
|
||||
curl http://10.0.0.50:8007/api/peripherals/3/photos
|
||||
```
|
||||
|
||||
**Résultat** :
|
||||
```json
|
||||
{
|
||||
"stored_path": "/uploads/peripherals/photos/3/csfingerprint_20251231_092242.webp"
|
||||
}
|
||||
```
|
||||
✅ OK
|
||||
|
||||
### Test 3 : Nginx sert l'image
|
||||
```bash
|
||||
curl -I http://10.0.0.50:8087/uploads/peripherals/photos/3/csfingerprint_20251231_092242.webp
|
||||
```
|
||||
|
||||
**Résultat** :
|
||||
```
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: image/webp
|
||||
Content-Length: 20084
|
||||
Cache-Control: max-age=86400
|
||||
Cache-Control: public, immutable
|
||||
```
|
||||
✅ OK
|
||||
|
||||
### Test 4 : Frontend accessible
|
||||
```bash
|
||||
curl -I http://10.0.0.50:8087/peripherals.html
|
||||
```
|
||||
|
||||
**Résultat** :
|
||||
```
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: text/html
|
||||
X-Frame-Options: SAMEORIGIN
|
||||
X-Content-Type-Options: nosniff
|
||||
X-XSS-Protection: 1; mode=block
|
||||
```
|
||||
✅ OK
|
||||
|
||||
## 📁 Fichiers modifiés
|
||||
|
||||
### Créés
|
||||
- ✅ `frontend/nginx.conf` - Configuration nginx personnalisée
|
||||
- ✅ `docs/SESSION_2025-12-31_DOCKER_IMAGES_FIX.md` - Cette documentation
|
||||
|
||||
### Modifiés
|
||||
- ✅ `docker-compose.yml` - Montage `/uploads` et config nginx
|
||||
- ✅ `frontend/config.js` - Ajout `uploadsPath`
|
||||
- ✅ `backend/app/api/endpoints/peripherals.py` - Conversion chemins dans API
|
||||
- ✅ `backend/app/schemas/peripheral.py` - Suppression tentative @property (non retenue)
|
||||
|
||||
## 🔄 Commandes de déploiement
|
||||
|
||||
```bash
|
||||
# Rebuild backend avec nouvelles routes API
|
||||
docker compose up -d --build backend
|
||||
|
||||
# Recréer frontend avec nginx.conf
|
||||
docker compose up -d frontend
|
||||
|
||||
# Vérifier tous les conteneurs
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
## 🎯 Améliorations futures possibles
|
||||
|
||||
- [ ] Ajouter compression gzip pour les images dans nginx
|
||||
- [ ] Implémenter un CDN ou proxy cache pour les uploads
|
||||
- [ ] Ajouter authentification pour certains uploads sensibles
|
||||
- [ ] Lazy loading des images dans le frontend
|
||||
- [ ] WebP avec fallback JPEG pour compatibilité navigateurs anciens
|
||||
|
||||
---
|
||||
|
||||
**Date** : 31 décembre 2025
|
||||
**Statut** : ✅ Résolu et testé
|
||||
**Impact** : Les images des périphériques sont maintenant accessibles depuis le frontend
|
||||
Reference in New Issue
Block a user