rinogeek commited on
Commit
e9d86db
·
0 Parent(s):

first commit

Browse files
.env.example ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Fichier d'exemple des variables d'environnement
2
+ # Créé par Marino ATOHOUN - FireWatch AI Project
3
+ # Copiez ce fichier vers .env et modifiez les valeurs selon vos besoins
4
+
5
+ # Configuration Django
6
+ DEBUG=True
7
+ SECRET_KEY=votre-cle-secrete-django-ici-changez-en-production
8
+ ALLOWED_HOSTS=localhost,127.0.0.1,0.0.0.0
9
+
10
+ # Base de données
11
+ # Pour SQLite (développement)
12
+ DATABASE_URL=sqlite:///db.sqlite3
13
+
14
+ # Pour PostgreSQL (production)
15
+ # DATABASE_URL=postgresql://user:password@localhost:5432/firewatch_db
16
+
17
+ # Chemins des modèles YOLOv8
18
+ FIRE_MODEL_PATH=models/incendies.pt
19
+ INTRUSION_MODEL_PATH=models/intrusion.pt
20
+
21
+ # Configuration des fichiers media
22
+ MEDIA_ROOT=media
23
+ STATIC_ROOT=staticfiles
24
+
25
+ # Limites de fichiers (en bytes)
26
+ FILE_UPLOAD_MAX_MEMORY_SIZE=52428800 # 50MB
27
+ DATA_UPLOAD_MAX_MEMORY_SIZE=52428800 # 50MB
28
+
29
+ # Configuration email (pour les notifications)
30
+ EMAIL_BACKEND=django.core.mail.backends.console.EmailBackend
31
+ EMAIL_HOST=smtp.gmail.com
32
+ EMAIL_PORT=587
33
+ EMAIL_USE_TLS=True
34
+ EMAIL_HOST_USER=votre-email@gmail.com
35
+ EMAIL_HOST_PASSWORD=votre-mot-de-passe-app
36
+
37
+ # Configuration Redis (pour le cache et Celery)
38
+ REDIS_URL=redis://localhost:6379/0
39
+
40
+ # Configuration AWS S3 (pour le stockage en production)
41
+ AWS_ACCESS_KEY_ID=votre-access-key
42
+ AWS_SECRET_ACCESS_KEY=votre-secret-key
43
+ AWS_STORAGE_BUCKET_NAME=firewatch-media
44
+ AWS_S3_REGION_NAME=eu-west-1
45
+
46
+ # Configuration de sécurité
47
+ SECURE_SSL_REDIRECT=False
48
+ SECURE_HSTS_SECONDS=0
49
+ SECURE_HSTS_INCLUDE_SUBDOMAINS=False
50
+ SECURE_HSTS_PRELOAD=False
51
+
52
+ # Logging
53
+ LOG_LEVEL=INFO
54
+ LOG_FILE=logs/firewatch.log
55
+
56
+ # Configuration Celery (pour les tâches asynchrones)
57
+ CELERY_BROKER_URL=redis://localhost:6379/0
58
+ CELERY_RESULT_BACKEND=redis://localhost:6379/0
59
+
60
+ # Configuration de monitoring
61
+ SENTRY_DSN=votre-sentry-dsn-ici
62
+
63
+ # Configuration spécifique à FireWatch AI
64
+ # Seuil de confiance minimum pour les détections
65
+ MIN_CONFIDENCE_THRESHOLD=0.5
66
+
67
+ # Nombre maximum de détections par image/vidéo
68
+ MAX_DETECTIONS_PER_ANALYSIS=100
69
+
70
+ # Durée de conservation des fichiers uploadés (en jours)
71
+ FILE_RETENTION_DAYS=30
72
+
73
+ # Configuration de l'analyse vidéo
74
+ VIDEO_FRAME_SKIP=30 # Analyser une frame toutes les 30 frames
75
+ MAX_VIDEO_DURATION=300 # Durée maximale de vidéo en secondes (5 minutes)
76
+
77
+ # Configuration de la caméra en temps réel
78
+ CAMERA_ANALYSIS_INTERVAL=5 # Intervalle d'analyse en secondes
79
+ CAMERA_RESOLUTION_WIDTH=1280
80
+ CAMERA_RESOLUTION_HEIGHT=720
81
+
.gitignore ADDED
@@ -0,0 +1,249 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Gitignore pour FireWatch AI - Créé par Marino ATOHOUN
2
+
3
+ # Byte-compiled / optimized / DLL files
4
+ __pycache__/
5
+ *.py[cod]
6
+ *$py.class
7
+
8
+ # C extensions
9
+ *.so
10
+
11
+ # Distribution / packaging
12
+ .Python
13
+ build/
14
+ develop-eggs/
15
+ dist/
16
+ downloads/
17
+ eggs/
18
+ .eggs/
19
+ lib/
20
+ lib64/
21
+ parts/
22
+ sdist/
23
+ var/
24
+ wheels/
25
+ pip-wheel-metadata/
26
+ share/python-wheels/
27
+ *.egg-info/
28
+ .installed.cfg
29
+ *.egg
30
+ MANIFEST
31
+
32
+ # PyInstaller
33
+ *.manifest
34
+ *.spec
35
+
36
+ # Installer logs
37
+ pip-log.txt
38
+ pip-delete-this-directory.txt
39
+
40
+ # Unit test / coverage reports
41
+ htmlcov/
42
+ .tox/
43
+ .nox/
44
+ .coverage
45
+ .coverage.*
46
+ .cache
47
+ nosetests.xml
48
+ coverage.xml
49
+ *.cover
50
+ *.py,cover
51
+ .hypothesis/
52
+ .pytest_cache/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ target/
76
+
77
+ # Jupyter Notebook
78
+ .ipynb_checkpoints
79
+
80
+ # IPython
81
+ profile_default/
82
+ ipython_config.py
83
+
84
+ # pyenv
85
+ .python-version
86
+
87
+ # pipenv
88
+ Pipfile.lock
89
+
90
+ # PEP 582
91
+ __pypackages__/
92
+
93
+ # Celery stuff
94
+ celerybeat-schedule
95
+ celerybeat.pid
96
+
97
+ # SageMath parsed files
98
+ *.sage.py
99
+
100
+ # Environments
101
+ .env
102
+ .venv
103
+ env/
104
+ venv/
105
+ ENV/
106
+ env.bak/
107
+ venv.bak/
108
+
109
+ # Spyder project settings
110
+ .spyderproject
111
+ .spyproject
112
+
113
+ # Rope project settings
114
+ .ropeproject
115
+
116
+ # mkdocs documentation
117
+ /site
118
+
119
+ # mypy
120
+ .mypy_cache/
121
+ .dmypy.json
122
+ dmypy.json
123
+
124
+ # Pyre type checker
125
+ .pyre/
126
+
127
+ # Django specific
128
+ media/uploads/
129
+ media/results/
130
+ staticfiles/
131
+ static/admin/
132
+ static/rest_framework/
133
+
134
+ # Logs
135
+ logs/
136
+ *.log
137
+
138
+ # IDE
139
+ .vscode/
140
+ .idea/
141
+ *.swp
142
+ *.swo
143
+ *~
144
+
145
+ # OS
146
+ .DS_Store
147
+ .DS_Store?
148
+ ._*
149
+ .Spotlight-V100
150
+ .Trashes
151
+ ehthumbs.db
152
+ Thumbs.db
153
+
154
+ # Modèles YOLOv8 (trop volumineux pour git)
155
+ models/*.pt
156
+ models/*.onnx
157
+ models/*.engine
158
+
159
+ # Fichiers temporaires
160
+ tmp/
161
+ temp/
162
+ *.tmp
163
+
164
+ # Sauvegardes
165
+ *.bak
166
+ *.backup
167
+
168
+ # Docker
169
+ .dockerignore
170
+
171
+ # Certificats SSL
172
+ ssl/
173
+ *.pem
174
+ *.crt
175
+ *.key
176
+
177
+ # Variables d'environnement sensibles
178
+ .env.local
179
+ .env.production
180
+ .env.staging
181
+
182
+ # Fichiers de configuration spécifiques
183
+ local_settings.py
184
+ production_settings.py
185
+
186
+ # Cache
187
+ .cache/
188
+ *.cache
189
+
190
+ # Fichiers de données
191
+ data/
192
+ datasets/
193
+
194
+ # Notebooks Jupyter
195
+ *.ipynb
196
+
197
+ # Fichiers de test volumineux
198
+ test_data/
199
+ test_images/
200
+ test_videos/
201
+
202
+ # Rapports de performance
203
+ profiling/
204
+ benchmarks/
205
+
206
+ # Documentation générée
207
+ docs/build/
208
+ docs/_build/
209
+
210
+ # Fichiers de déploiement sensibles
211
+ deploy_keys/
212
+ secrets/
213
+
214
+ # Monitoring
215
+ monitoring/
216
+ metrics/
217
+
218
+ # Sauvegardes de base de données
219
+ *.sql
220
+ *.dump
221
+
222
+ # Fichiers de migration personnalisés (garder les migrations par défaut)
223
+ # detection/migrations/0*_custom_*.py
224
+
225
+ # Fichiers de configuration Nginx/Apache
226
+ nginx.conf.local
227
+ apache.conf.local
228
+
229
+ # Fichiers de session
230
+ django_session/
231
+
232
+ # Fichiers de cache Redis
233
+ dump.rdb
234
+
235
+ # Fichiers de configuration Docker personnalisés
236
+ docker-compose.override.yml
237
+ docker-compose.local.yml
238
+
239
+ # Scripts personnalisés
240
+ scripts/custom_*.sh
241
+ scripts/local_*.sh
242
+
243
+ # Fichiers de test personnalisés
244
+ tests/custom/
245
+ tests/local/
246
+
247
+ # Signature Marino ATOHOUN
248
+ # Projet FireWatch AI - Système de détection d'incendie et d'intrusion
249
+
CHANGELOG.md ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Changelog - FireWatch AI
2
+
3
+ **Créé par Marino ATOHOUN**
4
+
5
+ Toutes les modifications notables de ce projet seront documentées dans ce fichier.
6
+
7
+ Le format est basé sur [Keep a Changelog](https://keepachangelog.com/fr/1.0.0/),
8
+ et ce projet adhère au [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
9
+
10
+ ## [1.0.0] - 2024-12-26
11
+
12
+ ### 🎉 Version initiale - Créée par Marino ATOHOUN
13
+
14
+ #### Ajouté
15
+ - **Système de détection IA complet**
16
+ - Intégration YOLOv8 pour la détection d'incendie et d'intrusion
17
+ - Support des modèles personnalisés (`incendies.pt`, `intrusion.pt`)
18
+ - Analyse en temps réel d'images, vidéos et flux caméra
19
+
20
+ - **Interface web moderne**
21
+ - Frontend responsive avec Tailwind CSS
22
+ - Interface utilisateur intuitive et accessible
23
+ - Support du drag & drop pour les fichiers
24
+ - Prévisualisation en temps réel des uploads
25
+
26
+ - **Backend Django robuste**
27
+ - Architecture MVC bien structurée
28
+ - API REST complète pour l'intégration
29
+ - Gestion sécurisée des uploads de fichiers
30
+ - Système de sessions de détection
31
+
32
+ - **Base de données et modèles**
33
+ - Modèles Django pour contacts, sessions et détections
34
+ - Support SQLite (développement) et PostgreSQL (production)
35
+ - Interface d'administration Django complète
36
+ - Historique complet des analyses
37
+
38
+ - **Fonctionnalités de détection**
39
+ - Analyse d'images avec boîtes englobantes
40
+ - Traitement vidéo frame par frame
41
+ - Capture et analyse depuis webcam
42
+ - Scores de confiance et métadonnées
43
+
44
+ - **Système de contact**
45
+ - Formulaire de contact intégré
46
+ - Stockage en base de données
47
+ - Interface d'administration pour la gestion
48
+
49
+ - **Configuration et déploiement**
50
+ - Support Docker et Docker Compose
51
+ - Scripts d'installation automatisés
52
+ - Configuration pour production et développement
53
+ - Documentation complète
54
+
55
+ - **Sécurité**
56
+ - Protection CSRF activée
57
+ - Validation des types de fichiers
58
+ - Limitation de taille des uploads
59
+ - Gestion sécurisée des sessions
60
+
61
+ - **Performance et monitoring**
62
+ - Cache Redis intégré
63
+ - Logs structurés
64
+ - Métriques de performance
65
+ - Support GPU pour l'IA
66
+
67
+ #### Fonctionnalités techniques
68
+ - **Framework**: Django 4.2+
69
+ - **IA/ML**: YOLOv8, OpenCV, PyTorch
70
+ - **Frontend**: HTML5, CSS3, JavaScript, Tailwind CSS
71
+ - **Base de données**: SQLite/PostgreSQL
72
+ - **Cache**: Redis
73
+ - **Serveur**: Gunicorn + Nginx
74
+ - **Conteneurisation**: Docker
75
+
76
+ #### Structure du projet
77
+ ```
78
+ firewatch_project/
79
+ ├── firewatch_project/ # Configuration Django
80
+ ├── detection/ # Application principale
81
+ ├── models/ # Modèles YOLOv8
82
+ ├── media/ # Fichiers uploadés
83
+ ├── static/ # Fichiers statiques
84
+ ├── scripts/ # Scripts utilitaires
85
+ ├── requirements.txt # Dépendances
86
+ ├── Dockerfile # Configuration Docker
87
+ ├── docker-compose.yml # Orchestration
88
+ └── README.md # Documentation
89
+ ```
90
+
91
+ #### API Endpoints
92
+ - `GET /` - Page principale
93
+ - `POST /contact/` - Formulaire de contact
94
+ - `POST /analyze/image/` - Analyse d'image
95
+ - `POST /analyze/video/` - Analyse de vidéo
96
+ - `GET /api/results/<session_id>/` - Récupération des résultats
97
+ - `GET /api/models/status/` - Statut des modèles IA
98
+
99
+ #### Configuration
100
+ - Variables d'environnement via `.env`
101
+ - Support multi-environnement (dev/prod)
102
+ - Configuration des seuils de détection
103
+ - Paramètres de performance ajustables
104
+
105
+ #### Tests
106
+ - Tests unitaires complets
107
+ - Tests d'intégration
108
+ - Tests de sécurité
109
+ - Tests de performance
110
+ - Couverture de code
111
+
112
+ #### Documentation
113
+ - README.md complet
114
+ - Guide d'installation détaillé
115
+ - Documentation API
116
+ - Exemples d'utilisation
117
+ - Guide de déploiement
118
+
119
+ ### 🔧 Configuration requise
120
+ - Python 3.11+
121
+ - Django 4.2+
122
+ - YOLOv8 (Ultralytics)
123
+ - OpenCV
124
+ - PostgreSQL (production)
125
+ - Redis (cache)
126
+ - Docker (optionnel)
127
+
128
+ ### 🚀 Installation rapide
129
+ ```bash
130
+ # Cloner le projet
131
+ git clone <repo-url>
132
+ cd firewatch_project
133
+
134
+ # Configuration automatique
135
+ chmod +x scripts/setup.sh
136
+ ./scripts/setup.sh
137
+
138
+ # Lancer le serveur
139
+ source venv/bin/activate
140
+ python manage.py runserver
141
+ ```
142
+
143
+ ### 📝 Notes de version
144
+ Cette version initiale fournit une base solide pour un système de détection d'incendie et d'intrusion. Le code est conçu pour être facilement extensible et maintenir une haute performance.
145
+
146
+ **Points forts:**
147
+ - Architecture modulaire et scalable
148
+ - Interface utilisateur moderne et responsive
149
+ - Intégration IA prête pour la production
150
+ - Documentation complète
151
+ - Tests exhaustifs
152
+ - Support multi-plateforme
153
+
154
+ **Prochaines améliorations prévues:**
155
+ - Notifications en temps réel (WebSockets)
156
+ - Tableau de bord analytique
157
+ - API mobile
158
+ - Intégration cloud (AWS, Azure)
159
+ - Modèles IA pré-entraînés
160
+ - Support multi-langues
161
+
162
+ ---
163
+
164
+ ## Format des versions futures
165
+
166
+ ### [X.Y.Z] - YYYY-MM-DD
167
+
168
+ #### Ajouté
169
+ - Nouvelles fonctionnalités
170
+
171
+ #### Modifié
172
+ - Changements dans les fonctionnalités existantes
173
+
174
+ #### Déprécié
175
+ - Fonctionnalités bientôt supprimées
176
+
177
+ #### Supprimé
178
+ - Fonctionnalités supprimées
179
+
180
+ #### Corrigé
181
+ - Corrections de bugs
182
+
183
+ #### Sécurité
184
+ - Corrections de vulnérabilités
185
+
186
+ ---
187
+
188
+ **FireWatch AI** - Créé avec passion par Marino ATOHOUN
189
+ *Solution intelligente de détection d'incendie et d'intrusion*
190
+
Dockerfile ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dockerfile pour FireWatch AI
2
+ # Créé par Marino ATOHOUN
3
+
4
+ FROM python:3.11-slim
5
+
6
+ # Métadonnées
7
+ LABEL maintainer="Marino ATOHOUN"
8
+ LABEL description="FireWatch AI - Système de détection d'incendie et d'intrusion"
9
+ LABEL version="1.0"
10
+
11
+ # Variables d'environnement
12
+ ENV PYTHONDONTWRITEBYTECODE=1
13
+ ENV PYTHONUNBUFFERED=1
14
+ ENV DEBIAN_FRONTEND=noninteractive
15
+
16
+ # Répertoire de travail
17
+ WORKDIR /app
18
+
19
+ # Installation des dépendances système
20
+ RUN apt-get update && apt-get install -y \
21
+ build-essential \
22
+ libpq-dev \
23
+ libgl1-mesa-glx \
24
+ libglib2.0-0 \
25
+ libsm6 \
26
+ libxext6 \
27
+ libxrender-dev \
28
+ libgomp1 \
29
+ libgthread-2.0-0 \
30
+ libgtk-3-0 \
31
+ python3-opencv \
32
+ ffmpeg \
33
+ wget \
34
+ curl \
35
+ && rm -rf /var/lib/apt/lists/*
36
+
37
+ # Copier les requirements et installer les dépendances Python
38
+ COPY requirements.txt .
39
+ RUN pip install --no-cache-dir --upgrade pip
40
+ RUN pip install --no-cache-dir -r requirements.txt
41
+
42
+ # Copier le code de l'application
43
+ COPY . .
44
+
45
+ # Créer les répertoires nécessaires
46
+ RUN mkdir -p media/uploads/images media/uploads/videos media/results models logs staticfiles
47
+
48
+ # Collecter les fichiers statiques
49
+ RUN python manage.py collectstatic --noinput
50
+
51
+ # Créer un utilisateur non-root pour la sécurité
52
+ RUN adduser --disabled-password --gecos '' appuser
53
+ RUN chown -R appuser:appuser /app
54
+ USER appuser
55
+
56
+ # Exposer le port
57
+ EXPOSE 8000
58
+
59
+ # Commande de santé
60
+ HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
61
+ CMD curl -f http://localhost:8000/ || exit 1
62
+
63
+ # Script de démarrage
64
+ COPY docker-entrypoint.sh /app/
65
+ RUN chmod +x /app/docker-entrypoint.sh
66
+
67
+ # Commande par défaut
68
+ ENTRYPOINT ["/app/docker-entrypoint.sh"]
69
+ CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "4", "--timeout", "120", "firewatch_project.wsgi:application"]
70
+
INSTALLATION.md ADDED
@@ -0,0 +1,419 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 📖 Guide d'Installation - FireWatch AI
2
+
3
+ **Créé par Marino ATOHOUN**
4
+
5
+ Ce guide vous accompagne pas à pas dans l'installation et la configuration de FireWatch AI.
6
+
7
+ ## 🎯 Prérequis Système
8
+
9
+ ### Minimum requis
10
+ - **OS** : Ubuntu 20.04+, Windows 10+, macOS 10.15+
11
+ - **Python** : 3.11 ou supérieur
12
+ - **RAM** : 8 GB minimum (16 GB recommandé)
13
+ - **Stockage** : 10 GB d'espace libre
14
+ - **GPU** : Optionnel mais recommandé (NVIDIA avec CUDA)
15
+
16
+ ### Logiciels requis
17
+ - Git
18
+ - Python 3.11+
19
+ - pip (gestionnaire de packages Python)
20
+ - Node.js 16+ (pour les outils de développement frontend)
21
+
22
+ ## 🔧 Installation Étape par Étape
23
+
24
+ ### Étape 1 : Préparation de l'environnement
25
+
26
+ #### Sur Ubuntu/Debian
27
+ ```bash
28
+ # Mise à jour du système
29
+ sudo apt update && sudo apt upgrade -y
30
+
31
+ # Installation des dépendances système
32
+ sudo apt install -y python3.11 python3.11-venv python3-pip git curl
33
+ sudo apt install -y libgl1-mesa-glx libglib2.0-0 libsm6 libxext6 libxrender-dev
34
+ sudo apt install -y libgomp1 libgthread-2.0-0 libgtk-3-0 python3-opencv ffmpeg
35
+
36
+ # Installation de PostgreSQL (optionnel, pour la production)
37
+ sudo apt install -y postgresql postgresql-contrib
38
+
39
+ # Installation de Redis (optionnel, pour le cache)
40
+ sudo apt install -y redis-server
41
+ ```
42
+
43
+ #### Sur Windows
44
+ ```powershell
45
+ # Installer Python depuis python.org ou via Microsoft Store
46
+ # Installer Git depuis git-scm.com
47
+ # Installer Visual Studio Build Tools pour les packages natifs
48
+
49
+ # Via Chocolatey (optionnel)
50
+ choco install python git nodejs postgresql redis-64
51
+ ```
52
+
53
+ #### Sur macOS
54
+ ```bash
55
+ # Via Homebrew
56
+ brew install python@3.11 git node postgresql redis opencv ffmpeg
57
+
58
+ # Ou via MacPorts
59
+ sudo port install python311 git nodejs postgresql redis opencv4
60
+ ```
61
+
62
+ ### Étape 2 : Clonage et Configuration du Projet
63
+
64
+ ```bash
65
+ # Cloner le projet
66
+ git clone https://github.com/votre-username/firewatch-ai.git
67
+ cd firewatch-ai
68
+
69
+ # Créer un environnement virtuel
70
+ python3.11 -m venv venv
71
+
72
+ # Activer l'environnement virtuel
73
+ # Sur Linux/macOS :
74
+ source venv/bin/activate
75
+ # Sur Windows :
76
+ venv\Scripts\activate
77
+
78
+ # Vérifier la version de Python
79
+ python --version # Doit afficher Python 3.11.x
80
+ ```
81
+
82
+ ### Étape 3 : Installation des Dépendances Python
83
+
84
+ ```bash
85
+ # Mettre à jour pip
86
+ pip install --upgrade pip
87
+
88
+ # Installer les dépendances de base
89
+ pip install -r requirements.txt
90
+
91
+ # Pour le développement (optionnel)
92
+ pip install -r requirements-dev.txt
93
+ ```
94
+
95
+ **Note** : L'installation peut prendre 10-15 minutes selon votre connexion internet.
96
+
97
+ ### Étape 4 : Configuration des Variables d'Environnement
98
+
99
+ ```bash
100
+ # Copier le fichier d'exemple
101
+ cp .env.example .env
102
+
103
+ # Éditer le fichier .env
104
+ nano .env # ou votre éditeur préféré
105
+ ```
106
+
107
+ **Configuration minimale pour commencer :**
108
+ ```bash
109
+ DEBUG=True
110
+ SECRET_KEY=votre-cle-secrete-django-changez-moi
111
+ ALLOWED_HOSTS=localhost,127.0.0.1,0.0.0.0
112
+ DATABASE_URL=sqlite:///db.sqlite3
113
+ ```
114
+
115
+ ### Étape 5 : Préparation des Modèles YOLOv8
116
+
117
+ ```bash
118
+ # Créer le répertoire des modèles
119
+ mkdir -p models
120
+
121
+ # Copier vos modèles YOLOv8 (vous devez les avoir entraînés)
122
+ cp /chemin/vers/vos/modeles/incendies.pt models/
123
+ cp /chemin/vers/vos/modeles/intrusion.pt models/
124
+
125
+ # Vérifier que les modèles sont présents
126
+ ls -la models/
127
+ # Doit afficher : incendies.pt et intrusion.pt
128
+ ```
129
+
130
+ **Si vous n'avez pas encore de modèles :**
131
+ ```bash
132
+ # L'application fonctionnera en mode simulation
133
+ # Vous pourrez tester l'interface et intégrer vos modèles plus tard
134
+ echo "Les modèles seront chargés quand vous les placerez dans models/"
135
+ ```
136
+
137
+ ### Étape 6 : Configuration de la Base de Données
138
+
139
+ #### Option A : SQLite (Développement)
140
+ ```bash
141
+ # Créer et appliquer les migrations
142
+ python manage.py makemigrations
143
+ python manage.py migrate
144
+
145
+ # Créer un superutilisateur
146
+ python manage.py createsuperuser
147
+ # Suivre les instructions à l'écran
148
+ ```
149
+
150
+ #### Option B : PostgreSQL (Production)
151
+ ```bash
152
+ # Se connecter à PostgreSQL
153
+ sudo -u postgres psql
154
+
155
+ # Créer la base de données et l'utilisateur
156
+ CREATE DATABASE firewatch_db;
157
+ CREATE USER firewatch WITH PASSWORD 'votre_mot_de_passe';
158
+ GRANT ALL PRIVILEGES ON DATABASE firewatch_db TO firewatch;
159
+ \q
160
+
161
+ # Modifier .env
162
+ DATABASE_URL=postgresql://firewatch:votre_mot_de_passe@localhost:5432/firewatch_db
163
+
164
+ # Appliquer les migrations
165
+ python manage.py migrate
166
+ python manage.py createsuperuser
167
+ ```
168
+
169
+ ### Étape 7 : Collecte des Fichiers Statiques
170
+
171
+ ```bash
172
+ # Collecter les fichiers statiques
173
+ python manage.py collectstatic --noinput
174
+
175
+ # Créer les répertoires media
176
+ mkdir -p media/uploads/images media/uploads/videos media/results
177
+ ```
178
+
179
+ ### Étape 8 : Test de l'Installation
180
+
181
+ ```bash
182
+ # Lancer le serveur de développement
183
+ python manage.py runserver 0.0.0.0:8000
184
+
185
+ # Dans un autre terminal, tester l'API
186
+ curl http://localhost:8000/
187
+ ```
188
+
189
+ **Ouvrir votre navigateur** : http://localhost:8000
190
+
191
+ Vous devriez voir la page d'accueil de FireWatch AI.
192
+
193
+ ## 🐳 Installation avec Docker (Recommandée)
194
+
195
+ ### Prérequis Docker
196
+ ```bash
197
+ # Installer Docker
198
+ curl -fsSL https://get.docker.com -o get-docker.sh
199
+ sudo sh get-docker.sh
200
+
201
+ # Installer Docker Compose
202
+ sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
203
+ sudo chmod +x /usr/local/bin/docker-compose
204
+
205
+ # Ajouter votre utilisateur au groupe docker
206
+ sudo usermod -aG docker $USER
207
+ # Redémarrer votre session
208
+ ```
209
+
210
+ ### Installation Docker
211
+ ```bash
212
+ # Cloner le projet
213
+ git clone https://github.com/votre-username/firewatch-ai.git
214
+ cd firewatch-ai
215
+
216
+ # Placer vos modèles
217
+ mkdir models
218
+ cp /chemin/vers/incendies.pt models/
219
+ cp /chemin/vers/intrusion.pt models/
220
+
221
+ # Lancer avec Docker Compose
222
+ docker-compose up -d
223
+
224
+ # Vérifier que tout fonctionne
225
+ docker-compose ps
226
+ docker-compose logs web
227
+ ```
228
+
229
+ **Accéder à l'application** : http://localhost:8000
230
+
231
+ ## ⚙️ Configuration Avancée
232
+
233
+ ### Configuration GPU (NVIDIA)
234
+
235
+ ```bash
236
+ # Installer NVIDIA Docker
237
+ distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
238
+ curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
239
+ curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list
240
+
241
+ sudo apt-get update && sudo apt-get install -y nvidia-docker2
242
+ sudo systemctl restart docker
243
+
244
+ # Modifier docker-compose.yml pour utiliser le GPU
245
+ # Ajouter sous le service web :
246
+ # deploy:
247
+ # resources:
248
+ # reservations:
249
+ # devices:
250
+ # - driver: nvidia
251
+ # count: 1
252
+ # capabilities: [gpu]
253
+ ```
254
+
255
+ ### Configuration Redis (Cache)
256
+
257
+ ```bash
258
+ # Installer Redis
259
+ sudo apt install redis-server
260
+
261
+ # Configurer Redis
262
+ sudo nano /etc/redis/redis.conf
263
+ # Modifier : bind 127.0.0.1
264
+ # Modifier : maxmemory 256mb
265
+ # Modifier : maxmemory-policy allkeys-lru
266
+
267
+ # Redémarrer Redis
268
+ sudo systemctl restart redis-server
269
+
270
+ # Tester Redis
271
+ redis-cli ping # Doit retourner PONG
272
+ ```
273
+
274
+ ### Configuration Nginx (Production)
275
+
276
+ ```bash
277
+ # Installer Nginx
278
+ sudo apt install nginx
279
+
280
+ # Créer la configuration
281
+ sudo nano /etc/nginx/sites-available/firewatch
282
+
283
+ # Contenu de la configuration :
284
+ server {
285
+ listen 80;
286
+ server_name votre-domaine.com;
287
+
288
+ client_max_body_size 50M;
289
+
290
+ location /static/ {
291
+ alias /path/to/firewatch/staticfiles/;
292
+ }
293
+
294
+ location /media/ {
295
+ alias /path/to/firewatch/media/;
296
+ }
297
+
298
+ location / {
299
+ proxy_pass http://127.0.0.1:8000;
300
+ proxy_set_header Host $host;
301
+ proxy_set_header X-Real-IP $remote_addr;
302
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
303
+ proxy_set_header X-Forwarded-Proto $scheme;
304
+ }
305
+ }
306
+
307
+ # Activer le site
308
+ sudo ln -s /etc/nginx/sites-available/firewatch /etc/nginx/sites-enabled/
309
+ sudo nginx -t
310
+ sudo systemctl restart nginx
311
+ ```
312
+
313
+ ## 🔍 Vérification de l'Installation
314
+
315
+ ### Tests de Base
316
+ ```bash
317
+ # Activer l'environnement virtuel
318
+ source venv/bin/activate
319
+
320
+ # Lancer les tests
321
+ python manage.py test
322
+
323
+ # Vérifier les modèles
324
+ python manage.py shell
325
+ >>> from detection.models import AIModelStatus
326
+ >>> AIModelStatus.objects.all()
327
+ ```
328
+
329
+ ### Tests Fonctionnels
330
+ 1. **Interface Web** : http://localhost:8000
331
+ 2. **Administration** : http://localhost:8000/admin/
332
+ 3. **API Status** : http://localhost:8000/api/models/status/
333
+ 4. **Upload d'image** : Tester via l'interface web
334
+
335
+ ### Logs et Débogage
336
+ ```bash
337
+ # Voir les logs Django
338
+ tail -f logs/firewatch.log
339
+
340
+ # Logs Docker
341
+ docker-compose logs -f web
342
+
343
+ # Déboguer les erreurs
344
+ python manage.py check
345
+ python manage.py check --deploy
346
+ ```
347
+
348
+ ## 🚨 Résolution des Problèmes Courants
349
+
350
+ ### Erreur : "No module named 'cv2'"
351
+ ```bash
352
+ pip install opencv-python
353
+ # Ou sur Ubuntu :
354
+ sudo apt install python3-opencv
355
+ ```
356
+
357
+ ### Erreur : "CUDA out of memory"
358
+ ```bash
359
+ # Réduire la taille des batches dans settings.py
360
+ # Ou utiliser CPU uniquement :
361
+ export CUDA_VISIBLE_DEVICES=""
362
+ ```
363
+
364
+ ### Erreur : "Permission denied" sur media/
365
+ ```bash
366
+ sudo chown -R $USER:$USER media/
367
+ chmod -R 755 media/
368
+ ```
369
+
370
+ ### Erreur de base de données
371
+ ```bash
372
+ # Réinitialiser la base de données
373
+ rm db.sqlite3
374
+ python manage.py migrate
375
+ python manage.py createsuperuser
376
+ ```
377
+
378
+ ### Port 8000 déjà utilisé
379
+ ```bash
380
+ # Utiliser un autre port
381
+ python manage.py runserver 0.0.0.0:8080
382
+
383
+ # Ou tuer le processus
384
+ sudo lsof -t -i tcp:8000 | xargs kill -9
385
+ ```
386
+
387
+ ## 📋 Checklist Post-Installation
388
+
389
+ - [ ] ✅ Python 3.11+ installé
390
+ - [ ] ✅ Environnement virtuel créé et activé
391
+ - [ ] ✅ Dépendances installées sans erreur
392
+ - [ ] ✅ Variables d'environnement configurées
393
+ - [ ] ✅ Base de données migrée
394
+ - [ ] ✅ Superutilisateur créé
395
+ - [ ] ✅ Modèles YOLOv8 placés (ou mode simulation)
396
+ - [ ] ✅ Serveur démarre sans erreur
397
+ - [ ] ✅ Interface web accessible
398
+ - [ ] ✅ Upload d'image fonctionne
399
+ - [ ] ✅ Administration accessible
400
+
401
+ ## 🎉 Félicitations !
402
+
403
+ Votre installation de FireWatch AI est maintenant terminée !
404
+
405
+ ### Prochaines étapes :
406
+ 1. **Tester l'application** avec vos propres images/vidéos
407
+ 2. **Intégrer vos modèles YOLOv8** personnalisés
408
+ 3. **Configurer la production** si nécessaire
409
+ 4. **Personnaliser l'interface** selon vos besoins
410
+
411
+ ### Ressources utiles :
412
+ - 📖 [Documentation complète](README.md)
413
+ - 🐛 [Signaler un bug](lien-vers-issues)
414
+ - 💬 [Support communautaire](lien-vers-forum)
415
+
416
+ ---
417
+
418
+ **Besoin d'aide ?** Contactez Marino ATOHOUN, créateur de FireWatch AI.
419
+
LICENSE ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Marino ATOHOUN - FireWatch AI Project
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ FireWatch AI - Système de Détection d'Incendie et d'Intrusion
26
+ Créé par Marino ATOHOUN
27
+
28
+ Ce projet utilise des technologies open source et des modèles d'intelligence
29
+ artificielle pour la détection d'incendie et d'intrusion en temps réel.
30
+
31
+ Pour plus d'informations, consultez le fichier README.md
32
+
README.md ADDED
@@ -0,0 +1,322 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🔥 FireWatch AI - Système de Détection d'Incendie et d'Intrusion
2
+
3
+ **Créé par Marino ATOHOUN**
4
+
5
+ FireWatch AI est une solution intelligente de détection d'incendie et d'intrusion utilisant des modèles YOLOv8 pour analyser des images, vidéos et flux de caméra en temps réel.
6
+
7
+ ## 🚀 Fonctionnalités
8
+
9
+ - **Détection d'incendie** : Identification précise des flammes et de la fumée
10
+ - **Détection d'intrusion** : Reconnaissance de personnes et mouvements suspects
11
+ - **Analyse multi-format** : Support des images, vidéos et flux caméra
12
+ - **Interface web moderne** : Interface utilisateur intuitive et responsive
13
+ - **API REST** : Intégration facile avec d'autres systèmes
14
+ - **Temps réel** : Analyse instantanée avec alertes
15
+ - **Administration** : Interface d'administration Django complète
16
+
17
+ ## 🛠️ Technologies Utilisées
18
+
19
+ - **Backend** : Django 4.2+, Python 3.11+
20
+ - **IA/ML** : YOLOv8 (Ultralytics), OpenCV, PyTorch
21
+ - **Frontend** : HTML5, CSS3, JavaScript, Tailwind CSS
22
+ - **Base de données** : SQLite (dev), PostgreSQL (prod)
23
+ - **Cache** : Redis
24
+ - **Déploiement** : Docker, Docker Compose
25
+ - **Serveur web** : Gunicorn, Nginx
26
+
27
+ ## 📋 Prérequis
28
+
29
+ - Python 3.11+
30
+ - pip
31
+ - Git
32
+ - Docker et Docker Compose (pour le déploiement)
33
+ - Modèles YOLOv8 entraînés (`incendies.pt`, `intrusion.pt`)
34
+
35
+ ## 🔧 Installation
36
+
37
+ ### Installation locale
38
+
39
+ 1. **Cloner le projet**
40
+ ```bash
41
+ git clone <url-du-repo>
42
+ cd firewatch_project
43
+ ```
44
+
45
+ 2. **Créer un environnement virtuel**
46
+ ```bash
47
+ python -m venv venv
48
+ source venv/bin/activate # Linux/Mac
49
+ # ou
50
+ venv\Scripts\activate # Windows
51
+ ```
52
+
53
+ 3. **Installer les dépendances**
54
+ ```bash
55
+ pip install -r requirements.txt
56
+ ```
57
+
58
+ 4. **Configurer les variables d'environnement**
59
+ ```bash
60
+ cp .env.example .env
61
+ # Éditer .env avec vos configurations
62
+ ```
63
+
64
+ 5. **Placer les modèles YOLOv8**
65
+ ```bash
66
+ # Copier vos modèles dans le répertoire models/
67
+ cp /chemin/vers/incendies.pt models/
68
+ cp /chemin/vers/intrusion.pt models/
69
+ ```
70
+
71
+ 6. **Effectuer les migrations**
72
+ ```bash
73
+ python manage.py migrate
74
+ ```
75
+
76
+ 7. **Créer un superutilisateur**
77
+ ```bash
78
+ python manage.py createsuperuser
79
+ ```
80
+
81
+ 8. **Lancer le serveur de développement**
82
+ ```bash
83
+ python manage.py runserver
84
+ ```
85
+
86
+ L'application sera accessible à l'adresse : http://127.0.0.1:8000
87
+
88
+ ### Installation avec Docker
89
+
90
+ 1. **Cloner le projet**
91
+ ```bash
92
+ git clone <url-du-repo>
93
+ cd firewatch_project
94
+ ```
95
+
96
+ 2. **Placer les modèles YOLOv8**
97
+ ```bash
98
+ mkdir models
99
+ cp /chemin/vers/incendies.pt models/
100
+ cp /chemin/vers/intrusion.pt models/
101
+ ```
102
+
103
+ 3. **Lancer avec Docker Compose**
104
+ ```bash
105
+ # Production
106
+ docker-compose up -d
107
+
108
+ # Développement
109
+ docker-compose -f docker-compose.yml -f docker-compose.dev.yml up
110
+ ```
111
+
112
+ L'application sera accessible à l'adresse : http://localhost:8000
113
+
114
+ ## 📁 Structure du Projet
115
+
116
+ ```
117
+ firewatch_project/
118
+ ├── firewatch_project/ # Configuration Django
119
+ │ ├── settings.py
120
+ │ ├── urls.py
121
+ │ └── wsgi.py
122
+ ├── detection/ # Application principale
123
+ │ ├── models.py # Modèles de données
124
+ │ ├── views.py # Logique métier
125
+ │ ├── urls.py # Routes
126
+ │ ├── admin.py # Interface d'administration
127
+ │ └── templates/ # Templates HTML
128
+ ├── models/ # Modèles YOLOv8
129
+ │ ├── incendies.pt # Modèle de détection d'incendie
130
+ │ └── intrusion.pt # Modèle de détection d'intrusion
131
+ ├── media/ # Fichiers uploadés
132
+ │ ├── uploads/ # Images/vidéos uploadées
133
+ │ └── results/ # Résultats avec détections
134
+ ├── static/ # Fichiers statiques
135
+ │ ├── css/
136
+ │ ├── js/
137
+ │ └── images/
138
+ ├── requirements.txt # Dépendances Python
139
+ ├── Dockerfile # Configuration Docker
140
+ ├── docker-compose.yml # Orchestration Docker
141
+ └── README.md # Documentation
142
+ ```
143
+
144
+ ## 🎯 Utilisation
145
+
146
+ ### Interface Web
147
+
148
+ 1. **Accéder à l'application** : http://localhost:8000
149
+ 2. **Choisir le type d'analyse** :
150
+ - **Image** : Upload d'une image pour analyse
151
+ - **Vidéo** : Upload d'une vidéo pour analyse frame par frame
152
+ - **Caméra** : Analyse en temps réel depuis la webcam
153
+ 3. **Visualiser les résultats** : Détections avec boîtes englobantes et scores de confiance
154
+ 4. **Télécharger les résultats** : Images annotées et données JSON
155
+
156
+ ### API REST
157
+
158
+ #### Analyser une image
159
+ ```bash
160
+ curl -X POST http://localhost:8000/analyze/image/ \
161
+ -F "image=@/chemin/vers/image.jpg" \
162
+ -H "X-CSRFToken: <token>"
163
+ ```
164
+
165
+ #### Analyser une vidéo
166
+ ```bash
167
+ curl -X POST http://localhost:8000/analyze/video/ \
168
+ -F "video=@/chemin/vers/video.mp4" \
169
+ -H "X-CSRFToken: <token>"
170
+ ```
171
+
172
+ #### Obtenir les résultats
173
+ ```bash
174
+ curl http://localhost:8000/api/results/<session_id>/
175
+ ```
176
+
177
+ ### Administration
178
+
179
+ Accéder à l'interface d'administration : http://localhost:8000/admin/
180
+
181
+ - Gérer les contacts
182
+ - Visualiser les sessions de détection
183
+ - Consulter les statistiques
184
+ - Configurer les modèles IA
185
+
186
+ ## 🔧 Configuration
187
+
188
+ ### Variables d'environnement importantes
189
+
190
+ ```bash
191
+ # Modèles IA
192
+ FIRE_MODEL_PATH=models/incendies.pt
193
+ INTRUSION_MODEL_PATH=models/intrusion.pt
194
+
195
+ # Seuils de détection
196
+ MIN_CONFIDENCE_THRESHOLD=0.5
197
+ MAX_DETECTIONS_PER_ANALYSIS=100
198
+
199
+ # Limites de fichiers
200
+ FILE_UPLOAD_MAX_MEMORY_SIZE=52428800 # 50MB
201
+ MAX_VIDEO_DURATION=300 # 5 minutes
202
+
203
+ # Base de données
204
+ DATABASE_URL=postgresql://user:pass@localhost:5432/firewatch_db
205
+ ```
206
+
207
+ ### Personnalisation des modèles
208
+
209
+ Pour utiliser vos propres modèles YOLOv8 :
210
+
211
+ 1. **Placer les fichiers `.pt`** dans le répertoire `models/`
212
+ 2. **Modifier les chemins** dans `.env` ou `settings.py`
213
+ 3. **Adapter les classes** dans `detection/views.py` si nécessaire
214
+ 4. **Redémarrer l'application**
215
+
216
+ ## 🧪 Tests
217
+
218
+ ```bash
219
+ # Installer les dépendances de test
220
+ pip install -r requirements-dev.txt
221
+
222
+ # Lancer les tests
223
+ python manage.py test
224
+
225
+ # Avec coverage
226
+ coverage run --source='.' manage.py test
227
+ coverage report
228
+ ```
229
+
230
+ ## 📊 Monitoring et Logs
231
+
232
+ ### Logs de l'application
233
+ ```bash
234
+ # Voir les logs en temps réel
235
+ tail -f logs/firewatch.log
236
+
237
+ # Logs Docker
238
+ docker-compose logs -f web
239
+ ```
240
+
241
+ ### Métriques
242
+ - Temps de traitement des analyses
243
+ - Nombre de détections par session
244
+ - Utilisation des ressources
245
+ - Erreurs et exceptions
246
+
247
+ ## 🚀 Déploiement en Production
248
+
249
+ ### Avec Docker (Recommandé)
250
+
251
+ 1. **Configurer les variables d'environnement**
252
+ ```bash
253
+ export SECRET_KEY="votre-cle-secrete-production"
254
+ export DEBUG=False
255
+ export ALLOWED_HOSTS="votre-domaine.com"
256
+ ```
257
+
258
+ 2. **Lancer en production**
259
+ ```bash
260
+ docker-compose up -d
261
+ ```
262
+
263
+ 3. **Configurer Nginx** (optionnel)
264
+ - Modifier `nginx.conf` selon vos besoins
265
+ - Ajouter les certificats SSL dans `ssl/`
266
+
267
+ ### Déploiement manuel
268
+
269
+ 1. **Serveur web** : Gunicorn + Nginx
270
+ 2. **Base de données** : PostgreSQL
271
+ 3. **Cache** : Redis
272
+ 4. **Stockage** : AWS S3 ou stockage local
273
+ 5. **Monitoring** : Sentry, Prometheus
274
+
275
+ ## 🔒 Sécurité
276
+
277
+ - **CSRF Protection** : Activé par défaut
278
+ - **Rate Limiting** : Limitation des requêtes par IP
279
+ - **File Validation** : Vérification des types et tailles de fichiers
280
+ - **SQL Injection** : Protection via l'ORM Django
281
+ - **XSS Protection** : Échappement automatique des templates
282
+
283
+ ## 🤝 Contribution
284
+
285
+ 1. Fork le projet
286
+ 2. Créer une branche feature (`git checkout -b feature/nouvelle-fonctionnalite`)
287
+ 3. Commit les changements (`git commit -am 'Ajouter nouvelle fonctionnalité'`)
288
+ 4. Push vers la branche (`git push origin feature/nouvelle-fonctionnalite`)
289
+ 5. Créer une Pull Request
290
+
291
+ ## 📝 Changelog
292
+
293
+ ### Version 1.0.0 (2024)
294
+ - ✅ Détection d'incendie et d'intrusion
295
+ - ✅ Interface web responsive
296
+ - ✅ API REST complète
297
+ - ✅ Support multi-format (image/vidéo/caméra)
298
+ - ✅ Administration Django
299
+ - ✅ Déploiement Docker
300
+
301
+ ## 📄 Licence
302
+
303
+ Ce projet est sous licence MIT. Voir le fichier `LICENSE` pour plus de détails.
304
+
305
+ ## 👨‍💻 Auteur
306
+
307
+ **Marino ATOHOUN**
308
+ - Data Scientist & Développeur Backend
309
+ - Expert en Intelligence Artificielle et Computer Vision
310
+
311
+ ## 📞 Support
312
+
313
+ Pour toute question ou support :
314
+ - 📧 Email : contact@firewatch.ai
315
+ - 🐛 Issues : [GitHub Issues](lien-vers-issues)
316
+ - 📖 Documentation : [Wiki du projet](lien-vers-wiki)
317
+
318
+ ---
319
+
320
+ **FireWatch AI** - Solution intelligente de détection d'incendie et d'intrusion
321
+ *Créé avec ❤️ par Marino ATOHOUN*
322
+
detection/__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # Detection App - Créé par Marino ATOHOUN - FireWatch AI Project
2
+
detection/admin.py ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Configuration de l'interface d'administration Django
3
+ Créé par Marino ATOHOUN - FireWatch AI Project
4
+ """
5
+ from django.contrib import admin
6
+ from django.utils.html import format_html
7
+ from .models import Contact, DetectionSession, Detection, AIModelStatus
8
+
9
+
10
+ @admin.register(Contact)
11
+ class ContactAdmin(admin.ModelAdmin):
12
+ """Administration des contacts - Par Marino ATOHOUN"""
13
+ list_display = ('name', 'email', 'created_at', 'is_read', 'message_preview')
14
+ list_filter = ('is_read', 'created_at')
15
+ search_fields = ('name', 'email', 'message')
16
+ readonly_fields = ('created_at',)
17
+ list_editable = ('is_read',)
18
+ ordering = ('-created_at',)
19
+
20
+ def message_preview(self, obj):
21
+ """Aperçu du message"""
22
+ return obj.message[:50] + "..." if len(obj.message) > 50 else obj.message
23
+ message_preview.short_description = "Aperçu du message"
24
+
25
+ fieldsets = (
26
+ ('Informations de contact', {
27
+ 'fields': ('name', 'email')
28
+ }),
29
+ ('Message', {
30
+ 'fields': ('message',)
31
+ }),
32
+ ('Statut', {
33
+ 'fields': ('is_read', 'created_at')
34
+ }),
35
+ )
36
+
37
+
38
+ class DetectionInline(admin.TabularInline):
39
+ """Inline pour afficher les détections dans une session"""
40
+ model = Detection
41
+ extra = 0
42
+ readonly_fields = ('class_name', 'confidence', 'bbox_x', 'bbox_y', 'bbox_width', 'bbox_height')
43
+
44
+
45
+ @admin.register(DetectionSession)
46
+ class DetectionSessionAdmin(admin.ModelAdmin):
47
+ """Administration des sessions de détection - Par Marino ATOHOUN"""
48
+ list_display = ('session_id', 'detection_type', 'created_at', 'is_processed', 'processing_time', 'detections_count')
49
+ list_filter = ('detection_type', 'is_processed', 'created_at')
50
+ search_fields = ('session_id',)
51
+ readonly_fields = ('session_id', 'created_at', 'processing_time')
52
+ inlines = [DetectionInline]
53
+ ordering = ('-created_at',)
54
+
55
+ def detections_count(self, obj):
56
+ """Nombre de détections dans la session"""
57
+ return obj.detections.count()
58
+ detections_count.short_description = "Nombre de détections"
59
+
60
+ fieldsets = (
61
+ ('Informations de session', {
62
+ 'fields': ('session_id', 'detection_type', 'created_at')
63
+ }),
64
+ ('Fichiers', {
65
+ 'fields': ('original_file', 'result_file')
66
+ }),
67
+ ('Traitement', {
68
+ 'fields': ('is_processed', 'processing_time')
69
+ }),
70
+ )
71
+
72
+
73
+ @admin.register(Detection)
74
+ class DetectionAdmin(admin.ModelAdmin):
75
+ """Administration des détections - Par Marino ATOHOUN"""
76
+ list_display = ('session', 'class_name', 'confidence_percent', 'frame_number', 'bbox_preview')
77
+ list_filter = ('class_name', 'session__detection_type', 'session__created_at')
78
+ search_fields = ('session__session_id', 'class_name')
79
+ readonly_fields = ('session', 'class_name', 'confidence', 'bbox_x', 'bbox_y', 'bbox_width', 'bbox_height')
80
+ ordering = ('-session__created_at', 'frame_number')
81
+
82
+ def confidence_percent(self, obj):
83
+ """Affichage de la confiance en pourcentage"""
84
+ return f"{obj.confidence:.1%}"
85
+ confidence_percent.short_description = "Confiance"
86
+
87
+ def bbox_preview(self, obj):
88
+ """Aperçu de la bounding box"""
89
+ return f"({obj.bbox_x:.0f}, {obj.bbox_y:.0f}) {obj.bbox_width:.0f}x{obj.bbox_height:.0f}"
90
+ bbox_preview.short_description = "Boîte englobante"
91
+
92
+
93
+ @admin.register(AIModelStatus)
94
+ class AIModelStatusAdmin(admin.ModelAdmin):
95
+ """Administration du statut des modèles IA - Par Marino ATOHOUN"""
96
+ list_display = ('model_type', 'is_loaded_display', 'model_version', 'accuracy_percent', 'last_loaded')
97
+ list_filter = ('model_type', 'is_loaded')
98
+ readonly_fields = ('created_at', 'updated_at', 'last_loaded')
99
+ ordering = ('model_type',)
100
+
101
+ def is_loaded_display(self, obj):
102
+ """Affichage coloré du statut de chargement"""
103
+ if obj.is_loaded:
104
+ return format_html('<span style="color: green;">✓ Chargé</span>')
105
+ else:
106
+ return format_html('<span style="color: red;">✗ Non chargé</span>')
107
+ is_loaded_display.short_description = "Statut"
108
+
109
+ def accuracy_percent(self, obj):
110
+ """Affichage de la précision en pourcentage"""
111
+ return f"{obj.accuracy:.1%}" if obj.accuracy else "N/A"
112
+ accuracy_percent.short_description = "Précision"
113
+
114
+ fieldsets = (
115
+ ('Configuration du modèle', {
116
+ 'fields': ('model_type', 'model_path', 'model_version')
117
+ }),
118
+ ('Statut', {
119
+ 'fields': ('is_loaded', 'last_loaded', 'accuracy')
120
+ }),
121
+ ('Dates', {
122
+ 'fields': ('created_at', 'updated_at'),
123
+ 'classes': ('collapse',)
124
+ }),
125
+ )
126
+
127
+
128
+ # Par Marino ATOHOUN: Personnalisation de l'interface d'administration
129
+ admin.site.site_header = "FireWatch AI - Administration"
130
+ admin.site.site_title = "FireWatch AI Admin"
131
+ admin.site.index_title = "Panneau d'administration - Créé par Marino ATOHOUN"
132
+
detection/apps.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Configuration de l'application Detection
3
+ Créé par Marino ATOHOUN - FireWatch AI Project
4
+ """
5
+ from django.apps import AppConfig
6
+
7
+
8
+ class DetectionConfig(AppConfig):
9
+ default_auto_field = 'django.db.models.BigAutoField'
10
+ name = 'detection'
11
+ verbose_name = 'Détection IA - FireWatch'
12
+
detection/migrations/0001_initial.py ADDED
@@ -0,0 +1,252 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 4.2.23 on 2025-08-26 00:58
2
+
3
+ import detection.models
4
+ from django.db import migrations, models
5
+ import django.db.models.deletion
6
+ import uuid
7
+
8
+
9
+ class Migration(migrations.Migration):
10
+
11
+ initial = True
12
+
13
+ dependencies = []
14
+
15
+ operations = [
16
+ migrations.CreateModel(
17
+ name="AIModelStatus",
18
+ fields=[
19
+ (
20
+ "id",
21
+ models.BigAutoField(
22
+ auto_created=True,
23
+ primary_key=True,
24
+ serialize=False,
25
+ verbose_name="ID",
26
+ ),
27
+ ),
28
+ (
29
+ "model_type",
30
+ models.CharField(
31
+ choices=[
32
+ ("fire", "Modèle Incendie"),
33
+ ("intrusion", "Modèle Intrusion"),
34
+ ],
35
+ max_length=20,
36
+ unique=True,
37
+ verbose_name="Type de modèle",
38
+ ),
39
+ ),
40
+ (
41
+ "model_path",
42
+ models.CharField(max_length=500, verbose_name="Chemin du modèle"),
43
+ ),
44
+ (
45
+ "is_loaded",
46
+ models.BooleanField(default=False, verbose_name="Modèle chargé"),
47
+ ),
48
+ (
49
+ "last_loaded",
50
+ models.DateTimeField(
51
+ blank=True, null=True, verbose_name="Dernière fois chargé"
52
+ ),
53
+ ),
54
+ (
55
+ "model_version",
56
+ models.CharField(
57
+ blank=True,
58
+ max_length=50,
59
+ null=True,
60
+ verbose_name="Version du modèle",
61
+ ),
62
+ ),
63
+ (
64
+ "accuracy",
65
+ models.FloatField(
66
+ blank=True, null=True, verbose_name="Précision du modèle"
67
+ ),
68
+ ),
69
+ (
70
+ "created_at",
71
+ models.DateTimeField(
72
+ auto_now_add=True, verbose_name="Date de création"
73
+ ),
74
+ ),
75
+ (
76
+ "updated_at",
77
+ models.DateTimeField(
78
+ auto_now=True, verbose_name="Date de mise à jour"
79
+ ),
80
+ ),
81
+ ],
82
+ options={
83
+ "verbose_name": "Statut du modèle IA",
84
+ "verbose_name_plural": "Statuts des modèles IA",
85
+ },
86
+ ),
87
+ migrations.CreateModel(
88
+ name="Contact",
89
+ fields=[
90
+ (
91
+ "id",
92
+ models.BigAutoField(
93
+ auto_created=True,
94
+ primary_key=True,
95
+ serialize=False,
96
+ verbose_name="ID",
97
+ ),
98
+ ),
99
+ ("name", models.CharField(max_length=100, verbose_name="Nom")),
100
+ ("email", models.EmailField(max_length=254, verbose_name="Email")),
101
+ ("message", models.TextField(verbose_name="Message")),
102
+ (
103
+ "created_at",
104
+ models.DateTimeField(
105
+ auto_now_add=True, verbose_name="Date de création"
106
+ ),
107
+ ),
108
+ ("is_read", models.BooleanField(default=False, verbose_name="Lu")),
109
+ ],
110
+ options={
111
+ "verbose_name": "Contact",
112
+ "verbose_name_plural": "Contacts",
113
+ "ordering": ["-created_at"],
114
+ },
115
+ ),
116
+ migrations.CreateModel(
117
+ name="DetectionSession",
118
+ fields=[
119
+ (
120
+ "id",
121
+ models.BigAutoField(
122
+ auto_created=True,
123
+ primary_key=True,
124
+ serialize=False,
125
+ verbose_name="ID",
126
+ ),
127
+ ),
128
+ (
129
+ "session_id",
130
+ models.UUIDField(default=uuid.uuid4, editable=False, unique=True),
131
+ ),
132
+ (
133
+ "detection_type",
134
+ models.CharField(
135
+ choices=[
136
+ ("image", "Image"),
137
+ ("video", "Vidéo"),
138
+ ("camera", "Caméra"),
139
+ ],
140
+ max_length=10,
141
+ verbose_name="Type de détection",
142
+ ),
143
+ ),
144
+ (
145
+ "original_file",
146
+ models.FileField(
147
+ blank=True,
148
+ null=True,
149
+ upload_to=detection.models.upload_to_images,
150
+ verbose_name="Fichier original",
151
+ ),
152
+ ),
153
+ (
154
+ "result_file",
155
+ models.FileField(
156
+ blank=True,
157
+ null=True,
158
+ upload_to=detection.models.upload_to_results,
159
+ verbose_name="Fichier résultat",
160
+ ),
161
+ ),
162
+ (
163
+ "created_at",
164
+ models.DateTimeField(
165
+ auto_now_add=True, verbose_name="Date de création"
166
+ ),
167
+ ),
168
+ (
169
+ "processing_time",
170
+ models.FloatField(
171
+ blank=True,
172
+ null=True,
173
+ verbose_name="Temps de traitement (secondes)",
174
+ ),
175
+ ),
176
+ (
177
+ "is_processed",
178
+ models.BooleanField(default=False, verbose_name="Traité"),
179
+ ),
180
+ ],
181
+ options={
182
+ "verbose_name": "Session de détection",
183
+ "verbose_name_plural": "Sessions de détection",
184
+ "ordering": ["-created_at"],
185
+ },
186
+ ),
187
+ migrations.CreateModel(
188
+ name="Detection",
189
+ fields=[
190
+ (
191
+ "id",
192
+ models.BigAutoField(
193
+ auto_created=True,
194
+ primary_key=True,
195
+ serialize=False,
196
+ verbose_name="ID",
197
+ ),
198
+ ),
199
+ (
200
+ "class_name",
201
+ models.CharField(
202
+ choices=[
203
+ ("fire", "Incendie"),
204
+ ("smoke", "Fumée"),
205
+ ("person", "Personne"),
206
+ ("intrusion", "Intrusion"),
207
+ ("vehicle", "Véhicule"),
208
+ ("other", "Autre"),
209
+ ],
210
+ max_length=20,
211
+ verbose_name="Classe détectée",
212
+ ),
213
+ ),
214
+ ("confidence", models.FloatField(verbose_name="Confiance")),
215
+ ("bbox_x", models.FloatField(verbose_name="Boîte englobante X")),
216
+ ("bbox_y", models.FloatField(verbose_name="Boîte englobante Y")),
217
+ (
218
+ "bbox_width",
219
+ models.FloatField(verbose_name="Largeur boîte englobante"),
220
+ ),
221
+ (
222
+ "bbox_height",
223
+ models.FloatField(verbose_name="Hauteur boîte englobante"),
224
+ ),
225
+ (
226
+ "frame_number",
227
+ models.IntegerField(
228
+ default=0, verbose_name="Numéro de frame (pour vidéo)"
229
+ ),
230
+ ),
231
+ (
232
+ "timestamp",
233
+ models.FloatField(
234
+ blank=True, null=True, verbose_name="Timestamp (pour vidéo)"
235
+ ),
236
+ ),
237
+ (
238
+ "session",
239
+ models.ForeignKey(
240
+ on_delete=django.db.models.deletion.CASCADE,
241
+ related_name="detections",
242
+ to="detection.detectionsession",
243
+ ),
244
+ ),
245
+ ],
246
+ options={
247
+ "verbose_name": "Détection",
248
+ "verbose_name_plural": "Détections",
249
+ "ordering": ["-session__created_at", "frame_number"],
250
+ },
251
+ ),
252
+ ]
detection/migrations/__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # Migrations - Créé par Marino ATOHOUN - FireWatch AI Project
2
+
detection/models.py ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Modèles de données pour l'application Detection
3
+ Créé par Marino ATOHOUN - FireWatch AI Project
4
+ """
5
+ from django.db import models
6
+ from django.utils import timezone
7
+ import uuid
8
+ import os
9
+
10
+
11
+ def upload_to_images(instance, filename):
12
+ """Fonction pour définir le chemin d'upload des images"""
13
+ ext = filename.split('.')[-1]
14
+ filename = f"{uuid.uuid4()}.{ext}"
15
+ return os.path.join('uploads/images/', filename)
16
+
17
+
18
+ def upload_to_videos(instance, filename):
19
+ """Fonction pour définir le chemin d'upload des vidéos"""
20
+ ext = filename.split('.')[-1]
21
+ filename = f"{uuid.uuid4()}.{ext}"
22
+ return os.path.join('uploads/videos/', filename)
23
+
24
+
25
+ def upload_to_results(instance, filename):
26
+ """Fonction pour définir le chemin d'upload des résultats"""
27
+ ext = filename.split('.')[-1]
28
+ filename = f"result_{uuid.uuid4()}.{ext}"
29
+ return os.path.join('results/', filename)
30
+
31
+
32
+ class Contact(models.Model):
33
+ """
34
+ Modèle pour stocker les messages de contact
35
+ Par Marino ATOHOUN
36
+ """
37
+ name = models.CharField(max_length=100, verbose_name="Nom")
38
+ email = models.EmailField(verbose_name="Email")
39
+ message = models.TextField(verbose_name="Message")
40
+ created_at = models.DateTimeField(auto_now_add=True, verbose_name="Date de création")
41
+ is_read = models.BooleanField(default=False, verbose_name="Lu")
42
+
43
+ class Meta:
44
+ verbose_name = "Contact"
45
+ verbose_name_plural = "Contacts"
46
+ ordering = ['-created_at']
47
+
48
+ def __str__(self):
49
+ return f"Message de {self.name} ({self.email}) - {self.created_at.strftime('%d/%m/%Y %H:%M')}"
50
+
51
+
52
+ class DetectionSession(models.Model):
53
+ """
54
+ Modèle pour stocker les sessions de détection
55
+ Par Marino ATOHOUN
56
+ """
57
+ DETECTION_TYPES = [
58
+ ('image', 'Image'),
59
+ ('video', 'Vidéo'),
60
+ ('camera', 'Caméra'),
61
+ ]
62
+
63
+ session_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
64
+ detection_type = models.CharField(max_length=10, choices=DETECTION_TYPES, verbose_name="Type de détection")
65
+ original_file = models.FileField(upload_to=upload_to_images, null=True, blank=True, verbose_name="Fichier original")
66
+ result_file = models.FileField(upload_to=upload_to_results, null=True, blank=True, verbose_name="Fichier résultat")
67
+ created_at = models.DateTimeField(auto_now_add=True, verbose_name="Date de création")
68
+ processing_time = models.FloatField(null=True, blank=True, verbose_name="Temps de traitement (secondes)")
69
+ is_processed = models.BooleanField(default=False, verbose_name="Traité")
70
+
71
+ class Meta:
72
+ verbose_name = "Session de détection"
73
+ verbose_name_plural = "Sessions de détection"
74
+ ordering = ['-created_at']
75
+
76
+ def __str__(self):
77
+ return f"Session {self.session_id} - {self.get_detection_type_display()} - {self.created_at.strftime('%d/%m/%Y %H:%M')}"
78
+
79
+
80
+ class Detection(models.Model):
81
+ """
82
+ Modèle pour stocker les résultats de détection individuels
83
+ Par Marino ATOHOUN
84
+ """
85
+ DETECTION_CLASSES = [
86
+ ('fire', 'Incendie'),
87
+ ('smoke', 'Fumée'),
88
+ ('person', 'Personne'),
89
+ ('intrusion', 'Intrusion'),
90
+ ('vehicle', 'Véhicule'),
91
+ ('other', 'Autre'),
92
+ ]
93
+
94
+ session = models.ForeignKey(DetectionSession, on_delete=models.CASCADE, related_name='detections')
95
+ class_name = models.CharField(max_length=20, choices=DETECTION_CLASSES, verbose_name="Classe détectée")
96
+ confidence = models.FloatField(verbose_name="Confiance")
97
+ bbox_x = models.FloatField(verbose_name="Boîte englobante X")
98
+ bbox_y = models.FloatField(verbose_name="Boîte englobante Y")
99
+ bbox_width = models.FloatField(verbose_name="Largeur boîte englobante")
100
+ bbox_height = models.FloatField(verbose_name="Hauteur boîte englobante")
101
+ frame_number = models.IntegerField(default=0, verbose_name="Numéro de frame (pour vidéo)")
102
+ timestamp = models.FloatField(null=True, blank=True, verbose_name="Timestamp (pour vidéo)")
103
+
104
+ class Meta:
105
+ verbose_name = "Détection"
106
+ verbose_name_plural = "Détections"
107
+ ordering = ['-session__created_at', 'frame_number']
108
+
109
+ def __str__(self):
110
+ return f"{self.get_class_name_display()} - {self.confidence:.2%} - Session {self.session.session_id}"
111
+
112
+ @property
113
+ def bbox_dict(self):
114
+ """Retourne la bounding box sous forme de dictionnaire"""
115
+ return {
116
+ 'x': self.bbox_x,
117
+ 'y': self.bbox_y,
118
+ 'width': self.bbox_width,
119
+ 'height': self.bbox_height
120
+ }
121
+
122
+
123
+ class AIModelStatus(models.Model):
124
+ """
125
+ Modèle pour suivre le statut des modèles IA
126
+ Par Marino ATOHOUN
127
+ """
128
+ MODEL_TYPES = [
129
+ ('fire', 'Modèle Incendie'),
130
+ ('intrusion', 'Modèle Intrusion'),
131
+ ]
132
+
133
+ model_type = models.CharField(max_length=20, choices=MODEL_TYPES, unique=True, verbose_name="Type de modèle")
134
+ model_path = models.CharField(max_length=500, verbose_name="Chemin du modèle")
135
+ is_loaded = models.BooleanField(default=False, verbose_name="Modèle chargé")
136
+ last_loaded = models.DateTimeField(null=True, blank=True, verbose_name="Dernière fois chargé")
137
+ model_version = models.CharField(max_length=50, null=True, blank=True, verbose_name="Version du modèle")
138
+ accuracy = models.FloatField(null=True, blank=True, verbose_name="Précision du modèle")
139
+ created_at = models.DateTimeField(auto_now_add=True, verbose_name="Date de création")
140
+ updated_at = models.DateTimeField(auto_now=True, verbose_name="Date de mise à jour")
141
+
142
+ class Meta:
143
+ verbose_name = "Statut du modèle IA"
144
+ verbose_name_plural = "Statuts des modèles IA"
145
+
146
+ def __str__(self):
147
+ status = "Chargé" if self.is_loaded else "Non chargé"
148
+ return f"{self.get_model_type_display()} - {status}"
149
+
detection/templates/detection/index.html ADDED
@@ -0,0 +1,718 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% load static %}
2
+ <!DOCTYPE html>
3
+ <!-- Fichier adapté pour Django par Marino ATOHOUN - FireWatch AI Project -->
4
+ <html lang="fr">
5
+ <head>
6
+ <meta charset="UTF-8">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
+ <title>FireWatch AI - Détection d'incendie et d'intrusion</title>
9
+ <script src="https://cdn.tailwindcss.com"></script>
10
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
11
+ <style>
12
+ :root {
13
+ --primary-dark: #0f172a;
14
+ --primary-blue: #1e40af;
15
+ --accent-violet: #7c3aed;
16
+ --accent-pink: #ec4899;
17
+ --light-gray: #f1f5f9;
18
+ }
19
+
20
+ body {
21
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
22
+ background-color: var(--light-gray);
23
+ }
24
+
25
+ .gradient-bg {
26
+ background: linear-gradient(135deg, var(--primary-blue) 0%, var(--accent-violet) 100%);
27
+ }
28
+
29
+ .card-glass {
30
+ background: rgba(255, 255, 255, 0.1);
31
+ backdrop-filter: blur(10px);
32
+ -webkit-backdrop-filter: blur(10px);
33
+ border-radius: 1rem;
34
+ border: 1px solid rgba(255, 255, 255, 0.2);
35
+ }
36
+
37
+ .pulse-animation {
38
+ animation: pulse 2s infinite;
39
+ }
40
+
41
+ @keyframes pulse {
42
+ 0% { box-shadow: 0 0 0 0 rgba(236, 72, 153, 0.4); }
43
+ 70% { box-shadow: 0 0 0 10px rgba(236, 72, 153, 0); }
44
+ 100% { box-shadow: 0 0 0 0 rgba(236, 72, 153, 0); }
45
+ }
46
+
47
+ .tab-active {
48
+ border-bottom: 3px solid var(--accent-pink);
49
+ color: var(--accent-pink);
50
+ }
51
+
52
+ .loading {
53
+ display: none;
54
+ }
55
+
56
+ .loading.show {
57
+ display: block;
58
+ }
59
+
60
+ .alert {
61
+ padding: 1rem;
62
+ margin: 1rem 0;
63
+ border-radius: 0.5rem;
64
+ display: none;
65
+ }
66
+
67
+ .alert.show {
68
+ display: block;
69
+ }
70
+
71
+ .alert-success {
72
+ background-color: #d1fae5;
73
+ color: #065f46;
74
+ border: 1px solid #a7f3d0;
75
+ }
76
+
77
+ .alert-error {
78
+ background-color: #fee2e2;
79
+ color: #991b1b;
80
+ border: 1px solid #fca5a5;
81
+ }
82
+ </style>
83
+ </head>
84
+ <body class="min-h-screen">
85
+ <!-- Header -->
86
+ <header class="gradient-bg text-white shadow-xl">
87
+ <div class="container mx-auto px-4 py-6">
88
+ <div class="flex justify-between items-center">
89
+ <div class="flex items-center space-x-2">
90
+ <i class="fas fa-fire text-2xl text-pink-500"></i>
91
+ <h1 class="text-2xl font-bold">FireWatch <span class="text-pink-400">AI</span></h1>
92
+ </div>
93
+ <nav class="hidden md:flex space-x-6">
94
+ <a href="#about" class="hover:text-pink-300 transition">À propos</a>
95
+ <a href="#demo" class="hover:text-pink-300 transition">Démonstration</a>
96
+ <a href="#creator" class="hover:text-pink-300 transition">Créateur</a>
97
+ <a href="#contact" class="hover:text-pink-300 transition">Contact</a>
98
+ </nav>
99
+ <button class="md:hidden text-xl">
100
+ <i class="fas fa-bars"></i>
101
+ </button>
102
+ </div>
103
+ </div>
104
+ </header>
105
+
106
+ <!-- Hero Section -->
107
+ <section class="gradient-bg text-white py-20">
108
+ <div class="container mx-auto px-4 flex flex-col md:flex-row items-center">
109
+ <div class="md:w-1/2 mb-10 md:mb-0">
110
+ <h2 class="text-4xl md:text-5xl font-bold mb-6">Détection intelligente d'incendie et d'intrusion</h2>
111
+ <p class="text-xl mb-8 text-gray-200">Notre solution IA avancée utilise YOLOv8 pour identifier les risques en temps réel à partir d'images, de vidéos ou de flux caméra.</p>
112
+ <a href="#demo" class="bg-pink-500 hover:bg-pink-600 text-white font-bold py-3 px-6 rounded-full transition duration-300 inline-flex items-center">
113
+ Essayer maintenant <i class="fas fa-arrow-right ml-2"></i>
114
+ </a>
115
+ </div>
116
+ <div class="md:w-1/2 flex justify-center">
117
+ <div class="card-glass p-6 w-full max-w-md">
118
+ <div class="bg-black rounded-lg overflow-hidden aspect-video flex items-center justify-center">
119
+ <i class="fas fa-play text-pink-500 text-4xl"></i>
120
+ </div>
121
+ <p class="text-center mt-4 text-gray-300">Démonstration en direct de la détection</p>
122
+ </div>
123
+ </div>
124
+ </div>
125
+ </section>
126
+
127
+ <!-- About Section -->
128
+ <section id="about" class="py-16 bg-white">
129
+ <div class="container mx-auto px-4">
130
+ <h2 class="text-3xl font-bold text-center mb-12 text-gray-800">À propos du projet</h2>
131
+ <div class="grid md:grid-cols-3 gap-8">
132
+ <div class="bg-gray-50 p-6 rounded-xl shadow-md hover:shadow-lg transition">
133
+ <div class="text-indigo-600 text-3xl mb-4">
134
+ <i class="fas fa-fire-extinguisher"></i>
135
+ </div>
136
+ <h3 class="text-xl font-semibold mb-3 text-gray-800">Détection d'incendie</h3>
137
+ <p class="text-gray-600">Notre modèle identifie avec précision les débuts d'incendie, même dans des conditions de faible visibilité.</p>
138
+ </div>
139
+ <div class="bg-gray-50 p-6 rounded-xl shadow-md hover:shadow-lg transition">
140
+ <div class="text-violet-600 text-3xl mb-4">
141
+ <i class="fas fa-user-secret"></i>
142
+ </div>
143
+ <h3 class="text-xl font-semibold mb-3 text-gray-800">Détection d'intrusion</h3>
144
+ <p class="text-gray-600">Système de surveillance intelligent qui repère les intrusions et mouvements suspects en temps réel.</p>
145
+ </div>
146
+ <div class="bg-gray-50 p-6 rounded-xl shadow-md hover:shadow-lg transition">
147
+ <div class="text-pink-600 text-3xl mb-4">
148
+ <i class="fas fa-bolt"></i>
149
+ </div>
150
+ <h3 class="text-xl font-semibold mb-3 text-gray-800">Temps réel</h3>
151
+ <p class="text-gray-600">Analyse instantanée des flux vidéo avec alertes en temps réel pour une intervention rapide.</p>
152
+ </div>
153
+ </div>
154
+ </div>
155
+ </section>
156
+
157
+ <!-- Demo Section -->
158
+ <section id="demo" class="py-16 bg-gray-50">
159
+ <div class="container mx-auto px-4">
160
+ <h2 class="text-3xl font-bold text-center mb-12 text-gray-800">Essayer la démonstration</h2>
161
+
162
+ <div class="max-w-4xl mx-auto bg-white rounded-xl shadow-lg overflow-hidden">
163
+ <!-- Tabs -->
164
+ <div class="flex border-b">
165
+ <button onclick="switchTab('image')" class="tab-button py-4 px-6 font-medium text-gray-500 hover:text-pink-500 transition tab-active" id="image-tab">
166
+ <i class="fas fa-image mr-2"></i> Image
167
+ </button>
168
+ <button onclick="switchTab('video')" class="tab-button py-4 px-6 font-medium text-gray-500 hover:text-pink-500 transition" id="video-tab">
169
+ <i class="fas fa-video mr-2"></i> Vidéo
170
+ </button>
171
+ <button onclick="switchTab('camera')" class="tab-button py-4 px-6 font-medium text-gray-500 hover:text-pink-500 transition" id="camera-tab">
172
+ <i class="fas fa-camera mr-2"></i> Caméra
173
+ </button>
174
+ </div>
175
+
176
+ <!-- Tab Content -->
177
+ <div class="p-6">
178
+ <!-- Image Tab Content -->
179
+ <div id="image-content" class="tab-content">
180
+ <form id="image-form" method="post" enctype="multipart/form-data">
181
+ {% csrf_token %}
182
+ <div class="border-2 border-dashed border-gray-300 rounded-lg p-8 text-center">
183
+ <i class="fas fa-cloud-upload-alt text-4xl text-indigo-500 mb-4"></i>
184
+ <h3 class="text-xl font-semibold mb-2">Uploader une image</h3>
185
+ <p class="text-gray-500 mb-4">Glissez-déposez une image ou cliquez pour sélectionner</p>
186
+ <input type="file" name="image" id="image-upload" accept="image/*" class="hidden" required>
187
+ <label for="image-upload" class="bg-indigo-500 hover:bg-indigo-600 text-white font-medium py-2 px-4 rounded cursor-pointer transition">
188
+ Sélectionner une image
189
+ </label>
190
+ <div id="image-preview" class="mt-4 hidden">
191
+ <img id="preview-img" src="" alt="Aperçu" class="max-w-full h-48 mx-auto rounded">
192
+ </div>
193
+ </div>
194
+ <div class="mt-6 flex justify-between">
195
+ <button type="button" onclick="resetImageForm()" class="bg-gray-200 hover:bg-gray-300 text-gray-800 font-medium py-2 px-4 rounded transition">
196
+ <i class="fas fa-redo mr-2"></i> Réinitialiser
197
+ </button>
198
+ <button type="submit" class="bg-pink-500 hover:bg-pink-600 text-white font-medium py-2 px-4 rounded transition">
199
+ <i class="fas fa-search mr-2"></i> Analyser
200
+ </button>
201
+ </div>
202
+ <div class="loading mt-4 text-center">
203
+ <i class="fas fa-spinner fa-spin text-2xl text-indigo-500"></i>
204
+ <p class="mt-2 text-gray-600">Analyse en cours...</p>
205
+ </div>
206
+ </form>
207
+ </div>
208
+
209
+ <!-- Video Tab Content -->
210
+ <div id="video-content" class="tab-content hidden">
211
+ <form id="video-form" method="post" enctype="multipart/form-data">
212
+ {% csrf_token %}
213
+ <div class="border-2 border-dashed border-gray-300 rounded-lg p-8 text-center">
214
+ <i class="fas fa-film text-4xl text-violet-500 mb-4"></i>
215
+ <h3 class="text-xl font-semibold mb-2">Uploader une vidéo</h3>
216
+ <p class="text-gray-500 mb-4">Glissez-déposez une vidéo ou cliquez pour sélectionner</p>
217
+ <input type="file" name="video" id="video-upload" accept="video/*" class="hidden" required>
218
+ <label for="video-upload" class="bg-violet-500 hover:bg-violet-600 text-white font-medium py-2 px-4 rounded cursor-pointer transition">
219
+ Sélectionner une vidéo
220
+ </label>
221
+ <div id="video-preview" class="mt-4 hidden">
222
+ <video id="preview-video" controls class="max-w-full h-48 mx-auto rounded">
223
+ <source src="" type="video/mp4">
224
+ Votre navigateur ne supporte pas la lecture vidéo.
225
+ </video>
226
+ </div>
227
+ </div>
228
+ <div class="mt-6 flex justify-between">
229
+ <button type="button" onclick="resetVideoForm()" class="bg-gray-200 hover:bg-gray-300 text-gray-800 font-medium py-2 px-4 rounded transition">
230
+ <i class="fas fa-redo mr-2"></i> Réinitialiser
231
+ </button>
232
+ <button type="submit" class="bg-pink-500 hover:bg-pink-600 text-white font-medium py-2 px-4 rounded transition">
233
+ <i class="fas fa-play mr-2"></i> Démarrer l'analyse
234
+ </button>
235
+ </div>
236
+ <div class="loading mt-4 text-center">
237
+ <i class="fas fa-spinner fa-spin text-2xl text-violet-500"></i>
238
+ <p class="mt-2 text-gray-600">Analyse vidéo en cours...</p>
239
+ </div>
240
+ </form>
241
+ </div>
242
+
243
+ <!-- Camera Tab Content -->
244
+ <div id="camera-content" class="tab-content hidden">
245
+ <div class="border-2 border-dashed border-gray-300 rounded-lg p-8 text-center">
246
+ <i class="fas fa-camera text-4xl text-pink-500 mb-4"></i>
247
+ <h3 class="text-xl font-semibold mb-2">Accéder à votre caméra</h3>
248
+ <p class="text-gray-500 mb-4">Autorisez l'accès à votre caméra pour une analyse en temps réel</p>
249
+ <button id="start-camera" class="bg-pink-500 hover:bg-pink-600 text-white font-medium py-2 px-4 rounded cursor-pointer transition pulse-animation">
250
+ <i class="fas fa-video mr-2"></i> Activer la caméra
251
+ </button>
252
+ <div id="camera-stream" class="mt-4 hidden">
253
+ <video id="camera-video" autoplay muted class="max-w-full h-64 mx-auto rounded bg-black"></video>
254
+ <canvas id="camera-canvas" class="hidden"></canvas>
255
+ </div>
256
+ </div>
257
+ <div class="mt-6 flex justify-center">
258
+ <button id="stop-camera" class="bg-gray-200 hover:bg-gray-300 text-gray-800 font-medium py-2 px-4 rounded transition hidden">
259
+ <i class="fas fa-stop mr-2"></i> Arrêter
260
+ </button>
261
+ <button id="capture-frame" class="bg-indigo-500 hover:bg-indigo-600 text-white font-medium py-2 px-4 rounded transition ml-4 hidden">
262
+ <i class="fas fa-camera mr-2"></i> Capturer et analyser
263
+ </button>
264
+ </div>
265
+ </div>
266
+ </div>
267
+ </div>
268
+
269
+ <!-- Results Section -->
270
+ <div id="results" class="max-w-4xl mx-auto mt-12 bg-white rounded-xl shadow-lg overflow-hidden hidden">
271
+ <div class="p-6">
272
+ <h3 class="text-xl font-semibold mb-4 text-gray-800">Résultats de l'analyse</h3>
273
+ <div class="flex flex-col md:flex-row gap-6">
274
+ <div class="md:w-1/2">
275
+ <div id="result-image-container" class="bg-black rounded-lg overflow-hidden aspect-video flex items-center justify-center">
276
+ <img id="result-image" src="" alt="Résultat de l'analyse" class="w-full h-full object-contain">
277
+ </div>
278
+ </div>
279
+ <div class="md:w-1/2">
280
+ <div class="bg-gray-50 p-4 rounded-lg">
281
+ <h4 class="font-medium text-gray-800 mb-3">Détections:</h4>
282
+ <ul id="detection-list" class="space-y-2">
283
+ <!-- Les détections seront insérées ici par JavaScript -->
284
+ </ul>
285
+ </div>
286
+ <div class="mt-4">
287
+ <button id="download-results" class="bg-indigo-500 hover:bg-indigo-600 text-white font-medium py-2 px-4 rounded transition">
288
+ <i class="fas fa-download mr-2"></i> Télécharger les résultats
289
+ </button>
290
+ </div>
291
+ </div>
292
+ </div>
293
+ </div>
294
+ </div>
295
+
296
+ <!-- Alert Messages -->
297
+ <div id="alert-container" class="max-w-4xl mx-auto mt-4">
298
+ <div id="success-alert" class="alert alert-success">
299
+ <i class="fas fa-check-circle mr-2"></i>
300
+ <span id="success-message"></span>
301
+ </div>
302
+ <div id="error-alert" class="alert alert-error">
303
+ <i class="fas fa-exclamation-circle mr-2"></i>
304
+ <span id="error-message"></span>
305
+ </div>
306
+ </div>
307
+ </div>
308
+ </section>
309
+
310
+ <!-- Creator Section -->
311
+ <section id="creator" class="py-16 bg-gradient-to-r from-indigo-900 to-violet-900 text-white">
312
+ <div class="container mx-auto px-4">
313
+ <h2 class="text-3xl font-bold text-center mb-12">À propos du créateur</h2>
314
+ <div class="max-w-4xl mx-auto flex flex-col md:flex-row items-center gap-8">
315
+ <div class="md:w-1/3 flex justify-center">
316
+ <div class="w-48 h-48 rounded-full bg-gray-300 overflow-hidden border-4 border-pink-400 shadow-lg">
317
+ <img src="https://via.placeholder.com/200" alt="Marino ATOHOUN" class="w-full h-full object-cover">
318
+ </div>
319
+ </div>
320
+ <div class="md:w-2/3">
321
+ <h3 class="text-2xl font-bold mb-2">Marino ATOHOUN</h3>
322
+ <p class="text-pink-300 font-medium mb-4">Data Scientist & Développeur Backend</p>
323
+ <p class="mb-4 text-gray-300">Expert en intelligence artificielle et développement logiciel, Marino ATOHOUN a conçu cette solution innovante pour répondre aux besoins croissants de sécurité et de prévention des risques.</p>
324
+ <p class="mb-4 text-gray-300">Avec une solide expérience dans le traitement d'images et l'analyse de données, il a développé ce système de détection basé sur YOLOv8 pour offrir une solution performante et accessible.</p>
325
+ <div class="flex space-x-4">
326
+ <a href="#" class="text-gray-300 hover:text-pink-300 transition">
327
+ <i class="fab fa-github text-xl"></i>
328
+ </a>
329
+ <a href="#" class="text-gray-300 hover:text-pink-300 transition">
330
+ <i class="fab fa-linkedin text-xl"></i>
331
+ </a>
332
+ <a href="#" class="text-gray-300 hover:text-pink-300 transition">
333
+ <i class="fas fa-envelope text-xl"></i>
334
+ </a>
335
+ </div>
336
+ </div>
337
+ </div>
338
+ </div>
339
+ </section>
340
+
341
+ <!-- Contact Section -->
342
+ <section id="contact" class="py-16 bg-white">
343
+ <div class="container mx-auto px-4">
344
+ <h2 class="text-3xl font-bold text-center mb-12 text-gray-800">Contactez-nous</h2>
345
+ <div class="max-w-2xl mx-auto bg-gray-50 rounded-xl shadow-md p-8">
346
+ <form id="contact-form" method="post">
347
+ {% csrf_token %}
348
+ <div id="contact-alert" class="alert">
349
+ <span id="contact-message"></span>
350
+ </div>
351
+ <div class="mb-6">
352
+ <label for="name" class="block text-gray-700 font-medium mb-2">Nom</label>
353
+ <input type="text" id="name" name="name" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500" required>
354
+ </div>
355
+ <div class="mb-6">
356
+ <label for="email" class="block text-gray-700 font-medium mb-2">Email</label>
357
+ <input type="email" id="email" name="email" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500" required>
358
+ </div>
359
+ <div class="mb-6">
360
+ <label for="message" class="block text-gray-700 font-medium mb-2">Message</label>
361
+ <textarea id="message" name="message" rows="4" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500" required></textarea>
362
+ </div>
363
+ <button type="submit" class="bg-indigo-500 hover:bg-indigo-600 text-white font-medium py-2 px-6 rounded-lg transition w-full">
364
+ <i class="fas fa-paper-plane mr-2"></i> Envoyer le message
365
+ </button>
366
+ </form>
367
+ </div>
368
+ </div>
369
+ </section>
370
+
371
+ <!-- Footer -->
372
+ <footer class="bg-gray-900 text-white py-8">
373
+ <div class="container mx-auto px-4">
374
+ <div class="flex flex-col md:flex-row justify-between items-center">
375
+ <div class="mb-4 md:mb-0">
376
+ <div class="flex items-center space-x-2">
377
+ <i class="fas fa-fire text-xl text-pink-500"></i>
378
+ <span class="text-xl font-bold">FireWatch <span class="text-pink-400">AI</span></span>
379
+ </div>
380
+ <p class="text-gray-400 mt-2">Solution intelligente de détection d'incendie et d'intrusion</p>
381
+ </div>
382
+ <div class="flex space-x-6">
383
+ <a href="#" class="text-gray-400 hover:text-pink-400 transition">
384
+ <i class="fab fa-facebook-f"></i>
385
+ </a>
386
+ <a href="#" class="text-gray-400 hover:text-pink-400 transition">
387
+ <i class="fab fa-twitter"></i>
388
+ </a>
389
+ <a href="#" class="text-gray-400 hover:text-pink-400 transition">
390
+ <i class="fab fa-instagram"></i>
391
+ </a>
392
+ <a href="#" class="text-gray-400 hover:text-pink-400 transition">
393
+ <i class="fab fa-github"></i>
394
+ </a>
395
+ </div>
396
+ </div>
397
+ <div class="border-t border-gray-800 mt-8 pt-8 text-center text-gray-400">
398
+ <p>&copy; 2024 FireWatch AI. Tous droits réservés. Conçu avec passion par Marino ATOHOUN.</p>
399
+ </div>
400
+ </div>
401
+ </footer>
402
+
403
+ <script>
404
+ // Par Marino ATOHOUN: JavaScript pour l'interaction avec le backend Django
405
+
406
+ // Obtenir le token CSRF
407
+ function getCSRFToken() {
408
+ return document.querySelector('[name=csrfmiddlewaretoken]').value;
409
+ }
410
+
411
+ // Fonctions utilitaires pour les alertes
412
+ function showAlert(type, message) {
413
+ const alertContainer = document.getElementById('alert-container');
414
+ const alert = document.getElementById(type + '-alert');
415
+ const messageSpan = document.getElementById(type + '-message');
416
+
417
+ messageSpan.textContent = message;
418
+ alert.classList.add('show');
419
+
420
+ setTimeout(() => {
421
+ alert.classList.remove('show');
422
+ }, 5000);
423
+ }
424
+
425
+ function showContactAlert(type, message) {
426
+ const alert = document.getElementById('contact-alert');
427
+ const messageSpan = document.getElementById('contact-message');
428
+
429
+ alert.className = 'alert show alert-' + type;
430
+ messageSpan.textContent = message;
431
+
432
+ setTimeout(() => {
433
+ alert.classList.remove('show');
434
+ }, 5000);
435
+ }
436
+
437
+ // Tab switching functionality
438
+ function switchTab(tabName) {
439
+ // Hide all tab contents
440
+ document.querySelectorAll('.tab-content').forEach(content => {
441
+ content.classList.add('hidden');
442
+ });
443
+
444
+ // Remove active class from all tabs
445
+ document.querySelectorAll('.tab-button').forEach(button => {
446
+ button.classList.remove('tab-active');
447
+ });
448
+
449
+ // Show selected tab content
450
+ document.getElementById(tabName + '-content').classList.remove('hidden');
451
+
452
+ // Add active class to selected tab
453
+ document.getElementById(tabName + '-tab').classList.add('tab-active');
454
+ }
455
+
456
+ // Fonctions de réinitialisation des formulaires
457
+ function resetImageForm() {
458
+ document.getElementById('image-form').reset();
459
+ document.getElementById('image-preview').classList.add('hidden');
460
+ document.getElementById('results').classList.add('hidden');
461
+ }
462
+
463
+ function resetVideoForm() {
464
+ document.getElementById('video-form').reset();
465
+ document.getElementById('video-preview').classList.add('hidden');
466
+ document.getElementById('results').classList.add('hidden');
467
+ }
468
+
469
+ // Aperçu des fichiers
470
+ document.getElementById('image-upload').addEventListener('change', function(e) {
471
+ const file = e.target.files[0];
472
+ if (file) {
473
+ const reader = new FileReader();
474
+ reader.onload = function(e) {
475
+ document.getElementById('preview-img').src = e.target.result;
476
+ document.getElementById('image-preview').classList.remove('hidden');
477
+ };
478
+ reader.readAsDataURL(file);
479
+ }
480
+ });
481
+
482
+ document.getElementById('video-upload').addEventListener('change', function(e) {
483
+ const file = e.target.files[0];
484
+ if (file) {
485
+ const url = URL.createObjectURL(file);
486
+ document.getElementById('preview-video').src = url;
487
+ document.getElementById('video-preview').classList.remove('hidden');
488
+ }
489
+ });
490
+
491
+ // Gestion du formulaire d'image
492
+ document.getElementById('image-form').addEventListener('submit', function(e) {
493
+ e.preventDefault();
494
+
495
+ const formData = new FormData(this);
496
+ const loadingDiv = this.querySelector('.loading');
497
+
498
+ loadingDiv.classList.add('show');
499
+
500
+ fetch("{% url 'detection:analyze_image' %}", {
501
+ method: 'POST',
502
+ body: formData,
503
+ headers: {
504
+ 'X-CSRFToken': getCSRFToken()
505
+ }
506
+ })
507
+ .then(response => response.json())
508
+ .then(data => {
509
+ loadingDiv.classList.remove('show');
510
+
511
+ if (data.success) {
512
+ displayResults(data);
513
+ showAlert('success', 'Analyse terminée avec succès!');
514
+ } else {
515
+ showAlert('error', data.error || 'Erreur lors de l\'analyse');
516
+ }
517
+ })
518
+ .catch(error => {
519
+ loadingDiv.classList.remove('show');
520
+ showAlert('error', 'Erreur de connexion au serveur');
521
+ console.error('Error:', error);
522
+ });
523
+ });
524
+
525
+ // Gestion du formulaire de vidéo
526
+ document.getElementById('video-form').addEventListener('submit', function(e) {
527
+ e.preventDefault();
528
+
529
+ const formData = new FormData(this);
530
+ const loadingDiv = this.querySelector('.loading');
531
+
532
+ loadingDiv.classList.add('show');
533
+
534
+ fetch("{% url 'detection:analyze_video' %}", {
535
+ method: 'POST',
536
+ body: formData,
537
+ headers: {
538
+ 'X-CSRFToken': getCSRFToken()
539
+ }
540
+ })
541
+ .then(response => response.json())
542
+ .then(data => {
543
+ loadingDiv.classList.remove('show');
544
+
545
+ if (data.success) {
546
+ displayResults(data);
547
+ showAlert('success', 'Analyse vidéo terminée avec succès!');
548
+ } else {
549
+ showAlert('error', data.error || 'Erreur lors de l\'analyse vidéo');
550
+ }
551
+ })
552
+ .catch(error => {
553
+ loadingDiv.classList.remove('show');
554
+ showAlert('error', 'Erreur de connexion au serveur');
555
+ console.error('Error:', error);
556
+ });
557
+ });
558
+
559
+ // Gestion du formulaire de contact
560
+ document.getElementById('contact-form').addEventListener('submit', function(e) {
561
+ e.preventDefault();
562
+
563
+ const formData = new FormData(this);
564
+
565
+ fetch("{% url 'detection:contact' %}", {
566
+ method: 'POST',
567
+ body: formData,
568
+ headers: {
569
+ 'X-CSRFToken': getCSRFToken()
570
+ }
571
+ })
572
+ .then(response => response.json())
573
+ .then(data => {
574
+ if (data.success) {
575
+ showContactAlert('success', data.message);
576
+ this.reset();
577
+ } else {
578
+ showContactAlert('error', data.error || 'Erreur lors de l\'envoi du message');
579
+ }
580
+ })
581
+ .catch(error => {
582
+ showContactAlert('error', 'Erreur de connexion au serveur');
583
+ console.error('Error:', error);
584
+ });
585
+ });
586
+
587
+ // Affichage des résultats
588
+ function displayResults(data) {
589
+ const resultsSection = document.getElementById('results');
590
+ const resultImage = document.getElementById('result-image');
591
+ const detectionList = document.getElementById('detection-list');
592
+
593
+ // Afficher l'image de résultat
594
+ if (data.result_image_url) {
595
+ resultImage.src = data.result_image_url;
596
+ }
597
+
598
+ // Afficher les détections
599
+ detectionList.innerHTML = '';
600
+ if (data.detections && data.detections.length > 0) {
601
+ data.detections.forEach(detection => {
602
+ const li = document.createElement('li');
603
+ li.className = 'flex items-center';
604
+
605
+ // Couleur selon le type de détection
606
+ let colorClass = 'bg-blue-500';
607
+ if (detection.class_name === 'fire' || detection.class_name === 'smoke') {
608
+ colorClass = 'bg-red-500';
609
+ } else if (detection.class_name === 'person' || detection.class_name === 'intrusion') {
610
+ colorClass = 'bg-orange-500';
611
+ }
612
+
613
+ li.innerHTML = `
614
+ <span class="w-3 h-3 ${colorClass} rounded-full mr-2"></span>
615
+ <span>${detection.label || detection.class_name}:
616
+ <span class="font-medium">${(detection.confidence * 100).toFixed(1)}% de confiance</span></span>
617
+ `;
618
+ detectionList.appendChild(li);
619
+ });
620
+ } else {
621
+ detectionList.innerHTML = '<li class="text-gray-500">Aucune détection trouvée</li>';
622
+ }
623
+
624
+ // Afficher la section des résultats
625
+ resultsSection.classList.remove('hidden');
626
+ resultsSection.scrollIntoView({ behavior: 'smooth' });
627
+ }
628
+
629
+ // Gestion de la caméra
630
+ let cameraStream = null;
631
+ let cameraVideo = document.getElementById('camera-video');
632
+ let cameraCanvas = document.getElementById('camera-canvas');
633
+
634
+ document.getElementById('start-camera').addEventListener('click', function() {
635
+ navigator.mediaDevices.getUserMedia({ video: true })
636
+ .then(function(stream) {
637
+ cameraStream = stream;
638
+ cameraVideo.srcObject = stream;
639
+
640
+ document.getElementById('camera-stream').classList.remove('hidden');
641
+ document.getElementById('stop-camera').classList.remove('hidden');
642
+ document.getElementById('capture-frame').classList.remove('hidden');
643
+ this.classList.remove('pulse-animation');
644
+ })
645
+ .catch(function(error) {
646
+ showAlert('error', 'Impossible d\'accéder à la caméra: ' + error.message);
647
+ });
648
+ });
649
+
650
+ document.getElementById('stop-camera').addEventListener('click', function() {
651
+ if (cameraStream) {
652
+ cameraStream.getTracks().forEach(track => track.stop());
653
+ cameraStream = null;
654
+ }
655
+
656
+ document.getElementById('camera-stream').classList.add('hidden');
657
+ document.getElementById('stop-camera').classList.add('hidden');
658
+ document.getElementById('capture-frame').classList.add('hidden');
659
+ document.getElementById('start-camera').classList.add('pulse-animation');
660
+ });
661
+
662
+ document.getElementById('capture-frame').addEventListener('click', function() {
663
+ if (cameraVideo.videoWidth > 0) {
664
+ // Capturer une frame de la vidéo
665
+ cameraCanvas.width = cameraVideo.videoWidth;
666
+ cameraCanvas.height = cameraVideo.videoHeight;
667
+
668
+ const ctx = cameraCanvas.getContext('2d');
669
+ ctx.drawImage(cameraVideo, 0, 0);
670
+
671
+ // Convertir en blob et envoyer pour analyse
672
+ cameraCanvas.toBlob(function(blob) {
673
+ const formData = new FormData();
674
+ formData.append('image', blob, 'camera_capture.jpg');
675
+
676
+ fetch("{% url 'detection:analyze_image' %}", {
677
+ method: 'POST',
678
+ body: formData,
679
+ headers: {
680
+ 'X-CSRFToken': getCSRFToken()
681
+ }
682
+ })
683
+ .then(response => response.json())
684
+ .then(data => {
685
+ if (data.success) {
686
+ displayResults(data);
687
+ showAlert('success', 'Capture analysée avec succès!');
688
+ } else {
689
+ showAlert('error', data.error || 'Erreur lors de l\'analyse');
690
+ }
691
+ })
692
+ .catch(error => {
693
+ showAlert('error', 'Erreur de connexion au serveur');
694
+ console.error('Error:', error);
695
+ });
696
+ }, 'image/jpeg', 0.8);
697
+ }
698
+ });
699
+
700
+ // Smooth scrolling for navigation links
701
+ document.querySelectorAll('a[href^="#"]').forEach(anchor => {
702
+ anchor.addEventListener('click', function(e) {
703
+ e.preventDefault();
704
+ const target = document.querySelector(this.getAttribute('href'));
705
+ if (target) {
706
+ target.scrollIntoView({
707
+ behavior: 'smooth'
708
+ });
709
+ }
710
+ });
711
+ });
712
+ </script>
713
+
714
+ <!-- Signature Marino ATOHOUN -->
715
+ <div style="display: none;">Code signé par Marino ATOHOUN - FireWatch AI Project</div>
716
+ </body>
717
+ </html>
718
+
detection/tests.py ADDED
@@ -0,0 +1,405 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Tests pour l'application Detection
3
+ Créé par Marino ATOHOUN - FireWatch AI Project
4
+ """
5
+ import os
6
+ import tempfile
7
+ from django.test import TestCase, Client
8
+ from django.urls import reverse
9
+ from django.core.files.uploadedfile import SimpleUploadedFile
10
+ from django.contrib.auth.models import User
11
+ from PIL import Image
12
+ import io
13
+ from .models import Contact, DetectionSession, Detection, AIModelStatus
14
+
15
+
16
+ class ContactModelTest(TestCase):
17
+ """Tests pour le modèle Contact - Par Marino ATOHOUN"""
18
+
19
+ def setUp(self):
20
+ self.contact = Contact.objects.create(
21
+ name="Test User",
22
+ email="test@example.com",
23
+ message="Message de test"
24
+ )
25
+
26
+ def test_contact_creation(self):
27
+ """Test de création d'un contact"""
28
+ self.assertEqual(self.contact.name, "Test User")
29
+ self.assertEqual(self.contact.email, "test@example.com")
30
+ self.assertFalse(self.contact.is_read)
31
+
32
+ def test_contact_str_method(self):
33
+ """Test de la méthode __str__ du contact"""
34
+ expected = f"Message de {self.contact.name} ({self.contact.email}) - {self.contact.created_at.strftime('%d/%m/%Y %H:%M')}"
35
+ self.assertEqual(str(self.contact), expected)
36
+
37
+
38
+ class DetectionSessionModelTest(TestCase):
39
+ """Tests pour le modèle DetectionSession - Par Marino ATOHOUN"""
40
+
41
+ def setUp(self):
42
+ self.session = DetectionSession.objects.create(
43
+ detection_type='image'
44
+ )
45
+
46
+ def test_session_creation(self):
47
+ """Test de création d'une session"""
48
+ self.assertEqual(self.session.detection_type, 'image')
49
+ self.assertFalse(self.session.is_processed)
50
+ self.assertIsNotNone(self.session.session_id)
51
+
52
+ def test_session_str_method(self):
53
+ """Test de la méthode __str__ de la session"""
54
+ expected = f"Session {self.session.session_id} - Image - {self.session.created_at.strftime('%d/%m/%Y %H:%M')}"
55
+ self.assertEqual(str(self.session), expected)
56
+
57
+
58
+ class DetectionModelTest(TestCase):
59
+ """Tests pour le modèle Detection - Par Marino ATOHOUN"""
60
+
61
+ def setUp(self):
62
+ self.session = DetectionSession.objects.create(detection_type='image')
63
+ self.detection = Detection.objects.create(
64
+ session=self.session,
65
+ class_name='fire',
66
+ confidence=0.92,
67
+ bbox_x=100,
68
+ bbox_y=100,
69
+ bbox_width=150,
70
+ bbox_height=150
71
+ )
72
+
73
+ def test_detection_creation(self):
74
+ """Test de création d'une détection"""
75
+ self.assertEqual(self.detection.class_name, 'fire')
76
+ self.assertEqual(self.detection.confidence, 0.92)
77
+
78
+ def test_bbox_dict_property(self):
79
+ """Test de la propriété bbox_dict"""
80
+ expected = {
81
+ 'x': 100,
82
+ 'y': 100,
83
+ 'width': 150,
84
+ 'height': 150
85
+ }
86
+ self.assertEqual(self.detection.bbox_dict, expected)
87
+
88
+
89
+ class ViewsTest(TestCase):
90
+ """Tests pour les vues - Par Marino ATOHOUN"""
91
+
92
+ def setUp(self):
93
+ self.client = Client()
94
+
95
+ def test_index_view(self):
96
+ """Test de la vue principale"""
97
+ response = self.client.get(reverse('detection:index'))
98
+ self.assertEqual(response.status_code, 200)
99
+ self.assertContains(response, 'FireWatch AI')
100
+
101
+ def test_contact_view_post(self):
102
+ """Test de soumission du formulaire de contact"""
103
+ data = {
104
+ 'name': 'Test User',
105
+ 'email': 'test@example.com',
106
+ 'message': 'Message de test'
107
+ }
108
+ response = self.client.post(reverse('detection:contact'), data)
109
+ self.assertEqual(response.status_code, 200)
110
+
111
+ # Vérifier que le contact a été créé
112
+ contact = Contact.objects.get(email='test@example.com')
113
+ self.assertEqual(contact.name, 'Test User')
114
+
115
+ def test_contact_view_invalid_data(self):
116
+ """Test avec des données invalides"""
117
+ data = {
118
+ 'name': '',
119
+ 'email': 'invalid-email',
120
+ 'message': ''
121
+ }
122
+ response = self.client.post(reverse('detection:contact'), data)
123
+ self.assertEqual(response.status_code, 400)
124
+
125
+ def create_test_image(self):
126
+ """Crée une image de test"""
127
+ image = Image.new('RGB', (100, 100), color='red')
128
+ image_io = io.BytesIO()
129
+ image.save(image_io, format='JPEG')
130
+ image_io.seek(0)
131
+ return SimpleUploadedFile(
132
+ "test_image.jpg",
133
+ image_io.getvalue(),
134
+ content_type="image/jpeg"
135
+ )
136
+
137
+ def test_analyze_image_view(self):
138
+ """Test d'analyse d'image"""
139
+ test_image = self.create_test_image()
140
+
141
+ response = self.client.post(
142
+ reverse('detection:analyze_image'),
143
+ {'image': test_image}
144
+ )
145
+
146
+ self.assertEqual(response.status_code, 200)
147
+ data = response.json()
148
+ self.assertTrue(data['success'])
149
+ self.assertIn('session_id', data)
150
+ self.assertIn('detections', data)
151
+
152
+ def test_analyze_image_no_file(self):
153
+ """Test d'analyse sans fichier"""
154
+ response = self.client.post(reverse('detection:analyze_image'))
155
+ self.assertEqual(response.status_code, 400)
156
+ data = response.json()
157
+ self.assertFalse(data['success'])
158
+
159
+ def test_models_status_api(self):
160
+ """Test de l'API de statut des modèles"""
161
+ # Créer un statut de modèle de test
162
+ AIModelStatus.objects.create(
163
+ model_type='fire',
164
+ model_path='models/incendies.pt',
165
+ is_loaded=True
166
+ )
167
+
168
+ response = self.client.get(reverse('detection:models_status'))
169
+ self.assertEqual(response.status_code, 200)
170
+ data = response.json()
171
+ self.assertTrue(data['success'])
172
+ self.assertIn('models', data)
173
+
174
+
175
+ class AdminTest(TestCase):
176
+ """Tests pour l'interface d'administration - Par Marino ATOHOUN"""
177
+
178
+ def setUp(self):
179
+ self.admin_user = User.objects.create_superuser(
180
+ username='admin',
181
+ email='admin@test.com',
182
+ password='admin123'
183
+ )
184
+ self.client = Client()
185
+ self.client.login(username='admin', password='admin123')
186
+
187
+ def test_admin_contact_list(self):
188
+ """Test de la liste des contacts dans l'admin"""
189
+ Contact.objects.create(
190
+ name="Test User",
191
+ email="test@example.com",
192
+ message="Message de test"
193
+ )
194
+
195
+ response = self.client.get('/admin/detection/contact/')
196
+ self.assertEqual(response.status_code, 200)
197
+ self.assertContains(response, 'Test User')
198
+
199
+ def test_admin_detection_session_list(self):
200
+ """Test de la liste des sessions dans l'admin"""
201
+ session = DetectionSession.objects.create(detection_type='image')
202
+
203
+ response = self.client.get('/admin/detection/detectionsession/')
204
+ self.assertEqual(response.status_code, 200)
205
+ self.assertContains(response, str(session.session_id))
206
+
207
+
208
+ class UtilityTest(TestCase):
209
+ """Tests pour les fonctions utilitaires - Par Marino ATOHOUN"""
210
+
211
+ def test_file_upload_paths(self):
212
+ """Test des chemins d'upload"""
213
+ from .models import upload_to_images, upload_to_videos, upload_to_results
214
+
215
+ # Mock instance
216
+ class MockInstance:
217
+ pass
218
+
219
+ instance = MockInstance()
220
+
221
+ # Test des chemins
222
+ image_path = upload_to_images(instance, 'test.jpg')
223
+ self.assertTrue(image_path.startswith('uploads/images/'))
224
+ self.assertTrue(image_path.endswith('.jpg'))
225
+
226
+ video_path = upload_to_videos(instance, 'test.mp4')
227
+ self.assertTrue(video_path.startswith('uploads/videos/'))
228
+ self.assertTrue(video_path.endswith('.mp4'))
229
+
230
+ result_path = upload_to_results(instance, 'result.jpg')
231
+ self.assertTrue(result_path.startswith('results/'))
232
+ self.assertTrue(result_path.endswith('.jpg'))
233
+
234
+
235
+ class IntegrationTest(TestCase):
236
+ """Tests d'intégration - Par Marino ATOHOUN"""
237
+
238
+ def setUp(self):
239
+ self.client = Client()
240
+
241
+ def test_full_image_analysis_workflow(self):
242
+ """Test du workflow complet d'analyse d'image"""
243
+ # 1. Créer une image de test
244
+ image = Image.new('RGB', (640, 480), color='red')
245
+ image_io = io.BytesIO()
246
+ image.save(image_io, format='JPEG')
247
+ image_io.seek(0)
248
+ test_image = SimpleUploadedFile(
249
+ "test_fire.jpg",
250
+ image_io.getvalue(),
251
+ content_type="image/jpeg"
252
+ )
253
+
254
+ # 2. Analyser l'image
255
+ response = self.client.post(
256
+ reverse('detection:analyze_image'),
257
+ {'image': test_image}
258
+ )
259
+
260
+ self.assertEqual(response.status_code, 200)
261
+ data = response.json()
262
+ self.assertTrue(data['success'])
263
+
264
+ session_id = data['session_id']
265
+
266
+ # 3. Vérifier que la session a été créée
267
+ session = DetectionSession.objects.get(session_id=session_id)
268
+ self.assertEqual(session.detection_type, 'image')
269
+ self.assertTrue(session.is_processed)
270
+
271
+ # 4. Récupérer les résultats via l'API
272
+ response = self.client.get(
273
+ reverse('detection:get_results', kwargs={'session_id': session_id})
274
+ )
275
+
276
+ self.assertEqual(response.status_code, 200)
277
+ results_data = response.json()
278
+ self.assertTrue(results_data['success'])
279
+ self.assertEqual(results_data['session_id'], session_id)
280
+
281
+ def test_contact_and_admin_workflow(self):
282
+ """Test du workflow contact et administration"""
283
+ # 1. Envoyer un message de contact
284
+ contact_data = {
285
+ 'name': 'John Doe',
286
+ 'email': 'john@example.com',
287
+ 'message': 'Question sur FireWatch AI'
288
+ }
289
+
290
+ response = self.client.post(reverse('detection:contact'), contact_data)
291
+ self.assertEqual(response.status_code, 200)
292
+
293
+ # 2. Vérifier que le contact existe
294
+ contact = Contact.objects.get(email='john@example.com')
295
+ self.assertEqual(contact.name, 'John Doe')
296
+ self.assertFalse(contact.is_read)
297
+
298
+ # 3. Marquer comme lu (simulation admin)
299
+ contact.is_read = True
300
+ contact.save()
301
+
302
+ # 4. Vérifier la mise à jour
303
+ updated_contact = Contact.objects.get(email='john@example.com')
304
+ self.assertTrue(updated_contact.is_read)
305
+
306
+
307
+ class PerformanceTest(TestCase):
308
+ """Tests de performance - Par Marino ATOHOUN"""
309
+
310
+ def test_multiple_detections_creation(self):
311
+ """Test de création de multiples détections"""
312
+ import time
313
+
314
+ session = DetectionSession.objects.create(detection_type='video')
315
+
316
+ start_time = time.time()
317
+
318
+ # Créer 100 détections
319
+ detections = []
320
+ for i in range(100):
321
+ detection = Detection(
322
+ session=session,
323
+ class_name='fire',
324
+ confidence=0.8 + (i % 20) * 0.01,
325
+ bbox_x=i * 10,
326
+ bbox_y=i * 10,
327
+ bbox_width=100,
328
+ bbox_height=100,
329
+ frame_number=i
330
+ )
331
+ detections.append(detection)
332
+
333
+ Detection.objects.bulk_create(detections)
334
+
335
+ end_time = time.time()
336
+ creation_time = end_time - start_time
337
+
338
+ # Vérifier que la création est rapide (moins de 1 seconde)
339
+ self.assertLess(creation_time, 1.0)
340
+
341
+ # Vérifier que toutes les détections ont été créées
342
+ self.assertEqual(Detection.objects.filter(session=session).count(), 100)
343
+
344
+
345
+ class SecurityTest(TestCase):
346
+ """Tests de sécurité - Par Marino ATOHOUN"""
347
+
348
+ def test_csrf_protection(self):
349
+ """Test de protection CSRF"""
350
+ # Tentative de POST sans token CSRF
351
+ response = self.client.post(reverse('detection:contact'), {
352
+ 'name': 'Test',
353
+ 'email': 'test@example.com',
354
+ 'message': 'Test'
355
+ })
356
+
357
+ # Django doit rejeter la requête sans CSRF token
358
+ self.assertEqual(response.status_code, 403)
359
+
360
+ def test_file_type_validation(self):
361
+ """Test de validation des types de fichiers"""
362
+ # Créer un fichier texte déguisé en image
363
+ fake_image = SimpleUploadedFile(
364
+ "fake.jpg",
365
+ b"This is not an image",
366
+ content_type="text/plain"
367
+ )
368
+
369
+ response = self.client.post(
370
+ reverse('detection:analyze_image'),
371
+ {'image': fake_image}
372
+ )
373
+
374
+ # La requête doit être rejetée
375
+ self.assertEqual(response.status_code, 400)
376
+
377
+ def test_large_file_rejection(self):
378
+ """Test de rejet des fichiers trop volumineux"""
379
+ # Créer un fichier de 100MB (simulé)
380
+ large_content = b"x" * (100 * 1024 * 1024) # 100MB
381
+ large_file = SimpleUploadedFile(
382
+ "large.jpg",
383
+ large_content,
384
+ content_type="image/jpeg"
385
+ )
386
+
387
+ # Note: Ce test peut ne pas fonctionner en environnement de test
388
+ # car Django limite la taille en mémoire
389
+ try:
390
+ response = self.client.post(
391
+ reverse('detection:analyze_image'),
392
+ {'image': large_file}
393
+ )
394
+ # Si la requête passe, elle doit être rejetée par notre validation
395
+ if response.status_code == 200:
396
+ data = response.json()
397
+ self.assertFalse(data.get('success', True))
398
+ except Exception:
399
+ # Exception attendue pour un fichier trop volumineux
400
+ pass
401
+
402
+
403
+ # Par Marino ATOHOUN: Commande pour lancer tous les tests
404
+ # python manage.py test detection.tests
405
+
detection/urls.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ URLs pour l'application Detection
3
+ Créé par Marino ATOHOUN - FireWatch AI Project
4
+ """
5
+ from django.urls import path
6
+ from . import views
7
+
8
+ app_name = 'detection'
9
+
10
+ urlpatterns = [
11
+ # Page principale
12
+ path('', views.index_view, name='index'),
13
+
14
+ # Formulaire de contact
15
+ path('contact/', views.contact_view, name='contact'),
16
+
17
+ # Analyse d'images
18
+ path('analyze/image/', views.analyze_image_view, name='analyze_image'),
19
+
20
+ # Analyse de vidéos
21
+ path('analyze/video/', views.analyze_video_view, name='analyze_video'),
22
+
23
+ # Analyse en temps réel (caméra)
24
+ path('analyze/camera/', views.analyze_camera_view, name='analyze_camera'),
25
+
26
+ # API pour obtenir les résultats
27
+ path('api/results/<uuid:session_id>/', views.get_results_api, name='get_results'),
28
+
29
+ # Téléchargement des résultats
30
+ path('download/results/<uuid:session_id>/', views.download_results, name='download_results'),
31
+
32
+ # Statut des modèles IA
33
+ path('api/models/status/', views.models_status_api, name='models_status'),
34
+ ]
35
+
detection/views.py ADDED
@@ -0,0 +1,630 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Vues pour l'application Detection
3
+ Créé par Marino ATOHOUN - FireWatch AI Project
4
+ """
5
+ import os
6
+ import json
7
+ import time
8
+ import uuid
9
+ import cv2
10
+ import numpy as np
11
+ from PIL import Image
12
+ from django.shortcuts import render
13
+ from django.http import JsonResponse, HttpResponse, Http404
14
+ from django.views.decorators.http import require_POST, require_GET
15
+ from django.views.decorators.csrf import csrf_exempt
16
+ from django.conf import settings
17
+ from django.core.files.storage import default_storage
18
+ from django.core.files.base import ContentFile
19
+ from django.utils import timezone
20
+ from .models import Contact, DetectionSession, Detection, AIModelStatus
21
+ import logging
22
+
23
+ # Par Marino ATOHOUN: Configuration du logging
24
+ logger = logging.getLogger(__name__)
25
+
26
+ # Par Marino ATOHOUN: Variables globales pour les modèles YOLOv8
27
+ # Ces variables seront initialisées au démarrage du serveur
28
+ fire_model = None
29
+ intrusion_model = None
30
+
31
+ # Par Marino ATOHOUN: Fonction pour charger les modèles YOLOv8
32
+ def load_yolo_models():
33
+ """
34
+ Charge les modèles YOLOv8 pour la détection d'incendie et d'intrusion
35
+ À appeler au démarrage du serveur Django
36
+ """
37
+ global fire_model, intrusion_model
38
+
39
+ try:
40
+ # Par Marino ATOHOUN: Importez YOLO ici quand vous avez ultralytics installé
41
+ # from ultralytics import YOLO
42
+
43
+ # Vérifier si les fichiers de modèles existent
44
+ fire_model_path = settings.FIRE_MODEL_PATH
45
+ intrusion_model_path = settings.INTRUSION_MODEL_PATH
46
+
47
+ # Par Marino ATOHOUN: Décommentez ces lignes quand vous avez vos modèles
48
+ # if os.path.exists(fire_model_path):
49
+ # fire_model = YOLO(fire_model_path)
50
+ # logger.info("Modèle d'incendie chargé avec succès")
51
+ #
52
+ # # Mettre à jour le statut dans la base de données
53
+ # AIModelStatus.objects.update_or_create(
54
+ # model_type='fire',
55
+ # defaults={
56
+ # 'model_path': str(fire_model_path),
57
+ # 'is_loaded': True,
58
+ # 'last_loaded': timezone.now(),
59
+ # 'model_version': '1.0'
60
+ # }
61
+ # )
62
+ # else:
63
+ # logger.warning(f"Modèle d'incendie non trouvé: {fire_model_path}")
64
+
65
+ # if os.path.exists(intrusion_model_path):
66
+ # intrusion_model = YOLO(intrusion_model_path)
67
+ # logger.info("Modèle d'intrusion chargé avec succès")
68
+ #
69
+ # # Mettre à jour le statut dans la base de données
70
+ # AIModelStatus.objects.update_or_create(
71
+ # model_type='intrusion',
72
+ # defaults={
73
+ # 'model_path': str(intrusion_model_path),
74
+ # 'is_loaded': True,
75
+ # 'last_loaded': timezone.now(),
76
+ # 'model_version': '1.0'
77
+ # }
78
+ # )
79
+ # else:
80
+ # logger.warning(f"Modèle d'intrusion non trouvé: {intrusion_model_path}")
81
+
82
+ # Par Marino ATOHOUN: Pour l'instant, on simule le chargement
83
+ logger.info("Simulation du chargement des modèles - Remplacez par le vrai code")
84
+
85
+ # Créer les entrées de statut pour la simulation
86
+ AIModelStatus.objects.update_or_create(
87
+ model_type='fire',
88
+ defaults={
89
+ 'model_path': str(fire_model_path),
90
+ 'is_loaded': False, # Sera True quand vous aurez les vrais modèles
91
+ 'last_loaded': None,
92
+ 'model_version': '1.0'
93
+ }
94
+ )
95
+
96
+ AIModelStatus.objects.update_or_create(
97
+ model_type='intrusion',
98
+ defaults={
99
+ 'model_path': str(intrusion_model_path),
100
+ 'is_loaded': False, # Sera True quand vous aurez les vrais modèles
101
+ 'last_loaded': None,
102
+ 'model_version': '1.0'
103
+ }
104
+ )
105
+
106
+ except Exception as e:
107
+ logger.error(f"Erreur lors du chargement des modèles: {e}")
108
+
109
+
110
+ # Par Marino ATOHOUN: Fonction pour effectuer la détection sur une image
111
+ def detect_objects_in_image(image_path, session):
112
+ """
113
+ Effectue la détection d'objets sur une image
114
+ Retourne une liste de détections
115
+ """
116
+ detections = []
117
+
118
+ try:
119
+ # Par Marino ATOHOUN: Ici vous intégrerez votre code de détection YOLOv8
120
+ # Exemple de ce que vous devrez faire:
121
+
122
+ # # Charger l'image
123
+ # image = cv2.imread(image_path)
124
+ #
125
+ # # Détection avec le modèle d'incendie
126
+ # if fire_model:
127
+ # fire_results = fire_model(image)
128
+ # for result in fire_results:
129
+ # boxes = result.boxes
130
+ # if boxes is not None:
131
+ # for box in boxes:
132
+ # x1, y1, x2, y2 = box.xyxy[0].cpu().numpy()
133
+ # confidence = box.conf[0].cpu().numpy()
134
+ # class_id = int(box.cls[0].cpu().numpy())
135
+ #
136
+ # # Mapper les classes selon votre modèle
137
+ # class_name = 'fire' if class_id == 0 else 'smoke'
138
+ #
139
+ # detection = Detection.objects.create(
140
+ # session=session,
141
+ # class_name=class_name,
142
+ # confidence=float(confidence),
143
+ # bbox_x=float(x1),
144
+ # bbox_y=float(y1),
145
+ # bbox_width=float(x2 - x1),
146
+ # bbox_height=float(y2 - y1)
147
+ # )
148
+ # detections.append(detection)
149
+ #
150
+ # # Détection avec le modèle d'intrusion
151
+ # if intrusion_model:
152
+ # intrusion_results = intrusion_model(image)
153
+ # # Traitement similaire...
154
+
155
+ # Par Marino ATOHOUN: Pour l'instant, on simule des détections
156
+ # Remplacez cette section par votre vraie logique de détection
157
+
158
+ # Simulation de détections aléatoires pour la démonstration
159
+ import random
160
+
161
+ simulation_detections = [
162
+ {'class_name': 'fire', 'confidence': 0.92, 'bbox': [100, 100, 150, 150]},
163
+ {'class_name': 'person', 'confidence': 0.87, 'bbox': [200, 150, 100, 200]},
164
+ {'class_name': 'smoke', 'confidence': 0.78, 'bbox': [50, 50, 80, 100]},
165
+ ]
166
+
167
+ for sim_det in simulation_detections:
168
+ if random.random() > 0.3: # 70% de chance d'apparaître
169
+ detection = Detection.objects.create(
170
+ session=session,
171
+ class_name=sim_det['class_name'],
172
+ confidence=sim_det['confidence'],
173
+ bbox_x=sim_det['bbox'][0],
174
+ bbox_y=sim_det['bbox'][1],
175
+ bbox_width=sim_det['bbox'][2],
176
+ bbox_height=sim_det['bbox'][3]
177
+ )
178
+ detections.append(detection)
179
+
180
+ logger.info(f"Détections simulées créées: {len(detections)}")
181
+
182
+ except Exception as e:
183
+ logger.error(f"Erreur lors de la détection: {e}")
184
+
185
+ return detections
186
+
187
+
188
+ # Par Marino ATOHOUN: Fonction pour dessiner les boîtes de détection
189
+ def draw_detections_on_image(image_path, detections, output_path):
190
+ """
191
+ Dessine les boîtes de détection sur l'image et sauvegarde le résultat
192
+ """
193
+ try:
194
+ # Par Marino ATOHOUN: Ici vous dessinerez les vraies boîtes de détection
195
+ # Exemple de ce que vous devrez faire:
196
+
197
+ # image = cv2.imread(image_path)
198
+ #
199
+ # for detection in detections:
200
+ # x1 = int(detection.bbox_x)
201
+ # y1 = int(detection.bbox_y)
202
+ # x2 = int(detection.bbox_x + detection.bbox_width)
203
+ # y2 = int(detection.bbox_y + detection.bbox_height)
204
+ #
205
+ # # Couleur selon le type de détection
206
+ # if detection.class_name in ['fire', 'smoke']:
207
+ # color = (0, 0, 255) # Rouge
208
+ # else:
209
+ # color = (255, 0, 0) # Bleu
210
+ #
211
+ # # Dessiner la boîte
212
+ # cv2.rectangle(image, (x1, y1), (x2, y2), color, 2)
213
+ #
214
+ # # Ajouter le label
215
+ # label = f"{detection.class_name}: {detection.confidence:.2f}"
216
+ # cv2.putText(image, label, (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
217
+ #
218
+ # # Sauvegarder l'image avec les détections
219
+ # cv2.imwrite(output_path, image)
220
+
221
+ # Par Marino ATOHOUN: Pour l'instant, on copie juste l'image originale
222
+ # Remplacez par votre vraie logique de dessin
223
+ import shutil
224
+ shutil.copy2(image_path, output_path)
225
+ logger.info(f"Image de résultat créée (simulation): {output_path}")
226
+
227
+ except Exception as e:
228
+ logger.error(f"Erreur lors du dessin des détections: {e}")
229
+
230
+
231
+ def index_view(request):
232
+ """
233
+ Vue principale - Affiche la page d'accueil
234
+ Par Marino ATOHOUN
235
+ """
236
+ return render(request, 'detection/index.html')
237
+
238
+
239
+ @require_POST
240
+ def contact_view(request):
241
+ """
242
+ Gère la soumission du formulaire de contact
243
+ Par Marino ATOHOUN
244
+ """
245
+ try:
246
+ name = request.POST.get('name', '').strip()
247
+ email = request.POST.get('email', '').strip()
248
+ message = request.POST.get('message', '').strip()
249
+
250
+ if not all([name, email, message]):
251
+ return JsonResponse({
252
+ 'success': False,
253
+ 'error': 'Tous les champs sont requis.'
254
+ }, status=400)
255
+
256
+ # Créer le contact dans la base de données
257
+ contact = Contact.objects.create(
258
+ name=name,
259
+ email=email,
260
+ message=message
261
+ )
262
+
263
+ logger.info(f"Nouveau contact créé: {contact.id} - {name} ({email})")
264
+
265
+ return JsonResponse({
266
+ 'success': True,
267
+ 'message': 'Votre message a été envoyé avec succès! Nous vous répondrons bientôt.'
268
+ })
269
+
270
+ except Exception as e:
271
+ logger.error(f"Erreur lors de la création du contact: {e}")
272
+ return JsonResponse({
273
+ 'success': False,
274
+ 'error': 'Une erreur est survenue lors de l\'envoi du message.'
275
+ }, status=500)
276
+
277
+
278
+ @require_POST
279
+ def analyze_image_view(request):
280
+ """
281
+ Gère l'upload et l'analyse d'une image
282
+ Par Marino ATOHOUN
283
+ """
284
+ try:
285
+ if 'image' not in request.FILES:
286
+ return JsonResponse({
287
+ 'success': False,
288
+ 'error': 'Aucune image fournie.'
289
+ }, status=400)
290
+
291
+ image_file = request.FILES['image']
292
+
293
+ # Vérifier le type de fichier
294
+ allowed_types = ['image/jpeg', 'image/jpg', 'image/png', 'image/bmp']
295
+ if image_file.content_type not in allowed_types:
296
+ return JsonResponse({
297
+ 'success': False,
298
+ 'error': 'Type de fichier non supporté. Utilisez JPG, PNG ou BMP.'
299
+ }, status=400)
300
+
301
+ # Créer une session de détection
302
+ session = DetectionSession.objects.create(
303
+ detection_type='image'
304
+ )
305
+
306
+ # Sauvegarder l'image uploadée
307
+ image_path = default_storage.save(
308
+ f'uploads/images/{session.session_id}_{image_file.name}',
309
+ ContentFile(image_file.read())
310
+ )
311
+ session.original_file = image_path
312
+ session.save()
313
+
314
+ # Obtenir le chemin complet
315
+ full_image_path = os.path.join(settings.MEDIA_ROOT, image_path)
316
+
317
+ # Mesurer le temps de traitement
318
+ start_time = time.time()
319
+
320
+ # Par Marino ATOHOUN: Effectuer la détection
321
+ detections = detect_objects_in_image(full_image_path, session)
322
+
323
+ # Créer l'image de résultat avec les détections
324
+ result_filename = f'results/result_{session.session_id}.jpg'
325
+ result_path = os.path.join(settings.MEDIA_ROOT, result_filename)
326
+ os.makedirs(os.path.dirname(result_path), exist_ok=True)
327
+
328
+ draw_detections_on_image(full_image_path, detections, result_path)
329
+
330
+ # Sauvegarder le chemin du résultat
331
+ session.result_file = result_filename
332
+ session.processing_time = time.time() - start_time
333
+ session.is_processed = True
334
+ session.save()
335
+
336
+ # Préparer la réponse
337
+ detections_data = []
338
+ for detection in detections:
339
+ detections_data.append({
340
+ 'class_name': detection.class_name,
341
+ 'label': detection.get_class_name_display(),
342
+ 'confidence': detection.confidence,
343
+ 'bbox': detection.bbox_dict
344
+ })
345
+
346
+ result_image_url = request.build_absolute_uri(settings.MEDIA_URL + result_filename)
347
+
348
+ logger.info(f"Analyse d'image terminée: Session {session.session_id}, {len(detections)} détections")
349
+
350
+ return JsonResponse({
351
+ 'success': True,
352
+ 'session_id': str(session.session_id),
353
+ 'detections': detections_data,
354
+ 'result_image_url': result_image_url,
355
+ 'processing_time': session.processing_time
356
+ })
357
+
358
+ except Exception as e:
359
+ logger.error(f"Erreur lors de l'analyse d'image: {e}")
360
+ return JsonResponse({
361
+ 'success': False,
362
+ 'error': 'Une erreur est survenue lors de l\'analyse de l\'image.'
363
+ }, status=500)
364
+
365
+
366
+ @require_POST
367
+ def analyze_video_view(request):
368
+ """
369
+ Gère l'upload et l'analyse d'une vidéo
370
+ Par Marino ATOHOUN
371
+ """
372
+ try:
373
+ if 'video' not in request.FILES:
374
+ return JsonResponse({
375
+ 'success': False,
376
+ 'error': 'Aucune vidéo fournie.'
377
+ }, status=400)
378
+
379
+ video_file = request.FILES['video']
380
+
381
+ # Vérifier le type de fichier
382
+ allowed_types = ['video/mp4', 'video/avi', 'video/mov', 'video/mkv']
383
+ if video_file.content_type not in allowed_types:
384
+ return JsonResponse({
385
+ 'success': False,
386
+ 'error': 'Type de fichier non supporté. Utilisez MP4, AVI, MOV ou MKV.'
387
+ }, status=400)
388
+
389
+ # Créer une session de détection
390
+ session = DetectionSession.objects.create(
391
+ detection_type='video'
392
+ )
393
+
394
+ # Sauvegarder la vidéo uploadée
395
+ video_path = default_storage.save(
396
+ f'uploads/videos/{session.session_id}_{video_file.name}',
397
+ ContentFile(video_file.read())
398
+ )
399
+ session.original_file = video_path
400
+ session.save()
401
+
402
+ # Par Marino ATOHOUN: Ici vous intégrerez votre logique d'analyse vidéo
403
+ # Exemple de ce que vous devrez faire:
404
+
405
+ # full_video_path = os.path.join(settings.MEDIA_ROOT, video_path)
406
+ # cap = cv2.VideoCapture(full_video_path)
407
+ #
408
+ # frame_count = 0
409
+ # detections = []
410
+ #
411
+ # while True:
412
+ # ret, frame = cap.read()
413
+ # if not ret:
414
+ # break
415
+ #
416
+ # # Analyser chaque frame (ou une frame sur N pour optimiser)
417
+ # if frame_count % 30 == 0: # Analyser une frame toutes les 30
418
+ # # Sauvegarder temporairement la frame
419
+ # temp_frame_path = f'/tmp/frame_{frame_count}.jpg'
420
+ # cv2.imwrite(temp_frame_path, frame)
421
+ #
422
+ # # Analyser la frame
423
+ # frame_detections = detect_objects_in_image(temp_frame_path, session)
424
+ #
425
+ # # Ajouter le numéro de frame et timestamp
426
+ # for detection in frame_detections:
427
+ # detection.frame_number = frame_count
428
+ # detection.timestamp = frame_count / cap.get(cv2.CAP_PROP_FPS)
429
+ # detection.save()
430
+ #
431
+ # detections.extend(frame_detections)
432
+ #
433
+ # frame_count += 1
434
+ #
435
+ # cap.release()
436
+
437
+ # Par Marino ATOHOUN: Pour l'instant, simulation de l'analyse vidéo
438
+ start_time = time.time()
439
+
440
+ # Simuler quelques détections sur différentes frames
441
+ import random
442
+ simulation_detections = []
443
+
444
+ for frame_num in [0, 30, 60, 90]:
445
+ if random.random() > 0.5:
446
+ detection = Detection.objects.create(
447
+ session=session,
448
+ class_name=random.choice(['fire', 'person', 'smoke']),
449
+ confidence=random.uniform(0.7, 0.95),
450
+ bbox_x=random.uniform(50, 200),
451
+ bbox_y=random.uniform(50, 200),
452
+ bbox_width=random.uniform(80, 150),
453
+ bbox_height=random.uniform(80, 150),
454
+ frame_number=frame_num,
455
+ timestamp=frame_num / 30.0 # Supposer 30 FPS
456
+ )
457
+ simulation_detections.append(detection)
458
+
459
+ session.processing_time = time.time() - start_time
460
+ session.is_processed = True
461
+ session.save()
462
+
463
+ # Préparer la réponse
464
+ detections_data = []
465
+ for detection in simulation_detections:
466
+ detections_data.append({
467
+ 'class_name': detection.class_name,
468
+ 'label': detection.get_class_name_display(),
469
+ 'confidence': detection.confidence,
470
+ 'bbox': detection.bbox_dict,
471
+ 'frame_number': detection.frame_number,
472
+ 'timestamp': detection.timestamp
473
+ })
474
+
475
+ logger.info(f"Analyse vidéo terminée: Session {session.session_id}, {len(simulation_detections)} détections")
476
+
477
+ return JsonResponse({
478
+ 'success': True,
479
+ 'session_id': str(session.session_id),
480
+ 'detections': detections_data,
481
+ 'processing_time': session.processing_time,
482
+ 'message': 'Analyse vidéo terminée. Fonctionnalité complète disponible avec vos modèles YOLOv8.'
483
+ })
484
+
485
+ except Exception as e:
486
+ logger.error(f"Erreur lors de l'analyse vidéo: {e}")
487
+ return JsonResponse({
488
+ 'success': False,
489
+ 'error': 'Une erreur est survenue lors de l\'analyse de la vidéo.'
490
+ }, status=500)
491
+
492
+
493
+ @require_POST
494
+ def analyze_camera_view(request):
495
+ """
496
+ Gère l'analyse en temps réel depuis la caméra
497
+ Par Marino ATOHOUN
498
+ """
499
+ # Cette vue est appelée quand l'utilisateur capture une frame depuis la caméra
500
+ # Le frontend envoie l'image capturée comme un blob
501
+ return analyze_image_view(request) # Réutiliser la logique d'analyse d'image
502
+
503
+
504
+ @require_GET
505
+ def get_results_api(request, session_id):
506
+ """
507
+ API pour récupérer les résultats d'une session de détection
508
+ Par Marino ATOHOUN
509
+ """
510
+ try:
511
+ session = DetectionSession.objects.get(session_id=session_id)
512
+ detections = session.detections.all()
513
+
514
+ detections_data = []
515
+ for detection in detections:
516
+ detections_data.append({
517
+ 'class_name': detection.class_name,
518
+ 'label': detection.get_class_name_display(),
519
+ 'confidence': detection.confidence,
520
+ 'bbox': detection.bbox_dict,
521
+ 'frame_number': detection.frame_number,
522
+ 'timestamp': detection.timestamp
523
+ })
524
+
525
+ result_image_url = None
526
+ if session.result_file:
527
+ result_image_url = request.build_absolute_uri(settings.MEDIA_URL + session.result_file.name)
528
+
529
+ return JsonResponse({
530
+ 'success': True,
531
+ 'session_id': str(session.session_id),
532
+ 'detection_type': session.detection_type,
533
+ 'detections': detections_data,
534
+ 'result_image_url': result_image_url,
535
+ 'processing_time': session.processing_time,
536
+ 'is_processed': session.is_processed,
537
+ 'created_at': session.created_at.isoformat()
538
+ })
539
+
540
+ except DetectionSession.DoesNotExist:
541
+ return JsonResponse({
542
+ 'success': False,
543
+ 'error': 'Session de détection non trouvée.'
544
+ }, status=404)
545
+ except Exception as e:
546
+ logger.error(f"Erreur lors de la récupération des résultats: {e}")
547
+ return JsonResponse({
548
+ 'success': False,
549
+ 'error': 'Une erreur est survenue.'
550
+ }, status=500)
551
+
552
+
553
+ def download_results(request, session_id):
554
+ """
555
+ Permet de télécharger les résultats d'une session
556
+ Par Marino ATOHOUN
557
+ """
558
+ try:
559
+ session = DetectionSession.objects.get(session_id=session_id)
560
+
561
+ if not session.result_file:
562
+ raise Http404("Aucun fichier de résultat disponible")
563
+
564
+ # Préparer le fichier pour téléchargement
565
+ file_path = session.result_file.path
566
+
567
+ if not os.path.exists(file_path):
568
+ raise Http404("Fichier de résultat non trouvé")
569
+
570
+ with open(file_path, 'rb') as f:
571
+ response = HttpResponse(f.read(), content_type='image/jpeg')
572
+ response['Content-Disposition'] = f'attachment; filename="firewatch_result_{session_id}.jpg"'
573
+ return response
574
+
575
+ except DetectionSession.DoesNotExist:
576
+ raise Http404("Session de détection non trouvée")
577
+ except Exception as e:
578
+ logger.error(f"Erreur lors du téléchargement: {e}")
579
+ raise Http404("Erreur lors du téléchargement")
580
+
581
+
582
+ @require_GET
583
+ def models_status_api(request):
584
+ """
585
+ API pour obtenir le statut des modèles IA
586
+ Par Marino ATOHOUN
587
+ """
588
+ try:
589
+ models_status = AIModelStatus.objects.all()
590
+
591
+ status_data = []
592
+ for model in models_status:
593
+ status_data.append({
594
+ 'model_type': model.model_type,
595
+ 'model_name': model.get_model_type_display(),
596
+ 'is_loaded': model.is_loaded,
597
+ 'last_loaded': model.last_loaded.isoformat() if model.last_loaded else None,
598
+ 'model_version': model.model_version,
599
+ 'accuracy': model.accuracy
600
+ })
601
+
602
+ return JsonResponse({
603
+ 'success': True,
604
+ 'models': status_data
605
+ })
606
+
607
+ except Exception as e:
608
+ logger.error(f"Erreur lors de la récupération du statut des modèles: {e}")
609
+ return JsonResponse({
610
+ 'success': False,
611
+ 'error': 'Une erreur est survenue.'
612
+ }, status=500)
613
+
614
+
615
+ # Par Marino ATOHOUN: Initialiser les modèles au démarrage
616
+ # Cette fonction sera appelée quand Django démarre
617
+ def initialize_models():
618
+ """
619
+ Initialise les modèles YOLOv8 au démarrage de Django
620
+ """
621
+ try:
622
+ load_yolo_models()
623
+ except Exception as e:
624
+ logger.error(f"Erreur lors de l'initialisation des modèles: {e}")
625
+
626
+
627
+ # Par Marino ATOHOUN: Appeler l'initialisation
628
+ # Décommentez cette ligne quand vous aurez vos modèles
629
+ initialize_models()
630
+
docker-compose.yml ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Docker Compose pour FireWatch AI
2
+ # Créé par Marino ATOHOUN
3
+
4
+ version: '3.8'
5
+
6
+ services:
7
+ # Application Django
8
+ web:
9
+ build: .
10
+ container_name: firewatch_web
11
+ ports:
12
+ - "8000:8000"
13
+ volumes:
14
+ - ./media:/app/media
15
+ - ./models:/app/models
16
+ - ./logs:/app/logs
17
+ environment:
18
+ - DEBUG=False
19
+ - SECRET_KEY=${SECRET_KEY:-django-insecure-change-me-in-production}
20
+ - DATABASE_URL=postgresql://firewatch:firewatch123@db:5432/firewatch_db
21
+ - REDIS_URL=redis://redis:6379/0
22
+ - CELERY_BROKER_URL=redis://redis:6379/0
23
+ - DJANGO_SUPERUSER_USERNAME=admin
24
+ - DJANGO_SUPERUSER_EMAIL=admin@firewatch.ai
25
+ - DJANGO_SUPERUSER_PASSWORD=admin123
26
+ depends_on:
27
+ - db
28
+ - redis
29
+ restart: unless-stopped
30
+ networks:
31
+ - firewatch_network
32
+
33
+ # Base de données PostgreSQL
34
+ db:
35
+ image: postgres:15
36
+ container_name: firewatch_db
37
+ environment:
38
+ - POSTGRES_DB=firewatch_db
39
+ - POSTGRES_USER=firewatch
40
+ - POSTGRES_PASSWORD=firewatch123
41
+ volumes:
42
+ - postgres_data:/var/lib/postgresql/data
43
+ ports:
44
+ - "5432:5432"
45
+ restart: unless-stopped
46
+ networks:
47
+ - firewatch_network
48
+
49
+ # Redis pour le cache et Celery
50
+ redis:
51
+ image: redis:7-alpine
52
+ container_name: firewatch_redis
53
+ ports:
54
+ - "6379:6379"
55
+ volumes:
56
+ - redis_data:/data
57
+ restart: unless-stopped
58
+ networks:
59
+ - firewatch_network
60
+
61
+ # Celery Worker pour les tâches asynchrones
62
+ celery:
63
+ build: .
64
+ container_name: firewatch_celery
65
+ command: celery -A firewatch_project worker --loglevel=info
66
+ volumes:
67
+ - ./media:/app/media
68
+ - ./models:/app/models
69
+ - ./logs:/app/logs
70
+ environment:
71
+ - DEBUG=False
72
+ - SECRET_KEY=${SECRET_KEY:-django-insecure-change-me-in-production}
73
+ - DATABASE_URL=postgresql://firewatch:firewatch123@db:5432/firewatch_db
74
+ - REDIS_URL=redis://redis:6379/0
75
+ - CELERY_BROKER_URL=redis://redis:6379/0
76
+ depends_on:
77
+ - db
78
+ - redis
79
+ restart: unless-stopped
80
+ networks:
81
+ - firewatch_network
82
+
83
+ # Nginx (optionnel, pour la production)
84
+ nginx:
85
+ image: nginx:alpine
86
+ container_name: firewatch_nginx
87
+ ports:
88
+ - "80:80"
89
+ - "443:443"
90
+ volumes:
91
+ - ./nginx.conf:/etc/nginx/nginx.conf
92
+ - ./media:/app/media
93
+ - ./staticfiles:/app/staticfiles
94
+ - ./ssl:/etc/nginx/ssl # Pour les certificats SSL
95
+ depends_on:
96
+ - web
97
+ restart: unless-stopped
98
+ networks:
99
+ - firewatch_network
100
+
101
+ volumes:
102
+ postgres_data:
103
+ driver: local
104
+ redis_data:
105
+ driver: local
106
+
107
+ networks:
108
+ firewatch_network:
109
+ driver: bridge
110
+
111
+ # Configuration pour le développement
112
+ # Utilisez: docker-compose -f docker-compose.yml -f docker-compose.dev.yml up
113
+ ---
114
+ # docker-compose.dev.yml
115
+ version: '3.8'
116
+
117
+ services:
118
+ web:
119
+ environment:
120
+ - DEBUG=True
121
+ - DJANGO_SUPERUSER_USERNAME=dev
122
+ - DJANGO_SUPERUSER_EMAIL=dev@firewatch.local
123
+ - DJANGO_SUPERUSER_PASSWORD=dev123
124
+ volumes:
125
+ - .:/app # Mount du code source pour le développement
126
+ ports:
127
+ - "8000:8000"
128
+ command: python manage.py runserver 0.0.0.0:8000
129
+
130
+ # Désactiver Nginx en développement
131
+ nginx:
132
+ profiles:
133
+ - production
134
+
docker-entrypoint.sh ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # Script d'entrée Docker pour FireWatch AI
3
+ # Créé par Marino ATOHOUN
4
+
5
+ set -e
6
+
7
+ # Couleurs pour les logs
8
+ RED='\033[0;31m'
9
+ GREEN='\033[0;32m'
10
+ YELLOW='\033[1;33m'
11
+ BLUE='\033[0;34m'
12
+ NC='\033[0m' # No Color
13
+
14
+ echo -e "${BLUE}🔥 FireWatch AI - Démarrage du conteneur${NC}"
15
+ echo -e "${BLUE}Créé par Marino ATOHOUN${NC}"
16
+
17
+ # Fonction de logging
18
+ log() {
19
+ echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] $1${NC}"
20
+ }
21
+
22
+ error() {
23
+ echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1${NC}"
24
+ }
25
+
26
+ warning() {
27
+ echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] WARNING: $1${NC}"
28
+ }
29
+
30
+ # Attendre que la base de données soit prête (si PostgreSQL)
31
+ if [ "$DATABASE_URL" ]; then
32
+ log "Attente de la base de données..."
33
+
34
+ # Extraire l'host et le port de DATABASE_URL
35
+ if [[ $DATABASE_URL == postgresql* ]]; then
36
+ DB_HOST=$(echo $DATABASE_URL | sed -n 's/.*@\([^:]*\):.*/\1/p')
37
+ DB_PORT=$(echo $DATABASE_URL | sed -n 's/.*:\([0-9]*\)\/.*/\1/p')
38
+
39
+ if [ "$DB_HOST" ] && [ "$DB_PORT" ]; then
40
+ log "Vérification de la connexion à PostgreSQL ($DB_HOST:$DB_PORT)..."
41
+
42
+ # Attendre que PostgreSQL soit prêt
43
+ until nc -z $DB_HOST $DB_PORT; do
44
+ warning "PostgreSQL n'est pas encore prêt - attente..."
45
+ sleep 2
46
+ done
47
+
48
+ log "PostgreSQL est prêt!"
49
+ fi
50
+ fi
51
+ fi
52
+
53
+ # Exécuter les migrations
54
+ log "Exécution des migrations de base de données..."
55
+ python manage.py migrate --noinput
56
+
57
+ # Créer un superutilisateur si les variables d'environnement sont définies
58
+ if [ "$DJANGO_SUPERUSER_USERNAME" ] && [ "$DJANGO_SUPERUSER_EMAIL" ] && [ "$DJANGO_SUPERUSER_PASSWORD" ]; then
59
+ log "Création du superutilisateur..."
60
+ python manage.py shell << EOF
61
+ from django.contrib.auth import get_user_model
62
+ User = get_user_model()
63
+ if not User.objects.filter(username='$DJANGO_SUPERUSER_USERNAME').exists():
64
+ User.objects.create_superuser('$DJANGO_SUPERUSER_USERNAME', '$DJANGO_SUPERUSER_EMAIL', '$DJANGO_SUPERUSER_PASSWORD')
65
+ print('Superutilisateur créé avec succès')
66
+ else:
67
+ print('Superutilisateur existe déjà')
68
+ EOF
69
+ fi
70
+
71
+ # Collecter les fichiers statiques
72
+ log "Collecte des fichiers statiques..."
73
+ python manage.py collectstatic --noinput
74
+
75
+ # Vérifier la présence des modèles YOLOv8
76
+ log "Vérification des modèles YOLOv8..."
77
+ if [ -f "models/incendies.pt" ]; then
78
+ log "✓ Modèle d'incendie trouvé"
79
+ else
80
+ warning "✗ Modèle d'incendie non trouvé (models/incendies.pt)"
81
+ fi
82
+
83
+ if [ -f "models/intrusion.pt" ]; then
84
+ log "✓ Modèle d'intrusion trouvé"
85
+ else
86
+ warning "✗ Modèle d'intrusion non trouvé (models/intrusion.pt)"
87
+ fi
88
+
89
+ # Créer les répertoires nécessaires
90
+ log "Création des répertoires nécessaires..."
91
+ mkdir -p media/uploads/images media/uploads/videos media/results logs
92
+
93
+ # Vérifier les permissions
94
+ log "Vérification des permissions..."
95
+ if [ ! -w "media" ]; then
96
+ error "Pas de permission d'écriture sur le répertoire media"
97
+ exit 1
98
+ fi
99
+
100
+ # Afficher les informations de configuration
101
+ log "Configuration:"
102
+ echo " - DEBUG: ${DEBUG:-False}"
103
+ echo " - ALLOWED_HOSTS: ${ALLOWED_HOSTS:-localhost}"
104
+ echo " - DATABASE_URL: ${DATABASE_URL:-sqlite:///db.sqlite3}"
105
+
106
+ # Démarrer Celery en arrière-plan si Redis est configuré
107
+ if [ "$CELERY_BROKER_URL" ]; then
108
+ log "Démarrage de Celery worker..."
109
+ celery -A firewatch_project worker --loglevel=info --detach
110
+ fi
111
+
112
+ log "🚀 Démarrage de l'application FireWatch AI..."
113
+
114
+ # Exécuter la commande passée en argument
115
+ exec "$@"
116
+
firewatch_project/__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # FireWatch AI Project - Créé par Marino ATOHOUN
2
+
firewatch_project/asgi.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ASGI config for firewatch_project project.
3
+ Créé par Marino ATOHOUN - FireWatch AI Project
4
+
5
+ It exposes the ASGI callable as a module-level variable named ``application``.
6
+
7
+ For more information on this file, see
8
+ https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
9
+ """
10
+
11
+ import os
12
+
13
+ from django.core.asgi import get_asgi_application
14
+
15
+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'firewatch_project.settings')
16
+
17
+ application = get_asgi_application()
18
+
firewatch_project/settings.py ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Django settings for firewatch_project project.
3
+ Créé par Marino ATOHOUN - FireWatch AI Project
4
+
5
+ Generated by 'django-admin startproject' using Django 4.2.
6
+
7
+ For more information on this file, see
8
+ https://docs.djangoproject.com/en/4.2/topics/settings/
9
+
10
+ For the full list of settings and their values, see
11
+ https://docs.djangoproject.com/en/4.2/ref/settings/
12
+ """
13
+
14
+ from pathlib import Path
15
+ import os
16
+
17
+ # Build paths inside the project like this: BASE_DIR / 'subdir'.
18
+ BASE_DIR = Path(__file__).resolve().parent.parent
19
+
20
+
21
+ # Quick-start development settings - unsuitable for production
22
+ # See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
23
+
24
+ # SECURITY WARNING: keep the secret key used in production secret!
25
+ SECRET_KEY = 'django-insecure-firewatch-ai-marino-atohoun-2024-secret-key-change-in-production'
26
+
27
+ # SECURITY WARNING: don't run with debug turned on in production!
28
+ DEBUG = True
29
+
30
+ ALLOWED_HOSTS = ['*'] # Par Marino ATOHOUN: Permettre tous les hosts pour le développement
31
+
32
+
33
+ # Application definition
34
+
35
+ INSTALLED_APPS = [
36
+ 'django.contrib.admin',
37
+ 'django.contrib.auth',
38
+ 'django.contrib.contenttypes',
39
+ 'django.contrib.sessions',
40
+ 'django.contrib.messages',
41
+ 'django.contrib.staticfiles',
42
+ 'detection', # Par Marino ATOHOUN: Notre application de détection
43
+ ]
44
+
45
+ MIDDLEWARE = [
46
+ 'django.middleware.security.SecurityMiddleware',
47
+ 'django.contrib.sessions.middleware.SessionMiddleware',
48
+ 'django.middleware.common.CommonMiddleware',
49
+ 'django.middleware.csrf.CsrfViewMiddleware',
50
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
51
+ 'django.contrib.messages.middleware.MessageMiddleware',
52
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
53
+ ]
54
+
55
+ ROOT_URLCONF = 'firewatch_project.urls'
56
+
57
+ TEMPLATES = [
58
+ {
59
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
60
+ 'DIRS': [BASE_DIR / 'templates'],
61
+ 'APP_DIRS': True,
62
+ 'OPTIONS': {
63
+ 'context_processors': [
64
+ 'django.template.context_processors.debug',
65
+ 'django.template.context_processors.request',
66
+ 'django.contrib.auth.context_processors.auth',
67
+ 'django.contrib.messages.context_processors.messages',
68
+ ],
69
+ },
70
+ },
71
+ ]
72
+
73
+ WSGI_APPLICATION = 'firewatch_project.wsgi.application'
74
+
75
+
76
+ # Database
77
+ # https://docs.djangoproject.com/en/4.2/ref/settings/#databases
78
+
79
+ DATABASES = {
80
+ 'default': {
81
+ 'ENGINE': 'django.db.backends.sqlite3',
82
+ 'NAME': BASE_DIR / 'db.sqlite3',
83
+ }
84
+ }
85
+
86
+
87
+ # Password validation
88
+ # https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
89
+
90
+ AUTH_PASSWORD_VALIDATORS = [
91
+ {
92
+ 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
93
+ },
94
+ {
95
+ 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
96
+ },
97
+ {
98
+ 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
99
+ },
100
+ {
101
+ 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
102
+ },
103
+ ]
104
+
105
+
106
+ # Internationalization
107
+ # https://docs.djangoproject.com/en/4.2/topics/i18n/
108
+
109
+ LANGUAGE_CODE = 'fr-fr'
110
+
111
+ TIME_ZONE = 'UTC'
112
+
113
+ USE_I18N = True
114
+
115
+ USE_TZ = True
116
+
117
+
118
+ # Static files (CSS, JavaScript, Images)
119
+ # https://docs.djangoproject.com/en/4.2/howto/static-files/
120
+
121
+ STATIC_URL = '/static/'
122
+ STATIC_ROOT = BASE_DIR / 'staticfiles'
123
+
124
+ STATICFILES_DIRS = [
125
+ BASE_DIR / 'static',
126
+ ]
127
+
128
+ # Par Marino ATOHOUN: Configuration pour les fichiers uploadés
129
+ MEDIA_URL = '/media/'
130
+ MEDIA_ROOT = BASE_DIR / 'media'
131
+
132
+ # Default primary key field type
133
+ # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
134
+
135
+ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
136
+
137
+ # Par Marino ATOHOUN: Configuration pour les uploads de fichiers
138
+ FILE_UPLOAD_MAX_MEMORY_SIZE = 50 * 1024 * 1024 # 50MB
139
+ DATA_UPLOAD_MAX_MEMORY_SIZE = 50 * 1024 * 1024 # 50MB
140
+
141
+ # Par Marino ATOHOUN: Configuration CORS pour permettre les requêtes depuis le frontend
142
+ CORS_ALLOW_ALL_ORIGINS = True
143
+ CORS_ALLOW_CREDENTIALS = True
144
+
145
+ # Par Marino ATOHOUN: Configuration pour les modèles YOLOv8
146
+ YOLO_MODELS_PATH = BASE_DIR / 'models'
147
+ FIRE_MODEL_PATH = YOLO_MODELS_PATH / 'incendies.pt'
148
+ INTRUSION_MODEL_PATH = YOLO_MODELS_PATH / 'intrusion.pt'
149
+
firewatch_project/urls.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """firewatch_project URL Configuration
2
+ Créé par Marino ATOHOUN - FireWatch AI Project
3
+
4
+ The `urlpatterns` list routes URLs to views. For more information please see:
5
+ https://docs.djangoproject.com/en/4.2/topics/http/urls/
6
+ Examples:
7
+ Function views
8
+ 1. Add an import: from my_app import views
9
+ 2. Add a URL to urlpatterns: path('', views.home, name='home')
10
+ Class-based views
11
+ 1. Add an import: from other_app.views import Home
12
+ 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
13
+ Including another URLconf
14
+ 1. Import the include() function: from django.urls import include, path
15
+ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
16
+ """
17
+ from django.contrib import admin
18
+ from django.urls import path, include
19
+ from django.conf import settings
20
+ from django.conf.urls.static import static
21
+
22
+ urlpatterns = [
23
+ path('admin/', admin.site.urls),
24
+ path('', include('detection.urls')), # Par Marino ATOHOUN: Inclure les URLs de l'app detection
25
+ ]
26
+
27
+ # Par Marino ATOHOUN: Servir les fichiers media en mode développement
28
+ if settings.DEBUG:
29
+ urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
30
+ urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
31
+
firewatch_project/wsgi.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ WSGI config for firewatch_project project.
3
+ Créé par Marino ATOHOUN - FireWatch AI Project
4
+
5
+ It exposes the WSGI callable as a module-level variable named ``application``.
6
+
7
+ For more information on this file, see
8
+ https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
9
+ """
10
+
11
+ import os
12
+
13
+ from django.core.wsgi import get_wsgi_application
14
+
15
+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'firewatch_project.settings')
16
+
17
+ application = get_wsgi_application()
18
+
manage.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ """Django's command-line utility for administrative tasks.
3
+ Créé par Marino ATOHOUN - FireWatch AI Project
4
+ """
5
+ import os
6
+ import sys
7
+
8
+
9
+ def main():
10
+ """Run administrative tasks."""
11
+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'firewatch_project.settings')
12
+ try:
13
+ from django.core.management import execute_from_command_line
14
+ except ImportError as exc:
15
+ raise ImportError(
16
+ "Couldn't import Django. Are you sure it's installed and "
17
+ "available on your PYTHONPATH environment variable? Did you "
18
+ "forget to activate a virtual environment?"
19
+ ) from exc
20
+ execute_from_command_line(sys.argv)
21
+
22
+
23
+ if __name__ == '__main__':
24
+ main()
25
+
media/README.md ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Répertoire Media - FireWatch AI
2
+
3
+ **Créé par Marino ATOHOUN**
4
+
5
+ Ce répertoire contient tous les fichiers uploadés par les utilisateurs et les résultats de détection.
6
+
7
+ ## Structure
8
+
9
+ ```
10
+ media/
11
+ ├── uploads/
12
+ │ ├── images/ # Images uploadées par les utilisateurs
13
+ │ └── videos/ # Vidéos uploadées par les utilisateurs
14
+ └── results/ # Images avec détections dessinées
15
+ ```
16
+
17
+ ## Sécurité
18
+
19
+ - Les fichiers sont automatiquement renommés avec des UUID pour éviter les conflits
20
+ - Seuls certains types de fichiers sont acceptés (voir `views.py`)
21
+ - Taille maximale configurée dans `settings.py`
22
+
23
+ ## Nettoyage
24
+
25
+ Il est recommandé de nettoyer périodiquement ce répertoire pour éviter l'accumulation de fichiers.
26
+
27
+ Vous pouvez créer une tâche cron pour supprimer les fichiers anciens :
28
+ ```bash
29
+ # Supprimer les fichiers de plus de 7 jours
30
+ find /path/to/media -type f -mtime +7 -delete
31
+ ```
32
+
models/README.md ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Modèles YOLOv8 - FireWatch AI
2
+
3
+ **Créé par Marino ATOHOUN**
4
+
5
+ Ce répertoire doit contenir vos modèles YOLOv8 entraînés :
6
+
7
+ ## Fichiers requis
8
+
9
+ 1. **incendies.pt** - Modèle pour la détection d'incendie et de fumée
10
+ 2. **intrusion.pt** - Modèle pour la détection d'intrusion et de personnes
11
+
12
+ ## Instructions d'installation
13
+
14
+ 1. Placez vos fichiers de modèles `.pt` dans ce répertoire
15
+ 2. Assurez-vous que les noms correspondent exactement à ceux attendus
16
+ 3. Vérifiez que les modèles sont compatibles avec la version d'ultralytics installée
17
+
18
+ ## Configuration
19
+
20
+ Les chemins des modèles sont configurés dans `settings.py` :
21
+ - `FIRE_MODEL_PATH` : Chemin vers le modèle d'incendie
22
+ - `INTRUSION_MODEL_PATH` : Chemin vers le modèle d'intrusion
23
+
24
+ ## Utilisation
25
+
26
+ Les modèles sont chargés automatiquement au démarrage du serveur Django dans `detection/views.py`.
27
+
28
+ Pour activer le chargement des modèles :
29
+ 1. Installez ultralytics : `pip install ultralytics`
30
+ 2. Décommentez les lignes de chargement dans `load_yolo_models()`
31
+ 3. Décommentez l'appel à `initialize_models()` à la fin de `views.py`
32
+
33
+ ## Statut des modèles
34
+
35
+ Vous pouvez vérifier le statut des modèles via :
36
+ - Interface d'administration Django : `/admin/`
37
+ - API : `/api/models/status/`
38
+
requirements-dev.txt ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Requirements de développement pour FireWatch AI - Créé par Marino ATOHOUN
2
+
3
+ # Inclure les requirements de base
4
+ -r requirements.txt
5
+
6
+ # Outils de développement
7
+ django-debug-toolbar>=4.1.0
8
+ django-extensions>=3.2.0
9
+ ipython>=8.14.0
10
+ jupyter>=1.0.0
11
+
12
+ # Tests et qualité de code
13
+ pytest>=7.4.0
14
+ pytest-django>=4.5.0
15
+ pytest-cov>=4.1.0
16
+ factory-boy>=3.3.0 # Pour créer des données de test
17
+ faker>=19.0.0 # Génération de données factices
18
+
19
+ # Linting et formatage
20
+ black>=23.0.0
21
+ isort>=5.12.0
22
+ flake8>=6.0.0
23
+ mypy>=1.5.0
24
+ pre-commit>=3.3.0
25
+
26
+ # Documentation
27
+ sphinx>=7.1.0
28
+ sphinx-rtd-theme>=1.3.0
29
+
30
+ # Profiling et performance
31
+ django-silk>=5.0.0 # Profiling des requêtes
32
+ memory-profiler>=0.61.0
33
+
34
+ # Base de données de développement
35
+ sqlite3 # Inclus avec Python
36
+
requirements.txt ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Requirements pour FireWatch AI - Créé par Marino ATOHOUN
2
+ # Version Django et dépendances de base
3
+ Django>=4.2.0,<5.0.0
4
+ Pillow>=10.0.0
5
+
6
+ # Computer Vision et IA
7
+ opencv-python>=4.8.0
8
+ numpy>=1.24.0
9
+ ultralytics>=8.0.0
10
+ torch>=2.0.0
11
+ torchvision>=0.15.0
12
+
13
+ # Traitement d'images et vidéos
14
+ imageio>=2.31.0
15
+ scikit-image>=0.21.0
16
+
17
+ # Base de données et ORM
18
+ psycopg2-binary>=2.9.0 # Pour PostgreSQL en production
19
+
20
+ # API et sérialisation
21
+ djangorestframework>=3.14.0
22
+ django-cors-headers>=4.0.0
23
+
24
+ # Gestion des fichiers et stockage
25
+ django-storages>=1.13.0 # Pour le stockage cloud (AWS S3, etc.)
26
+ boto3>=1.26.0 # Pour AWS S3
27
+
28
+ # Monitoring et logging
29
+ django-extensions>=3.2.0
30
+ python-decouple>=3.8 # Pour la gestion des variables d'environnement
31
+
32
+ # Sécurité
33
+ django-ratelimit>=4.0.0 # Limitation du taux de requêtes
34
+ django-csp>=3.7 # Content Security Policy
35
+
36
+ # Développement et tests
37
+ pytest>=7.4.0
38
+ pytest-django>=4.5.0
39
+ coverage>=7.2.0
40
+ black>=23.0.0 # Formatage du code
41
+ flake8>=6.0.0 # Linting
42
+
43
+ # Production
44
+ gunicorn>=21.0.0 # Serveur WSGI pour production
45
+ whitenoise>=6.5.0 # Gestion des fichiers statiques
46
+ redis>=4.6.0 # Cache et sessions
47
+ celery>=5.3.0 # Tâches asynchrones
48
+
49
+ # Utilitaires
50
+ python-magic>=0.4.27 # Détection du type MIME
51
+ requests>=2.31.0
52
+ python-dateutil>=2.8.0
53
+
scripts/deploy.sh ADDED
@@ -0,0 +1,428 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # Script de déploiement pour FireWatch AI
3
+ # Créé par Marino ATOHOUN
4
+
5
+ set -e
6
+
7
+ # Couleurs
8
+ RED='\033[0;31m'
9
+ GREEN='\033[0;32m'
10
+ YELLOW='\033[1;33m'
11
+ BLUE='\033[0;34m'
12
+ PURPLE='\033[0;35m'
13
+ NC='\033[0m'
14
+
15
+ # Configuration
16
+ PROJECT_NAME="firewatch_project"
17
+ DEPLOY_USER="firewatch"
18
+ DEPLOY_PATH="/opt/firewatch"
19
+ SERVICE_NAME="firewatch"
20
+ NGINX_SITE="firewatch"
21
+
22
+ log() {
23
+ echo -e "${GREEN}[$(date +'%H:%M:%S')] $1${NC}"
24
+ }
25
+
26
+ error() {
27
+ echo -e "${RED}[$(date +'%H:%M:%S')] ERROR: $1${NC}"
28
+ exit 1
29
+ }
30
+
31
+ warning() {
32
+ echo -e "${YELLOW}[$(date +'%H:%M:%S')] WARNING: $1${NC}"
33
+ }
34
+
35
+ info() {
36
+ echo -e "${BLUE}[$(date +'%H:%M:%S')] INFO: $1${NC}"
37
+ }
38
+
39
+ # Banner
40
+ echo -e "${PURPLE}"
41
+ echo "🚀 ========================================= 🚀"
42
+ echo " FireWatch AI - Déploiement Production"
43
+ echo " Créé par Marino ATOHOUN"
44
+ echo "🚀 ========================================= 🚀"
45
+ echo -e "${NC}"
46
+
47
+ # Vérification des droits root
48
+ if [[ $EUID -ne 0 ]]; then
49
+ error "Ce script doit être exécuté en tant que root (sudo)"
50
+ fi
51
+
52
+ # Mode de déploiement
53
+ echo "Modes de déploiement disponibles :"
54
+ echo "1. Docker (Recommandé)"
55
+ echo "2. Installation manuelle"
56
+ echo "3. Mise à jour"
57
+ read -p "Choisissez un mode (1-3): " deploy_mode
58
+
59
+ case $deploy_mode in
60
+ 1)
61
+ log "Déploiement avec Docker..."
62
+ deploy_docker
63
+ ;;
64
+ 2)
65
+ log "Installation manuelle..."
66
+ deploy_manual
67
+ ;;
68
+ 3)
69
+ log "Mise à jour..."
70
+ update_deployment
71
+ ;;
72
+ *)
73
+ error "Mode invalide"
74
+ ;;
75
+ esac
76
+
77
+ deploy_docker() {
78
+ log "Installation de Docker et Docker Compose..."
79
+
80
+ # Installation de Docker
81
+ if ! command -v docker &> /dev/null; then
82
+ curl -fsSL https://get.docker.com -o get-docker.sh
83
+ sh get-docker.sh
84
+ rm get-docker.sh
85
+ fi
86
+
87
+ # Installation de Docker Compose
88
+ if ! command -v docker-compose &> /dev/null; then
89
+ curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
90
+ chmod +x /usr/local/bin/docker-compose
91
+ fi
92
+
93
+ # Création du répertoire de déploiement
94
+ mkdir -p $DEPLOY_PATH
95
+ cd $DEPLOY_PATH
96
+
97
+ # Copie des fichiers
98
+ if [ -f "/tmp/firewatch-deploy.tar.gz" ]; then
99
+ tar -xzf /tmp/firewatch-deploy.tar.gz
100
+ else
101
+ error "Archive de déploiement non trouvée"
102
+ fi
103
+
104
+ # Configuration des variables d'environnement
105
+ if [ ! -f ".env" ]; then
106
+ cp .env.example .env
107
+
108
+ # Génération des mots de passe
109
+ DB_PASSWORD=$(openssl rand -base64 32)
110
+ SECRET_KEY=$(python3 -c "import secrets; print(secrets.token_urlsafe(50))")
111
+
112
+ sed -i "s/SECRET_KEY=.*/SECRET_KEY=$SECRET_KEY/" .env
113
+ sed -i "s/firewatch123/$DB_PASSWORD/g" .env
114
+
115
+ warning "Veuillez éditer .env avec vos configurations spécifiques"
116
+ fi
117
+
118
+ # Démarrage des services
119
+ log "Démarrage des services Docker..."
120
+ docker-compose up -d
121
+
122
+ # Attendre que les services soient prêts
123
+ sleep 30
124
+
125
+ # Vérification
126
+ if docker-compose ps | grep -q "Up"; then
127
+ log "✓ Services Docker démarrés avec succès"
128
+ else
129
+ error "Échec du démarrage des services"
130
+ fi
131
+
132
+ # Configuration Nginx (optionnel)
133
+ setup_nginx_docker
134
+ }
135
+
136
+ deploy_manual() {
137
+ log "Installation manuelle..."
138
+
139
+ # Installation des dépendances système
140
+ apt update
141
+ apt install -y python3.11 python3.11-venv python3-pip nginx postgresql redis-server
142
+ apt install -y libgl1-mesa-glx libglib2.0-0 libsm6 libxext6 libxrender-dev
143
+
144
+ # Création de l'utilisateur de déploiement
145
+ if ! id "$DEPLOY_USER" &>/dev/null; then
146
+ useradd -m -s /bin/bash $DEPLOY_USER
147
+ log "Utilisateur $DEPLOY_USER créé"
148
+ fi
149
+
150
+ # Création du répertoire de déploiement
151
+ mkdir -p $DEPLOY_PATH
152
+ chown $DEPLOY_USER:$DEPLOY_USER $DEPLOY_PATH
153
+
154
+ # Copie et extraction du code
155
+ if [ -f "/tmp/firewatch-deploy.tar.gz" ]; then
156
+ cd $DEPLOY_PATH
157
+ tar -xzf /tmp/firewatch-deploy.tar.gz
158
+ chown -R $DEPLOY_USER:$DEPLOY_USER .
159
+ else
160
+ error "Archive de déploiement non trouvée"
161
+ fi
162
+
163
+ # Configuration de l'environnement virtuel
164
+ sudo -u $DEPLOY_USER python3.11 -m venv $DEPLOY_PATH/venv
165
+ sudo -u $DEPLOY_USER $DEPLOY_PATH/venv/bin/pip install -r $DEPLOY_PATH/requirements.txt
166
+
167
+ # Configuration de PostgreSQL
168
+ setup_postgresql
169
+
170
+ # Configuration de Redis
171
+ systemctl enable redis-server
172
+ systemctl start redis-server
173
+
174
+ # Configuration de l'application
175
+ setup_application
176
+
177
+ # Configuration du service systemd
178
+ setup_systemd_service
179
+
180
+ # Configuration Nginx
181
+ setup_nginx_manual
182
+
183
+ log "Installation manuelle terminée"
184
+ }
185
+
186
+ setup_postgresql() {
187
+ log "Configuration de PostgreSQL..."
188
+
189
+ # Démarrage de PostgreSQL
190
+ systemctl enable postgresql
191
+ systemctl start postgresql
192
+
193
+ # Création de la base de données et de l'utilisateur
194
+ sudo -u postgres psql << EOF
195
+ CREATE DATABASE firewatch_db;
196
+ CREATE USER firewatch WITH PASSWORD 'firewatch_secure_password_2024';
197
+ GRANT ALL PRIVILEGES ON DATABASE firewatch_db TO firewatch;
198
+ \q
199
+ EOF
200
+
201
+ log "✓ PostgreSQL configuré"
202
+ }
203
+
204
+ setup_application() {
205
+ log "Configuration de l'application..."
206
+
207
+ cd $DEPLOY_PATH
208
+
209
+ # Configuration des variables d'environnement
210
+ if [ ! -f ".env" ]; then
211
+ cp .env.example .env
212
+
213
+ SECRET_KEY=$(python3 -c "import secrets; print(secrets.token_urlsafe(50))")
214
+ sed -i "s/SECRET_KEY=.*/SECRET_KEY=$SECRET_KEY/" .env
215
+ sed -i "s/DEBUG=True/DEBUG=False/" .env
216
+ sed -i "s/DATABASE_URL=.*/DATABASE_URL=postgresql:\/\/firewatch:firewatch_secure_password_2024@localhost:5432\/firewatch_db/" .env
217
+ fi
218
+
219
+ # Migrations et collecte des fichiers statiques
220
+ sudo -u $DEPLOY_USER $DEPLOY_PATH/venv/bin/python manage.py migrate
221
+ sudo -u $DEPLOY_USER $DEPLOY_PATH/venv/bin/python manage.py collectstatic --noinput
222
+
223
+ # Création des répertoires
224
+ mkdir -p $DEPLOY_PATH/media $DEPLOY_PATH/logs
225
+ chown -R $DEPLOY_USER:$DEPLOY_USER $DEPLOY_PATH/media $DEPLOY_PATH/logs
226
+
227
+ log "✓ Application configurée"
228
+ }
229
+
230
+ setup_systemd_service() {
231
+ log "Configuration du service systemd..."
232
+
233
+ cat > /etc/systemd/system/$SERVICE_NAME.service << EOF
234
+ [Unit]
235
+ Description=FireWatch AI Django Application
236
+ After=network.target postgresql.service redis.service
237
+
238
+ [Service]
239
+ Type=exec
240
+ User=$DEPLOY_USER
241
+ Group=$DEPLOY_USER
242
+ WorkingDirectory=$DEPLOY_PATH
243
+ Environment=PATH=$DEPLOY_PATH/venv/bin
244
+ ExecStart=$DEPLOY_PATH/venv/bin/gunicorn --bind 127.0.0.1:8000 --workers 4 --timeout 120 $PROJECT_NAME.wsgi:application
245
+ ExecReload=/bin/kill -s HUP \$MAINPID
246
+ Restart=always
247
+ RestartSec=10
248
+
249
+ [Install]
250
+ WantedBy=multi-user.target
251
+ EOF
252
+
253
+ systemctl daemon-reload
254
+ systemctl enable $SERVICE_NAME
255
+ systemctl start $SERVICE_NAME
256
+
257
+ log "✓ Service systemd configuré et démarré"
258
+ }
259
+
260
+ setup_nginx_manual() {
261
+ log "Configuration de Nginx..."
262
+
263
+ cat > /etc/nginx/sites-available/$NGINX_SITE << EOF
264
+ server {
265
+ listen 80;
266
+ server_name _;
267
+
268
+ client_max_body_size 50M;
269
+
270
+ location /static/ {
271
+ alias $DEPLOY_PATH/staticfiles/;
272
+ expires 1y;
273
+ add_header Cache-Control "public, immutable";
274
+ }
275
+
276
+ location /media/ {
277
+ alias $DEPLOY_PATH/media/;
278
+ expires 1y;
279
+ add_header Cache-Control "public";
280
+ }
281
+
282
+ location / {
283
+ proxy_pass http://127.0.0.1:8000;
284
+ proxy_set_header Host \$host;
285
+ proxy_set_header X-Real-IP \$remote_addr;
286
+ proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
287
+ proxy_set_header X-Forwarded-Proto \$scheme;
288
+ proxy_connect_timeout 60s;
289
+ proxy_send_timeout 60s;
290
+ proxy_read_timeout 60s;
291
+ }
292
+ }
293
+ EOF
294
+
295
+ # Activation du site
296
+ ln -sf /etc/nginx/sites-available/$NGINX_SITE /etc/nginx/sites-enabled/
297
+ rm -f /etc/nginx/sites-enabled/default
298
+
299
+ # Test et redémarrage
300
+ nginx -t
301
+ systemctl enable nginx
302
+ systemctl restart nginx
303
+
304
+ log "✓ Nginx configuré"
305
+ }
306
+
307
+ setup_nginx_docker() {
308
+ log "Configuration de Nginx pour Docker..."
309
+
310
+ cat > nginx.conf << EOF
311
+ events {
312
+ worker_connections 1024;
313
+ }
314
+
315
+ http {
316
+ upstream firewatch_app {
317
+ server web:8000;
318
+ }
319
+
320
+ server {
321
+ listen 80;
322
+ server_name _;
323
+
324
+ client_max_body_size 50M;
325
+
326
+ location /static/ {
327
+ alias /app/staticfiles/;
328
+ }
329
+
330
+ location /media/ {
331
+ alias /app/media/;
332
+ }
333
+
334
+ location / {
335
+ proxy_pass http://firewatch_app;
336
+ proxy_set_header Host \$host;
337
+ proxy_set_header X-Real-IP \$remote_addr;
338
+ proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
339
+ proxy_set_header X-Forwarded-Proto \$scheme;
340
+ }
341
+ }
342
+ }
343
+ EOF
344
+
345
+ log "✓ Configuration Nginx pour Docker créée"
346
+ }
347
+
348
+ update_deployment() {
349
+ log "Mise à jour du déploiement..."
350
+
351
+ if [ -f "$DEPLOY_PATH/docker-compose.yml" ]; then
352
+ # Mise à jour Docker
353
+ cd $DEPLOY_PATH
354
+ docker-compose down
355
+
356
+ # Sauvegarde
357
+ backup_dir="/tmp/firewatch-backup-$(date +%Y%m%d-%H%M%S)"
358
+ mkdir -p $backup_dir
359
+ cp -r media models .env $backup_dir/
360
+
361
+ # Extraction de la nouvelle version
362
+ if [ -f "/tmp/firewatch-deploy.tar.gz" ]; then
363
+ tar -xzf /tmp/firewatch-deploy.tar.gz
364
+ fi
365
+
366
+ # Restauration des données
367
+ cp -r $backup_dir/* .
368
+
369
+ # Redémarrage
370
+ docker-compose up -d
371
+
372
+ else
373
+ # Mise à jour manuelle
374
+ systemctl stop $SERVICE_NAME
375
+
376
+ cd $DEPLOY_PATH
377
+
378
+ # Sauvegarde
379
+ backup_dir="/tmp/firewatch-backup-$(date +%Y%m%d-%H%M%S)"
380
+ mkdir -p $backup_dir
381
+ cp -r media models .env $backup_dir/
382
+
383
+ # Extraction de la nouvelle version
384
+ if [ -f "/tmp/firewatch-deploy.tar.gz" ]; then
385
+ tar -xzf /tmp/firewatch-deploy.tar.gz
386
+ fi
387
+
388
+ # Restauration des données
389
+ cp -r $backup_dir/* .
390
+ chown -R $DEPLOY_USER:$DEPLOY_USER .
391
+
392
+ # Mise à jour des dépendances
393
+ sudo -u $DEPLOY_USER $DEPLOY_PATH/venv/bin/pip install -r requirements.txt
394
+
395
+ # Migrations
396
+ sudo -u $DEPLOY_USER $DEPLOY_PATH/venv/bin/python manage.py migrate
397
+ sudo -u $DEPLOY_USER $DEPLOY_PATH/venv/bin/python manage.py collectstatic --noinput
398
+
399
+ # Redémarrage
400
+ systemctl start $SERVICE_NAME
401
+ systemctl restart nginx
402
+ fi
403
+
404
+ log "✓ Mise à jour terminée"
405
+ }
406
+
407
+ # Vérification finale
408
+ log "Vérification du déploiement..."
409
+ sleep 10
410
+
411
+ if curl -f http://localhost/ > /dev/null 2>&1; then
412
+ log "✅ Déploiement réussi ! FireWatch AI est accessible"
413
+ echo ""
414
+ echo -e "${GREEN}🎉 FireWatch AI est maintenant en production ! 🎉${NC}"
415
+ echo -e "${BLUE}URL: http://votre-serveur/${NC}"
416
+ echo -e "${BLUE}Admin: http://votre-serveur/admin/${NC}"
417
+ echo ""
418
+ echo -e "${YELLOW}N'oubliez pas de :${NC}"
419
+ echo "1. Configurer un nom de domaine"
420
+ echo "2. Installer un certificat SSL"
421
+ echo "3. Configurer les sauvegardes"
422
+ echo "4. Placer vos modèles YOLOv8 dans models/"
423
+ echo ""
424
+ echo -e "${PURPLE}Créé par Marino ATOHOUN${NC}"
425
+ else
426
+ error "Échec du déploiement - L'application n'est pas accessible"
427
+ fi
428
+
scripts/setup.sh ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # Script de configuration automatique pour FireWatch AI
3
+ # Créé par Marino ATOHOUN
4
+
5
+ set -e
6
+
7
+ # Couleurs pour les messages
8
+ RED='\033[0;31m'
9
+ GREEN='\033[0;32m'
10
+ YELLOW='\033[1;33m'
11
+ BLUE='\033[0;34m'
12
+ PURPLE='\033[0;35m'
13
+ NC='\033[0m' # No Color
14
+
15
+ # Fonction de logging
16
+ log() {
17
+ echo -e "${GREEN}[$(date +'%H:%M:%S')] $1${NC}"
18
+ }
19
+
20
+ error() {
21
+ echo -e "${RED}[$(date +'%H:%M:%S')] ERROR: $1${NC}"
22
+ exit 1
23
+ }
24
+
25
+ warning() {
26
+ echo -e "${YELLOW}[$(date +'%H:%M:%S')] WARNING: $1${NC}"
27
+ }
28
+
29
+ info() {
30
+ echo -e "${BLUE}[$(date +'%H:%M:%S')] INFO: $1${NC}"
31
+ }
32
+
33
+ # Banner
34
+ echo -e "${PURPLE}"
35
+ echo "🔥 ========================================= 🔥"
36
+ echo " FireWatch AI - Configuration Setup"
37
+ echo " Créé par Marino ATOHOUN"
38
+ echo "🔥 ========================================= 🔥"
39
+ echo -e "${NC}"
40
+
41
+ # Vérification des prérequis
42
+ log "Vérification des prérequis..."
43
+
44
+ # Vérifier Python
45
+ if ! command -v python3.11 &> /dev/null; then
46
+ if ! command -v python3 &> /dev/null; then
47
+ error "Python 3.11+ requis. Veuillez l'installer d'abord."
48
+ else
49
+ PYTHON_VERSION=$(python3 --version | cut -d' ' -f2 | cut -d'.' -f1,2)
50
+ if [[ $(echo "$PYTHON_VERSION < 3.11" | bc -l) -eq 1 ]]; then
51
+ error "Python 3.11+ requis. Version actuelle: $PYTHON_VERSION"
52
+ fi
53
+ PYTHON_CMD="python3"
54
+ fi
55
+ else
56
+ PYTHON_CMD="python3.11"
57
+ fi
58
+
59
+ log "✓ Python trouvé: $($PYTHON_CMD --version)"
60
+
61
+ # Vérifier pip
62
+ if ! command -v pip3 &> /dev/null; then
63
+ error "pip3 requis. Veuillez l'installer d'abord."
64
+ fi
65
+
66
+ log "✓ pip trouvé: $(pip3 --version)"
67
+
68
+ # Vérifier git
69
+ if ! command -v git &> /dev/null; then
70
+ error "git requis. Veuillez l'installer d'abord."
71
+ fi
72
+
73
+ log "✓ git trouvé: $(git --version)"
74
+
75
+ # Configuration de l'environnement virtuel
76
+ log "Configuration de l'environnement virtuel..."
77
+
78
+ if [ ! -d "venv" ]; then
79
+ info "Création de l'environnement virtuel..."
80
+ $PYTHON_CMD -m venv venv
81
+ else
82
+ info "Environnement virtuel existant trouvé"
83
+ fi
84
+
85
+ # Activation de l'environnement virtuel
86
+ log "Activation de l'environnement virtuel..."
87
+ source venv/bin/activate
88
+
89
+ # Mise à jour de pip
90
+ log "Mise à jour de pip..."
91
+ pip install --upgrade pip
92
+
93
+ # Installation des dépendances
94
+ log "Installation des dépendances Python..."
95
+ if [ -f "requirements.txt" ]; then
96
+ pip install -r requirements.txt
97
+ else
98
+ error "Fichier requirements.txt non trouvé"
99
+ fi
100
+
101
+ # Configuration des variables d'environnement
102
+ log "Configuration des variables d'environnement..."
103
+ if [ ! -f ".env" ]; then
104
+ if [ -f ".env.example" ]; then
105
+ cp .env.example .env
106
+ info "Fichier .env créé à partir de .env.example"
107
+ warning "Veuillez éditer .env avec vos configurations"
108
+ else
109
+ error "Fichier .env.example non trouvé"
110
+ fi
111
+ else
112
+ info "Fichier .env existant trouvé"
113
+ fi
114
+
115
+ # Génération d'une clé secrète Django
116
+ log "Génération d'une clé secrète Django..."
117
+ SECRET_KEY=$(python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())")
118
+ sed -i "s/SECRET_KEY=.*/SECRET_KEY=$SECRET_KEY/" .env
119
+ info "Clé secrète générée et configurée"
120
+
121
+ # Création des répertoires nécessaires
122
+ log "Création des répertoires nécessaires..."
123
+ mkdir -p media/uploads/images media/uploads/videos media/results models logs static/css static/js static/images
124
+
125
+ # Configuration de la base de données
126
+ log "Configuration de la base de données..."
127
+ python manage.py makemigrations
128
+ python manage.py migrate
129
+
130
+ # Collecte des fichiers statiques
131
+ log "Collecte des fichiers statiques..."
132
+ python manage.py collectstatic --noinput
133
+
134
+ # Vérification des modèles YOLOv8
135
+ log "Vérification des modèles YOLOv8..."
136
+ if [ -f "models/incendies.pt" ]; then
137
+ log "✓ Modèle d'incendie trouvé"
138
+ else
139
+ warning "✗ Modèle d'incendie non trouvé (models/incendies.pt)"
140
+ info "L'application fonctionnera en mode simulation"
141
+ fi
142
+
143
+ if [ -f "models/intrusion.pt" ]; then
144
+ log "✓ Modèle d'intrusion trouvé"
145
+ else
146
+ warning "✗ Modèle d'intrusion non trouvé (models/intrusion.pt)"
147
+ info "L'application fonctionnera en mode simulation"
148
+ fi
149
+
150
+ # Création d'un superutilisateur (optionnel)
151
+ echo ""
152
+ read -p "Voulez-vous créer un superutilisateur maintenant ? (y/N): " create_superuser
153
+ if [[ $create_superuser =~ ^[Yy]$ ]]; then
154
+ log "Création du superutilisateur..."
155
+ python manage.py createsuperuser
156
+ fi
157
+
158
+ # Tests de base
159
+ log "Exécution des tests de base..."
160
+ python manage.py check
161
+
162
+ # Résumé de l'installation
163
+ echo ""
164
+ echo -e "${GREEN}🎉 Configuration terminée avec succès ! 🎉${NC}"
165
+ echo ""
166
+ echo -e "${BLUE}Prochaines étapes :${NC}"
167
+ echo "1. Activez l'environnement virtuel : source venv/bin/activate"
168
+ echo "2. Lancez le serveur : python manage.py runserver"
169
+ echo "3. Ouvrez votre navigateur : http://127.0.0.1:8000"
170
+ echo "4. Accédez à l'admin : http://127.0.0.1:8000/admin/"
171
+ echo ""
172
+ echo -e "${YELLOW}Pour utiliser vos modèles YOLOv8 :${NC}"
173
+ echo "1. Placez incendies.pt et intrusion.pt dans le dossier models/"
174
+ echo "2. Décommentez les lignes de chargement dans detection/views.py"
175
+ echo "3. Redémarrez le serveur"
176
+ echo ""
177
+ echo -e "${PURPLE}FireWatch AI est prêt ! Créé par Marino ATOHOUN${NC}"
178
+
static/css/custom.css ADDED
@@ -0,0 +1,236 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * Styles personnalisés pour FireWatch AI
3
+ * Créé par Marino ATOHOUN
4
+ */
5
+
6
+ /* Variables CSS personnalisées */
7
+ :root {
8
+ --firewatch-primary: #1e40af;
9
+ --firewatch-secondary: #7c3aed;
10
+ --firewatch-accent: #ec4899;
11
+ --firewatch-dark: #0f172a;
12
+ --firewatch-light: #f1f5f9;
13
+ }
14
+
15
+ /* Styles pour les alertes personnalisées */
16
+ .firewatch-alert {
17
+ border-radius: 0.5rem;
18
+ padding: 1rem;
19
+ margin: 1rem 0;
20
+ border-left: 4px solid;
21
+ animation: slideIn 0.3s ease-out;
22
+ }
23
+
24
+ .firewatch-alert-success {
25
+ background-color: #d1fae5;
26
+ color: #065f46;
27
+ border-left-color: #10b981;
28
+ }
29
+
30
+ .firewatch-alert-error {
31
+ background-color: #fee2e2;
32
+ color: #991b1b;
33
+ border-left-color: #ef4444;
34
+ }
35
+
36
+ .firewatch-alert-info {
37
+ background-color: #dbeafe;
38
+ color: #1e40af;
39
+ border-left-color: #3b82f6;
40
+ }
41
+
42
+ /* Animation pour les alertes */
43
+ @keyframes slideIn {
44
+ from {
45
+ opacity: 0;
46
+ transform: translateY(-10px);
47
+ }
48
+ to {
49
+ opacity: 1;
50
+ transform: translateY(0);
51
+ }
52
+ }
53
+
54
+ /* Styles pour les boutons de chargement */
55
+ .firewatch-loading {
56
+ position: relative;
57
+ overflow: hidden;
58
+ }
59
+
60
+ .firewatch-loading::after {
61
+ content: '';
62
+ position: absolute;
63
+ top: 0;
64
+ left: -100%;
65
+ width: 100%;
66
+ height: 100%;
67
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
68
+ animation: loading 1.5s infinite;
69
+ }
70
+
71
+ @keyframes loading {
72
+ 0% { left: -100%; }
73
+ 100% { left: 100%; }
74
+ }
75
+
76
+ /* Styles pour les résultats de détection */
77
+ .detection-item {
78
+ transition: all 0.3s ease;
79
+ border-radius: 0.5rem;
80
+ padding: 0.75rem;
81
+ margin: 0.5rem 0;
82
+ background: rgba(255, 255, 255, 0.8);
83
+ backdrop-filter: blur(10px);
84
+ }
85
+
86
+ .detection-item:hover {
87
+ transform: translateX(5px);
88
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
89
+ }
90
+
91
+ /* Indicateurs de confiance */
92
+ .confidence-bar {
93
+ height: 4px;
94
+ border-radius: 2px;
95
+ background: linear-gradient(90deg, #ef4444, #f59e0b, #10b981);
96
+ margin-top: 0.25rem;
97
+ }
98
+
99
+ /* Styles pour la prévisualisation des fichiers */
100
+ .file-preview {
101
+ border: 2px dashed #d1d5db;
102
+ border-radius: 0.5rem;
103
+ padding: 2rem;
104
+ text-align: center;
105
+ transition: all 0.3s ease;
106
+ }
107
+
108
+ .file-preview.dragover {
109
+ border-color: var(--firewatch-accent);
110
+ background-color: rgba(236, 72, 153, 0.1);
111
+ }
112
+
113
+ .file-preview img,
114
+ .file-preview video {
115
+ max-width: 100%;
116
+ max-height: 200px;
117
+ border-radius: 0.5rem;
118
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
119
+ }
120
+
121
+ /* Styles pour les onglets */
122
+ .firewatch-tab {
123
+ position: relative;
124
+ transition: all 0.3s ease;
125
+ }
126
+
127
+ .firewatch-tab.active::after {
128
+ content: '';
129
+ position: absolute;
130
+ bottom: 0;
131
+ left: 0;
132
+ right: 0;
133
+ height: 3px;
134
+ background: linear-gradient(90deg, var(--firewatch-primary), var(--firewatch-accent));
135
+ border-radius: 2px 2px 0 0;
136
+ }
137
+
138
+ /* Styles pour les statistiques */
139
+ .stat-card {
140
+ background: linear-gradient(135deg, rgba(255,255,255,0.1), rgba(255,255,255,0.05));
141
+ backdrop-filter: blur(10px);
142
+ border: 1px solid rgba(255,255,255,0.2);
143
+ border-radius: 1rem;
144
+ padding: 1.5rem;
145
+ transition: all 0.3s ease;
146
+ }
147
+
148
+ .stat-card:hover {
149
+ transform: translateY(-5px);
150
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
151
+ }
152
+
153
+ /* Responsive design */
154
+ @media (max-width: 768px) {
155
+ .firewatch-alert {
156
+ margin: 0.5rem;
157
+ padding: 0.75rem;
158
+ }
159
+
160
+ .detection-item {
161
+ padding: 0.5rem;
162
+ }
163
+
164
+ .file-preview {
165
+ padding: 1rem;
166
+ }
167
+ }
168
+
169
+ /* Styles pour l'accessibilité */
170
+ .sr-only {
171
+ position: absolute;
172
+ width: 1px;
173
+ height: 1px;
174
+ padding: 0;
175
+ margin: -1px;
176
+ overflow: hidden;
177
+ clip: rect(0, 0, 0, 0);
178
+ white-space: nowrap;
179
+ border: 0;
180
+ }
181
+
182
+ /* Focus styles pour l'accessibilité */
183
+ button:focus,
184
+ input:focus,
185
+ textarea:focus,
186
+ select:focus {
187
+ outline: 2px solid var(--firewatch-accent);
188
+ outline-offset: 2px;
189
+ }
190
+
191
+ /* Styles pour les tooltips */
192
+ .firewatch-tooltip {
193
+ position: relative;
194
+ display: inline-block;
195
+ }
196
+
197
+ .firewatch-tooltip .tooltip-text {
198
+ visibility: hidden;
199
+ width: 200px;
200
+ background-color: var(--firewatch-dark);
201
+ color: white;
202
+ text-align: center;
203
+ border-radius: 6px;
204
+ padding: 8px;
205
+ position: absolute;
206
+ z-index: 1;
207
+ bottom: 125%;
208
+ left: 50%;
209
+ margin-left: -100px;
210
+ opacity: 0;
211
+ transition: opacity 0.3s;
212
+ font-size: 0.875rem;
213
+ }
214
+
215
+ .firewatch-tooltip:hover .tooltip-text {
216
+ visibility: visible;
217
+ opacity: 1;
218
+ }
219
+
220
+ /* Signature Marino ATOHOUN */
221
+ .marino-signature {
222
+ font-family: 'Courier New', monospace;
223
+ font-size: 0.75rem;
224
+ color: #6b7280;
225
+ text-align: center;
226
+ margin-top: 2rem;
227
+ }
228
+
229
+ .marino-signature::before {
230
+ content: "/* ";
231
+ }
232
+
233
+ .marino-signature::after {
234
+ content: " */";
235
+ }
236
+
static/js/firewatch.js ADDED
@@ -0,0 +1,401 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * JavaScript personnalisé pour FireWatch AI
3
+ * Créé par Marino ATOHOUN
4
+ */
5
+
6
+ // Namespace pour éviter les conflits
7
+ const FireWatch = {
8
+ // Configuration
9
+ config: {
10
+ maxFileSize: 50 * 1024 * 1024, // 50MB
11
+ allowedImageTypes: ['image/jpeg', 'image/jpg', 'image/png', 'image/bmp'],
12
+ allowedVideoTypes: ['video/mp4', 'video/avi', 'video/mov', 'video/mkv'],
13
+ apiEndpoints: {
14
+ analyzeImage: '/analyze/image/',
15
+ analyzeVideo: '/analyze/video/',
16
+ contact: '/contact/',
17
+ modelsStatus: '/api/models/status/'
18
+ }
19
+ },
20
+
21
+ // Utilitaires
22
+ utils: {
23
+ /**
24
+ * Obtient le token CSRF pour les requêtes AJAX
25
+ */
26
+ getCSRFToken: function() {
27
+ const token = document.querySelector('[name=csrfmiddlewaretoken]');
28
+ return token ? token.value : '';
29
+ },
30
+
31
+ /**
32
+ * Formate la taille d'un fichier en format lisible
33
+ */
34
+ formatFileSize: function(bytes) {
35
+ if (bytes === 0) return '0 Bytes';
36
+ const k = 1024;
37
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
38
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
39
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
40
+ },
41
+
42
+ /**
43
+ * Valide un fichier selon les critères définis
44
+ */
45
+ validateFile: function(file, allowedTypes) {
46
+ if (!file) {
47
+ return { valid: false, error: 'Aucun fichier sélectionné' };
48
+ }
49
+
50
+ if (file.size > FireWatch.config.maxFileSize) {
51
+ return {
52
+ valid: false,
53
+ error: `Fichier trop volumineux. Taille maximale: ${FireWatch.utils.formatFileSize(FireWatch.config.maxFileSize)}`
54
+ };
55
+ }
56
+
57
+ if (!allowedTypes.includes(file.type)) {
58
+ return {
59
+ valid: false,
60
+ error: `Type de fichier non supporté: ${file.type}`
61
+ };
62
+ }
63
+
64
+ return { valid: true };
65
+ },
66
+
67
+ /**
68
+ * Affiche une notification toast
69
+ */
70
+ showToast: function(message, type = 'info', duration = 5000) {
71
+ const toast = document.createElement('div');
72
+ toast.className = `firewatch-toast firewatch-toast-${type}`;
73
+ toast.innerHTML = `
74
+ <div class="flex items-center">
75
+ <i class="fas fa-${type === 'success' ? 'check-circle' : type === 'error' ? 'exclamation-circle' : 'info-circle'} mr-2"></i>
76
+ <span>${message}</span>
77
+ <button class="ml-auto text-lg" onclick="this.parentElement.parentElement.remove()">×</button>
78
+ </div>
79
+ `;
80
+
81
+ // Styles inline pour le toast
82
+ Object.assign(toast.style, {
83
+ position: 'fixed',
84
+ top: '20px',
85
+ right: '20px',
86
+ zIndex: '9999',
87
+ padding: '1rem',
88
+ borderRadius: '0.5rem',
89
+ boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
90
+ maxWidth: '400px',
91
+ animation: 'slideInRight 0.3s ease-out'
92
+ });
93
+
94
+ // Couleurs selon le type
95
+ const colors = {
96
+ success: { bg: '#d1fae5', text: '#065f46', border: '#10b981' },
97
+ error: { bg: '#fee2e2', text: '#991b1b', border: '#ef4444' },
98
+ info: { bg: '#dbeafe', text: '#1e40af', border: '#3b82f6' }
99
+ };
100
+
101
+ const color = colors[type] || colors.info;
102
+ Object.assign(toast.style, {
103
+ backgroundColor: color.bg,
104
+ color: color.text,
105
+ borderLeft: `4px solid ${color.border}`
106
+ });
107
+
108
+ document.body.appendChild(toast);
109
+
110
+ // Auto-suppression
111
+ setTimeout(() => {
112
+ if (toast.parentElement) {
113
+ toast.remove();
114
+ }
115
+ }, duration);
116
+ }
117
+ },
118
+
119
+ // Gestion des fichiers
120
+ fileHandler: {
121
+ /**
122
+ * Configure le drag & drop pour un élément
123
+ */
124
+ setupDragDrop: function(element, callback) {
125
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
126
+ element.addEventListener(eventName, preventDefaults, false);
127
+ });
128
+
129
+ function preventDefaults(e) {
130
+ e.preventDefault();
131
+ e.stopPropagation();
132
+ }
133
+
134
+ ['dragenter', 'dragover'].forEach(eventName => {
135
+ element.addEventListener(eventName, () => element.classList.add('dragover'), false);
136
+ });
137
+
138
+ ['dragleave', 'drop'].forEach(eventName => {
139
+ element.addEventListener(eventName, () => element.classList.remove('dragover'), false);
140
+ });
141
+
142
+ element.addEventListener('drop', function(e) {
143
+ const files = e.dataTransfer.files;
144
+ if (files.length > 0) {
145
+ callback(files[0]);
146
+ }
147
+ }, false);
148
+ },
149
+
150
+ /**
151
+ * Crée un aperçu pour un fichier image
152
+ */
153
+ createImagePreview: function(file, container) {
154
+ const reader = new FileReader();
155
+ reader.onload = function(e) {
156
+ container.innerHTML = `
157
+ <img src="${e.target.result}" alt="Aperçu" class="file-preview-image">
158
+ <p class="mt-2 text-sm text-gray-600">${file.name} (${FireWatch.utils.formatFileSize(file.size)})</p>
159
+ `;
160
+ container.classList.remove('hidden');
161
+ };
162
+ reader.readAsDataURL(file);
163
+ },
164
+
165
+ /**
166
+ * Crée un aperçu pour un fichier vidéo
167
+ */
168
+ createVideoPreview: function(file, container) {
169
+ const url = URL.createObjectURL(file);
170
+ container.innerHTML = `
171
+ <video controls class="file-preview-video">
172
+ <source src="${url}" type="${file.type}">
173
+ Votre navigateur ne supporte pas la lecture vidéo.
174
+ </video>
175
+ <p class="mt-2 text-sm text-gray-600">${file.name} (${FireWatch.utils.formatFileSize(file.size)})</p>
176
+ `;
177
+ container.classList.remove('hidden');
178
+ }
179
+ },
180
+
181
+ // Gestion de la caméra
182
+ camera: {
183
+ stream: null,
184
+ video: null,
185
+ canvas: null,
186
+
187
+ /**
188
+ * Initialise l'accès à la caméra
189
+ */
190
+ init: function(videoElement, canvasElement) {
191
+ this.video = videoElement;
192
+ this.canvas = canvasElement;
193
+ },
194
+
195
+ /**
196
+ * Démarre le flux de la caméra
197
+ */
198
+ start: function() {
199
+ return navigator.mediaDevices.getUserMedia({
200
+ video: {
201
+ width: { ideal: 1280 },
202
+ height: { ideal: 720 }
203
+ }
204
+ })
205
+ .then(stream => {
206
+ this.stream = stream;
207
+ this.video.srcObject = stream;
208
+ return stream;
209
+ });
210
+ },
211
+
212
+ /**
213
+ * Arrête le flux de la caméra
214
+ */
215
+ stop: function() {
216
+ if (this.stream) {
217
+ this.stream.getTracks().forEach(track => track.stop());
218
+ this.stream = null;
219
+ this.video.srcObject = null;
220
+ }
221
+ },
222
+
223
+ /**
224
+ * Capture une frame de la caméra
225
+ */
226
+ captureFrame: function() {
227
+ if (!this.video || this.video.videoWidth === 0) {
228
+ throw new Error('Caméra non initialisée ou pas de signal vidéo');
229
+ }
230
+
231
+ this.canvas.width = this.video.videoWidth;
232
+ this.canvas.height = this.video.videoHeight;
233
+
234
+ const ctx = this.canvas.getContext('2d');
235
+ ctx.drawImage(this.video, 0, 0);
236
+
237
+ return new Promise(resolve => {
238
+ this.canvas.toBlob(resolve, 'image/jpeg', 0.8);
239
+ });
240
+ }
241
+ },
242
+
243
+ // Gestion des résultats
244
+ results: {
245
+ /**
246
+ * Affiche les résultats de détection
247
+ */
248
+ display: function(data) {
249
+ const resultsSection = document.getElementById('results');
250
+ const resultImage = document.getElementById('result-image');
251
+ const detectionList = document.getElementById('detection-list');
252
+
253
+ // Afficher l'image de résultat
254
+ if (data.result_image_url) {
255
+ resultImage.src = data.result_image_url;
256
+ resultImage.onerror = function() {
257
+ this.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjQwIiBoZWlnaHQ9IjQ4MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZGRkIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxOCIgZmlsbD0iIzk5OSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPkltYWdlIG5vbiBkaXNwb25pYmxlPC90ZXh0Pjwvc3ZnPg==';
258
+ };
259
+ }
260
+
261
+ // Afficher les détections
262
+ detectionList.innerHTML = '';
263
+ if (data.detections && data.detections.length > 0) {
264
+ data.detections.forEach((detection, index) => {
265
+ const li = document.createElement('li');
266
+ li.className = 'detection-item flex items-center justify-between';
267
+
268
+ // Couleur selon le type de détection
269
+ const colorMap = {
270
+ 'fire': 'bg-red-500',
271
+ 'smoke': 'bg-orange-500',
272
+ 'person': 'bg-blue-500',
273
+ 'intrusion': 'bg-purple-500',
274
+ 'vehicle': 'bg-green-500'
275
+ };
276
+
277
+ const colorClass = colorMap[detection.class_name] || 'bg-gray-500';
278
+ const confidence = (detection.confidence * 100).toFixed(1);
279
+
280
+ li.innerHTML = `
281
+ <div class="flex items-center">
282
+ <span class="w-3 h-3 ${colorClass} rounded-full mr-3"></span>
283
+ <div>
284
+ <span class="font-medium">${detection.label || detection.class_name}</span>
285
+ <div class="text-sm text-gray-600">
286
+ Confiance: ${confidence}%
287
+ ${detection.frame_number !== undefined ? ` | Frame: ${detection.frame_number}` : ''}
288
+ </div>
289
+ </div>
290
+ </div>
291
+ <div class="text-right">
292
+ <div class="confidence-bar" style="width: ${confidence}%; background: linear-gradient(90deg, #ef4444, #f59e0b, #10b981);"></div>
293
+ </div>
294
+ `;
295
+
296
+ detectionList.appendChild(li);
297
+ });
298
+ } else {
299
+ detectionList.innerHTML = '<li class="text-gray-500 text-center py-4">Aucune détection trouvée</li>';
300
+ }
301
+
302
+ // Afficher la section des résultats avec animation
303
+ resultsSection.classList.remove('hidden');
304
+ resultsSection.scrollIntoView({ behavior: 'smooth' });
305
+
306
+ // Ajouter les statistiques si disponibles
307
+ if (data.processing_time) {
308
+ const statsHtml = `
309
+ <div class="mt-4 p-3 bg-gray-100 rounded-lg">
310
+ <div class="text-sm text-gray-600">
311
+ <i class="fas fa-clock mr-1"></i>
312
+ Temps de traitement: ${data.processing_time.toFixed(2)}s
313
+ </div>
314
+ </div>
315
+ `;
316
+ detectionList.insertAdjacentHTML('afterend', statsHtml);
317
+ }
318
+ }
319
+ },
320
+
321
+ // API
322
+ api: {
323
+ /**
324
+ * Envoie une requête POST avec gestion d'erreurs
325
+ */
326
+ post: function(url, formData, options = {}) {
327
+ const defaultOptions = {
328
+ method: 'POST',
329
+ headers: {
330
+ 'X-CSRFToken': FireWatch.utils.getCSRFToken()
331
+ },
332
+ body: formData
333
+ };
334
+
335
+ return fetch(url, { ...defaultOptions, ...options })
336
+ .then(response => {
337
+ if (!response.ok) {
338
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
339
+ }
340
+ return response.json();
341
+ })
342
+ .catch(error => {
343
+ console.error('Erreur API:', error);
344
+ throw error;
345
+ });
346
+ },
347
+
348
+ /**
349
+ * Vérifie le statut des modèles IA
350
+ */
351
+ checkModelsStatus: function() {
352
+ return fetch(FireWatch.config.apiEndpoints.modelsStatus)
353
+ .then(response => response.json())
354
+ .catch(error => {
355
+ console.error('Erreur lors de la vérification du statut des modèles:', error);
356
+ return { success: false, error: error.message };
357
+ });
358
+ }
359
+ },
360
+
361
+ // Initialisation
362
+ init: function() {
363
+ console.log('FireWatch AI - Initialisation par Marino ATOHOUN');
364
+
365
+ // Vérifier le statut des modèles au chargement
366
+ this.api.checkModelsStatus().then(data => {
367
+ if (data.success && data.models) {
368
+ console.log('Statut des modèles:', data.models);
369
+ }
370
+ });
371
+
372
+ // Ajouter les styles CSS dynamiquement si nécessaire
373
+ if (!document.querySelector('#firewatch-styles')) {
374
+ const style = document.createElement('style');
375
+ style.id = 'firewatch-styles';
376
+ style.textContent = `
377
+ @keyframes slideInRight {
378
+ from { transform: translateX(100%); opacity: 0; }
379
+ to { transform: translateX(0); opacity: 1; }
380
+ }
381
+ .firewatch-toast { font-family: system-ui, -apple-system, sans-serif; }
382
+ `;
383
+ document.head.appendChild(style);
384
+ }
385
+
386
+ // Message de bienvenue en mode développement
387
+ if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
388
+ console.log('%cFireWatch AI - Mode Développement', 'color: #ec4899; font-size: 16px; font-weight: bold;');
389
+ console.log('%cCréé par Marino ATOHOUN', 'color: #7c3aed; font-size: 12px;');
390
+ }
391
+ }
392
+ };
393
+
394
+ // Initialisation automatique quand le DOM est prêt
395
+ document.addEventListener('DOMContentLoaded', function() {
396
+ FireWatch.init();
397
+ });
398
+
399
+ // Export pour utilisation globale
400
+ window.FireWatch = FireWatch;
401
+