Commit Graph

48 Commits

Author SHA1 Message Date
3e4f688484 Fix vertical alignment of DNS Mode dropdown in jail config
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.
2026-03-14 09:51:00 +01:00
c110352e9e Config page tasks 1-4: dropdowns, key props, inactive jail full GUI, banaction fix
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
2026-03-14 09:28:30 +01:00
201cca8b66 Clean up Config page: remove Export tab, add CreateJailDialog, fix UI details
- 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
2026-03-14 08:33:46 +01:00
6e35c5d269 feat: frontend Actions Tab with structured API, assign/create/remove dialogs (Task 3.3)
- 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
2026-03-13 19:21:58 +01:00
2f60b0915e Redesign FiltersTab with active/inactive layout and assign/create dialogs (Tasks 2.3/2.4)
- 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)
2026-03-13 18:46:45 +01:00
4c138424a5 Add filter discovery endpoints with active/inactive status (Task 2.1)
- 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
2026-03-13 16:48:27 +01:00
8d9d63b866 feat(stage-1): inactive jail discovery and activation
- 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
2026-03-13 15:44:36 +01:00
c250439326 chore: update styles, exports, api, tests, and mark config redesign task complete
- configStyles.ts: add listDetailRoot, listPane, listItem, listItemSelected,
  detailPane style slots
- index.ts: export ConfigListDetail and RawConfigSection
- api/config.ts: add writeFilterFile and writeActionFile API helpers
- setupTests.ts: add ResizeObserver and matchMedia mocks for Fluent UI v9
- ConfigPageLogPath.test.tsx: update to render inside FluentProvider
- Docs/Tasks.md: mark config view redesign task as complete
2026-03-13 14:35:04 +01:00
a284d38f56 feat(frontend): redesign Jails, Filters, and Actions tabs to list/detail layout
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.
2026-03-13 14:34:57 +01:00
0c0acd7f51 feat(frontend): add ConfigListDetail, RawConfigSection components and useConfigActiveStatus hook
- 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
2026-03-13 14:34:49 +01:00
44f3fb8718 chore: add GitHub Copilot agent, fix ESLint config, update task list
- .github/agents/ProcessTasks.agent.md: Copilot agent definition
- eslint.config.ts: minor lint rule adjustment
- Docs/Tasks.md: update completed and in-progress task status
2026-03-13 13:48:20 +01:00
9b73f6719d refactor(frontend): decompose ConfigPage into dedicated config components
- Extract tab components: JailsTab, ActionsTab, FiltersTab, JailFilesTab,
  GlobalTab, ServerTab, ConfFilesTab, RegexTesterTab, MapTab, ExportTab
- Add form components: JailFileForm, ActionForm, FilterForm
- Add AutoSaveIndicator, RegexList, configStyles, and barrel index
- ConfigPage now composes these components; greatly reduces file size
- Add tests: ConfigPage.test.tsx, useAutoSave.test.ts
2026-03-13 13:48:09 +01:00
a0e8566ff8 feat(frontend): add config hooks for jail, action, filter, and auto-save
- 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
2026-03-13 13:47:55 +01:00
8bdad3529f feat(frontend): add config types and API client for file-config endpoints
- 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
2026-03-13 13:47:45 +01:00
d0b8b78d12 Expose usedns, date_pattern, and prefregex in jail config UI
- 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
2026-03-12 21:00:51 +01:00
e3375fd187 Expose ban-time escalation settings in jail detail and config UI
- 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
2026-03-12 20:30:21 +01:00
ea35695221 Add better jail configuration: file CRUD, enable/disable, log paths
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
2026-03-12 20:08:33 +01:00
59464a1592 Add log path to jail via inline form in ConfigPage
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.
2026-03-12 19:16:20 +01:00
2f602e45f7 Add DashboardFilterBar and move global filters to top of dashboard
- 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)
2026-03-11 19:05:52 +01:00
0a73c49d01 Fix ruff and ESLint warnings in tests and tsconfig
- 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
2026-03-11 18:35:30 +01:00
576ec43854 Polish dashboard charts and add frontend tests (Stage 6)
Task 6.1 - Consistent loading/error/empty states across all charts:
- Add ChartStateWrapper shared component with Spinner, error MessageBar
  + Retry button, and friendly empty message
