"""Configuration view/edit Pydantic models. Request, response, and domain models for the config router and service. """ from pydantic import BaseModel, ConfigDict, Field # --------------------------------------------------------------------------- # Jail configuration models # --------------------------------------------------------------------------- class JailConfig(BaseModel): """Configuration snapshot of a single jail (editable fields).""" model_config = ConfigDict(strict=True) name: str = Field(..., description="Jail name as configured in fail2ban.") ban_time: int = Field(..., description="Ban duration in seconds. -1 for permanent.") max_retry: int = Field(..., ge=1, description="Number of failures before a ban is issued.") find_time: int = Field(..., ge=1, description="Time window (seconds) for counting failures.") fail_regex: list[str] = Field(default_factory=list, description="Failure detection regex patterns.") ignore_regex: list[str] = Field(default_factory=list, description="Regex patterns that bypass the ban logic.") log_paths: list[str] = Field(default_factory=list, description="Monitored log files.") date_pattern: str | None = Field(default=None, description="Custom date pattern for log parsing.") log_encoding: str = Field(default="UTF-8", description="Log file encoding.") backend: str = Field(default="polling", description="Log monitoring backend.") actions: list[str] = Field(default_factory=list, description="Names of actions attached to this jail.") class JailConfigResponse(BaseModel): """Response for ``GET /api/config/jails/{name}``.""" model_config = ConfigDict(strict=True) jail: JailConfig class JailConfigListResponse(BaseModel): """Response for ``GET /api/config/jails``.""" model_config = ConfigDict(strict=True) jails: list[JailConfig] = Field(default_factory=list) total: int = Field(..., ge=0) class JailConfigUpdate(BaseModel): """Payload for ``PUT /api/config/jails/{name}``.""" model_config = ConfigDict(strict=True) ban_time: int | None = Field(default=None, description="Ban duration in seconds. -1 for permanent.") max_retry: int | None = Field(default=None, ge=1) find_time: int | None = Field(default=None, ge=1) fail_regex: list[str] | None = Field(default=None, description="Failure detection regex patterns.") ignore_regex: list[str] | None = Field(default=None) date_pattern: str | None = Field(default=None) dns_mode: str | None = Field(default=None, description="DNS lookup mode: raw | warn | no.") enabled: bool | None = Field(default=None) # --------------------------------------------------------------------------- # Regex tester models # --------------------------------------------------------------------------- class RegexTestRequest(BaseModel): """Payload for ``POST /api/config/regex-test``.""" model_config = ConfigDict(strict=True) log_line: str = Field(..., description="Sample log line to test against.") fail_regex: str = Field(..., description="Regex pattern to match.") class RegexTestResponse(BaseModel): """Result of a regex test.""" model_config = ConfigDict(strict=True) matched: bool = Field(..., description="Whether the pattern matched the log line.") groups: list[str] = Field( default_factory=list, description="Named groups captured by a successful match.", ) error: str | None = Field( default=None, description="Compilation error message if the regex is invalid.", ) # --------------------------------------------------------------------------- # Global config models # --------------------------------------------------------------------------- class GlobalConfigResponse(BaseModel): """Response for ``GET /api/config/global``.""" model_config = ConfigDict(strict=True) log_level: str log_target: str db_purge_age: int = Field(..., description="Seconds after which ban records are purged from the fail2ban DB.") db_max_matches: int = Field(..., description="Maximum stored log-line matches per ban record.") class GlobalConfigUpdate(BaseModel): """Payload for ``PUT /api/config/global``.""" model_config = ConfigDict(strict=True) log_level: str | None = Field( default=None, description="Log level: CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG.", ) log_target: str | None = Field( default=None, description="Log target: STDOUT, STDERR, SYSLOG, SYSTEMD-JOURNAL, or a file path.", ) db_purge_age: int | None = Field(default=None, ge=0) db_max_matches: int | None = Field(default=None, ge=0) # --------------------------------------------------------------------------- # Log observation / preview models # --------------------------------------------------------------------------- class AddLogPathRequest(BaseModel): """Payload for ``POST /api/config/jails/{name}/logpath``.""" model_config = ConfigDict(strict=True) log_path: str = Field(..., description="Absolute path to the log file to monitor.") tail: bool = Field( default=True, description="If true, monitor from current end of file (tail). If false, read from the beginning.", ) class LogPreviewRequest(BaseModel): """Payload for ``POST /api/config/preview-log``.""" model_config = ConfigDict(strict=True) log_path: str = Field(..., description="Absolute path to the log file to preview.") fail_regex: str = Field(..., description="Regex pattern to test against log lines.") num_lines: int = Field(default=200, ge=1, le=5000, description="Number of lines to read from the end of the file.") class LogPreviewLine(BaseModel): """A single log line with match information.""" model_config = ConfigDict(strict=True) line: str matched: bool groups: list[str] = Field(default_factory=list) class LogPreviewResponse(BaseModel): """Response for ``POST /api/config/preview-log``.""" model_config = ConfigDict(strict=True) lines: list[LogPreviewLine] = Field(default_factory=list) total_lines: int = Field(..., ge=0) matched_count: int = Field(..., ge=0) regex_error: str | None = Field(default=None, description="Set if the regex failed to compile.") # --------------------------------------------------------------------------- # Map color threshold models # --------------------------------------------------------------------------- class MapColorThresholdsResponse(BaseModel): """Response for ``GET /api/config/map-thresholds``.""" model_config = ConfigDict(strict=True) threshold_high: int = Field( ..., description="Ban count for red coloring." ) threshold_medium: int = Field( ..., description="Ban count for yellow coloring." ) threshold_low: int = Field( ..., description="Ban count for green coloring." ) class MapColorThresholdsUpdate(BaseModel): """Payload for ``PUT /api/config/map-thresholds``.""" model_config = ConfigDict(strict=True) threshold_high: int = Field(..., gt=0, description="Ban count for red.") threshold_medium: int = Field( ..., gt=0, description="Ban count for yellow." ) threshold_low: int = Field(..., gt=0, description="Ban count for green.")