# 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. --- ## Config View Redesign — List/Detail Layout with Active Status and Raw Export ### Overview 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. 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. ### References - [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. --- ### Task A — Shared List/Detail Layout Component **File:** `frontend/src/components/config/ConfigListDetail.tsx` Create a reusable layout component that renders a two-pane master/detail view. **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. **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. **Props interface (suggestion):** ```typescript interface ConfigListDetailProps { 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. --- ### Task B — Active Status for Jails, Filters, and Actions Determine "active" status for each config type so it can be shown in the list. **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. **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. **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. **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, activeFilters: Set, activeActions: Set, 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. --- ### Task C — Redesign JailsTab to List/Detail Layout **File:** `frontend/src/components/config/JailsTab.tsx` Replace the current `Accordion`-based layout with the `ConfigListDetail` component from Task A. 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`. --- ### Task D — Redesign FiltersTab to List/Detail Layout **File:** `frontend/src/components/config/FiltersTab.tsx` Replace the current `Accordion`-based layout with `ConfigListDetail`. 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`. --- ### Task E — Redesign ActionsTab to List/Detail Layout **File:** `frontend/src/components/config/ActionsTab.tsx` Same pattern as Task D but for actions. 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. --- ### Task F — Raw Export Section Component **File:** `frontend/src/components/config/RawConfigSection.tsx` Extract the raw-export pattern into a reusable component so Jails, Filters, and Actions tabs don't duplicate logic. **Props:** ```typescript interface RawConfigSectionProps { /** Async function that returns the raw file content string. */ fetchContent: () => Promise; /** Async function that saves updated raw content. */ saveContent: (content: string) => Promise; /** Label shown in the collapsible header, e.g. "Raw Jail Configuration". */ label?: string; } ``` **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. --- ### Task G — Update Barrel Exports and ConfigPage 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). --- ### Task H — Testing and Validation 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. --- ### Implementation Order 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. ---