- Expose reload() in useBanTrend, useJailDistribution,
  useDashboardCountryData hooks
- Update BanTrendChart and JailDistributionChart to use ChartStateWrapper
- Add empty state to TopCountriesBarChart and TopCountriesPieChart
- Replace manual loading/error logic in DashboardPage with ChartStateWrapper

Task 6.2 - Frontend tests (5 files, 20 tests):
- Install Vitest v4, jsdom, @testing-library/react, @testing-library/jest-dom
- Add vitest.config.ts (separate from vite.config.ts to avoid Vite v5/v7 clash)
- Add src/setupTests.ts with jest-dom matchers and ResizeObserver/matchMedia stubs
- Tests: ChartStateWrapper (7), BanTrendChart (4), JailDistributionChart (4),
  TopCountriesPieChart (2), TopCountriesBarChart (3)

Task 6.3 - Full QA:
- ruff: clean
- mypy --strict: 52 files, no issues
- pytest: 497 passed
- tsc --noEmit: clean
- eslint: clean (added test-file override for explicit-function-return-type)
- vite build: success
2026-03-11 17:25:28 +01:00
fe8eefa173 Add jail distribution chart (Stage 5)
- 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
2026-03-11 17:01:19 +01:00
df0528b2c2 Integrate BanTrendChart into DashboardPage (Task 4.3)
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.
2026-03-11 16:50:04 +01:00
259ff17eba Add BanTrendChart component and useBanTrend hook
- 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
2026-03-11 16:48:49 +01:00
2ddfddfbbb Add dashboard country charts (Stages 1–3)
- 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
2026-03-11 16:06:24 +01:00
4773ae1c7a Add mass unban: DELETE /api/bans/all clears all active bans
- 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
2026-03-07 21:16:49 +01:00
207be94c42 Show blocklist import error badge in navigation
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).
2026-03-07 21:00:00 +01:00
12a859061c Fix missing country: neg cache, geoip2 fallback, re-resolve endpoint
- 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)
2026-03-07 20:42:34 +01:00
ddfc8a0b02 Optimise geo lookup and aggregation for 10k+ IPs
- 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
2026-03-07 20:28:51 +01:00
53d664de4f Add origin field and filter for ban sources (Tasks 1 & 2)
- 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
2026-03-07 20:03:43 +01:00
cbad4ea706 Add ban management features and update documentation
- 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
2026-03-06 20:33:42 +01:00
401a5d4169 Fix double /api prefix in fetchAccesses by using ENDPOINTS constant
fetchAccesses was passing the hardcoded absolute path /api/dashboard/accesses
to get(), which prepends BASE_URL (/api), producing /api/api/dashboard/accesses.
Added ENDPOINTS.dashboardAccesses and switched to use it, consistent with every
other function in dashboard.ts.
2026-03-01 21:05:30 +01:00
f06fea699f Fix WorldMap crash when path is undefined during early render
react-simple-maps types declare path as always non-null, but it is
undefined during the initial render before the MapProvider context
has fully initialised. Guard with an early return after all hooks
have been called, casting through unknown to reflect the true runtime
type without triggering the @typescript-eslint/no-unnecessary-condition
rule.
2026-03-01 20:27:50 +01:00
36e1e12a8a Fix console errors: React Router v7 flags, form a11y, bans 500
- Add v7_startTransition and v7_relativeSplatPath future flags to
  BrowserRouter to silence React Router deprecation warnings
