Add mass unban: DELETE /api/bans/all clears all active bans

- Send fail2ban's `unban --all` command via new `unban_all_ips()` service
  function; returns the count of unbanned IPs
- Add `UnbanAllResponse` Pydantic model (message + count)
- Add `DELETE /api/bans/all` router endpoint; handles 502 on socket error
- Frontend: `bansAll` endpoint constant, `unbanAllBans()` API call,
  `UnbanAllResponse` type, `unbanAll` action in `useActiveBans` hook
- JailsPage: "Clear All Bans" button (visible when bans > 0) with a
  Fluent UI confirmation Dialog before executing the operation
- 7 new tests (3 service, 4 router); 440 total pass, 82% coverage
This commit is contained in:
2026-03-07 21:16:49 +01:00
parent 207be94c42
commit 4773ae1c7a
11 changed files with 382 additions and 10 deletions

View File

@@ -20,6 +20,7 @@ import {
setJailIdle,
startJail,
stopJail,
unbanAllBans,
unbanIp,
} from "../api/jails";
import type {
@@ -27,6 +28,7 @@ import type {
IpLookupResponse,
Jail,
JailSummary,
UnbanAllResponse,
} from "../types/jail";
// ---------------------------------------------------------------------------
@@ -238,6 +240,8 @@ export interface UseActiveBansResult {
banIp: (jail: string, ip: string) => Promise<void>;
/** Unban an IP from a jail (or all jails when `jail` is omitted). */
unbanIp: (ip: string, jail?: string) => Promise<void>;
/** Unban every currently banned IP across all jails. */
unbanAll: () => Promise<UnbanAllResponse>;
}
/**
@@ -293,6 +297,12 @@ export function useActiveBans(): UseActiveBansResult {
load();
};
const doUnbanAll = async (): Promise<UnbanAllResponse> => {
const result = await unbanAllBans();
load();
return result;
};
return {
bans,
total,
@@ -301,6 +311,7 @@ export function useActiveBans(): UseActiveBansResult {
refresh: load,
banIp: doBan,
unbanIp: doUnban,
unbanAll: doUnbanAll,
};
}