- Task 1: Mark imported blocklist IP addresses
- Add BanOrigin type and _derive_origin() to ban.py model
- Populate origin field in ban_service list_bans() and bans_by_country()
- BanTable and MapPage companion table show origin badge column
- Tests: origin derivation in test_ban_service.py and test_dashboard.py
- Task 2: Add origin filter to dashboard and world map
- ban_service: _origin_sql_filter() helper; origin param on list_bans()
and bans_by_country()
- dashboard router: optional origin query param forwarded to service
- Frontend: BanOriginFilter type + BAN_ORIGIN_FILTER_LABELS in ban.ts
- fetchBans / fetchBansByCountry forward origin to API
- useBans / useMapData accept and pass origin; page resets on change
- BanTable accepts origin prop; DashboardPage adds segmented filter
- MapPage adds origin Select next to time-range picker
- Tests: origin filter assertions in test_ban_service and test_dashboard
205 lines
7.2 KiB
Python
205 lines
7.2 KiB
Python
"""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.")
|