- Add hidden autocomplete='username' inputs to LoginPage and SetupPage
  so password managers and browsers stop warning about missing username
  fields in password forms
- Mount fail2ban-dev-config volume into backend container at /config:ro
  so ban_service can open the fail2ban SQLite database returned by
  'get dbfile'; this fixes the 500 on GET /api/dashboard/bans
- Track compose.debug.yml in git (was previously untracked)
2026-03-01 20:15:23 +01:00
e8214b5856 fix: use backend service name in Vite proxy target
Vite runs inside the frontend container where 'localhost' resolves to
the container itself, not the backend.  Change the /api proxy target
from http://localhost:8000 to http://backend:8000 so the request is
routed to the backend service over the compose network.
2026-03-01 19:21:30 +01:00
c097e55222 fix: setup routing, async bcrypt, password hashing, clean command
- Add SetupGuard component: redirects to /setup if setup not complete,
  shown as spinner while loading. All routes except /setup now wrapped.
- SetupPage redirects to /login on mount when setup already done.
- Fix async blocking: offload bcrypt.hashpw and bcrypt.checkpw to
  run_in_executor so they never stall the asyncio event loop.
- Hash password with SHA-256 (SubtleCrypto) before transmission; added
  src/utils/crypto.ts with sha256Hex(). Backend stores bcrypt(sha256).
- Add Makefile with make up/down/restart/logs/clean targets.
- Add tests: _check_password async, concurrent bcrypt, expired session,
  login-without-setup, run_setup event-loop interleaving.
- Update Architekture.md and Features.md to reflect all changes.
2026-03-01 19:16:49 +01:00
1cdc97a729 Stage 11: polish, cross-cutting concerns & hardening
- 11.1 MainLayout health indicator: warning MessageBar when fail2ban offline
- 11.2 formatDate utility + TimezoneProvider + GET /api/setup/timezone
- 11.3 Responsive sidebar: auto-collapse <640px, media query listener
- 11.4 PageFeedback (PageLoading/PageError/PageEmpty), BanTable updated
- 11.5 prefers-reduced-motion: disable sidebar transition
- 11.6 WorldMap ARIA: role/tabIndex/aria-label/onKeyDown for countries
- 11.7 Health transition logging (fail2ban_came_online/went_offline)
- 11.8 Global handlers: Fail2BanConnectionError/ProtocolError -> 502
- 11.9 379 tests pass, 82% coverage, ruff+mypy+tsc+eslint clean
- Timezone endpoint: setup_service.get_timezone, 5 new tests
2026-03-01 15:59:06 +01:00
1efa0e973b Stage 10: external blocklist importer — backend + frontend
- blocklist_repo.py: CRUD for blocklist_sources table
- import_log_repo.py: add/list/get-last log entries
- blocklist_service.py: source CRUD, preview, import (download/validate/ban),
  import_all, schedule get/set/info
- blocklist_import.py: APScheduler task (hourly/daily/weekly schedule triggers)
- blocklist.py router: 9 endpoints (list/create/update/delete/preview/import/
  schedule-get+put/log)
- blocklist.py models: ScheduleFrequency (StrEnum), ScheduleConfig, ScheduleInfo,
  ImportSourceResult, ImportRunResult, PreviewResponse
- 59 new tests (18 repo + 19 service + 22 router); 374 total pass
- ruff clean, mypy clean for Stage 10 files
- types/blocklist.ts, api/blocklist.ts, hooks/useBlocklist.ts
- BlocklistsPage.tsx: source management, schedule picker, import log table
- Frontend tsc + ESLint clean
2026-03-01 15:33:24 +01:00
b8f3a1c562 Stage 9: ban history — backend service, router, frontend history page
- history.py models: HistoryBanItem, HistoryListResponse, IpTimelineEvent, IpDetailResponse
- history_service.py: list_history() with dynamic WHERE clauses (range/jail/ip
  prefix/all-time), get_ip_detail() with timeline aggregation
