""" Configuration management pour IPWatch Charge et valide le fichier config.yaml """ import yaml from pathlib import Path from typing import Dict, Any, List, Optional from pydantic import BaseModel, Field class AppConfig(BaseModel): """Configuration de l'application""" name: str = "IPWatch" version: str = "1.0.0" debug: bool = False class NetworkConfig(BaseModel): """Configuration réseau""" cidr: str gateway: Optional[str] = None dns: Optional[List[str]] = None class ScanConfig(BaseModel): """Configuration des scans""" ping_interval: int = 60 # secondes ping_count: int = 1 # Nombre de ping par IP port_scan_interval: int = 300 # secondes parallel_pings: int = 50 timeout: float = 1.0 force_vendor_update: bool = False class PortsConfig(BaseModel): """Configuration des ports à scanner""" ranges: List[str] = ["22", "80", "443", "3389", "8080"] protocols: Optional[Dict[int, str]] = None # Mapping port -> protocole class HistoryConfig(BaseModel): """Configuration de l'historique""" retention_hours: int = 24 class UIConfig(BaseModel): """Configuration UI""" offline_transparency: float = 0.5 show_mac: bool = True show_vendor: bool = True cell_size: int = 30 font_size: int = 10 cell_gap: float = 2 details_font_size: int = 13 details_spacing: int = 2 architecture_title_font_size: int = 18 class LinksConfig(BaseModel): """Configuration des liens""" hardware_bench_url: Optional[str] = None class ColorsConfig(BaseModel): """Configuration des couleurs""" free: str = "#75715E" online_known: str = "#A6E22E" online_unknown: str = "#66D9EF" offline_known: str = "#F92672" offline_unknown: str = "#AE81FF" mac_changed: str = "#FD971F" network_device: str = "#1E3A8A" class OPNsenseConfig(BaseModel): """Configuration OPNsense API""" enabled: bool = False host: str = "" api_key: str = "" api_secret: str = "" verify_ssl: bool = False protocol: str = "http" # "http" ou "https" class DatabaseConfig(BaseModel): """Configuration base de données""" path: str = "./data/db.sqlite" class SubnetConfig(BaseModel): """Configuration d'un sous-réseau""" name: str cidr: str start: str end: str description: str class HostConfig(BaseModel): """Configuration d'un hôte avec sa localisation""" name: str location: str ip: Optional[str] = None ip_parent: Optional[str] = None ip_enfant: Optional[List[str]] = None class IPWatchConfig(BaseModel): """Configuration complète IPWatch""" model_config = {"arbitrary_types_allowed": True} app: AppConfig = Field(default_factory=AppConfig) network: NetworkConfig subnets: List[SubnetConfig] = Field(default_factory=list) ip_classes: Dict[str, Any] = Field(default_factory=dict) scan: ScanConfig = Field(default_factory=ScanConfig) ports: PortsConfig = Field(default_factory=PortsConfig) locations: List[str] = Field(default_factory=list) hosts: List[HostConfig] = Field(default_factory=list) history: HistoryConfig = Field(default_factory=HistoryConfig) ui: UIConfig = Field(default_factory=UIConfig) links: LinksConfig = Field(default_factory=LinksConfig) colors: ColorsConfig = Field(default_factory=ColorsConfig) database: DatabaseConfig = Field(default_factory=DatabaseConfig) opnsense: OPNsenseConfig = Field(default_factory=OPNsenseConfig) class ConfigManager: """Gestionnaire de configuration singleton""" _instance: Optional['ConfigManager'] = None _config: Optional[IPWatchConfig] = None def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance def load_config(self, config_path: str = "./config.yaml") -> IPWatchConfig: """Charge la configuration depuis le fichier YAML""" path = Path(config_path) if not path.exists(): raise FileNotFoundError(f"Fichier de configuration non trouvé: {config_path}") with open(path, 'r', encoding='utf-8') as f: yaml_data = yaml.safe_load(f) self._config = IPWatchConfig(**yaml_data) self._config_path = config_path return self._config def reload_config(self) -> IPWatchConfig: """Recharge la configuration depuis le fichier""" if not hasattr(self, '_config_path'): self._config_path = "./config.yaml" return self.load_config(self._config_path) @property def config(self) -> IPWatchConfig: """Retourne la configuration actuelle""" if self._config is None: raise RuntimeError("Configuration non chargée. Appelez load_config() d'abord.") return self._config # Instance globale config_manager = ConfigManager()