Commit ·
e9d86db
0
Parent(s):
first commit
Browse files- .env.example +81 -0
- .gitignore +249 -0
- CHANGELOG.md +190 -0
- Dockerfile +70 -0
- INSTALLATION.md +419 -0
- LICENSE +32 -0
- README.md +322 -0
- detection/__init__.py +2 -0
- detection/admin.py +132 -0
- detection/apps.py +12 -0
- detection/migrations/0001_initial.py +252 -0
- detection/migrations/__init__.py +2 -0
- detection/models.py +149 -0
- detection/templates/detection/index.html +718 -0
- detection/tests.py +405 -0
- detection/urls.py +35 -0
- detection/views.py +630 -0
- docker-compose.yml +134 -0
- docker-entrypoint.sh +116 -0
- firewatch_project/__init__.py +2 -0
- firewatch_project/asgi.py +18 -0
- firewatch_project/settings.py +149 -0
- firewatch_project/urls.py +31 -0
- firewatch_project/wsgi.py +18 -0
- manage.py +25 -0
- media/README.md +32 -0
- models/README.md +38 -0
- requirements-dev.txt +36 -0
- requirements.txt +53 -0
- scripts/deploy.sh +428 -0
- scripts/setup.sh +178 -0
- static/css/custom.css +236 -0
- static/js/firewatch.js +401 -0
.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>© 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 |
+
|