- history.py router: GET /api/history + GET /api/history/{ip} (404 for unknown)
- Fixed latent bug in ban_service._parse_data_json: json.loads('null') -> None
  -> AttributeError; now checks isinstance(parsed, dict) before assigning obj
- 317 tests pass (27 new), ruff + mypy clean (46 files)
- types/history.ts, api/history.ts, hooks/useHistory.ts created
- HistoryPage.tsx: filter bar (time range/jail/IP), DataGrid table,
  high-ban-count row highlighting, per-IP IpDetailView with timeline,
  pagination
- Frontend tsc + ESLint clean (0 errors/warnings)
- Tasks.md Stage 9 marked done
2026-03-01 15:09:22 +01:00
54313fd3e0 Stage 8: world map view — backend endpoint, map component, map page
- BansByCountryResponse model added to ban.py
- bans_by_country() service: parallel geo lookup via asyncio.gather,
  aggregation by ISO alpha-2 country code (up to 2 000 bans)
- GET /api/dashboard/bans/by-country endpoint in dashboard router
- 290 tests pass (5 new), ruff + mypy clean (44 files)
- isoNumericToAlpha2.ts: 249-entry ISO numeric → alpha-2 static map
- types/map.ts, api/map.ts, hooks/useMapData.ts created
- WorldMap.tsx: react-simple-maps Mercator SVG map, per-country ban
  count overlay, colour intensity scaling, country click filtering,
  GeoLayer nested-component pattern for useGeographies context
- MapPage.tsx: time-range selector, WorldMap, country filter info bar,
  summary line, companion FluentUI Table with country filter
- Frontend tsc + ESLint clean (0 errors/warnings)
2026-03-01 14:53:49 +01:00
7f81f0614b Stage 7: configuration view — backend service, routers, tests, and frontend
- config_service.py: read/write jail config via asyncio.gather, global
  settings, in-process regex validation, log preview via _read_tail_lines
- server_service.py: read/write server settings, flush logs
- config router: 9 endpoints for jail/global config, regex-test,
  logpath management, log preview
- server router: GET/PUT settings, POST flush-logs
- models/config.py expanded with JailConfig, GlobalConfigUpdate,
  LogPreview* models
- 285 tests pass (68 new), ruff clean, mypy clean (44 files)
- Frontend: types/config.ts, api/config.ts, hooks/useConfig.ts,
  ConfigPage.tsx full implementation (Jails accordion editor,
  Global config, Server settings, Regex Tester with preview)
- Fixed pre-existing frontend lint: JSX.Element → React.JSX.Element
  (10 files), void/promise patterns in useServerStatus + useJails,
  no-misused-spread in client.ts, eslint.config.ts self-excluded
2026-03-01 14:37:55 +01:00
ebec5e0f58 Stage 6: jail management — backend service, routers, tests, and frontend
- jail_service.py: list/detail/control/ban/unban/ignore-list/IP-lookup
- jails.py router: 11 endpoints including ignore list management
- bans.py router: active bans, ban, unban
- geo.py router: IP lookup with geo enrichment
- models: Jail.actions, ActiveBan.country/.banned_at optional, GeoDetail
- 217 tests pass (40 service + 36 router + 141 existing), 76% coverage
- Frontend: types/jail.ts, api/jails.ts, hooks/useJails.ts
- JailsPage: jail overview table with controls, ban/unban forms,
  active bans table, IP lookup
- JailDetailPage: full detail, start/stop/idle/reload, patterns,
  ignore list management
2026-03-01 14:09:02 +01:00
9ac7f8d22d feat: implement dashboard ban overview (Stage 5)
- Add ban_service reading fail2ban SQLite DB via read-only aiosqlite
- Add geo_service resolving IPs via ip-api.com with 10k in-memory cache
- Add GET /api/dashboard/bans and GET /api/dashboard/accesses endpoints
- Add TimeRange, DashboardBanItem, DashboardBanListResponse, AccessListItem,
  AccessListResponse models in models/ban.py
