""" 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.routers import services as services_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) app.include_router(services_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 )