From c110352e9e7b2ccf9e9ddd739ccbf2e8fb2183a4 Mon Sep 17 00:00:00 2001 From: Lukas Date: Sat, 14 Mar 2026 09:28:30 +0100 Subject: [PATCH] Config page tasks 1-4: dropdowns, key props, inactive jail full GUI, banaction fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task 1: Backend/LogEncoding/DatePattern dropdowns in JailConfigDetail - Added BACKENDS, LOG_ENCODINGS, DATE_PATTERN_PRESETS constants - Backend and Log Encoding: 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 --- Docs/Tasks.md | 296 ++++++++++-------- backend/app/models/config.py | 43 +++ backend/app/services/config_file_service.py | 89 ++++++ backend/app/services/config_service.py | 4 + frontend/src/components/config/ActionsTab.tsx | 1 + frontend/src/components/config/FiltersTab.tsx | 1 + frontend/src/components/config/JailsTab.tsx | 273 ++++++++++------ frontend/src/components/config/RegexList.tsx | 58 ++-- frontend/src/types/config.ts | 22 ++ 9 files changed, 541 insertions(+), 246 deletions(-) diff --git a/Docs/Tasks.md b/Docs/Tasks.md index a54e663..621bd4a 100644 --- a/Docs/Tasks.md +++ b/Docs/Tasks.md @@ -4,157 +4,203 @@ This document breaks the entire BanGUI project into development stages, ordered --- -## Stage: Config Page Cleanup & Improvements +## ✅ Task 1 — Convert Backend, Log Encoding, and Date Pattern to Dropdowns in Jail Config -These tasks refine the Configuration page UI. Each task is self-contained and can be completed independently. References point to the specific files that need modification. +**Completed.** Backend and Log Encoding are now `` fields (Backend and Log Encoding are read-only, Date Pattern is a free-text input). These should be `` fields with `` fields. Wire their `onChange` to local state and include them in the auto-save payload sent to the backend. +2. The backend model at `backend/app/models/config.py` line 67 already accepts `backend: str` and `log_encoding: str`, so no backend changes are needed unless you want to add server-side validation. +3. Date Pattern should use a Fluent UI `Combobox` (editable dropdown) instead of a `Select`, so users can still type a custom pattern. +4. Follow the existing DNS Mode dropdown pattern at lines 271–280 of `JailsTab.tsx` as a reference for styling and onChange handling. --- -### ✅ Task 4 — Remove the "Active" badge from the Filter and Action detail panes +## Task 2 — Fix Raw Action Configuration Always Blank in Actions Tab -**Status:** Done — Removed the `
` wrapper with Active/Inactive and "Has local override" badges from `FilterDetail` in `FiltersTab.tsx` and from `ActionDetail` in `ActionsTab.tsx`. Removed unused `Badge` import from both files. +**Problem:** In the Config → Actions page, the **"Raw Action Configuration"** accordion section is always blank when expanded. -**Goal:** Remove the `` component that shows "Active" or "Inactive" in the detail (right) pane of both the Filters tab and the Actions tab. The badge in the list pane (left side, inside `ConfigListDetail`) should remain — only the one in the detail section needs to be removed. +**Where to look:** -**Files to modify:** -- `frontend/src/components/config/FiltersTab.tsx` -- `frontend/src/components/config/ActionsTab.tsx` +- **`frontend/src/components/config/ActionsTab.tsx`** — the `ActionDetail` component (lines 65–185). The `fetchRaw` callback (line 82) calls `fetchActionFile(action.name)` and returns `result.content`. +- **`frontend/src/components/config/RawConfigSection.tsx`** — the collapsible raw editor. It uses a `loadedRef` (line 62) to fetch content only on first accordion expansion, and never resets. +- **`frontend/src/api/config.ts`** line 257 — `fetchActionFile()` calls `GET /config/actions/{name}`. +- **`backend/app/services/file_config_service.py`** line 734 — `get_action_file()` reads from `action.d/` using `_read_conf_file()`, which tries `.conf` first, then `.local`. -**What to do in `FiltersTab.tsx`:** -1. In the `FilterDetail` component (around lines 90–112), locate the `
` block that wraps the Active/Inactive `` and the optional "Has local override" ``. -2. Remove this entire `
` block, including both badges inside it. -3. If the `Badge` import from `@fluentui/react-components` is no longer used in this file, remove it. +**Root cause investigation — check these two possibilities:** -**What to do in `ActionsTab.tsx`:** -1. In the `ActionDetail` component (around lines 115–137), locate the same `
` block with the Active/Inactive badge and the "Has local override" badge. -2. Remove this entire `
` block, including both badges. -3. Clean up unused `Badge` import if applicable. - -**Verification:** Selecting a filter or action in the list still works. The detail pane no longer shows the Active/Inactive badge or the "Has local override" badge. The list pane badges are unaffected. - ---- - -### ✅ Task 5 — Fix Jail log paths: remove path text spans, populate editable fields - -**Status:** Done — Replaced the read-only `` elements for existing log paths with editable `` fields, each with an `onChange` handler that updates the corresponding index in `logPaths` state. Delete button preserved. - -**Goal:** In the active jail detail pane, the "Log Paths" field currently renders each existing log path as a read-only `` text element with a delete button. The actual `` field below is always empty and is only used for adding new paths. The user wants to remove the read-only text spans and instead make each log path an editable field. - -**Files to modify:** -- `frontend/src/components/config/JailsTab.tsx` - -**What to do:** -1. In the `JailConfigDetail` component, locate the `` block (around lines 292–350). -2. Remove the read-only span list that renders existing log paths: - ```tsx - {logPaths.length === 0 ? ( - (none) - ) : ( - logPaths.map((p) => ( -
- {p} -
- )) - )} - ``` -3. Replace it with editable `` fields for each existing log path. Each row should have: - - An `` pre-filled with the path value (`value={p}`), editable. - - An `onChange` handler that updates the corresponding entry in the `logPaths` state array. - - The same delete button (reuse the existing `handleDeleteLogPath` callback). -4. Keep the "Add new log path" form at the bottom unchanged (the `` with placeholder, `` for tail/head, and "Add" button). -5. Ensure the `logPaths` state is properly initialized from `jail.log_paths` (this is already the case at line 87). - -**Implementation hint:** Map over `logPaths` with an index, render an `` for each, and on change update the array at that index. Example: +### Possibility A: Stale `loadedRef` across action switches +`ActionDetail` at line 301 of `ActionsTab.tsx` is rendered **without a `key` prop**: ```tsx -logPaths.map((p, i) => ( -
- { - setLogPaths((prev) => prev.map((v, j) => (j === i ? d.value : v))); - }} - /> -
-)) + { setAssignOpen(true); }} + onRemovedFromJail={handleRemovedFromJail} +/> +``` +Without `key={selectedAction.name}`, React reuses the same component instance when switching between actions. The `RawConfigSection` inside `ActionDetail` keeps its `loadedRef.current = true` from a previous expansion, so it never re-fetches for the new action. + +**Fix:** Add `key={selectedAction.name}` to `ActionDetail` so it fully unmounts/remounts on action switch, resetting all internal state including `loadedRef`. + +### Possibility B: Backend returning empty content +The `_read_conf_file` function at `file_config_service.py` line 492 tries `.conf` first then `.local`. If the action only has a `.conf` file and it's readable, content should be returned. Verify by: +1. Checking browser DevTools Network tab for the `GET /api/config/actions/{name}` response body. +2. Confirming the response has a non-empty `content` field. + +**Implementation:** + +1. Add `key={selectedAction.name}` to the `` component in `ActionsTab.tsx` line 301. +2. Test by selecting different actions and expanding the raw config accordion — each should show the correct file content. +3. If the backend is returning empty content, debug `_read_conf_file` to check which file path it resolves and whether it can read the file. + +--- + +## Task 3 — Give Inactive Jail Configs the Same GUI as Active Ones + +**Problem:** In the Config → Jails page, **inactive jails** show a minimal read-only preview (`InactiveJailDetail` component) with only basic fields (filter, port, ban time, find time, max retry, log paths, actions, source file). **Active jails** like `bangui-sim` show a full editable form (`JailConfigDetail` component) with all fields including Backend, Log Encoding, Date Pattern, DNS Mode, Prefix Regex, editable log paths, Fail/Ignore Regex lists, Ban-time Escalation, and a Raw Configuration editor. + +The inactive jail detail should display the same rich GUI as the active one — same layout, same fields — but in read-only mode (since the jail is not running on fail2ban). + +**Where to change:** + +- **`frontend/src/components/config/JailsTab.tsx`**: + - `InactiveJailDetail` component (lines 493–580): the current minimal read-only view. + - `JailConfigDetail` component (lines ~200–490): the full editable form for active jails. + - Selection logic at lines 721–752 determines which component renders based on jail kind. + +- **`frontend/src/types/config.ts`** — `InactiveJail` type (around line 484) defines the data shape for inactive jails. Compare with `JailConfig` to see which fields are missing. + +- **Backend** — check that the inactive jail endpoint returns enough data. The backend model `InactiveJail` may need additional fields (backend, log_encoding, date_pattern, dns_mode, prefix_regex, failregex, ignoreregex, ban-time escalation settings) to match the active `JailConfig` model. + +**Implementation approach:** + +1. **Extend the `InactiveJail` model** in the backend (`backend/app/models/config.py`) to include all fields that `JailConfig` has: `backend`, `log_encoding`, `date_pattern`, `usedns`, `prefixregex`, `failregex`, `ignoreregex`, `bantime_increment` (escalation settings), etc. +2. **Update the backend service** that parses inactive jail configs to extract these additional fields from the `.conf`/`.local` files. +3. **Rewrite `InactiveJailDetail`** to mirror `JailConfigDetail`'s layout but with all fields in **read-only** mode (all `` have `readOnly`, all ` { setBanTime(d.value); }} @@ -229,6 +273,7 @@ function JailConfigDetail({ { setFindTime(d.value); }} @@ -238,6 +283,7 @@ function JailConfigDetail({ { setMaxRetry(d.value); }} @@ -246,26 +292,57 @@ function JailConfigDetail({
- + - +
- { - setDatePattern(d.value); + selectedOptions={[datePattern]} + freeform + disabled={readOnly} + onOptionSelect={(_e, d) => { + setDatePattern(d.optionValue ?? ""); }} - /> + onChange={(e) => { + setDatePattern(e.target.value); + }} + > + {DATE_PATTERN_PRESETS.map((p) => ( + + ))} +
+ )}
@@ -360,6 +444,7 @@ function JailConfigDetail({ label="Ignore Regex" patterns={ignoreRegex} onChange={setIgnoreRegex} + readOnly={readOnly} />
{jail.actions.length > 0 && ( @@ -387,6 +472,7 @@ function JailConfigDetail({ { setEscEnabled(d.checked); }} @@ -398,6 +484,7 @@ function JailConfigDetail({ { setEscFactor(d.value); }} @@ -407,6 +494,7 @@ function JailConfigDetail({ { setEscMaxTime(d.value); }} @@ -416,6 +504,7 @@ function JailConfigDetail({ { setEscRndTime(d.value); }} @@ -425,6 +514,7 @@ function JailConfigDetail({ { setEscFormula(d.value); }} @@ -433,6 +523,7 @@ function JailConfigDetail({ { setEscMultipliers(d.value); }} @@ -441,6 +532,7 @@ function JailConfigDetail({ { setEscOverallJails(d.checked); }} @@ -449,15 +541,17 @@ function JailConfigDetail({ )}
-
- -
+ {!readOnly && ( +
+ +
+ )} - {onDeactivate !== undefined && ( + {!readOnly && onDeactivate !== undefined && (
+
+ )} + + {/* Raw Configuration — hidden in read-only (inactive jail) mode */} + {!readOnly && ( +
+ +
+ )}
); } @@ -491,7 +599,12 @@ interface InactiveJailDetailProps { } /** - * Read-only detail view for an inactive jail, with an Activate button. + * Detail view for an inactive jail. + * + * Maps the parsed config fields to a JailConfig-compatible object and renders + * JailConfigDetail in read-only mode, so the UI is identical to the active + * jail view but with all fields disabled and an Activate button instead of + * a Deactivate button. * * @param props - Component props. * @returns JSX element. @@ -502,13 +615,29 @@ function InactiveJailDetail({ }: InactiveJailDetailProps): React.JSX.Element { const styles = useConfigStyles(); - return ( -
- - {jail.name} - + const jailConfig = useMemo( + () => ({ + name: jail.name, + ban_time: jail.ban_time_seconds, + find_time: jail.find_time_seconds, + max_retry: jail.maxretry ?? 5, + fail_regex: jail.fail_regex, + ignore_regex: jail.ignore_regex, + log_paths: jail.logpath, + date_pattern: jail.date_pattern, + log_encoding: jail.log_encoding, + backend: jail.backend, + use_dns: jail.use_dns, + prefregex: jail.prefregex, + actions: jail.actions, + bantime_escalation: jail.bantime_escalation, + }), + [jail], + ); -
+ return ( +
+
@@ -516,63 +645,15 @@ function InactiveJailDetail({
- -
- - - - - - - - - -
- - - {jail.logpath.length === 0 ? ( - (none) - ) : ( -
- {jail.logpath.map((p) => ( - - {p} - - ))} -
- )} -
- - {jail.actions.length > 0 && ( - -
- {jail.actions.map((a) => ( - - {a} - - ))} -
-
- )} - - + - -
- -
+ { /* read-only — never called */ }} + readOnly + onActivate={onActivate} + />
); } diff --git a/frontend/src/components/config/RegexList.tsx b/frontend/src/components/config/RegexList.tsx index d9cd3e8..72889c8 100644 --- a/frontend/src/components/config/RegexList.tsx +++ b/frontend/src/components/config/RegexList.tsx @@ -17,6 +17,8 @@ export interface RegexListProps { patterns: string[]; /** Called when the list changes (add, delete, or edit). */ onChange: (next: string[]) => void; + /** When true, patterns are displayed read-only with no add/delete controls. */ + readOnly?: boolean; } /** @@ -29,6 +31,7 @@ export function RegexList({ label, patterns, onChange, + readOnly = false, }: RegexListProps): React.JSX.Element { const styles = useConfigStyles(); const [newPattern, setNewPattern] = useState(""); @@ -64,6 +67,7 @@ export function RegexList({ { const next = [...patterns]; @@ -71,33 +75,37 @@ export function RegexList({ onChange(next); }} /> -
))} -
- { - setNewPattern(d.value); - }} - onKeyDown={(e) => { - if (e.key === "Enter") handleAdd(); - }} - /> - -
+ {!readOnly && ( +
+ { + setNewPattern(d.value); + }} + onKeyDown={(e) => { + if (e.key === "Enter") handleAdd(); + }} + /> + +
+ )}
); } diff --git a/frontend/src/types/config.ts b/frontend/src/types/config.ts index eaf32b1..812be64 100644 --- a/frontend/src/types/config.ts +++ b/frontend/src/types/config.ts @@ -77,6 +77,8 @@ export interface JailConfigUpdate { prefregex?: string | null; date_pattern?: string | null; dns_mode?: string | null; + backend?: string | null; + log_encoding?: string | null; enabled?: boolean | null; bantime_escalation?: BantimeEscalationUpdate | null; } @@ -498,6 +500,26 @@ export interface InactiveJail { findtime: string | null; /** Number of failures before a ban is issued, or null. */ maxretry: number | null; + /** Ban duration in seconds, parsed from bantime. */ + ban_time_seconds: number; + /** Failure-counting window in seconds, parsed from findtime. */ + find_time_seconds: number; + /** Log encoding, e.g. ``"auto"`` or ``"utf-8"``. */ + log_encoding: string; + /** Log-monitoring backend. */ + backend: string; + /** Date pattern for log parsing, or null for auto-detect. */ + date_pattern: string | null; + /** DNS resolution mode. */ + use_dns: string; + /** Prefix regex prepended to every failregex. */ + prefregex: string; + /** List of failure regex patterns. */ + fail_regex: string[]; + /** List of ignore regex patterns. */ + ignore_regex: string[]; + /** Ban-time escalation configuration, or null. */ + bantime_escalation: BantimeEscalation | null; /** Absolute path to the config file where this jail is defined. */ source_file: string; /** Effective ``enabled`` value — always ``false`` for inactive jails. */