Rename GET/PUT /api/config/actions/{name} to /actions/{name}/raw in
file_config.py to eliminate the route-shadowing conflict with config.py,
which registers its own GET /actions/{name} returning ActionConfig.
Add configActionRaw endpoint helper in endpoints.ts and update
fetchActionFile/updateActionFile in config.ts to use it. Add
TestGetActionFileRaw and TestUpdateActionFileRaw test classes.
- Delete RecoveryBanner.tsx component and its test
- Remove RecoveryBanner from MainLayout
- Remove onCrashDetected prop from ActivateJailDialog, JailsTab
- Remove fetchPendingRecovery, rollbackJail API functions
- Remove configJailRollback, configPendingRecovery endpoints
- Remove PendingRecovery type
- Add has_local_override field to InactiveJail model
- Update _build_inactive_jail and list_inactive_jails to compute the field
- Add delete_jail_local_override() service function
- Add DELETE /api/config/jails/{name}/local router endpoint
- Surface has_local_override in frontend InactiveJail type
- Show Deactivate Jail button in JailsTab when has_local_override is true
- Add tests: TestBuildInactiveJail, TestListInactiveJails, TestDeleteJailLocalOverride
Adds ability to reload or restart fail2ban service from the Server tab UI.
Backend changes:
- Add new restart() method to jail_service.py that sends 'restart' command
- Add new POST /api/config/restart endpoint in config router
- Endpoint returns 204 on success, 502 if fail2ban unreachable
- Includes structured logging via 'fail2ban_restarted' log entry
Frontend changes:
- Add configRestart endpoint to endpoints.ts
- Add restartFail2Ban() API function in config.ts API module
- Import ArrowSync24Regular icon from Fluent UI
- Add reload and restart button handlers to ServerTab
- Display 'Reload fail2ban' and 'Restart fail2ban' buttons in action row
- Show loading spinner during operation
- Display success/error MessageBar with appropriate feedback
- Update ServerTab docstring to document new buttons
All 115 frontend tests pass.
The Log tab provided a service health panel and log viewer. These are
consolidated into the Server tab with a new ServerHealthSection component
that encapsulates all log-related functionality.
- Extract service health panel and log viewer into ServerHealthSection component
- Add severity-based log line color coding (ERROR=red, WARNING=yellow, DEBUG=gray)
- Implement log filtering, line count selection, and auto-refresh controls
- Scroll to bottom when new log data arrives
- Render health metrics grid with version, jail count, bans, failures
- Show read-only log level and log target in health section
- Handle non-file targets with informational banner
- Import ServerHealthSection in ServerTab and render after map thresholds
- Remove LogTab component import from ConfigPage
- Remove 'log' from TabValue type
- Remove Log tab element from TabList
- Remove conditional render for LogTab
- Remove LogTab from barrel export (index.ts)
- Delete LogTab.tsx and LogTab.test.tsx files
- Update ConfigPage docstring
All 115 frontend tests pass (8 fewer due to deleted LogTab tests).
The Map tab provided a form for editing world-map color thresholds
(low, medium, high). Moving this into the Server tab consolidates all
server-side configuration in one place.
- Add map color thresholds section to ServerTab with full validation
- Load map thresholds on component mount with useEffect
- Implement auto-save for threshold changes via useAutoSave hook
- Display threshold color interpolation guide
- Remove MapTab component import from ConfigPage
- Remove 'map' from TabValue type
- Remove Map tab element from TabList
- Remove conditional render for MapTab
- Remove MapTab from barrel export (index.ts)
- Delete MapTab.tsx file
- Update ConfigPage test to remove MapTab mock
All 123 frontend tests pass.
Global tab provided the same four editable fields as Server tab:
log_level, log_target, db_purge_age, db_max_matches. Server tab already
has these fields plus additional read-only info (db_path, syslog_socket)
and a Flush Logs button.
- Add hint text to DB Purge Age and DB Max Matches fields in ServerTab
- Remove GlobalTab component import from ConfigPage
- Remove 'global' from TabValue type
- Remove Global tab element from TabList
- Remove conditional render for GlobalTab
- Remove GlobalTab from barrel export (index.ts)
- Delete GlobalTab.tsx file
- Update ConfigPage test to remove Global tab test case
All 123 frontend tests pass.
The pie and bar charts were rendering with transparent/missing colors because
resolveFluentToken queried document.documentElement for CSS custom properties.
Fluent UI v9 injects these on its own wrapper div (.fui-FluentProvider), not
on :root. Changed to query that element with a fallback to document.html.
This fixes the fill colors for all four chart components.
Task 1: Move Configuration to last position in sidebar NAV_ITEMS
Task 2: Add automatic rollback when jail activation fails
- Back up .local override file before writing
- Restore original file (or delete) on reload failure, health-check
failure, or jail not appearing post-reload
- Return recovered=True/False in JailActivationResponse
- Show warning/critical banner in ActivateJailDialog based on recovery
- Add _restore_local_file_sync and _rollback_activation_async helpers
- Add 3 new tests: rollback on reload failure, health-check failure,
and double failure (recovered=False)
Task 3: Color pie chart legend labels to match their slice color
- legendFormatter now returns ReactNode with span style={{ color }}
- Import LegendPayload from recharts/types/component/DefaultLegendContent
Implements the missing UI control for POST /api/jails/{name}/ignoreself:
- Add jailIgnoreSelf endpoint constant to endpoints.ts
- Add toggleIgnoreSelf(name, on) API function to jails.ts
- Expose toggleIgnoreSelf action from useJailDetail hook
- Replace read-only 'ignore self' badge with a Fluent Switch in
IgnoreListSection to allow enabling/disabling the flag per jail
- Add 5 vitest tests for checked/unchecked state and toggle behaviour
Two frontend bugs and one mypy false positive fixed:
- ActivateJailDialog: Activate button was never disabled when
blockingIssues.length > 0 (missing condition in disabled prop).
- ActivateJailDialog: handleConfirm called onActivated() even when
the backend returned active=false (blocked activation). Dialog now
stays open and shows result.message instead.
- config.py: Settings() call flagged by mypy --strict because
pydantic-settings loads required fields from env vars at runtime;
suppressed with a targeted type: ignore[call-arg] comment.
Tests: added ActivateJailDialog.test.tsx (5 tests covering button state,
backend-rejection handling, success path, and crash detection callback).
backend/tests/test_routers/test_file_config.py:
- TestListActionFiles.test_200_returns_files: GET /api/config/actions is
handled by config.router (registered before file_config.router), so mock
config_file_service.list_actions and assert on ActionListResponse.actions
- TestCreateActionFile.test_201_creates_file: same route conflict; mock
config_file_service.create_action and use ActionCreateRequest body format
frontend/src/components/__tests__/ConfigPageLogPath.test.tsx:
- Log paths are rendered as <Input value={path}>, not text nodes; replace
getByText() with getByDisplayValue() for both test assertions
Task 2: adds a new Log tab to the Configuration page.
Backend:
- New Pydantic models: Fail2BanLogResponse, ServiceStatusResponse
(backend/app/models/config.py)
- New service methods in config_service.py:
read_fail2ban_log() — queries socket for log target/level, validates the
resolved path against a safe-prefix allowlist (/var/log) to prevent
path traversal, then reads the tail of the file via the existing
_read_tail_lines() helper; optional substring filter applied server-side.
get_service_status() — delegates to health_service.probe() and appends
log level/target from the socket.
- New endpoints in routers/config.py:
GET /api/config/fail2ban-log?lines=200&filter=...
GET /api/config/service-status
Both require authentication; log endpoint returns 400 for non-file log
targets or path-traversal attempts, 502 when fail2ban is unreachable.
Frontend:
- New LogTab.tsx component:
Service Health panel (Running/Offline badge, version, jail count, bans,
failures, log level/target, offline warning banner).
Log viewer with color-coded lines (error=red, warning=yellow,
debug=grey), toolbar (filter input + debounce, lines selector, manual
refresh, auto-refresh with interval selector), truncation notice, and
auto-scroll to bottom on data updates.
fetchData uses Promise.allSettled so a log-read failure never hides the
service-health panel.
- Types: Fail2BanLogResponse, ServiceStatusResponse (types/config.ts)
- API functions: fetchFail2BanLog, fetchServiceStatus (api/config.ts)
- Endpoint constants (api/endpoints.ts)
- ConfigPage.tsx: Log tab added after existing tabs
Tests:
- Backend service tests: TestReadFail2BanLog (6), TestGetServiceStatus (2)
- Backend router tests: TestGetFail2BanLog (8), TestGetServiceStatus (3)
- Frontend: LogTab.test.tsx (8 tests)
Docs:
- Features.md: Log section added under Configuration View
- Architekture.md: config.py router and config_service.py descriptions updated
- Tasks.md: Task 2 marked done
The Jail page is now a pure operational view showing only jails that
fail2ban reports as active. The backend GET /api/jails already queried
only the fail2ban socket status command, so no backend changes were
needed.
Frontend changes:
- Remove Inactive Jails table, Show-inactive toggle, and all related
state (showInactive, inactiveJails, activateTarget)
- Remove fetchInactiveJails() call and loadInactive/handleActivated
callbacks
- Remove ActivateJailDialog import and usage
- Remove unused imports: useCallback, useEffect, Switch, InactiveJail
Inactive-jail discovery and activation remain fully functional via the
Configuration page Jails tab (JailsTab.tsx) — unchanged.
Add alignItems: "end" to the fieldRow grid style so that all grid
cells align their content to the bottom edge of the row. This ensures
the DNS Mode <Select> and the Date Pattern <Combobox> sit on the same
horizontal baseline even though Date Pattern carries a hint line that
makes it taller.
All other fieldRow usages have consistent hint presence across their
fields, so no visual regressions are introduced.
Task 1: Backend/LogEncoding/DatePattern dropdowns in JailConfigDetail
- Added BACKENDS, LOG_ENCODINGS, DATE_PATTERN_PRESETS constants
- Backend and Log Encoding: <Input readOnly> → <Select> (editable, auto-saves)
- Date Pattern: <Input> → <Combobox freeform> with presets
- Extended JailConfigUpdate model (backend, log_encoding) and service
- Added readOnly prop to JailConfigDetail (all fields, toggles, buttons)
- Extended RegexList with readOnly prop
Task 2: Fix raw action/filter config always blank
- Added key={selectedAction.name} to ActionDetail in ActionsTab
- Added key={selectedFilter.name} to FilterDetail in FiltersTab
Task 3: Inactive jail full GUI same as active jails
- Extended InactiveJail Pydantic model with all config fields
- Added _parse_time_to_seconds helper to config_file_service
- Updated _build_inactive_jail to populate all extended fields
- Extended InactiveJail TypeScript type to match
- Rewrote InactiveJailDetail to reuse JailConfigDetail (readOnly=true)
Task 4: Fix banaction interpolation error when activating jails
- _write_local_override_sync now includes banaction=iptables-multiport
and banaction_allports=iptables-allports in every .local file
- Remove Export tab and all its imports from ConfigPage.tsx
- Remove Refresh and Reload fail2ban buttons from JailsTab; clean up
associated state (reloading, reloadMsg, deactivating) and handlers
- Add Create Config button to Jails tab list pane (listHeader pattern);
create CreateJailDialog component that calls createJailConfigFile API
- Remove Active/Inactive and 'Has local override' badges from FilterDetail
and ActionDetail; remove now-unused Badge imports
- Replace read-only log path spans with editable Input fields in JailConfigDetail
- Export CreateJailDialog from components/config/index.ts
- Mark all 5 tasks done in Docs/Tasks.md
- ActionsTab rewritten with master/detail layout (mirrors FiltersTab)
- New AssignActionDialog and CreateActionDialog components
- ActionConfig type extended with active, used_by_jails, source_file, has_local_override
- New API functions: fetchActions, fetchAction, updateAction, createAction, deleteAction, assignActionToJail, removeActionFromJail
- useActionConfig updated to use new structured endpoints
- index.ts barrel exports updated
- Rewrite FiltersTab: use fetchFilters() for FilterConfig[] with embedded active
status; show 'Active — sshd, apache-auth' badge labels; FilterDetail sub-
component with source_file/override badges, FilterForm, Assign button, raw
config section
- New AssignFilterDialog: selects jail from enabled-jails list, calls
POST /config/jails/{name}/filter with optional fail2ban reload
- New CreateFilterDialog: name+failregex+ignoreregex form, calls
POST /config/filters, closes and selects new filter on success
- Extend ConfigListDetail: add listHeader (for Create button) and
itemBadgeLabel (for custom badge text) optional props
- Fix updateFilterFile bug: was PUT /config/filters/{name} (structured
endpoint), now correctly PUT /config/filters/{name}/raw
- Fix createFilterFile bug: was POST /config/filters, now POST /config/filters/raw
- Add updateFilter, createFilter, deleteFilter, assignFilterToJail to api/config.ts
- Add FilterUpdateRequest, FilterCreateRequest, AssignFilterRequest to types/config.ts
- Add configFiltersRaw, configJailFilter endpoints
- Tests: 24 new tests across FiltersTab, AssignFilterDialog, CreateFilterDialog
(all 89 frontend tests passing)
- Add list_filters() and get_filter() to config_file_service.py:
scans filter.d/, parses [Definition] + [Init] sections, merges .local
overrides, and cross-references running jails to set active/used_by_jails
- Add FilterConfig.active, used_by_jails, source_file, has_local_override
fields to the Pydantic model; add FilterListResponse and FilterNotFoundError
- Add GET /api/config/filters and GET /api/config/filters/{name} to config.py
- Remove the shadowed GET /api/config/filters list route from file_config.py;
rename GET /api/config/filters/{name} raw variant to /filters/{name}/raw
- Update frontend: fetchFilterFiles() adapts FilterListResponse -> ConfFilesResponse;
add fetchFilters() and fetchFilter() to api/config.ts; remove unused
fetchFilterFiles/fetchActionFiles calls from useConfigActiveStatus
- Fix ConfigPageLogPath test mock to include fetchInactiveJails and related
exports introduced by Stage 1
- Backend: 169 tests pass, mypy --strict clean, ruff clean
- Frontend: 63 tests pass, tsc --noEmit clean, eslint clean
- Backend: config_file_service.py parses jail.conf/jail.local/jail.d/*
following fail2ban merge order; discovers jails not running in fail2ban
- Backend: 3 new API endpoints (GET /jails/inactive, POST /jails/{name}/activate,
POST /jails/{name}/deactivate); moved /jails/inactive before /jails/{name}
to fix route-ordering conflict
- Frontend: ActivateJailDialog component with optional parameter overrides
- Frontend: JailsTab extended with inactive jail list and InactiveJailDetail pane
- Frontend: JailsPage JailOverviewSection shows inactive jails with toggle
- Tests: 57 service tests + 16 router tests for all new endpoints (all pass)
- Docs: Features.md, Architekture.md, Tasks.md updated; Tasks 1.1-1.5 marked done
Replace Accordion-based config tabs with the new ConfigListDetail two-pane
layout. Each tab now shows a searchable list with active/inactive badges
(active items sorted first) on the left and a structured form editor with
a collapsible raw-text export section on the right.
- ConfigListDetail: reusable two-pane master/detail layout (list + detail)
with active/inactive badges, sorted active-first, keyboard navigation,
and responsive collapse to Dropdown below 900 px
- RawConfigSection: collapsible raw-text editor with save/feedback for
any config file, backed by configurable fetch/save callbacks
- useConfigActiveStatus: hook that derives active jail, filter, and action
sets from the live jails list and jail config data
- useJailFileConfig: manages jail.local section state with dirty tracking
- useActionConfig: manages action .conf file state
- useFilterConfig: manages filter .conf file state
- useAutoSave: debounced auto-save with status indicator support
- types/config.ts: TypeScript interfaces for ActionConfig, FilterConfig,
JailFileConfig, ConfFileContent, and related request/response shapes
- api/config.ts: typed API functions for reading and writing conf files
- api/endpoints.ts: add /config/file/* endpoint constants
- Add use_dns and prefregex fields to JailConfig model (backend + frontend types)
- Add prefregex to JailConfigUpdate; validate as regex before writing
- Fetch usedns and prefregex in get_jail_config via asyncio.gather
- Write usedns and prefregex in update_jail_config
- ConfigPage JailAccordionPanel: editable date_pattern input, dns_mode
Select dropdown (yes/warn/no/raw), and prefregex input
- 8 new service unit tests + 3 new router integration tests
- 628 tests pass; 85% line coverage; ruff/mypy/tsc/eslint clean
- Backend: Add BantimeEscalation + BantimeEscalationUpdate Pydantic models
to app/models/config.py; add bantime_escalation field to Jail in jail.py
- Backend: jail_service.get_jail_detail() fetches 7 bantime.* socket commands
(increment, factor, formula, multipliers, maxtime, rndtime, overalljails)
and populates bantime_escalation on the returned Jail object
- Backend: config_service.get_jail_config() fetches same 7 commands;
update_jail_config() writes escalation fields when provided
- Frontend: Add BantimeEscalation + BantimeEscalationUpdate interfaces to
types/config.ts; extend JailConfig + JailConfigUpdate; extend Jail in
types/jail.ts
- Frontend: JailDetailPage.tsx adds BantimeEscalationSection component that
renders only when increment is enabled (shows factor, formula, multipliers,
max_time, rnd_time, overall_jails)
- Frontend: ConfigPage.tsx JailAccordionPanel adds full escalation edit form
(Switch for enable/disable, number inputs for factor/max_time/rnd_time,
text inputs for formula/multipliers, Switch for overall_jails);
handleSave includes bantime_escalation in the JailConfigUpdate payload
- Tests: Update ConfigPageLogPath.test.tsx mock to include bantime_escalation:null
- Docs: Mark Task 6 as DONE in Tasks.md
Task 4 (Better Jail Configuration) implementation:
- Add fail2ban_config_dir setting to app/config.py
- New file_config_service: list/view/edit/create jail.d, filter.d, action.d files
with path-traversal prevention and 512 KB content size limit
- New file_config router: GET/PUT/POST endpoints for jail files, filter files,
and action files; PUT .../enabled for toggle on/off
- Extend config_service with delete_log_path() and add_log_path()
- Add DELETE /api/config/jails/{name}/logpath and POST /api/config/jails/{name}/logpath
- Extend geo router with re-resolve endpoint; add geo_re_resolve background task
- Update blocklist_service with revised scheduling helpers
- Update Docker compose files with BANGUI_FAIL2BAN_CONFIG_DIR env var and
rw volume mount for the fail2ban config directory
- Frontend: new Jail Files, Filters, Actions tabs in ConfigPage; file editor
with accordion-per-file, editable textarea, save/create; add/delete log paths
- Frontend: types in types/config.ts; API calls in api/config.ts and api/endpoints.ts
- 63 new backend tests (test_file_config_service, test_file_config, test_geo_re_resolve)
- 6 new frontend tests in ConfigPageLogPath.test.tsx
- ruff, mypy --strict, tsc --noEmit, eslint: all clean; 617 backend tests pass
The JailAccordionPanel previously allowed deleting log paths but
had no UI to add new ones. The backend endpoint, API helper, and
hook all existed; only the UI was missing.
Changes:
- ConfigPage.tsx: import addLogPath/AddLogPathRequest; add state
(newLogPath, newLogPathTail, addingLogPath) and handleAddLogPath
callback to JailAccordionPanel; render inline form below the
log-path list with Input, Switch (tail/head), and labeled Add
button that appends on success and surfaces errors inline.
- ConfigPageLogPath.test.tsx: 6 tests covering render, disabled
state, enabled state, successful add, success feedback, and API
error handling. All 33 frontend tests pass.
- Create DashboardFilterBar component with time-range and origin-filter
toggle-button groups in a single card row (Stage 7, Tasks 7.1–7.3)
- Integrate filter bar below ServerStatusBar in DashboardPage; remove
filter toolbars from the Ban List section header (Task 7.2)
- Add 6 tests covering rendering, active-state reflection, and callbacks
- tsc --noEmit, eslint, npm run build, npm test all pass (27/27 tests)
- Wrap long dict literal in test_geo_service.py across multiple lines (E501)
- Combine nested with statements in test_jail_service.py (SIM117)
- Add vitest.config.ts to tsconfig.node.json include so ESLint
parserOptions.project resolves it correctly
- backend: GET /api/dashboard/bans/by-jail endpoint
- JailBanCount + BansByJailResponse Pydantic models in ban.py
- bans_by_jail() service function with origin filter support
- Route added to dashboard router
- 17 new tests (7 service, 10 router); full suite 497 passed, 83% coverage
- frontend: JailDistributionChart component
- JailBanCount / BansByJailResponse types in types/ban.ts
- dashboardBansByJail endpoint constant in api/endpoints.ts
- fetchBansByJail() in api/dashboard.ts
- useJailDistribution hook in hooks/useJailDistribution.ts
- JailDistributionChart component (horizontal bar chart, Recharts)
- DashboardPage: full-width Jail Distribution section below Top Countries
Add full-width 'Ban Trend' section card above the country charts.
BanTrendChart shares the existing timeRange / originFilter state;
loading, error, and empty states are handled inside the component.
- Add BanTrendBucket / BanTrendResponse interfaces to types/ban.ts
- Add dashboardBansTrend endpoint constant to api/endpoints.ts
- Add fetchBanTrend() to api/dashboard.ts
- Create useBanTrend hook with abort-safe data fetching
- Create BanTrendChart: AreaChart with gradient fill, dynamic
X-axis labels per range, custom tooltip, loading/error/empty states
- tsc --noEmit and ESLint pass with zero warnings
- Install Recharts v3 as the project charting library
- Add chartTheme utility with Fluent UI v9 token resolution helper
and a 5-colour categorical palette (resolves CSS vars at runtime)
- Add TopCountriesPieChart: top-4 + Other slice, Tooltip, Legend
- Add TopCountriesBarChart: horizontal top-20 bar chart
- Add useDashboardCountryData hook (wraps /api/dashboard/bans/by-country)
- Integrate both charts into DashboardPage in a responsive chartsRow
(side-by-side on wide screens, stacked on narrow)
- All tsc --noEmit and eslint checks pass with zero warnings
- Send fail2ban's `unban --all` command via new `unban_all_ips()` service
function; returns the count of unbanned IPs
- Add `UnbanAllResponse` Pydantic model (message + count)
- Add `DELETE /api/bans/all` router endpoint; handles 502 on socket error
- Frontend: `bansAll` endpoint constant, `unbanAllBans()` API call,
`UnbanAllResponse` type, `unbanAll` action in `useActiveBans` hook
- JailsPage: "Clear All Bans" button (visible when bans > 0) with a
Fluent UI confirmation Dialog before executing the operation
- 7 new tests (3 service, 4 router); 440 total pass, 82% coverage
When the most recent scheduled import completed with errors, surface the
failure in the persistent app shell:
- A warning MessageBar appears at top of main content area
- An amber badge is rendered on the Blocklists sidebar nav item
Backend: add last_run_errors: bool | None to ScheduleInfo model and
populate it in get_schedule_info() from the latest import_log row.
Frontend: extend ScheduleInfo type, add useBlocklistStatus polling hook,
wire both indicators into MainLayout.
Tests: 3 new service tests + 1 new router test (433 total, all pass).
- Add 5-min negative cache (_neg_cache) so failing IPs are throttled
rather than hammering the API on every request
- Add MaxMind GeoLite2 fallback (init_geoip / _geoip_lookup) that fires
when ip-api fails; controlled by BANGUI_GEOIP_DB_PATH env var
- Fix lookup_batch bug: failed API results were stored in positive cache
- Add _persist_neg_entry: INSERT OR IGNORE into geo_cache with NULL
country_code so re-resolve can find historically failed IPs
- Add POST /api/geo/re-resolve: clears neg cache, batch-retries all
geo_cache rows with country_code IS NULL, returns resolved/total count
- BanTable + MapPage: wrap the country — placeholder in a Fluent UI
Tooltip explaining the retry behaviour
- Add geoip2>=4.8.0 dependency; geoip_db_path config setting
- Tests: add TestNegativeCache (4), TestGeoipFallback (4), TestReResolve (4)
- Add persistent geo_cache SQLite table (db.py)
- Rewrite geo_service: batch API (100 IPs/call), two-tier cache,
no caching of failed lookups so they are retried
- Pre-warm geo cache from DB on startup (main.py lifespan)
- Rewrite bans_by_country: SQL GROUP BY ip aggregation + lookup_batch
instead of 2000-row fetch + asyncio.gather individual calls
- Pre-warm geo cache after blocklist import (blocklist_service)
- Add 300ms debounce to useMapData hook to cancel stale requests
- Add perf benchmark asserting <2s for 10k bans
- Add seed_10k_bans.py script for manual perf testing
- 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
- Implement ban model, service, and router endpoints in backend
- Add ban table component and dashboard integration in frontend
- Update ban-related types and API endpoints
- Add comprehensive tests for ban service and dashboard router
- Update documentation (Features, Tasks, Architecture, Web-Design)
- Clean up old fail2ban configuration files
- Update Makefile with new commands