222 lines
6.9 KiB
Python
Executable File
222 lines
6.9 KiB
Python
Executable File
"""
|
|
Application FastAPI principale pour IPWatch
|
|
Point d'entrée du backend
|
|
"""
|
|
from fastapi import FastAPI
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.staticfiles import StaticFiles
|
|
from fastapi.responses import FileResponse
|
|
from contextlib import asynccontextmanager
|
|
from pathlib import Path
|
|
|
|
from backend.app.core.config import config_manager
|
|
from backend.app.core.database import init_database, get_db
|
|
from backend.app.routers import ips_router, scan_router, websocket_router
|
|
from backend.app.routers import architecture as architecture_router
|
|
from backend.app.routers import config as config_router
|
|
from backend.app.routers import system as system_router
|
|
from backend.app.routers import tracking as tracking_router
|
|
from backend.app.routers import opnsense as opnsense_router
|
|
from backend.app.services.scheduler import scan_scheduler
|
|
from backend.app.routers.scan import perform_scan
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
"""
|
|
Gestionnaire du cycle de vie de l'application
|
|
Initialise et nettoie les ressources
|
|
"""
|
|
# Startup
|
|
print("=== Démarrage IPWatch ===")
|
|
|
|
# 1. Charger la configuration
|
|
try:
|
|
config = config_manager.load_config("./config.yaml")
|
|
print(f"✓ Configuration chargée: {config.network.cidr}")
|
|
except Exception as e:
|
|
print(f"✗ Erreur chargement config: {e}")
|
|
raise
|
|
|
|
# 2. Initialiser la base de données
|
|
try:
|
|
init_database(config.database.path)
|
|
print(f"✓ Base de données initialisée: {config.database.path}")
|
|
except Exception as e:
|
|
print(f"✗ Erreur initialisation DB: {e}")
|
|
raise
|
|
|
|
# 3. Démarrer le scheduler
|
|
try:
|
|
scan_scheduler.start()
|
|
|
|
# Créer une session DB pour les scans planifiés
|
|
from backend.app.core.database import SessionLocal
|
|
|
|
async def scheduled_scan():
|
|
"""Wrapper pour scan planifié avec DB session"""
|
|
db = SessionLocal()
|
|
try:
|
|
await perform_scan(db)
|
|
finally:
|
|
db.close()
|
|
|
|
# Configurer les tâches périodiques
|
|
scan_scheduler.add_ping_scan_job(
|
|
scheduled_scan,
|
|
interval_seconds=config.scan.ping_interval
|
|
)
|
|
|
|
scan_scheduler.add_port_scan_job(
|
|
scheduled_scan,
|
|
interval_seconds=config.scan.port_scan_interval
|
|
)
|
|
|
|
# Tâche de nettoyage historique
|
|
async def cleanup_history():
|
|
"""Nettoie l'historique ancien"""
|
|
from backend.app.models.ip import IPHistory
|
|
from datetime import datetime, timedelta
|
|
|
|
db = SessionLocal()
|
|
try:
|
|
cutoff = datetime.utcnow() - timedelta(hours=config.history.retention_hours)
|
|
deleted = db.query(IPHistory).filter(IPHistory.timestamp < cutoff).delete()
|
|
db.commit()
|
|
print(f"Nettoyage historique: {deleted} entrées supprimées")
|
|
finally:
|
|
db.close()
|
|
|
|
scan_scheduler.add_cleanup_job(cleanup_history, interval_hours=1)
|
|
|
|
print("✓ Scheduler démarré")
|
|
except Exception as e:
|
|
print(f"✗ Erreur démarrage scheduler: {e}")
|
|
|
|
print("=== IPWatch prêt ===\n")
|
|
|
|
yield
|
|
|
|
# Shutdown
|
|
print("\n=== Arrêt IPWatch ===")
|
|
scan_scheduler.stop()
|
|
print("✓ Scheduler arrêté")
|
|
|
|
|
|
# Créer l'application FastAPI
|
|
app = FastAPI(
|
|
title="IPWatch API",
|
|
description="API backend pour IPWatch - Scanner réseau temps réel",
|
|
version="1.0.0",
|
|
lifespan=lifespan
|
|
)
|
|
|
|
# Configuration CORS pour le frontend
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["*"], # À restreindre en production
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
# Enregistrer les routers API
|
|
app.include_router(ips_router)
|
|
app.include_router(scan_router)
|
|
app.include_router(websocket_router)
|
|
app.include_router(config_router.router)
|
|
app.include_router(system_router.router)
|
|
app.include_router(tracking_router.router)
|
|
app.include_router(architecture_router.router)
|
|
app.include_router(opnsense_router.router)
|
|
|
|
# Servir les ressources d'architecture
|
|
architecture_dir = Path("./architecture")
|
|
architecture_dir.mkdir(parents=True, exist_ok=True)
|
|
app.mount("/architecture", StaticFiles(directory=str(architecture_dir)), name="architecture")
|
|
|
|
|
|
@app.get("/health")
|
|
async def health_check():
|
|
"""Health check endpoint"""
|
|
return {
|
|
"status": "healthy",
|
|
"scheduler": scan_scheduler.is_running
|
|
}
|
|
|
|
|
|
# Servir les fichiers statiques du frontend
|
|
frontend_dist = Path(__file__).parent.parent.parent / "frontend" / "dist"
|
|
|
|
if frontend_dist.exists():
|
|
# Monter les assets statiques
|
|
app.mount("/assets", StaticFiles(directory=str(frontend_dist / "assets")), name="assets")
|
|
|
|
# Monter les icônes partagées
|
|
icons_dir = Path("./data/icons")
|
|
icons_dir.mkdir(parents=True, exist_ok=True)
|
|
app.mount("/icons", StaticFiles(directory=str(icons_dir)), name="icons")
|
|
|
|
# Servir les fichiers statiques à la racine (favicon, manifest, etc.)
|
|
@app.get("/favicon.ico")
|
|
async def serve_favicon():
|
|
favicon_path = frontend_dist / "favicon.ico"
|
|
if favicon_path.exists():
|
|
return FileResponse(favicon_path)
|
|
return {"error": "Favicon non trouvée"}
|
|
|
|
# Route racine pour servir index.html
|
|
@app.get("/")
|
|
async def serve_frontend():
|
|
"""Servir le frontend Vue"""
|
|
index_file = frontend_dist / "index.html"
|
|
if index_file.exists():
|
|
return FileResponse(index_file)
|
|
return {
|
|
"name": "IPWatch API",
|
|
"version": "1.0.0",
|
|
"status": "running",
|
|
"error": "Frontend non trouvé"
|
|
}
|
|
|
|
# Catch-all pour le routing Vue (SPA)
|
|
@app.get("/{full_path:path}")
|
|
async def catch_all(full_path: str):
|
|
"""Catch-all pour le routing Vue Router"""
|
|
# Ne pas intercepter les routes API
|
|
if full_path.startswith("api/") or full_path.startswith("ws"):
|
|
return {"error": "Not found"}
|
|
|
|
# Servir les fichiers statiques à la racine si présents
|
|
if ".." not in full_path:
|
|
candidate = (frontend_dist / full_path).resolve()
|
|
if frontend_dist in candidate.parents and candidate.is_file():
|
|
return FileResponse(candidate)
|
|
|
|
# Servir index.html pour toutes les autres routes
|
|
index_file = frontend_dist / "index.html"
|
|
if index_file.exists():
|
|
return FileResponse(index_file)
|
|
return {"error": "Frontend non trouvé"}
|
|
else:
|
|
@app.get("/")
|
|
async def root():
|
|
"""Endpoint racine (mode développement sans frontend)"""
|
|
return {
|
|
"name": "IPWatch API",
|
|
"version": "1.0.0",
|
|
"status": "running",
|
|
"note": "Frontend non buildé - utilisez le mode dev"
|
|
}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
|
|
uvicorn.run(
|
|
"backend.app.main:app",
|
|
host="0.0.0.0",
|
|
port=8080,
|
|
reload=True
|
|
)
|