Add ignore-self toggle to Jail Detail page

Implements the missing UI control for POST /api/jails/{name}/ignoreself:
- Add jailIgnoreSelf endpoint constant to endpoints.ts
- Add toggleIgnoreSelf(name, on) API function to jails.ts
- Expose toggleIgnoreSelf action from useJailDetail hook
- Replace read-only 'ignore self' badge with a Fluent Switch in
  IgnoreListSection to allow enabling/disabling the flag per jail
- Add 5 vitest tests for checked/unchecked state and toggle behaviour
This commit is contained in:
2026-03-14 20:24:49 +01:00
parent d3b2022ffb
commit 6bb38dbd8c
6 changed files with 370 additions and 8 deletions

View File

@@ -468,3 +468,135 @@ Write tests covering:
.venv/bin/mypy backend/app/ --strict
```
Zero errors.
---
## Task 8 — Add "ignore self" toggle to Jail Detail page
**Status:** done
**Summary:** Added `jailIgnoreSelf` endpoint constant to `endpoints.ts`; added `toggleIgnoreSelf(name, on)` API function to `jails.ts`; extended `useJailDetail` return type and hook implementation to expose `toggleIgnoreSelf`; replaced the read-only "ignore self" badge in `IgnoreListSection` (`JailDetailPage.tsx`) with a Fluent UI `Switch` that calls the toggle action and surfaces any error in the existing `opError` message bar; added 5 new tests in `JailDetailIgnoreSelf.test.tsx` covering checked/unchecked rendering, toggle-on, toggle-off, and error display.
**Page:** `/jails/:name` — rendered by `frontend/src/pages/JailDetailPage.tsx`
### Problem description
`Features.md` §5 (Jail Management / IP Whitelist) requires: "Toggle the 'ignore self' option per jail, which automatically excludes the server's own IP addresses."
The backend already exposes `POST /api/jails/{name}/ignoreself` (accepts a JSON boolean `on`). The `useJailDetail` hook reads `ignore_self` from the `GET /api/jails/{name}` response and exposes it as `ignoreSelf: boolean`. However:
1. **No API wrapper** — `frontend/src/api/jails.ts` has no `toggleIgnoreSelf` function, and `frontend/src/api/endpoints.ts` has no `jailIgnoreSelf` entry.
2. **No hook action** — `UseJailDetailResult` in `useJails.ts` does not expose a `toggleIgnoreSelf` helper.
3. **Read-only UI** — `IgnoreListSection` in `JailDetailPage.tsx` shows an "ignore self" badge when enabled but has no control to change the setting.
As a result, users must use the fail2ban CLI to manage this flag even though the backend is ready.
### What to do
#### Part A — Add endpoint constant (frontend)
**File:** `frontend/src/api/endpoints.ts`
Inside the Jails block, after `jailIgnoreIp`, add:
```ts
jailIgnoreSelf: (name: string): string => `/jails/${encodeURIComponent(name)}/ignoreself`,
```
#### Part B — Add API wrapper function (frontend)
**File:** `frontend/src/api/jails.ts`
After the `delIgnoreIp` function in the "Ignore list" section, add:
```ts
/**
* Enable or disable the `ignoreself` flag for a jail.
*
* When enabled, fail2ban automatically adds the server's own IP addresses to
* the ignore list so the host can never ban itself.
*
* @param name - Jail name.
* @param on - `true` to enable, `false` to disable.
* @returns A {@link JailCommandResponse} confirming the change.
* @throws {ApiError} On non-2xx responses.
*/
export async function toggleIgnoreSelf(
name: string,
on: boolean,
): Promise<JailCommandResponse> {
return post<JailCommandResponse>(ENDPOINTS.jailIgnoreSelf(name), on);
}
```
#### Part C — Expose toggle action from hook (frontend)
**File:** `frontend/src/hooks/useJails.ts`
1. Import `toggleIgnoreSelf` at the top (alongside the other API imports).
2. Add `toggleIgnoreSelf: (on: boolean) => Promise<void>` to the `UseJailDetailResult` interface with a JSDoc comment: `/** Enable or disable the ignoreself option for this jail. */`.
3. Inside `useJailDetail`, add the implementation:
```ts
const toggleIgnoreSelf = async (on: boolean): Promise<void> => {
await toggleIgnoreSelfApi(name, on);
load();
};
```
Alias the import as `toggleIgnoreSelfApi` to avoid shadowing the local function name.
4. Add the function to the returned object.
#### Part D — Add toggle control to UI (frontend)
**File:** `frontend/src/pages/JailDetailPage.tsx`
1. Accept `toggleIgnoreSelf: (on: boolean) => Promise<void>` in the `IgnoreListSectionProps` interface.
2. Pass the function from `useJailDetail` down via the existing destructuring and JSX prop.
3. Inside `IgnoreListSection`, render a `<Switch>` next to (or in place of) the read-only badge:
```tsx
<Switch
label="Ignore self (exclude this server's own IPs)"
checked={ignoreSelf}
onChange={(_e, data): void => {
toggleIgnoreSelf(data.checked).catch((err: unknown) => {
const msg = err instanceof Error ? err.message : String(err);
setOpError(msg);
});
}}
/>
```
Import `Switch` from `"@fluentui/react-components"`. Remove the existing read-only badge (it is replaced by the labelled switch, which is self-explanatory). Keep the existing `opError` state and `<MessageBar>` for error display.
### Tests to add
**File:** `frontend/src/pages/__tests__/JailDetailIgnoreSelf.test.tsx` (new file)
Write tests that render the `IgnoreListSection` component (or the full `JailDetailPage` via a shallow-enough render) and cover:
1. **`test_ignore_self_switch_is_checked_when_ignore_self_true`** — when `ignoreSelf=true`, the switch is checked.
2. **`test_ignore_self_switch_is_unchecked_when_ignore_self_false`** — when `ignoreSelf=false`, the switch is unchecked.
3. **`test_toggling_switch_calls_toggle_ignore_self`** — clicking the switch calls `toggleIgnoreSelf` with `false` (when it was `true`).
4. **`test_toggle_error_shows_message_bar`** — when `toggleIgnoreSelf` rejects, the error message bar is rendered.
### Verification
1. Run frontend type check and lint:
```bash
cd frontend && npx tsc --noEmit && npx eslint src/api/jails.ts src/api/endpoints.ts src/hooks/useJails.ts src/pages/JailDetailPage.tsx
```
Zero errors and zero warnings.
2. Run frontend tests:
```bash
cd frontend && npx vitest run src/pages/__tests__/JailDetailIgnoreSelf
```
All 4 new tests pass.
3. **Manual test with running server:**
- Go to `/jails`, click a running jail.
- On the Jail Detail page, scroll to "Ignore List (IP Whitelist)".
- Toggle the "Ignore self" switch on and off — the switch should reflect the live state and the change should survive a page refresh.