Some checks failed
Tests / Backend Tests (Python) (3.10) (push) Has been cancelled
Tests / Backend Tests (Python) (3.11) (push) Has been cancelled
Tests / Backend Tests (Python) (3.12) (push) Has been cancelled
Tests / Frontend Tests (JS) (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / All Tests Passed (push) Has been cancelled
265 lines
9.7 KiB
Python
265 lines
9.7 KiB
Python
"""
|
|
Factory pour créer l'application FastAPI.
|
|
|
|
Ce module contient la fonction create_app() qui configure et retourne
|
|
une instance FastAPI prête à l'emploi.
|
|
"""
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.staticfiles import StaticFiles
|
|
from fastapi.responses import HTMLResponse
|
|
from fastapi.responses import FileResponse
|
|
|
|
from app.core.config import settings
|
|
from app.models.database import init_db, async_session_maker
|
|
from app.routes import api_router
|
|
from app.routes.websocket import router as ws_router
|
|
|
|
|
|
def create_app() -> FastAPI:
|
|
"""Crée et configure l'application FastAPI.
|
|
|
|
Returns:
|
|
Instance FastAPI configurée avec tous les routers et middleware
|
|
"""
|
|
app = FastAPI(
|
|
title=settings.api_title,
|
|
description=settings.api_description,
|
|
version=settings.api_version,
|
|
docs_url="/docs",
|
|
redoc_url="/redoc",
|
|
)
|
|
|
|
# Configuration CORS
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=settings.cors_origins,
|
|
allow_credentials=settings.cors_allow_credentials,
|
|
allow_methods=settings.cors_allow_methods,
|
|
allow_headers=settings.cors_allow_headers,
|
|
)
|
|
|
|
# Monter les fichiers statiques (main.js, etc.)
|
|
app.mount("/static", StaticFiles(directory=settings.base_dir, html=False), name="static")
|
|
|
|
# Inclure les routers API
|
|
app.include_router(api_router, prefix="/api")
|
|
|
|
# Inclure le router WebSocket (sans préfixe /api)
|
|
app.include_router(ws_router)
|
|
|
|
# Routes racine
|
|
@app.get("/", response_class=HTMLResponse)
|
|
async def root():
|
|
"""Page d'accueil - redirige vers le dashboard."""
|
|
# Essayer de servir index.html
|
|
index_path = settings.base_dir / "index.html"
|
|
if index_path.exists():
|
|
return index_path.read_text(encoding='utf-8')
|
|
return """
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>Homelab Automation</title>
|
|
<meta http-equiv="refresh" content="0; url=/docs">
|
|
</head>
|
|
<body>
|
|
<p>Redirecting to <a href="/docs">API Documentation</a>...</p>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
@app.get("/favicon.ico", include_in_schema=False)
|
|
async def favicon():
|
|
favicon_path = settings.base_dir / "static" / "favicon.ico"
|
|
if favicon_path.exists():
|
|
return FileResponse(favicon_path)
|
|
return FileResponse(settings.base_dir / "favicon.ico")
|
|
|
|
@app.get("/api", response_class=HTMLResponse)
|
|
async def api_home():
|
|
"""Page d'accueil de l'API."""
|
|
return """
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>Homelab Automation API</title>
|
|
<style>
|
|
body { font-family: system-ui, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; }
|
|
h1 { color: #1a1a2e; }
|
|
.links { margin-top: 30px; }
|
|
.links a { display: inline-block; margin-right: 20px; padding: 10px 20px;
|
|
background: #7c3aed; color: white; text-decoration: none; border-radius: 5px; }
|
|
.links a:hover { background: #6d28d9; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>🏠 Homelab Automation API</h1>
|
|
<p>API REST pour la gestion automatisée de votre homelab avec Ansible.</p>
|
|
<div class="links">
|
|
<a href="/docs">📚 Documentation Swagger</a>
|
|
<a href="/redoc">📖 Documentation ReDoc</a>
|
|
<a href="/">🖥️ Dashboard</a>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
# Événements de démarrage et d'arrêt
|
|
@app.on_event("startup")
|
|
async def startup_event():
|
|
"""Événement de démarrage de l'application."""
|
|
# Démarrer la capture des logs console en premier
|
|
from app.services import console_log_service
|
|
console_log_service.start_capture()
|
|
|
|
print("🚀 Homelab Automation Dashboard démarré")
|
|
|
|
# Afficher les paramètres d'environnement
|
|
print("\n📋 Configuration:")
|
|
print(f" BASE_DIR: {settings.base_dir}")
|
|
print(f" ANSIBLE_DIR: {settings.ansible_dir}")
|
|
print(f" TASKS_LOGS_DIR: {settings.tasks_logs_dir}")
|
|
print(f" DATABASE_URL: {settings.async_database_url}")
|
|
print(f" SSH_KEY_PATH: {settings.ssh_key_path}")
|
|
print(f" SSH_USER: {settings.ssh_user}")
|
|
print(f" SSH_REMOTE_USER: {settings.ssh_remote_user}")
|
|
print(f" API_KEY: {'*' * 8}...{settings.api_key[-4:] if len(settings.api_key) > 4 else '****'}")
|
|
print(f" NTFY_ENABLED: {settings.ntfy_enabled}")
|
|
print(f" NTFY_BASE_URL: {settings.ntfy_base_url}")
|
|
print(f" NTFY_TOPIC: {settings.ntfy_default_topic}")
|
|
print()
|
|
|
|
# Validation des chemins critiques
|
|
validation_ok = True
|
|
if not settings.ansible_dir.exists():
|
|
print(f"⚠️ ANSIBLE_DIR n'existe pas: {settings.ansible_dir}")
|
|
validation_ok = False
|
|
else:
|
|
print(f"✅ ANSIBLE_DIR OK: {settings.ansible_dir}")
|
|
|
|
inventory_path = settings.ansible_dir / "inventory" / "hosts.yml"
|
|
if not inventory_path.exists():
|
|
print(f"⚠️ Inventaire Ansible non trouvé: {inventory_path}")
|
|
else:
|
|
print(f"✅ Inventaire Ansible OK: {inventory_path}")
|
|
|
|
playbooks_dir = settings.ansible_dir / "playbooks"
|
|
if not playbooks_dir.exists():
|
|
print(f"⚠️ Dossier playbooks non trouvé: {playbooks_dir}")
|
|
else:
|
|
playbook_count = len(list(playbooks_dir.glob("*.yml")))
|
|
print(f"✅ Dossier playbooks OK: {playbook_count} playbook(s) trouvé(s)")
|
|
print()
|
|
|
|
# Initialiser la base de données
|
|
await init_db()
|
|
print("📦 Base de données SQLite initialisée")
|
|
|
|
# Charger les services
|
|
from app.services import (
|
|
bootstrap_status_service,
|
|
scheduler_service,
|
|
notification_service,
|
|
)
|
|
from app.crud.log import LogRepository
|
|
|
|
# Charger les statuts bootstrap depuis la BD
|
|
await bootstrap_status_service.load_from_db()
|
|
|
|
# Démarrer le scheduler
|
|
await scheduler_service.start_async()
|
|
|
|
# Charger et appliquer la planification de la collecte des métriques
|
|
from app.services.metrics_collection_scheduler import load_and_apply_from_db
|
|
await load_and_apply_from_db()
|
|
|
|
# Ajouter les jobs Docker au scheduler
|
|
from app.services.docker_service import docker_service
|
|
from app.services.docker_alerts import docker_alerts_service
|
|
|
|
scheduler_service.scheduler.add_job(
|
|
docker_service.collect_all_hosts,
|
|
trigger="interval",
|
|
seconds=60, # Toutes les minutes
|
|
id="docker_collect",
|
|
name="Docker Metrics Collection",
|
|
replace_existing=True
|
|
)
|
|
|
|
scheduler_service.scheduler.add_job(
|
|
docker_alerts_service.check_all_alerts,
|
|
trigger="interval",
|
|
seconds=30, # Toutes les 30 secondes
|
|
id="docker_alerts",
|
|
name="Docker Alerts Check",
|
|
replace_existing=True
|
|
)
|
|
print("🐳 Docker monitoring jobs scheduled")
|
|
|
|
# Start terminal session GC task
|
|
from app.services.terminal_service import terminal_service
|
|
terminal_service.set_db_session_factory(async_session_maker)
|
|
await terminal_service.start_gc_task()
|
|
print("🖥️ Terminal session GC task started")
|
|
|
|
# Afficher l'état du service de notification
|
|
ntfy_status = "activé" if notification_service.enabled else "désactivé"
|
|
print(f"🔔 Service de notification ntfy: {ntfy_status} ({notification_service.config.base_url})")
|
|
|
|
# Log de démarrage en base
|
|
async with async_session_maker() as session:
|
|
repo = LogRepository(session)
|
|
await repo.create(
|
|
level="INFO",
|
|
message="Application démarrée - Services initialisés (BD)",
|
|
source="system",
|
|
)
|
|
await session.commit()
|
|
|
|
# Notification ntfy au démarrage
|
|
startup_notif = notification_service.templates.app_started()
|
|
await notification_service.send(
|
|
message=startup_notif.message,
|
|
topic=startup_notif.topic,
|
|
title=startup_notif.title,
|
|
priority=startup_notif.priority,
|
|
tags=startup_notif.tags,
|
|
)
|
|
|
|
@app.on_event("shutdown")
|
|
async def shutdown_event():
|
|
"""Événement d'arrêt de l'application."""
|
|
print("👋 Arrêt de l'application...")
|
|
|
|
from app.services import scheduler_service, notification_service
|
|
from app.services.terminal_service import terminal_service
|
|
|
|
# Arrêter la capture des logs console
|
|
from app.services import console_log_service
|
|
console_log_service.stop_capture()
|
|
|
|
# Stop terminal session GC task
|
|
await terminal_service.stop_gc_task()
|
|
print("🖥️ Terminal session GC task stopped")
|
|
|
|
# Arrêter le scheduler
|
|
scheduler_service.shutdown()
|
|
|
|
# Notification ntfy à l'arrêt
|
|
shutdown_notif = notification_service.templates.app_stopped()
|
|
await notification_service.send(
|
|
message=shutdown_notif.message,
|
|
topic=shutdown_notif.topic,
|
|
title=shutdown_notif.title,
|
|
priority=shutdown_notif.priority,
|
|
tags=shutdown_notif.tags,
|
|
)
|
|
|
|
# Fermer le client HTTP
|
|
await notification_service.close()
|
|
print("✅ Services arrêtés proprement")
|
|
|
|
return app
|