""" Schémas Pydantic pour les tâches - modèles API. """ from datetime import datetime, timezone from typing import Optional, List, Literal, Dict, Any from pydantic import BaseModel, Field, ConfigDict, field_validator class Task(BaseModel): """Modèle complet d'une tâche pour l'API.""" id: str name: str host: str status: Literal["pending", "running", "completed", "failed", "cancelled"] = "pending" progress: int = Field(ge=0, le=100, default=0) start_time: Optional[datetime] = None end_time: Optional[datetime] = None duration: Optional[str] = None output: Optional[str] = None error: Optional[str] = None model_config = ConfigDict( json_encoders={datetime: lambda v: v.isoformat() if v else None} ) class TaskRequest(BaseModel): """Requête de création d'une tâche.""" host: Optional[str] = Field(default=None, description="Hôte cible") group: Optional[str] = Field(default=None, description="Groupe cible") action: str = Field(..., description="Action à exécuter") cmd: Optional[str] = Field(default=None, description="Commande personnalisée") extra_vars: Optional[Dict[str, Any]] = Field(default=None, description="Variables Ansible") tags: Optional[List[str]] = Field(default=None, description="Tags Ansible") dry_run: bool = Field(default=False, description="Mode simulation") ssh_user: Optional[str] = Field(default=None, description="Utilisateur SSH") ssh_password: Optional[str] = Field(default=None, description="Mot de passe SSH") @field_validator('action') @classmethod def validate_action(cls, v: str) -> str: valid_actions = ['upgrade', 'reboot', 'health-check', 'backup', 'deploy', 'rollback', 'maintenance', 'bootstrap'] if v not in valid_actions: raise ValueError(f'Action doit être l\'une de: {", ".join(valid_actions)}') return v class TaskLogFile(BaseModel): """Représentation d'un fichier de log de tâche.""" id: str filename: str path: str task_name: str target: str status: str date: str # Format YYYY-MM-DD year: str month: str day: str created_at: datetime size_bytes: int start_time: Optional[str] = None end_time: Optional[str] = None duration: Optional[str] = None duration_seconds: Optional[int] = None hosts: List[str] = Field(default_factory=list) category: Optional[str] = None subcategory: Optional[str] = None target_type: Optional[str] = None source_type: Optional[str] = None class TasksFilterParams(BaseModel): """Paramètres de filtrage des tâches.""" status: Optional[str] = None year: Optional[str] = None month: Optional[str] = None day: Optional[str] = None hour_start: Optional[str] = None hour_end: Optional[str] = None target: Optional[str] = None source_type: Optional[str] = None search: Optional[str] = None limit: int = 50 offset: int = 0 class TaskLogsResponse(BaseModel): """Réponse API pour les logs de tâches.""" logs: List[dict] = Field(default_factory=list) count: int = 0 total_count: int = 0 has_more: bool = False filters: Dict[str, Optional[str]] = Field(default_factory=dict) pagination: Dict[str, int] = Field(default_factory=dict) class TaskLogDatesResponse(BaseModel): """Structure des dates disponibles pour le filtrage.""" years: Dict[str, Any] = Field(default_factory=dict) class TaskStatsResponse(BaseModel): """Statistiques des tâches.""" total: int = 0 completed: int = 0 failed: int = 0 running: int = 0 pending: int = 0 class RunningTasksResponse(BaseModel): """Réponse API pour les tâches en cours.""" tasks: List[dict] = Field(default_factory=list) count: int = 0