9.1 KiB
BanGUI — Architecture Issues & Refactoring Plan
This document catalogues architecture violations, code smells, and structural issues found during a full project review. Issues are grouped by category and prioritised.
1. Service-to-Service Coupling (Backend)
The architecture mandates that dependencies flow routers → services → repositories, yet 15 service-to-service imports exist, with 7 using lazy imports to work around circular dependencies.
| Source Service | Imports From | Line | Mechanism |
|---|---|---|---|
history_service |
ban_service |
L29 | Direct import of 3 private functions: _get_fail2ban_db_path, _parse_data_json, _ts_to_iso |
auth_service |
setup_service |
L23 | Top-level import |
config_service |
setup_service |
L47 | Top-level import |
config_service |
health_service |
L891 | Lazy import inside function |
config_file_service |
jail_service |
L57–58 | Top-level import + re-export of JailNotFoundError |
blocklist_service |
jail_service |
L299 | Lazy import |
blocklist_service |
geo_service |
L343 | Lazy import |
jail_service |
geo_service |
L860, L1047 | Lazy import (2 sites) |
ban_service |
geo_service |
L251, L392 | Lazy import (2 sites) |
history_service |
geo_service |
L19 | TYPE_CHECKING import |
Impact: Circular dependency risk; lazy imports hide coupling; private function imports create fragile links between services.
Recommendation:
- Extract
_get_fail2ban_db_path(),_parse_data_json(),_ts_to_iso()fromban_servicetoapp/utils/fail2ban_db_utils.py(shared utility). - Pass geo-enrichment as a callback parameter instead of each service importing
geo_servicedirectly. The router or dependency layer should wire this. - Where services depend on another service's domain exceptions (e.g.,
JailNotFoundError), move exceptions toapp/models/or a sharedapp/exceptions.py.
2. God Modules (Backend)
Several service files far exceed a reasonable size for a single-domain module:
| File | Lines | Functions | Issue |
|---|---|---|---|
config_file_service.py |
3105 | 73 | Handles jails, filters, and actions — three distinct domains crammed into one file |
jail_service.py |
1382 | 34 | Manages jail listing, status, controls, banned-IP queries, and geo enrichment |
config_service.py |
921 | ~20 | Socket-based config, log preview, regex testing, and service status |
file_config_service.py |
1011 | ~20 | Raw file I/O for jails, filters, and actions |
Recommendation:
- Split
config_file_service.pyintofilter_config_service.py,action_config_service.py, and a slimmed-downjail_config_service.py. - Extract log-preview / regex-test functionality from
config_service.pyinto a dedicatedlog_service.py.
3. Confusing Config Service Naming (Backend)
Three services with overlapping names handle different aspects of configuration, causing developer confusion:
| Current Name | Purpose |
|---|---|
config_service |
Read/write via the fail2ban socket |
config_file_service |
Parse/activate/deactivate jails from files on disk |
file_config_service |
Raw file I/O for jail/filter/action .conf files |
config_file_service vs file_config_service differ only by word order, making it easy to import the wrong one.
Recommendation: Rename for clarity:
config_service→ keep (socket-based)config_file_service→jail_activation_service(its main job is activating/deactivating jails)file_config_service→raw_config_io_serviceor merge intoconfig_file_service
4. Architecture Doc Drift
The architecture doc does not fully reflect the current codebase:
| Category | In Architecture Doc | Actually Exists | Notes |
|---|---|---|---|
| Repositories | 4 listed | 6 files | fail2ban_db_repo.py and geo_cache_repo.py are missing from the doc |
| Utils | 4 listed | 8 files | conffile_parser.py, config_parser.py, config_writer.py, jail_config.py are undocumented |
| Tasks | 3 listed | 4 files | geo_re_resolve.py is missing from the doc |
| Services | conffile_parser listed as a service |
Actually in app/utils/ |
Doc says "Services" but the file is in utils/ |
| Routers | file_config.py not listed |
Exists | Missing from router table |
Recommendation: Update the Architecture doc to reflect the actual file inventory.
5. Shared Private Functions Cross Service Boundary (Backend)
history_service.py imports three underscore-prefixed ("private") functions from ban_service.py:
from app.services.ban_service import _get_fail2ban_db_path, _parse_data_json, _ts_to_iso
These are implementation details of ban_service that should not be consumed externally. Their _ prefix signals they are not part of the public API.
Recommendation: Move these to app/utils/fail2ban_db_utils.py as public functions and import from there in both services.
6. Missing Error Boundaries (Frontend)
No React Error Boundary component exists anywhere in the frontend. A single unhandled exception in any component will crash the entire application with a white screen.
Recommendation: Add an <ErrorBoundary> wrapper in MainLayout.tsx or App.tsx with a fallback UI that shows the error and offers a retry/reload.
7. Duplicated Formatting Functions (Frontend)
Several formatting functions are independently defined in multiple files instead of being shared:
| Function | Defined In | Also In |
|---|---|---|
formatTimestamp() |
BanTable.tsx (L103) |
— (but fmtTime() in BannedIpsSection.tsx does the same thing) |
fmtSeconds() |
JailDetailPage.tsx (L152) |
JailsPage.tsx (L147) — identical |
fmtTime() |
BannedIpsSection.tsx (L139) |
— |
Recommendation: Consolidate into src/utils/formatDate.ts and import from there.
8. Duplicated Hook Logic (Frontend)
Three hooks follow an identical fetch-then-save pattern with near-identical code:
| Hook | Lines | Pattern |
|---|---|---|
useFilterConfig.ts |
91 | Load item → expose save → handle abort |
useActionConfig.ts |
89 | Load item → expose save → handle abort |
useJailFileConfig.ts |
76 | Load item → expose save → handle abort |
Recommendation: Create a generic useConfigItem<T>() hook that takes fetchFn and saveFn parameters and eliminates the triplication.
9. Inconsistent Error Handling in Hooks (Frontend)
Hooks handle errors differently:
- Some filter out
AbortError(e.g.,useHistory,useMapData) - Others catch all errors indiscriminately (e.g.,
useBans,useBlocklist)
This means some hooks surface spurious "request aborted" errors to the UI while others don't.
Recommendation: Standardise a shared error-catching pattern, e.g. a handleFetchError(err, setError) utility that always filters AbortError.
10. No Global Request State / Caching (Frontend)
Each hook manages its own loading/error/data state independently. There is:
- No request deduplication (two components fetching the same data trigger two requests)
- No stale-while-revalidate caching
- No automatic background refetching
Recommendation: Consider adopting React Query (TanStack Query) or SWR for data-fetching hooks. This would eliminate boilerplate in every hook (abort handling, loading state, error state, caching) and provide automatic deduplication.
11. Large Frontend Components
| Component | Lines | Issue |
|---|---|---|
BlocklistsPage.tsx |
968 | Page does a lot: source list, add/edit dialogs, import log, schedule config |
JailsTab.tsx |
939 | Combines jail list, config editing, raw config, validation, activate/deactivate |
JailsPage.tsx |
691 | Mixes jail table, detail drawer, ban/unban forms |
JailDetailPage.tsx |
663 | Full detail view with multiple sections |
Recommendation: Extract sub-sections into focused child components. For example, JailsTab.tsx could delegate to <JailConfigEditor>, <JailValidation>, and <JailActivateDialog>.
12. Duplicated Section Styles (Frontend)
The same card/section styling pattern (backgroundColor, borderRadius, border, padding using Fluent UI tokens) is repeated across 13+ files. Each page recreates it in its own makeStyles block.
Recommendation: Define a shared useCardStyles() or export a sectionStyle in src/theme/commonStyles.ts and import it.
Summary by Priority
| Priority | Issue | Section |
|---|---|---|
| High | Service-to-service coupling / circular deps | §1 |
| High | God module config_file_service.py (3105 lines, 73 functions) |
§2 |
| High | Shared private function imports across services | §5 |
| Medium | Confusing config service naming | §3 |
| Medium | Architecture doc drift | §4 |
| Medium | Missing error boundaries (frontend) | §6 |
| Medium | No global request state / caching (frontend) | §10 |
| Low | Duplicated formatting functions (frontend) | §7 |
| Low | Duplicated hook logic (frontend) | §8 |
| Low | Inconsistent error handling in hooks (frontend) | §9 |
| Low | Large frontend components | §11 |
| Low | Duplicated section styles (frontend) | §12 |