19 KiB
BanGUI — Task List
This document breaks the entire BanGUI project into development stages, ordered so that each stage builds on the previous one. Every task is described in prose with enough detail for a developer to begin work. References point to the relevant documentation.
Reference: Docs/Refactoring.md for full analysis of each issue.
Open Issues
Task 1 — Extract shared private functions to a utility module (✅ completed)
Priority: High
Refactoring ref: Refactoring.md §1, §5
Affected files:
backend/app/services/ban_service.py(defines_get_fail2ban_db_path~L117,_parse_data_json~L152,_ts_to_iso~L105)backend/app/services/history_service.py(imports these three private functions fromban_service)
What to do:
- Create a new file
backend/app/utils/fail2ban_db_utils.py. - Move the three functions
_get_fail2ban_db_path(),_parse_data_json(), and_ts_to_iso()frombackend/app/services/ban_service.pyinto the new utility file. Rename them to remove the leading underscore (they are now public utilities):get_fail2ban_db_path(),parse_data_json(),ts_to_iso(). - In
backend/app/services/ban_service.py, replace the function bodies with imports from the new utility:from app.utils.fail2ban_db_utils import get_fail2ban_db_path, parse_data_json, ts_to_iso. Update all internal call sites withinban_service.pythat reference the old_-prefixed names. - In
backend/app/services/history_service.py, replace the importfrom app.services.ban_service import _get_fail2ban_db_path, _parse_data_json, _ts_to_isowithfrom app.utils.fail2ban_db_utils import get_fail2ban_db_path, parse_data_json, ts_to_iso. Update all call sites inhistory_service.py. - Search the entire
backend/tree for any other references to the old_-prefixed names and update them. - Run existing tests:
cd backend && python -m pytest tests/— all tests must pass.
Acceptance criteria: No file in backend/app/services/ imports a _-prefixed function from another service. The three functions live in backend/app/utils/fail2ban_db_utils.py and are imported from there.
Task 2 — Decouple geo-enrichment from services (✅ completed)
Priority: High
Refactoring ref: Refactoring.md §1
Affected files:
backend/app/services/jail_service.py(lazy importsgeo_serviceat ~L860, ~L1047)backend/app/services/ban_service.py(lazy importsgeo_serviceat ~L251, ~L392)backend/app/services/blocklist_service.py(lazy importsgeo_serviceat ~L343)backend/app/services/history_service.py(TYPE_CHECKING import ofgeo_serviceat ~L19)backend/app/services/geo_service.py(the service being imported)- Router files that call these services:
backend/app/routers/jails.py,backend/app/routers/bans.py,backend/app/routers/dashboard.py,backend/app/routers/history.py,backend/app/routers/blocklist.py
What to do:
- In each affected service function that currently lazy-imports
geo_service, change the function signature to accept an optional geo-enrichment callback parameter (e.g.,enrich_geo: Callable | None = None). The callback signature should match whatgeo_serviceprovides (typicallyasync def enrich(ip: str) -> GeoInfo | None). - Remove all lazy imports of
geo_servicefromjail_service.py,ban_service.py,blocklist_service.py, andhistory_service.py. - In the corresponding router files, import
geo_serviceand pass its enrichment function as the callback when calling the service functions. The router layer is where wiring belongs. - Run existing tests:
cd backend && python -m pytest tests/— all tests must pass. If tests mockgeo_serviceinside a service, update mocks to inject the callback instead.
Acceptance criteria: No service file imports geo_service (directly or lazily). Geo-enrichment is injected from routers via callback parameters.
Task 3 — Move shared domain exceptions to a central module (✅ completed)
Priority: High
Refactoring ref: Refactoring.md §1
Affected files:
backend/app/services/config_file_service.py(definesJailNotFoundErrorand other domain exceptions)backend/app/services/jail_service.py(may define or re-export exceptions)- Any service or router that imports exceptions cross-service (e.g.,
config_file_serviceimportsJailNotFoundErrorfromjail_serviceat ~L57-58)
What to do:
- Create
backend/app/exceptions.py. - Grep the entire
backend/app/services/directory for all custom exception class definitions (classes inheriting fromExceptionorHTTPException). Collect every exception that is imported by more than one module. - Move those shared exception classes into
backend/app/exceptions.py. - Update all import statements across
backend/app/services/,backend/app/routers/, andbackend/app/to import frombackend/app/exceptions.py. - Exception classes used only within a single service may remain in that service file.
- Run existing tests:
cd backend && python -m pytest tests/— all tests must pass.
Acceptance criteria: backend/app/exceptions.py exists and contains all cross-service exceptions. No service imports an exception class from another service module.
Task 4 — Split config_file_service.py (god module)
Priority: High
Refactoring ref: Refactoring.md §2
Affected files:
backend/app/services/config_file_service.py(~2232 lines, ~73 functions)backend/app/routers/files that import fromconfig_file_service
What to do:
- Read
backend/app/services/config_file_service.pyand categorise every function into one of three domains:- Jail config — functions dealing with jail activation, deactivation, listing jail configs
- Filter config — functions dealing with fail2ban filter files (reading, writing, listing filters)
- Action config — functions dealing with fail2ban action files (reading, writing, listing actions)
- Create three new service files:
backend/app/services/jail_config_service.py— jail-related functionsbackend/app/services/filter_config_service.py— filter-related functionsbackend/app/services/action_config_service.py— action-related functions
- Move functions from
config_file_service.pyinto the appropriate new file. Any truly shared helpers used across all three domains should remain inconfig_file_service.py(renamed to a shared helper) or move tobackend/app/utils/. - Delete
config_file_service.pyonce empty (or keep it as a thin re-export layer for backward compatibility during transition). - Update all imports in
backend/app/routers/andbackend/app/services/that referencedconfig_file_service. - Run existing tests:
cd backend && python -m pytest tests/— all tests must pass.
Acceptance criteria: No single service file exceeds ~800 lines. The three new files each handle one domain. All routers import from the correct new module.
Task 5 — Extract log-preview / regex-test from config_service.py
Priority: Medium
Refactoring ref: Refactoring.md §2
Affected files:
backend/app/services/config_service.py(~1845 lines)backend/app/routers/config.py(routes that call log-preview / regex-test functions)
What to do:
- Read
backend/app/services/config_service.pyand identify all functions related to log-preview and regex-testing (these are distinct from the core socket-based config reading/writing functions). - Create
backend/app/services/log_service.py. - Move the log-preview and regex-test functions into
log_service.py. - Update imports in
backend/app/routers/config.py(or create a newbackend/app/routers/log.pyif the endpoints are logically separate). - Run existing tests:
cd backend && python -m pytest tests/— all tests must pass.
Acceptance criteria: config_service.py no longer contains log-preview or regex-test logic. log_service.py exists and is used by the appropriate router.
Task 6 — Rename confusing config service files
Priority: Medium
Refactoring ref: Refactoring.md §3
Affected files:
backend/app/services/config_file_service.py→ rename tojail_activation_service.py(or the split modules from Task 4)backend/app/services/file_config_service.py→ rename toraw_config_io_service.py- All files importing from the old names
Note: This task depends on Task 4 being completed first. If Task 4 splits config_file_service.py, this task only needs to rename file_config_service.py.
What to do:
- Rename
backend/app/services/file_config_service.pytobackend/app/services/raw_config_io_service.py. - Update all import statements across the codebase (
backend/app/services/,backend/app/routers/,backend/app/tasks/, tests) that referencefile_config_serviceto referenceraw_config_io_service. - Also rename the corresponding router if one exists: check
backend/app/routers/file_config.pyand rename accordingly. - Run existing tests:
cd backend && python -m pytest tests/— all tests must pass.
Acceptance criteria: No file named file_config_service.py exists. The new name raw_config_io_service.py is used everywhere.
Task 7 — Remove remaining service-to-service coupling
Priority: Medium
Refactoring ref: Refactoring.md §1
Affected files:
backend/app/services/auth_service.py(importssetup_serviceat ~L23)backend/app/services/config_service.py(importssetup_serviceat ~L47, lazy-importshealth_serviceat ~L891)backend/app/services/blocklist_service.py(lazy-importsjail_serviceat ~L299)
What to do:
- For each remaining service-to-service import, determine why the dependency exists (read the calling code).
- Refactor using one of these strategies:
- Dependency injection: The router passes the needed data or function from service A when calling service B.
- Shared utility: If the imported function is a pure utility, move it to
backend/app/utils/. - Event / callback: The service accepts a callback parameter instead of importing another service directly.
- Remove all direct and lazy imports between service modules.
- Run existing tests:
cd backend && python -m pytest tests/— all tests must pass.
Acceptance criteria: Running grep -r "from app.services" backend/app/services/ returns zero results (no service imports another service). All wiring happens in the router or dependency-injection layer.
Task 8 — Update Architecture documentation
Priority: Medium
Refactoring ref: Refactoring.md §4
Affected files:
Docs/Architekture.md
What to do:
- Read
Docs/Architekture.mdand the actual file listings below. - Add the following missing items to the appropriate sections:
- Repositories: Add
fail2ban_db_repo.pyandgeo_cache_repo.py(inbackend/app/repositories/) - Utils: Add
conffile_parser.py,config_parser.py,config_writer.py,jail_config.py(inbackend/app/utils/) - Tasks: Add
geo_re_resolve.py(inbackend/app/tasks/) - Services: Correct the entry that lists
conffile_parseras a service — it is inapp/utils/ - Routers: Add
file_config.py(inbackend/app/routers/)
- Repositories: Add
- If Tasks 1–7 have already been completed, also reflect any new files or renames (e.g.,
fail2ban_db_utils.py,exceptions.py, the split service files, renamed services). - Verify no other files exist that are missing from the doc by comparing the doc's file lists against
ls backend/app/*/.
Acceptance criteria: Every .py file under backend/app/ (excluding __init__.py and __pycache__) is mentioned in the Architecture doc.
Task 9 — Add React Error Boundary to the frontend
Priority: Medium
Refactoring ref: Refactoring.md §6
Affected files:
- New file:
frontend/src/components/ErrorBoundary.tsx frontend/src/App.tsxorfrontend/src/layouts/(wherever the top-level layout lives)
What to do:
- Create
frontend/src/components/ErrorBoundary.tsx— a React class component implementingcomponentDidCatchandgetDerivedStateFromError. It should:- Catch any rendering error in its children.
- Display a user-friendly fallback UI (e.g., "Something went wrong" message with a "Reload" button that calls
window.location.reload()). - Log the error (console.error is sufficient for now).
- Read
frontend/src/App.tsxto find the main layout/route wrapper. - Wrap the main content (inside
<App>or<MainLayout>) with<ErrorBoundary>so that any component crash shows the fallback instead of a white screen. - Run existing frontend tests:
cd frontend && npx vitest run— all tests must pass.
Acceptance criteria: An <ErrorBoundary> component exists and wraps the application's main content. A component throwing during render shows a fallback UI instead of crashing the whole app.
Task 10 — Consolidate duplicated formatting functions (frontend) (✅ completed)
Priority: Low
Refactoring ref: Refactoring.md §7
Affected files:
frontend/src/components/BanTable.tsx(hasformatTimestamp()~L103)frontend/src/components/jail/BannedIpsSection.tsx(hasfmtTime()~L139)frontend/src/pages/JailDetailPage.tsx(hasfmtSeconds()~L152)frontend/src/pages/JailsPage.tsx(hasfmtSeconds()~L147)
What was done:
- Added shared helper
frontend/src/utils/formatDate.tswithformatTimestamp()+formatSeconds(). - Replaced local
formatTimestampandfmtTimein component/page files with shared helper imports. - Ensured no local formatting helpers are left in the target files.
- Ran frontend tests (
cd frontend && npx vitest run --run): all tests passed.formatSeconds(seconds: number): string— consolidation of the two identicalfmtSecondsfunctions
- In each of the four affected files, remove the local function definition and replace it with an import from
src/utils/formatDate.ts. Adjust call sites if the function name changed. - Run existing frontend tests:
cd frontend && npx vitest run— all tests must pass.
Acceptance criteria: No formatting function for dates/times is defined locally in a component or page file. All import from src/utils/formatDate.ts.
Task 11 — Create generic useConfigItem<T> hook (frontend)
Priority: Low
Refactoring ref: Refactoring.md §8
Affected files:
frontend/src/hooks/useFilterConfig.ts(~91 lines)frontend/src/hooks/useActionConfig.ts(~88 lines)frontend/src/hooks/useJailFileConfig.ts(~76 lines)
What to do:
- Read all three hook files. Identify the common pattern: load item via fetch → store in state → expose save function → handle abort controller cleanup.
- Create
frontend/src/hooks/useConfigItem.tswith a generic hook:function useConfigItem<T>( fetchFn: (signal: AbortSignal) => Promise<T>, saveFn: (data: T) => Promise<void> ): { data: T | null; loading: boolean; error: string | null; save: (data: T) => Promise<void> } - Rewrite
useFilterConfig.ts,useActionConfig.ts, anduseJailFileConfig.tsto be thin wrappers arounduseConfigItem<T>— each file should be <20 lines, just providing the specific fetch/save functions. - Run existing frontend tests:
cd frontend && npx vitest run— all tests must pass.
Acceptance criteria: useConfigItem.ts exists. The three original hooks use it and each reduced to <20 lines of domain-specific glue.
Task 12 — Standardise error handling in frontend hooks
Priority: Low
Refactoring ref: Refactoring.md §9
Affected files:
- All hook files in
frontend/src/hooks/that do fetch calls (at least:useHistory.ts,useMapData.ts,useBans.ts,useBlocklist.ts, and others)
What to do:
- Create a utility function in
frontend/src/utils/fetchError.ts:export function handleFetchError(err: unknown, setError: (msg: string | null) => void): void { if (err instanceof DOMException && err.name === "AbortError") return; setError(err instanceof Error ? err.message : "Unknown error"); } - Grep all hook files for
catchblocks. In every hook that catches fetch errors:- Replace the catch body with a call to
handleFetchError(err, setError).
- Replace the catch body with a call to
- Run existing frontend tests:
cd frontend && npx vitest run— all tests must pass.
Acceptance criteria: Every hook that fetches data uses handleFetchError() in its catch block. No hook surfaces AbortError to the UI.
Task 13 — Extract sub-components from large frontend pages
Priority: Low
Refactoring ref: Refactoring.md §11
Affected files:
frontend/src/pages/BlocklistsPage.tsx(~968 lines)frontend/src/components/config/JailsTab.tsx(~939 lines)frontend/src/pages/JailsPage.tsx(~691 lines)frontend/src/pages/JailDetailPage.tsx(~663 lines)
What to do:
- For each large file, identify logical UI sections that can be extracted into their own component files.
- Suggested splits (adjust after reading the actual code):
- BlocklistsPage.tsx: Extract
<BlocklistSourceList>,<BlocklistAddEditDialog>,<BlocklistImportLog>,<BlocklistScheduleConfig>. - JailsTab.tsx: Extract
<JailConfigEditor>,<JailRawConfig>,<JailValidation>,<JailActivateDialog>. - JailsPage.tsx: Extract
<JailDetailDrawer>,<BanUnbanForm>. - JailDetailPage.tsx: Extract logical sections (examine the JSX to identify).
- BlocklistsPage.tsx: Extract
- Place extracted components in appropriate directories (e.g.,
frontend/src/components/blocklist/,frontend/src/components/jail/). - Each parent page should import and compose the new child components. Props should be passed down — avoid prop drilling deeper than 2 levels (use context if needed).
- Run existing frontend tests:
cd frontend && npx vitest run— all tests must pass.
Acceptance criteria: No single page or component file exceeds ~400 lines. Each extracted component is in its own file.
Task 14 — Consolidate duplicated section/card styles (frontend)
Priority: Low
Refactoring ref: Refactoring.md §12
Affected files:
- 13+ files across
frontend/src/pages/andfrontend/src/components/that define identical card/section styles usingmakeStyleswithbackgroundColor,borderRadius,border,paddingusing Fluent UI tokens.
What to do:
- Grep the frontend codebase for
makeStylescalls that containbackgroundColorandborderRadiustogether. Identify the common pattern. - Create
frontend/src/theme/commonStyles.tswith a shareduseCardStyles()hook that exports the common section/card style class. - In each of the 13+ files, remove the local
makeStylesdefinition for the card/section style and importuseCardStylesfromcommonStyles.tsinstead. Keep any file-specific style overrides local. - Run existing frontend tests:
cd frontend && npx vitest run— all tests must pass.
Acceptance criteria: A shared useCardStyles() exists in frontend/src/theme/commonStyles.ts. At least 10 files import it instead of defining their own card styles.