feat: Task 4 — paginated banned-IPs section on jail detail page
Backend:
- Add JailBannedIpsResponse Pydantic model (ban.py)
- Add get_jail_banned_ips() service: server-side pagination, optional
IP substring search, geo enrichment on page slice only (jail_service.py)
- Add GET /api/jails/{name}/banned endpoint with page/page_size/search
query params, 400/404/502 error handling (routers/jails.py)
- 23 new tests: 13 service tests + 10 router tests (all passing)
Frontend:
- Add JailBannedIpsResponse TS interface (types/jail.ts)
- Add jailBanned endpoint helper (api/endpoints.ts)
- Add fetchJailBannedIps() API function (api/jails.ts)
- Add BannedIpsSection component: Fluent UI DataGrid, debounced search
(300 ms), prev/next pagination, page-size dropdown, per-row unban
button, loading spinner, empty state, error MessageBar (BannedIpsSection.tsx)
- Mount BannedIpsSection in JailDetailPage between stats and patterns
- 12 new Vitest tests for BannedIpsSection (all passing)
This commit is contained in:
@@ -38,6 +38,7 @@ export const ENDPOINTS = {
|
||||
// -------------------------------------------------------------------------
|
||||
jails: "/jails",
|
||||
jail: (name: string): string => `/jails/${encodeURIComponent(name)}`,
|
||||
jailBanned: (name: string): string => `/jails/${encodeURIComponent(name)}/banned`,
|
||||
jailStart: (name: string): string => `/jails/${encodeURIComponent(name)}/start`,
|
||||
jailStop: (name: string): string => `/jails/${encodeURIComponent(name)}/stop`,
|
||||
jailIdle: (name: string): string => `/jails/${encodeURIComponent(name)}/idle`,
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ENDPOINTS } from "./endpoints";
|
||||
import type {
|
||||
ActiveBanListResponse,
|
||||
IpLookupResponse,
|
||||
JailBannedIpsResponse,
|
||||
JailCommandResponse,
|
||||
JailDetailResponse,
|
||||
JailListResponse,
|
||||
@@ -224,3 +225,37 @@ export async function unbanAllBans(): Promise<UnbanAllResponse> {
|
||||
export async function lookupIp(ip: string): Promise<IpLookupResponse> {
|
||||
return get<IpLookupResponse>(ENDPOINTS.geoLookup(ip));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Jail-specific paginated bans
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Fetch the currently banned IPs for a specific jail, paginated.
|
||||
*
|
||||
* Only the requested page is geo-enriched on the backend, so this call
|
||||
* remains fast even when a jail has thousands of banned IPs.
|
||||
*
|
||||
* @param jailName - Jail name (e.g. `"sshd"`).
|
||||
* @param page - 1-based page number (default 1).
|
||||
* @param pageSize - Items per page; max 100 (default 25).
|
||||
* @param search - Optional case-insensitive IP substring filter.
|
||||
* @returns A {@link JailBannedIpsResponse} with paginated ban entries.
|
||||
* @throws {ApiError} On non-2xx responses (404 if jail unknown, 502 if fail2ban down).
|
||||
*/
|
||||
export async function fetchJailBannedIps(
|
||||
jailName: string,
|
||||
page = 1,
|
||||
pageSize = 25,
|
||||
search?: string,
|
||||
): Promise<JailBannedIpsResponse> {
|
||||
const params: Record<string, string> = {
|
||||
page: String(page),
|
||||
page_size: String(pageSize),
|
||||
};
|
||||
if (search !== undefined && search !== "") {
|
||||
params.search = search;
|
||||
}
|
||||
const query = new URLSearchParams(params).toString();
|
||||
return get<JailBannedIpsResponse>(`${ENDPOINTS.jailBanned(jailName)}?${query}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user