- Build BanTable component (Fluent UI DataGrid) with bans/accesses modes,
  pagination, loading/error/empty states, and ban-count badges
- Build useBans hook managing time-range and pagination state
- Update DashboardPage: status bar + time-range toolbar + tab switcher
- Add 37 new backend tests (ban service, geo service, dashboard router)
- All 141 tests pass; ruff/mypy --strict/tsc --noEmit clean
2026-03-01 12:57:19 +01:00
94661d7877 chore(frontend): add frontend-specific .gitignore
Adds a dedicated .gitignore for the React/Vite/TypeScript frontend
covering node_modules, build output, TypeScript incremental build
info, test coverage, and environment files.
2026-02-28 21:50:40 +01:00
60683da3ca feat: Stage 4 — fail2ban connection and server status 2026-02-28 21:48:03 +01:00
a41a99dad4 feat: Stage 3 — application shell and navigation 2026-02-28 21:37:42 +01:00
750785680b feat: Stage 2 — authentication and setup flow
Backend (tasks 2.1–2.6, 2.10):
- settings_repo: get/set/delete/get_all CRUD for the key-value settings table
- session_repo: create/get/delete/delete_expired for session rows
- setup_service: bcrypt password hashing, one-time-only enforcement,
  run_setup() / is_setup_complete() / get_password_hash()
- auth_service: login() with bcrypt verify + token creation,
  validate_session() with expiry check, logout()
- setup router: GET /api/setup (status), POST /api/setup (201 / 409)
- auth router: POST /api/auth/login (token + HttpOnly cookie),
               POST /api/auth/logout (clears cookie, idempotent)
- SetupRedirectMiddleware: 307 → /api/setup for all API paths until setup done
- require_auth dependency: cookie or Bearer token → Session or 401
- conftest.py: manually bootstraps app.state.db for router tests
  (ASGITransport does not trigger ASGI lifespan)
- 85 tests pass; ruff 0 errors; mypy --strict 0 errors

Frontend (tasks 2.7–2.9):
- types/auth.ts, types/setup.ts, api/auth.ts, api/setup.ts
- AuthProvider: sessionStorage-backed context (isAuthenticated, login, logout)
- RequireAuth: guard component → /login?next=<path> when unauthenticated
- SetupPage: Fluent UI form, client-side validation, inline errors
- LoginPage: single password input, ?next= redirect after success
- DashboardPage: placeholder (full impl Stage 5)
- App.tsx: full route tree (/setup, /login, /, *)
2026-02-28 21:33:30 +01:00
7392c930d6 feat: Stage 1 — backend and frontend scaffolding
Backend (tasks 1.1, 1.5–1.8):
- pyproject.toml with FastAPI, Pydantic v2, aiosqlite, APScheduler 3.x,
  structlog, bcrypt; ruff + mypy strict configured
- Pydantic Settings (BANGUI_ prefix env vars, fail-fast validation)
- SQLite schema: settings, sessions, blocklist_sources, import_log;
  WAL mode + foreign keys; idempotent init_db()
- FastAPI app factory with lifespan (DB, aiohttp session, scheduler),
  CORS, unhandled-exception handler, GET /api/health
- Fail2BanClient: async Unix-socket wrapper using run_in_executor,
  custom error types, async context manager
- Utility modules: ip_utils, time_utils, constants
- 47 tests; ruff 0 errors; mypy --strict 0 errors

Frontend (tasks 1.2–1.4):
- Vite + React 18 + TypeScript strict; Fluent UI v9; ESLint + Prettier
- Custom brand theme (#0F6CBD, WCAG AA contrast) with light/dark variants
- Typed fetch API client (ApiError, get/post/put/del) + endpoints constants
- tsc --noEmit 0 errors
2026-02-28 21:15:01 +01:00