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
This commit is contained in:
@@ -171,6 +171,7 @@ The business logic layer. Services orchestrate operations, enforce rules, and co
|
||||
| `ban_service.py` | Executes ban and unban commands via the fail2ban socket, queries the currently banned IP list, validates IPs before banning |
|
||||
| `config_service.py` | Reads active jail and filter configuration from fail2ban, writes configuration changes, validates regex patterns, triggers reload |
|
||||
| `file_config_service.py` | Reads and writes raw fail2ban config files on disk (jail.d/, filter.d/, action.d/); lists files, reads content, overwrites files, toggles enabled/disabled |
|
||||
| `config_file_service.py` | Parses jail.conf / jail.local / jail.d/* to discover inactive jails; writes .local overrides to activate or deactivate jails; triggers fail2ban reload |
|
||||
| `conffile_parser.py` | Parses fail2ban `.conf` files into structured Python types (jail config, filter config, action config); also serialises back to text |
|
||||
| `history_service.py` | Queries the fail2ban database for historical ban records, builds per-IP timelines, computes ban counts and repeat-offender flags |
|
||||
| `blocklist_service.py` | Downloads blocklists via aiohttp, validates IPs/CIDRs, applies bans through fail2ban or iptables, logs import results |
|
||||
|
||||
@@ -90,6 +90,8 @@ A dedicated view for managing fail2ban jails and taking manual ban actions.
|
||||
- A list of all jails showing their name, current status (running / stopped / idle), backend type, and key metrics.
|
||||
- For each jail: number of currently banned IPs, total bans since start, current failures detected, and total failures.
|
||||
- Quick indicators for the jail's find time, ban time, and max retries.
|
||||
- A toggle to also show **Inactive Jails** — jails that are defined in fail2ban config files but are not currently running.
|
||||
- Each inactive jail has an **Activate** button that enables and reloads it immediately, with optional overrides for ban time, find time, max retries, port, and log path.
|
||||
|
||||
### Jail Detail
|
||||
|
||||
@@ -154,6 +156,7 @@ A page to inspect and modify the fail2ban configuration without leaving the web
|
||||
- A scrollable left pane lists all items (jail names, filter filenames, action filenames).
|
||||
- Each item displays an **Active** or **Inactive** badge. Active items are sorted to the top; items within each group are sorted alphabetically.
|
||||
- A jail is "active" if fail2ban reports it as enabled at runtime. A filter or action is "active" if it is referenced by at least one enabled jail.
|
||||
- Inactive jails (present in config files but not running) are discoverable from the Jails tab. Selecting one shows its config file settings and allows activating it.
|
||||
- Clicking an item loads its structured configuration form in the right detail pane.
|
||||
- On narrow screens (< 900 px) the list pane collapses into a dropdown above the detail pane.
|
||||
- Show global fail2ban settings (ban time, find time, max retries, etc.) on the Global Settings tab.
|
||||
@@ -169,6 +172,8 @@ A page to inspect and modify the fail2ban configuration without leaving the web
|
||||
- Configure ban-time escalation: enable incremental banning and set factor, formula, multipliers, maximum ban time, and random jitter.
|
||||
- Save changes and optionally reload fail2ban to apply them immediately.
|
||||
- Validation feedback if a regex pattern or setting value is invalid before saving.
|
||||
- **Activate** an inactive jail directly from the Jails tab detail pane, with optional parameter overrides.
|
||||
- **Deactivate** a running jail from the Jails tab; writes ``enabled = false`` to a local override file and reloads fail2ban.
|
||||
|
||||
### Raw Configuration Editing
|
||||
|
||||
|
||||
437
Docs/Tasks.md
437
Docs/Tasks.md
@@ -4,217 +4,348 @@ This document breaks the entire BanGUI project into development stages, ordered
|
||||
|
||||
---
|
||||
|
||||
## Config View Redesign — List/Detail Layout with Active Status and Raw Export ✅ COMPLETE
|
||||
## Stage 1 — Inactive Jail Discovery and Activation
|
||||
|
||||
### Overview
|
||||
Currently BanGUI only shows jails that are actively running in fail2ban. fail2ban ships with dozens of pre-defined jails in `jail.conf` (sshd, apache-auth, nginx-http-auth, postfix, etc.) that are disabled by default. Users must be able to see these inactive jails and activate them from the web interface.
|
||||
|
||||
Redesign the Jails, Filters, and Actions tabs on the Configuration page (`frontend/src/pages/ConfigPage.tsx`) to use a **master/detail list layout** instead of the current accordion pattern. The left pane shows a scrollable list of config items (jail names, filter names, action names). Each item displays an active/inactive badge. Active items sort to the top. Clicking an item shows its structured form editor in the right pane, with a collapsible raw-text export section appended at the bottom.
|
||||
### Task 1.1 — Backend: Parse Inactive Jails from Config Files ✅ DONE
|
||||
|
||||
Use only **Fluent UI React v9** (`@fluentui/react-components`, `@fluentui/react-icons`) components as specified in [Web-Design.md](Web-Design.md) and [Web-Development.md](Web-Development.md). No additional UI libraries.
|
||||
**Goal:** Read all jail definitions from the fail2ban configuration files and identify which ones are not currently enabled.
|
||||
|
||||
### References
|
||||
**Details:**
|
||||
|
||||
- [Web-Design.md](Web-Design.md) — Design rules: Fluent UI components, tokens, spacing, accessibility.
|
||||
- [Web-Development.md](Web-Development.md) — Code rules: TypeScript strict, `makeStyles`, component structure, hooks, API layer.
|
||||
- Fluent UI v9 docs: https://github.com/microsoft/fluentui — components reference.
|
||||
- Create a new service `config_file_service.py` in `app/services/` responsible for reading and writing fail2ban config files on disk.
|
||||
- Parse `jail.conf` and any `jail.local` / `jail.d/*.conf` / `jail.d/*.local` override files. Use Python's `configparser` (INI format) since fail2ban configs follow that format. Local files override `.conf` files (fail2ban merge order: `jail.conf` → `jail.local` → `jail.d/*.conf` → `jail.d/*.local`).
|
||||
- For each jail section found, extract at minimum: jail name, `enabled` flag (defaults to `false` if absent), `filter` name (defaults to the jail name if absent), `action` references, `port`, `logpath`, `backend`, `bantime`, `findtime`, `maxretry`.
|
||||
- Build a list of `InactiveJail` objects for jails where `enabled = false` or that are defined in config but not reported by `fail2ban-client status`.
|
||||
- The fail2ban config base path should come from the application settings (default: `/etc/fail2ban/`). The path is already available via `config.py` settings or can be derived from the fail2ban socket connection.
|
||||
- Handle the `[DEFAULT]` section properly — its values provide defaults inherited by every jail section.
|
||||
- Handle `[INCLUDES]` sections (`before` / `after` directives) so that path includes like `paths-debian.conf` are resolved, providing variables like `%(sshd_log)s`.
|
||||
|
||||
**Files to create/modify:**
|
||||
- `app/services/config_file_service.py` (new)
|
||||
- `app/models/config.py` (add `InactiveJail`, `InactiveJailListResponse` models)
|
||||
|
||||
**References:** [Features.md §6](Features.md), [Architekture.md §2](Architekture.md), [Backend-Development.md](Backend-Development.md)
|
||||
|
||||
---
|
||||
|
||||
### Task A — Shared List/Detail Layout Component
|
||||
### Task 1.2 — Backend: API Endpoints for Inactive Jails ✅ DONE
|
||||
|
||||
**File:** `frontend/src/components/config/ConfigListDetail.tsx`
|
||||
**Goal:** Expose inactive jail data and an activation endpoint via the REST API.
|
||||
|
||||
Create a reusable layout component that renders a two-pane master/detail view.
|
||||
**Details:**
|
||||
|
||||
**Left pane (list):**
|
||||
- Fixed width ~280 px, full height of the tab content area, with its own vertical scroll.
|
||||
- Use a vertical stack of clickable items. Each item is a Fluent UI `Card` (or a simple styled `div` with `tokens.colorNeutralBackground2` on hover) displaying:
|
||||
- The config **name** (e.g. `sshd`, `iptables-multiport`), truncated with ellipsis + tooltip for long names.
|
||||
- A Fluent UI `Badge` to the right of the name: **"Active"** (`appearance="filled"`, `color="success"`) or **"Inactive"** (`appearance="outline"`, `color="informative"`).
|
||||
- The selected item gets a left border accent (`tokens.colorBrandBackground`) and a highlighted background (`tokens.colorNeutralBackground1Selected`).
|
||||
- Items are sorted: **active items first**, then inactive, alphabetical within each group.
|
||||
- Keyboard navigable (arrow keys, Enter to select). Follow the accessibility rules from Web-Design.md §15.
|
||||
- Add a `GET /api/config/jails/inactive` endpoint to the config router that returns all inactive jails found in the config files. Each entry should include: `name`, `filter`, `actions` (list), `port`, `logpath`, `bantime`, `findtime`, `maxretry`, and the source file where the jail is defined.
|
||||
- Add a `POST /api/config/jails/{name}/activate` endpoint that enables an inactive jail. This endpoint should:
|
||||
1. Validate the jail name exists in the parsed config.
|
||||
2. Write `enabled = true` into the appropriate `.local` override file (`jail.local` or `jail.d/{name}.local`). Never modify `.conf` files directly — always use `.local` overrides following fail2ban convention.
|
||||
3. Optionally accept a request body with override values (`bantime`, `findtime`, `maxretry`, `port`, `logpath`) that get written alongside the `enabled = true` line.
|
||||
4. Trigger a fail2ban reload so the jail starts immediately.
|
||||
5. Return the jail's new status after activation.
|
||||
- Add a `POST /api/config/jails/{name}/deactivate` endpoint that sets `enabled = false` in the `.local` file and reloads fail2ban to stop the jail.
|
||||
- All endpoints require authentication. Return 404 if the jail name is not found in any config file. Return 409 if the jail is already active/inactive.
|
||||
|
||||
**Right pane (detail):**
|
||||
- Takes remaining width. Renders whatever `children` or render-prop content the parent tab passes for the currently selected item.
|
||||
- Displays an empty state (`"Select an item from the list"`) when nothing is selected.
|
||||
- Uses `Skeleton` / `Spinner` while the detail is loading.
|
||||
**Files to create/modify:**
|
||||
- `app/routers/config.py` (add endpoints)
|
||||
- `app/services/config_file_service.py` (add activate/deactivate logic)
|
||||
- `app/models/config.py` (add request/response models: `ActivateJailRequest`, `JailActivationResponse`)
|
||||
|
||||
**Props interface (suggestion):**
|
||||
|
||||
```typescript
|
||||
interface ConfigListDetailProps<T extends { name: string }> {
|
||||
items: T[];
|
||||
isActive: (item: T) => boolean;
|
||||
selectedName: string | null;
|
||||
onSelect: (name: string) => void;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
children: React.ReactNode; // detail content for selected item
|
||||
}
|
||||
```
|
||||
|
||||
**Styles:** Add new style slots to `frontend/src/components/config/configStyles.ts`:
|
||||
- `listDetailRoot` — flex row, gap `tokens.spacingHorizontalM`.
|
||||
- `listPane` — fixed width 280 px, overflow-y auto, border-right `tokens.colorNeutralStroke2`.
|
||||
- `listItem` — padding, hover background, cursor pointer.
|
||||
- `listItemSelected` — left brand-colour border, selected background.
|
||||
- `detailPane` — flex 1, overflow-y auto, padding.
|
||||
|
||||
Responsive: On screens < 900 px, collapse the list pane into a `Dropdown` / `Select` above the detail pane so it doesn't take too much horizontal space.
|
||||
**References:** [Features.md §6](Features.md), [Backend-Development.md §3](Backend-Development.md)
|
||||
|
||||
---
|
||||
|
||||
### Task B — Active Status for Jails, Filters, and Actions
|
||||
### Task 1.3 — Frontend: Inactive Jails in Configuration View ✅ DONE
|
||||
|
||||
Determine "active" status for each config type so it can be shown in the list.
|
||||
**Goal:** Display inactive jails in the Configuration page and let users activate them.
|
||||
|
||||
**Jails:**
|
||||
- The existing `JailConfig` type does not carry an `enabled` flag directly. The jails API (`GET /api/jails`) returns `JailSummary` objects which contain `enabled: boolean`. To get the active status, fetch the jails list from `fetchJails()` (in `api/jails.ts`) alongside the jail configs from `fetchJailConfigs()`. Merge the two by name: a jail is "active" if `enabled === true` in the jails list.
|
||||
- Alternatively, check the `JailConfigFile` entries from the jail files API which have `enabled: boolean` — determine which data source is more reliable for showing runtime active state.
|
||||
**Details:**
|
||||
|
||||
**Filters:**
|
||||
- A filter is "active" if it is referenced by at least one active jail. After fetching jail configs (`JailConfig[]`), collect all unique filter names used by enabled jails. Cross-reference with the filter files list (`ConfFileEntry[]`). Mark used ones as active.
|
||||
- This requires correlating data: jail config has a `name` field that often matches the filter name (convention in fail2ban: jail name = filter name unless overridden). For accuracy, the filter name a jail uses can be inferred from the jail name or, if the backend provides a `filter` field on `JailConfig`, from that.
|
||||
- In the **Jails tab** of the Configuration page, extend the existing master/detail list layout. The left pane currently shows active jails — add a section or toggle that also shows inactive jails. Use the **Active/Inactive badge** system described in [Features.md §6](Features.md): active jails sorted to the top with an "Active" badge, inactive jails below with an "Inactive" badge, alphabetical within each group.
|
||||
- Clicking an inactive jail in the list loads its config details in the right detail pane. Show all parsed fields (filter, actions, port, logpath, bantime, findtime, maxretry) as read-only or editable fields.
|
||||
- Add an **"Activate Jail"** button (prominent, primary style) in the detail pane for inactive jails. Clicking it opens a confirmation dialog that allows the user to override default values (bantime, findtime, maxretry, port, logpath) before activation. On confirm, call `POST /api/config/jails/{name}/activate` with the overrides.
|
||||
- After successful activation, refresh the jail list and show the jail under the active group with a success toast notification.
|
||||
- For active jails, add a **"Deactivate Jail"** button (secondary/danger style) that calls the deactivate endpoint after confirmation.
|
||||
- Add the API functions `fetchInactiveJails()`, `activateJail(name, overrides)`, `deactivateJail(name)` to `frontend/src/api/config.ts`.
|
||||
- Add corresponding TypeScript types (`InactiveJail`, `ActivateJailRequest`) to `frontend/src/types/config.ts`.
|
||||
|
||||
**Actions:**
|
||||
- An action is "active" if it is referenced by at least one active jail's `actions` array. After fetching jail configs, collect all unique action names from `JailConfig.actions[]` arrays of enabled jails. Cross-reference with action files.
|
||||
**Files to create/modify:**
|
||||
- `frontend/src/api/config.ts` (add API calls)
|
||||
- `frontend/src/types/config.ts` (add types)
|
||||
- `frontend/src/components/config/JailsTab.tsx` (add inactive jail list + activation UI)
|
||||
- New component: `frontend/src/components/config/ActivateJailDialog.tsx`
|
||||
|
||||
**Implementation:**
|
||||
- Create a new hook `frontend/src/hooks/useConfigActiveStatus.ts` that:
|
||||
1. Fetches jails list (`fetchJails`), jail configs (`fetchJailConfigs`), filter files (`fetchFilterFiles`), and action files (`fetchActionFiles`) in parallel.
|
||||
2. Computes and returns `{ activeJails: Set<string>, activeFilters: Set<string>, activeActions: Set<string>, loading: boolean, error: string | null }`.
|
||||
3. Cache results and re-fetch on demand (expose a `refresh` function).
|
||||
- The hook is consumed by the three tabs so each tab knows which items are active.
|
||||
**References:** [Features.md §6](Features.md), [Web-Design.md](Web-Design.md), [Web-Development.md](Web-Development.md)
|
||||
|
||||
---
|
||||
|
||||
### Task C — Redesign JailsTab to List/Detail Layout
|
||||
### Task 1.4 — Frontend: Inactive Jails on Jails Page ✅ DONE
|
||||
|
||||
**File:** `frontend/src/components/config/JailsTab.tsx`
|
||||
**Goal:** Show inactive jails alongside active jails in the Jail Management overview.
|
||||
|
||||
Replace the current `Accordion`-based layout with the `ConfigListDetail` component from Task A.
|
||||
**Details:**
|
||||
|
||||
1. Fetch jail configs via `useJailConfigs` (already exists in `hooks/useConfig.ts`).
|
||||
2. Use the active-status hook from Task B to get `activeJails`.
|
||||
3. Render `ConfigListDetail` with:
|
||||
- `items` = jail config list.
|
||||
- `isActive` = `(jail) => activeJails.has(jail.name)`.
|
||||
- `onSelect` updates `selectedName` state.
|
||||
4. The right-pane detail content is the **existing `JailAccordionPanel` logic** (the form fields for ban_time, find_time, max_retry, regex patterns, log paths, escalation, etc.) — extract it into a standalone `JailConfigDetail` component if not already. Remove the accordion wrapper; it is just the form body now.
|
||||
5. **Export section** (new, at the bottom of the detail pane):
|
||||
- A collapsible section (use `Accordion` with a single item, or a `Button` toggle) titled **"Raw Configuration"**.
|
||||
- Contains a Fluent UI `Textarea` (monospace font, full width, ~20 rows) pre-filled with the raw plain-text representation of the jail config. The raw text is fetched via the existing `fetchJailConfig` or `fetchJailConfigFile` endpoint that returns the file content as a string.
|
||||
- The textarea is **editable**: the user can modify the raw text and click a "Save Raw" button to push the changes back via the existing `updateJailConfigFile` PUT endpoint.
|
||||
- Show an `AutoSaveIndicator` or manual save button + success/error `MessageBar`.
|
||||
- On the **Jails Page** (`JailsPage.tsx`), extend the jail overview table to include inactive jails. Add a column or badge showing each jail's activation state.
|
||||
- Inactive jails should appear in the table with dimmed/muted styling and show "Inactive" as status. They should not have start/stop/idle/reload controls — only an "Activate" action button.
|
||||
- Add a toggle or filter above the table: "Show inactive jails" (default: on). When off, only active jails are displayed.
|
||||
- Clicking an inactive jail's name navigates to the Configuration page's jail detail for that jail, pre-selecting it in the list.
|
||||
|
||||
**Files to create/modify:**
|
||||
- `frontend/src/pages/JailsPage.tsx`
|
||||
- `frontend/src/hooks/useJails.ts` (extend to optionally fetch inactive jails)
|
||||
|
||||
**References:** [Features.md §5](Features.md), [Web-Design.md](Web-Design.md)
|
||||
|
||||
---
|
||||
|
||||
### Task D — Redesign FiltersTab to List/Detail Layout
|
||||
### Task 1.5 — Tests: Inactive Jail Parsing and Activation ✅ DONE
|
||||
|
||||
**File:** `frontend/src/components/config/FiltersTab.tsx`
|
||||
**Goal:** Full test coverage for the new inactive-jail functionality.
|
||||
|
||||
Replace the current `Accordion`-based layout with `ConfigListDetail`.
|
||||
**Details:**
|
||||
|
||||
1. Fetch filter file list via `fetchFilterFiles` (already used).
|
||||
2. Use `activeFilters` from the active-status hook (Task B).
|
||||
3. Render `ConfigListDetail` with:
|
||||
- `items` = filter file entries.
|
||||
- `isActive` = `(f) => activeFilters.has(f.name)`.
|
||||
4. On item select, lazily load the parsed filter via `useFilterConfig` (already exists in `hooks/useFilterConfig.ts`).
|
||||
5. Right pane renders the **existing `FilterForm`** component with the loaded config.
|
||||
6. **Export section** at the bottom of the detail pane:
|
||||
- Collapsible "Raw Configuration" section.
|
||||
- `Textarea` (monospace) pre-filled with the raw file content fetched via `fetchFilterFile(name)` (returns `ConfFileContent` with a `content: string` field).
|
||||
- Editable with a "Save Raw" `Button` that calls `updateFilterFile(name, { content })`.
|
||||
- Success/error feedback via `MessageBar`.
|
||||
- **Service tests** (`tests/test_services/test_config_file_service.py`): Test config file parsing with mock jail.conf content containing multiple jails (enabled and disabled). Test the merge order (`.conf` → `.local` → `jail.d/`). Test DEFAULT section inheritance. Test variable interpolation (`%(sshd_log)s`). Test activation writes to `.local` files. Test deactivation.
|
||||
- **Router tests** (`tests/test_routers/test_config.py`): Add tests for the three new endpoints. Test 404 for unknown jail names. Test 409 for already-active/inactive jails. Test that override values are passed through correctly.
|
||||
- **Frontend tests**: Add unit tests for the new API functions. Add component tests for the activation dialog and the inactive jail display.
|
||||
|
||||
**Files to create/modify:**
|
||||
- `backend/tests/test_services/test_config_file_service.py` (new)
|
||||
- `backend/tests/test_routers/test_config.py` (extend)
|
||||
- `frontend/src/components/config/__tests__/` (new tests)
|
||||
|
||||
**References:** [Backend-Development.md](Backend-Development.md), [Web-Development.md](Web-Development.md)
|
||||
|
||||
---
|
||||
|
||||
### Task E — Redesign ActionsTab to List/Detail Layout
|
||||
## Stage 2 — Filter Configuration Discovery and Activation
|
||||
|
||||
**File:** `frontend/src/components/config/ActionsTab.tsx`
|
||||
fail2ban ships with a large collection of filter definitions in `filter.d/` (over 80 files). Users need to see all available filters — both those currently in use by active jails and those available but unused — and assign them to jails.
|
||||
|
||||
Same pattern as Task D but for actions.
|
||||
### Task 2.1 — Backend: List All Available Filters with Active/Inactive Status
|
||||
|
||||
1. Fetch action file list via `fetchActionFiles`.
|
||||
2. Use `activeActions` from the active-status hook (Task B).
|
||||
3. Render `ConfigListDetail` with:
|
||||
- `items` = action file entries.
|
||||
- `isActive` = `(a) => activeActions.has(a.name)`.
|
||||
4. On item select, lazily load the parsed action via `useActionConfig` (already exists).
|
||||
5. Right pane renders the **existing `ActionForm`** component.
|
||||
6. **Export section** at the bottom:
|
||||
- Collapsible "Raw Configuration" section.
|
||||
- `Textarea` (monospace) with raw file content from `fetchActionFile(name)`.
|
||||
- "Save Raw" button calling `updateActionFile(name, { content })`.
|
||||
- Feedback messages.
|
||||
**Goal:** Enumerate all filter config files and mark each as active or inactive.
|
||||
|
||||
**Details:**
|
||||
|
||||
- Add a method `list_filters()` to `config_file_service.py` that scans the `filter.d/` directory within the fail2ban config path.
|
||||
- For each `.conf` file found, parse its `[Definition]` section to extract: `failregex` (list of patterns), `ignoreregex` (list), `datepattern`, `journalmatch`, and any other fields present. Also parse `[Init]` sections for default variable bindings.
|
||||
- Handle `.local` overrides: if `sshd.local` exists alongside `sshd.conf`, merge the local values on top of the conf values (local wins).
|
||||
- Determine active/inactive status: a filter is "active" if its name matches the `filter` field of any currently enabled jail (cross-reference with the active jail list from fail2ban and the inactive jail configs). Mark it accordingly.
|
||||
- Return a list of `FilterConfig` objects with: `name`, `active` (bool), `used_by_jails` (list of jail names using this filter), `failregex` (list), `ignoreregex` (list), `datepattern`, `source_file` (path to the `.conf` file), `has_local_override` (bool).
|
||||
- Add a `GET /api/config/filters` endpoint to the config router returning all filters.
|
||||
- Add a `GET /api/config/filters/{name}` endpoint returning the full parsed detail of a single filter.
|
||||
|
||||
**Files to create/modify:**
|
||||
- `app/services/config_file_service.py` (add `list_filters`, `get_filter`)
|
||||
- `app/models/config.py` (add `FilterConfig`, `FilterListResponse`, `FilterDetailResponse`)
|
||||
- `app/routers/config.py` (add endpoints)
|
||||
|
||||
**References:** [Features.md §6](Features.md), [Architekture.md §2](Architekture.md)
|
||||
|
||||
---
|
||||
|
||||
### Task F — Raw Export Section Component
|
||||
### Task 2.2 — Backend: Activate and Edit Filters
|
||||
|
||||
**File:** `frontend/src/components/config/RawConfigSection.tsx`
|
||||
**Goal:** Allow users to assign a filter to a jail and edit filter regex patterns.
|
||||
|
||||
Extract the raw-export pattern into a reusable component so Jails, Filters, and Actions tabs don't duplicate logic.
|
||||
**Details:**
|
||||
|
||||
**Props:**
|
||||
- Add a `PUT /api/config/filters/{name}` endpoint that writes changes to a filter's `.local` override file. Accepts updated `failregex`, `ignoreregex`, `datepattern`, and `journalmatch` values. Never write to the `.conf` file directly.
|
||||
- Add a `POST /api/config/jails/{jail_name}/filter` endpoint that changes which filter a jail uses. This writes `filter = {filter_name}` to the jail's `.local` config. Requires a reload for the change to take effect.
|
||||
- Add a `POST /api/config/filters` endpoint to create a brand-new filter. Accepts a name and the filter definition fields. Creates a new file at `filter.d/{name}.local`.
|
||||
- Add a `DELETE /api/config/filters/{name}` endpoint that deletes a custom filter's `.local` file. Refuse to delete files that are `.conf` (shipped defaults) — only user-created `.local` files without a corresponding `.conf` can be fully removed.
|
||||
- Validate all regex patterns using Python's `re` module before writing them to disk. Return 422 with specific error details if any pattern is invalid.
|
||||
- After any write operation, optionally trigger a fail2ban reload if the user requests it (query param `?reload=true`).
|
||||
|
||||
```typescript
|
||||
interface RawConfigSectionProps {
|
||||
/** Async function that returns the raw file content string. */
|
||||
fetchContent: () => Promise<string>;
|
||||
/** Async function that saves updated raw content. */
|
||||
saveContent: (content: string) => Promise<void>;
|
||||
/** Label shown in the collapsible header, e.g. "Raw Jail Configuration". */
|
||||
label?: string;
|
||||
}
|
||||
```
|
||||
**Files to create/modify:**
|
||||
- `app/services/config_file_service.py` (add filter write/create/delete methods)
|
||||
- `app/routers/config.py` (add endpoints)
|
||||
- `app/models/config.py` (add `FilterUpdateRequest`, `FilterCreateRequest`)
|
||||
|
||||
**Behaviour:**
|
||||
- Renders a collapsible section (single `AccordionItem` or a disclosure `Button`).
|
||||
- When expanded for the first time, calls `fetchContent()` and fills the `Textarea`.
|
||||
- Uses monospace font (`fontFamily: "monospace"`) and a left brand-colour accent border (reuse `styles.codeInput` from `configStyles.ts`).
|
||||
- "Save Raw" `Button` with `appearance="primary"` calls `saveContent(text)`.
|
||||
- Shows `AutoSaveIndicator`-style feedback (idle → saving → saved / error).
|
||||
- The `Textarea` is resizable vertically, minimum 15 rows.
|
||||
**References:** [Features.md §6](Features.md), [Backend-Development.md](Backend-Development.md)
|
||||
|
||||
---
|
||||
|
||||
### Task G — Update Barrel Exports and ConfigPage
|
||||
### Task 2.3 — Frontend: Filters Tab with Active/Inactive Display and Activation
|
||||
|
||||
1. **`frontend/src/components/config/index.ts`** — Add exports for `ConfigListDetail` and `RawConfigSection`.
|
||||
2. **`frontend/src/pages/ConfigPage.tsx`** — No structural changes needed if the tabs internally switch to the new layout. Verify the page still renders all tabs correctly.
|
||||
3. **`frontend/src/components/config/configStyles.ts`** — Add the new style slots described in Task A. Do not remove existing styles that may still be used by other tabs (Global, Server, Map, Regex Tester, Export).
|
||||
**Goal:** Enhance the Filters tab in the Configuration page to show all filters with their active/inactive status and allow editing.
|
||||
|
||||
**Details:**
|
||||
|
||||
- Redesign the **Filters tab** to use the master/detail list layout described in [Features.md §6](Features.md).
|
||||
- The **left pane** lists all filter names with an Active or Inactive badge. Active filters (those used by at least one enabled jail) are sorted to the top. Within each group, sort alphabetically.
|
||||
- The badge should also show which jails use the filter (e.g., "Active — used by sshd, apache-auth").
|
||||
- Clicking a filter loads its detail in the **right pane**: `failregex` patterns (editable list), `ignoreregex` patterns (editable list), `datepattern` (editable), source file info, and whether a `.local` override exists.
|
||||
- Add a **"Save Changes"** button that calls `PUT /api/config/filters/{name}` to persist edits to the `.local` override. Show save-state feedback (idle → saving → saved → error).
|
||||
- Add an **"Assign to Jail"** button that opens a dialog where the user selects a jail and assigns this filter to it. This calls the assign-filter endpoint.
|
||||
- Add a **"Create Filter"** button at the top of the list pane that opens a dialog for entering a new filter name and regex patterns.
|
||||
- On narrow screens (< 900 px), collapse the list pane into a dropdown as specified in [Features.md §6](Features.md).
|
||||
- Include the existing **Raw Configuration** collapsible section at the bottom of the detail pane for direct file editing.
|
||||
|
||||
**Files to create/modify:**
|
||||
- `frontend/src/components/config/FiltersTab.tsx` (rewrite with master/detail layout)
|
||||
- `frontend/src/components/config/FilterForm.tsx` (update for editable fields)
|
||||
- `frontend/src/api/config.ts` (add `fetchFilters`, `fetchFilter`, `updateFilter`, `createFilter`, `deleteFilter`, `assignFilterToJail`)
|
||||
- `frontend/src/types/config.ts` (add types)
|
||||
- New component: `frontend/src/components/config/AssignFilterDialog.tsx`
|
||||
|
||||
**References:** [Features.md §6](Features.md), [Web-Design.md](Web-Design.md), [Web-Development.md](Web-Development.md)
|
||||
|
||||
---
|
||||
|
||||
### Task H — Testing and Validation
|
||||
### Task 2.4 — Tests: Filter Discovery and Management
|
||||
|
||||
1. **Type-check:** Run `npx tsc --noEmit` — zero errors.
|
||||
2. **Lint:** Run `npm run lint` — zero warnings.
|
||||
3. **Existing tests:** Run `npx vitest run` — all existing tests pass.
|
||||
4. **Manual verification:**
|
||||
- Navigate to Config → Jails tab. List pane shows jail names with active badges. Active jails appear at the top. Click a jail → right pane shows configuration form + collapsible raw editor.
|
||||
- Navigate to Config → Filters tab. Same list/detail pattern. Active filters (used by running jails) show "Active" badge.
|
||||
- Navigate to Config → Actions tab. Same pattern.
|
||||
- Resize window below 900 px — list collapses to a dropdown selector above the detail.
|
||||
- Keyboard: Tab into the list, arrow-key navigate, Enter to select.
|
||||
5. **New tests (optional but recommended):**
|
||||
- Unit test `ConfigListDetail` rendering with mock items, verifying sort order (active first) and selection callback.
|
||||
- Unit test `RawConfigSection` with mocked fetch/save functions.
|
||||
**Goal:** Test coverage for filter listing, editing, creation, and assignment.
|
||||
|
||||
**Details:**
|
||||
|
||||
- **Service tests**: Mock the `filter.d/` directory with sample `.conf` and `.local` files. Test parsing of `[Definition]` sections. Test merge of `.local` over `.conf`. Test active/inactive detection by cross-referencing with mock jail data. Test write operations create correct `.local` content. Test regex validation rejects bad patterns.
|
||||
- **Router tests**: Test all new filter endpoints (list, detail, update, create, delete, assign). Test auth required. Test 404/409/422 responses.
|
||||
- **Frontend tests**: Test the filter list rendering with mixed active/inactive items. Test the form submission in the detail pane. Test the assign dialog.
|
||||
|
||||
**Files to create/modify:**
|
||||
- `backend/tests/test_services/test_config_file_service.py` (extend)
|
||||
- `backend/tests/test_routers/test_config.py` (extend)
|
||||
- `frontend/src/components/config/__tests__/` (add filter tests)
|
||||
|
||||
---
|
||||
|
||||
### Implementation Order
|
||||
## Stage 3 — Action Configuration Discovery and Activation
|
||||
|
||||
1. Task F (RawConfigSection) — standalone, no dependencies.
|
||||
2. Task A (ConfigListDetail layout) — standalone component.
|
||||
3. Task B (useConfigActiveStatus hook) — needs only the existing API layer.
|
||||
4. Task C (JailsTab redesign) — depends on A, B, F.
|
||||
5. Task D (FiltersTab redesign) — depends on A, B, F.
|
||||
6. Task E (ActionsTab redesign) — depends on A, B, F.
|
||||
7. Task G (exports and wiring) — after C/D/E.
|
||||
8. Task H (testing) — last.
|
||||
fail2ban ships with many action definitions in `action.d/` (iptables, firewalld, cloudflare, sendmail, etc.). Users need to see all available actions, understand which are in use, and assign them to jails.
|
||||
|
||||
### Task 3.1 — Backend: List All Available Actions with Active/Inactive Status
|
||||
|
||||
**Goal:** Enumerate all action config files and mark each as active or inactive based on jail usage.
|
||||
|
||||
**Details:**
|
||||
|
||||
- Add a method `list_actions()` to `config_file_service.py` that scans the `action.d/` directory.
|
||||
- For each `.conf` file, parse the `[Definition]` section to extract: `actionstart`, `actionstop`, `actioncheck`, `actionban`, `actionunban` commands. Also parse `[Init]` for default variable bindings (port, protocol, chain, etc.).
|
||||
- Handle `.local` overrides the same way as filters.
|
||||
- Determine active/inactive status: an action is "active" if its name appears in the `action` field of any currently enabled jail. Cross-reference against both running jails (from fail2ban) and the config files.
|
||||
- Return `ActionConfig` objects with: `name`, `active` (bool), `used_by_jails` (list), `actionban` (command template), `actionunban` (command template), `actionstart`, `actionstop`, `init_params` (dict of Init variables), `source_file`, `has_local_override`.
|
||||
- Add `GET /api/config/actions` endpoint returning all actions.
|
||||
- Add `GET /api/config/actions/{name}` endpoint returning the full parsed detail of one action.
|
||||
|
||||
**Files to create/modify:**
|
||||
- `app/services/config_file_service.py` (add `list_actions`, `get_action`)
|
||||
- `app/models/config.py` (add `ActionConfig`, `ActionListResponse`, `ActionDetailResponse`)
|
||||
- `app/routers/config.py` (add endpoints)
|
||||
|
||||
**References:** [Features.md §6](Features.md), [Architekture.md §2](Architekture.md)
|
||||
|
||||
---
|
||||
|
||||
### Task 3.2 — Backend: Activate and Edit Actions
|
||||
|
||||
**Goal:** Allow users to assign actions to jails, edit action definitions, and create new actions.
|
||||
|
||||
**Details:**
|
||||
|
||||
- Add a `PUT /api/config/actions/{name}` endpoint that writes changes to an action's `.local` override file. Accepts updated `actionban`, `actionunban`, `actionstart`, `actionstop`, `actioncheck` values and any `[Init]` parameters.
|
||||
- Add a `POST /api/config/jails/{jail_name}/action` endpoint that adds an action to a jail's action list. This writes the action reference into the jail's `.local` config file. Multiple actions per jail are supported (fail2ban allows comma-separated action lists). Include optional parameters for the action (e.g., port, protocol).
|
||||
- Add a `DELETE /api/config/jails/{jail_name}/action/{action_name}` endpoint that removes an action from a jail's configuration.
|
||||
- Add a `POST /api/config/actions` endpoint to create a brand-new action definition (`.local` file).
|
||||
- Add a `DELETE /api/config/actions/{name}` endpoint to delete a custom action's `.local` file. Same safety rules as filters — refuse to delete shipped `.conf` files.
|
||||
- After any write, optionally reload fail2ban (`?reload=true`).
|
||||
|
||||
**Files to create/modify:**
|
||||
- `app/services/config_file_service.py` (add action write/create/delete/assign methods)
|
||||
- `app/routers/config.py` (add endpoints)
|
||||
- `app/models/config.py` (add `ActionUpdateRequest`, `ActionCreateRequest`, `AssignActionRequest`)
|
||||
|
||||
**References:** [Features.md §6](Features.md), [Backend-Development.md](Backend-Development.md)
|
||||
|
||||
---
|
||||
|
||||
### Task 3.3 — Frontend: Actions Tab with Active/Inactive Display and Activation
|
||||
|
||||
**Goal:** Enhance the Actions tab in the Configuration page to show all actions with active/inactive status and allow editing and assignment.
|
||||
|
||||
**Details:**
|
||||
|
||||
- Redesign the **Actions tab** to use the same master/detail list layout as Filters.
|
||||
- The **left pane** lists all action names with Active/Inactive badges. Active actions (used by at least one enabled jail) sorted to the top.
|
||||
- Badge shows which jails reference the action (e.g., "Active — used by sshd, postfix").
|
||||
- Clicking an action loads its detail in the **right pane**: `actionban`, `actionunban`, `actionstart`, `actionstop`, `actioncheck` (each in a code/textarea editor), `[Init]` parameters as key-value fields.
|
||||
- Add a **"Save Changes"** button for persisting edits to the `.local` override.
|
||||
- Add an **"Assign to Jail"** button that opens a dialog for selecting a jail and providing action parameters (port, protocol, chain). Calls the assign-action endpoint.
|
||||
- Add a **"Remove from Jail"** option in the detail pane — shows a list of jails currently using the action with a remove button next to each.
|
||||
- Add a **"Create Action"** button at the top of the list pane.
|
||||
- Raw Configuration collapsible section at the bottom.
|
||||
- Responsive collapse on narrow screens.
|
||||
|
||||
**Files to create/modify:**
|
||||
- `frontend/src/components/config/ActionsTab.tsx` (rewrite with master/detail layout)
|
||||
- `frontend/src/components/config/ActionForm.tsx` (update for editable fields)
|
||||
- `frontend/src/api/config.ts` (add `fetchActions`, `fetchAction`, `updateAction`, `createAction`, `deleteAction`, `assignActionToJail`, `removeActionFromJail`)
|
||||
- `frontend/src/types/config.ts` (add types)
|
||||
- New component: `frontend/src/components/config/AssignActionDialog.tsx`
|
||||
|
||||
**References:** [Features.md §6](Features.md), [Web-Design.md](Web-Design.md), [Web-Development.md](Web-Development.md)
|
||||
|
||||
---
|
||||
|
||||
### Task 3.4 — Tests: Action Discovery and Management
|
||||
|
||||
**Goal:** Test coverage for action listing, editing, creation, and assignment.
|
||||
|
||||
**Details:**
|
||||
|
||||
- **Service tests**: Mock `action.d/` with sample configs. Test parsing of `[Definition]` and `[Init]` sections. Test active/inactive detection. Test write and create operations. Test delete safety (refuse `.conf` deletion).
|
||||
- **Router tests**: Test all new action endpoints. Test auth, 404, 409, 422 responses.
|
||||
- **Frontend tests**: Test action list rendering, form editing, assign dialog, remove-from-jail flow.
|
||||
|
||||
**Files to create/modify:**
|
||||
- `backend/tests/test_services/test_config_file_service.py` (extend)
|
||||
- `backend/tests/test_routers/test_config.py` (extend)
|
||||
- `frontend/src/components/config/__tests__/` (add action tests)
|
||||
|
||||
---
|
||||
|
||||
## Stage 4 — Unified Config File Service and Shared Utilities
|
||||
|
||||
### Task 4.1 — Config File Parser Utility
|
||||
|
||||
**Goal:** Build a robust, reusable parser for fail2ban INI-style config files that all config-related features share.
|
||||
|
||||
**Details:**
|
||||
|
||||
- Create `app/utils/config_parser.py` with a `Fail2BanConfigParser` class that wraps Python's `configparser.ConfigParser` with fail2ban-specific behaviour:
|
||||
- **Merge order**: `.conf` file first, then `.local` overlay, then `*.d/` directory overrides.
|
||||
- **Variable interpolation**: Support `%(variable)s` syntax, resolving against `[DEFAULT]` and `[Init]` sections.
|
||||
- **Include directives**: Process `before = filename` and `after = filename` in `[INCLUDES]` sections, resolving paths relative to the config directory.
|
||||
- **Multi-line values**: Handle backslash-continuation and multi-line regex lists.
|
||||
- **Comments**: Strip `#` and `;` line/inline comments correctly.
|
||||
- The parser should return structured `dict` data that the `config_file_service` methods consume.
|
||||
- This is a pure utility — no I/O aside from reading files. Everything is synchronous, callable from async context via `run_in_executor`.
|
||||
- Write comprehensive unit tests in `tests/test_utils/test_config_parser.py` covering all edge cases: empty sections, missing files, circular includes, invalid interpolation, multi-line regex, override merging.
|
||||
|
||||
**Files to create/modify:**
|
||||
- `app/utils/config_parser.py` (new)
|
||||
- `tests/test_utils/test_config_parser.py` (new)
|
||||
|
||||
**References:** [Backend-Development.md](Backend-Development.md)
|
||||
|
||||
---
|
||||
|
||||
### Task 4.2 — Config File Writer Utility
|
||||
|
||||
**Goal:** Build a safe writer utility for creating and updating `.local` override files.
|
||||
|
||||
**Details:**
|
||||
|
||||
- Create `app/utils/config_writer.py` with functions for:
|
||||
- `write_local_override(base_path, section, key_values)` — Writes or updates a `.local` file. If the file exists, update only the specified keys under the given section. If it does not exist, create it with a header comment explaining it's a BanGUI-managed override.
|
||||
- `remove_local_key(base_path, section, key)` — Removes a specific key from a `.local` file.
|
||||
- `delete_local_file(path)` — Deletes a `.local` file, but only if no corresponding `.conf` exists or the caller explicitly allows orphan deletion.
|
||||
- All write operations must be atomic: write to a temporary file first, then rename into place (`os.replace`). This prevents corruption on crash.
|
||||
- Add a file-level lock (per config file) to prevent concurrent writes from racing.
|
||||
- Never write to `.conf` files — assert this in every write function.
|
||||
|
||||
**Files to create/modify:**
|
||||
- `app/utils/config_writer.py` (new)
|
||||
- `tests/test_utils/test_config_writer.py` (new)
|
||||
|
||||
**References:** [Backend-Development.md](Backend-Development.md)
|
||||
|
||||
Reference in New Issue
Block a user