- .github/agents/ProcessTasks.agent.md: Copilot agent definition - eslint.config.ts: minor lint rule adjustment - Docs/Tasks.md: update completed and in-progress task status
12 KiB
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 and Web-Development.md. No additional UI libraries.
References
- Web-Design.md — Design rules: Fluent UI components, tokens, spacing, accessibility.
- 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 styleddivwithtokens.colorNeutralBackground2on hover) displaying:- The config name (e.g.
sshd,iptables-multiport), truncated with ellipsis + tooltip for long names. - A Fluent UI
Badgeto the right of the name: "Active" (appearance="filled",color="success") or "Inactive" (appearance="outline",color="informative").
- The config name (e.g.
- 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
childrenor 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/Spinnerwhile the detail is loading.
Props interface (suggestion):
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, gaptokens.spacingHorizontalM.listPane— fixed width 280 px, overflow-y auto, border-righttokens.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
JailConfigtype does not carry anenabledflag directly. The jails API (GET /api/jails) returnsJailSummaryobjects which containenabled: boolean. To get the active status, fetch the jails list fromfetchJails()(inapi/jails.ts) alongside the jail configs fromfetchJailConfigs(). Merge the two by name: a jail is "active" ifenabled === truein the jails list. - Alternatively, check the
JailConfigFileentries from the jail files API which haveenabled: 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
namefield 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 afilterfield onJailConfig, from that.
Actions:
- An action is "active" if it is referenced by at least one active jail's
actionsarray. After fetching jail configs, collect all unique action names fromJailConfig.actions[]arrays of enabled jails. Cross-reference with action files.
Implementation:
- Create a new hook
frontend/src/hooks/useConfigActiveStatus.tsthat:- Fetches jails list (
fetchJails), jail configs (fetchJailConfigs), filter files (fetchFilterFiles), and action files (fetchActionFiles) in parallel. - Computes and returns
{ activeJails: Set<string>, activeFilters: Set<string>, activeActions: Set<string>, loading: boolean, error: string | null }. - Cache results and re-fetch on demand (expose a
refreshfunction).
- Fetches jails list (
- 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.
- Fetch jail configs via
useJailConfigs(already exists inhooks/useConfig.ts). - Use the active-status hook from Task B to get
activeJails. - Render
ConfigListDetailwith:items= jail config list.isActive=(jail) => activeJails.has(jail.name).onSelectupdatesselectedNamestate.
- The right-pane detail content is the existing
JailAccordionPanellogic (the form fields for ban_time, find_time, max_retry, regex patterns, log paths, escalation, etc.) — extract it into a standaloneJailConfigDetailcomponent if not already. Remove the accordion wrapper; it is just the form body now. - Export section (new, at the bottom of the detail pane):
- A collapsible section (use
Accordionwith a single item, or aButtontoggle) 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 existingfetchJailConfigorfetchJailConfigFileendpoint 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
updateJailConfigFilePUT endpoint. - Show an
AutoSaveIndicatoror manual save button + success/errorMessageBar.
- A collapsible section (use
Task D — Redesign FiltersTab to List/Detail Layout
File: frontend/src/components/config/FiltersTab.tsx
Replace the current Accordion-based layout with ConfigListDetail.
- Fetch filter file list via
fetchFilterFiles(already used). - Use
activeFiltersfrom the active-status hook (Task B). - Render
ConfigListDetailwith:items= filter file entries.isActive=(f) => activeFilters.has(f.name).
- On item select, lazily load the parsed filter via
useFilterConfig(already exists inhooks/useFilterConfig.ts). - Right pane renders the existing
FilterFormcomponent with the loaded config. - Export section at the bottom of the detail pane:
- Collapsible "Raw Configuration" section.
Textarea(monospace) pre-filled with the raw file content fetched viafetchFilterFile(name)(returnsConfFileContentwith acontent: stringfield).- Editable with a "Save Raw"
Buttonthat callsupdateFilterFile(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.
- Fetch action file list via
fetchActionFiles. - Use
activeActionsfrom the active-status hook (Task B). - Render
ConfigListDetailwith:items= action file entries.isActive=(a) => activeActions.has(a.name).
- On item select, lazily load the parsed action via
useActionConfig(already exists). - Right pane renders the existing
ActionFormcomponent. - Export section at the bottom:
- Collapsible "Raw Configuration" section.
Textarea(monospace) with raw file content fromfetchActionFile(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:
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;
}
Behaviour:
- Renders a collapsible section (single
AccordionItemor a disclosureButton). - When expanded for the first time, calls
fetchContent()and fills theTextarea. - Uses monospace font (
fontFamily: "monospace") and a left brand-colour accent border (reusestyles.codeInputfromconfigStyles.ts). - "Save Raw"
Buttonwithappearance="primary"callssaveContent(text). - Shows
AutoSaveIndicator-style feedback (idle → saving → saved / error). - The
Textareais resizable vertically, minimum 15 rows.
Task G — Update Barrel Exports and ConfigPage
frontend/src/components/config/index.ts— Add exports forConfigListDetailandRawConfigSection.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.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
- Type-check: Run
npx tsc --noEmit— zero errors. - Lint: Run
npm run lint— zero warnings. - Existing tests: Run
npx vitest run— all existing tests pass. - 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.
- New tests (optional but recommended):
- Unit test
ConfigListDetailrendering with mock items, verifying sort order (active first) and selection callback. - Unit test
RawConfigSectionwith mocked fetch/save functions.
- Unit test
Implementation Order
- Task F (RawConfigSection) — standalone, no dependencies.
- Task A (ConfigListDetail layout) — standalone component.
- Task B (useConfigActiveStatus hook) — needs only the existing API layer.
- Task C (JailsTab redesign) — depends on A, B, F.
- Task D (FiltersTab redesign) — depends on A, B, F.
- Task E (ActionsTab redesign) — depends on A, B, F.
- Task G (exports and wiring) — after C/D/E.
- Task H (testing) — last.