Files
BanGUI/Docs/Tasks.md

19 KiB
Raw Blame History

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 from ban_service)

What to do:

  1. Create a new file backend/app/utils/fail2ban_db_utils.py.
  2. Move the three functions _get_fail2ban_db_path(), _parse_data_json(), and _ts_to_iso() from backend/app/services/ban_service.py into 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().
  3. 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 within ban_service.py that reference the old _-prefixed names.
  4. In backend/app/services/history_service.py, replace the import from app.services.ban_service import _get_fail2ban_db_path, _parse_data_json, _ts_to_iso with from app.utils.fail2ban_db_utils import get_fail2ban_db_path, parse_data_json, ts_to_iso. Update all call sites in history_service.py.
  5. Search the entire backend/ tree for any other references to the old _-prefixed names and update them.
  6. 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 imports geo_service at ~L860, ~L1047)
  • backend/app/services/ban_service.py (lazy imports geo_service at ~L251, ~L392)
  • backend/app/services/blocklist_service.py (lazy imports geo_service at ~L343)
  • backend/app/services/history_service.py (TYPE_CHECKING import of geo_service at ~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:

  1. 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 what geo_service provides (typically async def enrich(ip: str) -> GeoInfo | None).
  2. Remove all lazy imports of geo_service from jail_service.py, ban_service.py, blocklist_service.py, and history_service.py.
  3. In the corresponding router files, import geo_service and pass its enrichment function as the callback when calling the service functions. The router layer is where wiring belongs.
  4. Run existing tests: cd backend && python -m pytest tests/ — all tests must pass. If tests mock geo_service inside 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 (defines JailNotFoundError and 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_service imports JailNotFoundError from jail_service at ~L57-58)

What to do:

  1. Create backend/app/exceptions.py.
  2. Grep the entire backend/app/services/ directory for all custom exception class definitions (classes inheriting from Exception or HTTPException). Collect every exception that is imported by more than one module.
  3. Move those shared exception classes into backend/app/exceptions.py.
  4. Update all import statements across backend/app/services/, backend/app/routers/, and backend/app/ to import from backend/app/exceptions.py.
  5. Exception classes used only within a single service may remain in that service file.
  6. 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 from config_file_service

What to do:

  1. Read backend/app/services/config_file_service.py and 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)
  2. Create three new service files:
    • backend/app/services/jail_config_service.py — jail-related functions
    • backend/app/services/filter_config_service.py — filter-related functions
    • backend/app/services/action_config_service.py — action-related functions
  3. Move functions from config_file_service.py into the appropriate new file. Any truly shared helpers used across all three domains should remain in config_file_service.py (renamed to a shared helper) or move to backend/app/utils/.
  4. Delete config_file_service.py once empty (or keep it as a thin re-export layer for backward compatibility during transition).
  5. Update all imports in backend/app/routers/ and backend/app/services/ that referenced config_file_service.
  6. 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:

  1. Read backend/app/services/config_service.py and identify all functions related to log-preview and regex-testing (these are distinct from the core socket-based config reading/writing functions).
  2. Create backend/app/services/log_service.py.
  3. Move the log-preview and regex-test functions into log_service.py.
  4. Update imports in backend/app/routers/config.py (or create a new backend/app/routers/log.py if the endpoints are logically separate).
  5. 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 to jail_activation_service.py (or the split modules from Task 4)
  • backend/app/services/file_config_service.py → rename to raw_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:

  1. Rename backend/app/services/file_config_service.py to backend/app/services/raw_config_io_service.py.
  2. Update all import statements across the codebase (backend/app/services/, backend/app/routers/, backend/app/tasks/, tests) that reference file_config_service to reference raw_config_io_service.
  3. Also rename the corresponding router if one exists: check backend/app/routers/file_config.py and rename accordingly.
  4. 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 (imports setup_service at ~L23)
  • backend/app/services/config_service.py (imports setup_service at ~L47, lazy-imports health_service at ~L891)
  • backend/app/services/blocklist_service.py (lazy-imports jail_service at ~L299)

What to do:

  1. For each remaining service-to-service import, determine why the dependency exists (read the calling code).
  2. 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.
  3. Remove all direct and lazy imports between service modules.
  4. 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:

  1. Read Docs/Architekture.md and the actual file listings below.
  2. Add the following missing items to the appropriate sections:
    • Repositories: Add fail2ban_db_repo.py and geo_cache_repo.py (in backend/app/repositories/)
    • Utils: Add conffile_parser.py, config_parser.py, config_writer.py, jail_config.py (in backend/app/utils/)
    • Tasks: Add geo_re_resolve.py (in backend/app/tasks/)
    • Services: Correct the entry that lists conffile_parser as a service — it is in app/utils/
    • Routers: Add file_config.py (in backend/app/routers/)
  3. If Tasks 17 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).
  4. 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.tsx or frontend/src/layouts/ (wherever the top-level layout lives)

What to do:

  1. Create frontend/src/components/ErrorBoundary.tsx — a React class component implementing componentDidCatch and getDerivedStateFromError. 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).
  2. Read frontend/src/App.tsx to find the main layout/route wrapper.
  3. Wrap the main content (inside <App> or <MainLayout>) with <ErrorBoundary> so that any component crash shows the fallback instead of a white screen.
  4. 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)

Priority: Low
Refactoring ref: Refactoring.md §7
Affected files:

  • frontend/src/components/BanTable.tsx (has formatTimestamp() ~L103)
  • frontend/src/components/jail/BannedIpsSection.tsx (has fmtTime() ~L139)
  • frontend/src/pages/JailDetailPage.tsx (has fmtSeconds() ~L152)
  • frontend/src/pages/JailsPage.tsx (has fmtSeconds() ~L147)

What to do:

  1. Create frontend/src/utils/formatDate.ts.
  2. Define three exported functions:
    • formatTimestamp(ts: string): string — consolidation of formatTimestamp and fmtTime
    • formatSeconds(seconds: number): string — consolidation of the two identical fmtSeconds functions
  3. 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.
  4. 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:

  1. Read all three hook files. Identify the common pattern: load item via fetch → store in state → expose save function → handle abort controller cleanup.
  2. Create frontend/src/hooks/useConfigItem.ts with 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> }
    
  3. Rewrite useFilterConfig.ts, useActionConfig.ts, and useJailFileConfig.ts to be thin wrappers around useConfigItem<T> — each file should be <20 lines, just providing the specific fetch/save functions.
  4. 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:

  1. 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");
    }
    
  2. Grep all hook files for catch blocks. In every hook that catches fetch errors:
    • Replace the catch body with a call to handleFetchError(err, setError).
  3. 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:

  1. For each large file, identify logical UI sections that can be extracted into their own component files.
  2. 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).
  3. Place extracted components in appropriate directories (e.g., frontend/src/components/blocklist/, frontend/src/components/jail/).
  4. 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).
  5. 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/ and frontend/src/components/ that define identical card/section styles using makeStyles with backgroundColor, borderRadius, border, padding using Fluent UI tokens.

What to do:

  1. Grep the frontend codebase for makeStyles calls that contain backgroundColor and borderRadius together. Identify the common pattern.
  2. Create frontend/src/theme/commonStyles.ts with a shared useCardStyles() hook that exports the common section/card style class.
  3. In each of the 13+ files, remove the local makeStyles definition for the card/section style and import useCardStyles from commonStyles.ts instead. Keep any file-specific style overrides local.
  4. 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.