Redesign FiltersTab with active/inactive layout and assign/create dialogs (Tasks 2.3/2.4)

- Rewrite FiltersTab: use fetchFilters() for FilterConfig[] with embedded active
  status; show 'Active — sshd, apache-auth' badge labels; FilterDetail sub-
  component with source_file/override badges, FilterForm, Assign button, raw
  config section
- New AssignFilterDialog: selects jail from enabled-jails list, calls
  POST /config/jails/{name}/filter with optional fail2ban reload
- New CreateFilterDialog: name+failregex+ignoreregex form, calls
  POST /config/filters, closes and selects new filter on success
- Extend ConfigListDetail: add listHeader (for Create button) and
  itemBadgeLabel (for custom badge text) optional props
- Fix updateFilterFile bug: was PUT /config/filters/{name} (structured
  endpoint), now correctly PUT /config/filters/{name}/raw
- Fix createFilterFile bug: was POST /config/filters, now POST /config/filters/raw
- Add updateFilter, createFilter, deleteFilter, assignFilterToJail to api/config.ts
- Add FilterUpdateRequest, FilterCreateRequest, AssignFilterRequest to types/config.ts
- Add configFiltersRaw, configJailFilter endpoints
- Tests: 24 new tests across FiltersTab, AssignFilterDialog, CreateFilterDialog
  (all 89 frontend tests passing)
This commit is contained in:
2026-03-13 18:46:45 +01:00
parent e15ad8fb62
commit 2f60b0915e
12 changed files with 1358 additions and 83 deletions

View File

@@ -9,13 +9,16 @@ import type {
ActionConfigUpdate,
ActivateJailRequest,
AddLogPathRequest,
AssignFilterRequest,
ConfFileContent,
ConfFileCreateRequest,
ConfFilesResponse,
ConfFileUpdateRequest,
FilterConfig,
FilterConfigUpdate,
FilterCreateRequest,
FilterListResponse,
FilterUpdateRequest,
GlobalConfig,
GlobalConfigUpdate,
InactiveJailListResponse,
@@ -224,17 +227,19 @@ export async function fetchFilterFile(name: string): Promise<ConfFileContent> {
return get<ConfFileContent>(ENDPOINTS.configFilterRaw(name));
}
/** Save raw content to a filter definition file (``PUT /filters/{name}/raw``). */
export async function updateFilterFile(
name: string,
req: ConfFileUpdateRequest
): Promise<void> {
await put<undefined>(ENDPOINTS.configFilter(name), req);
await put<undefined>(ENDPOINTS.configFilterRaw(name), req);
}
/** Create a new raw filter file (``POST /filters/raw``). */
export async function createFilterFile(
req: ConfFileCreateRequest
): Promise<ConfFileContent> {
return post<ConfFileContent>(ENDPOINTS.configFilters, req);
return post<ConfFileContent>(ENDPOINTS.configFiltersRaw, req);
}
// ---------------------------------------------------------------------------
@@ -263,7 +268,7 @@ export async function createActionFile(
}
// ---------------------------------------------------------------------------
// Parsed filter config (Task 2.2)
// Parsed filter config (Task 2.2 / legacy /parsed endpoint)
// ---------------------------------------------------------------------------
export async function fetchParsedFilter(name: string): Promise<FilterConfig> {
@@ -277,6 +282,69 @@ export async function updateParsedFilter(
await put<undefined>(ENDPOINTS.configFilterParsed(name), update);
}
// ---------------------------------------------------------------------------
// Filter structured update / create / delete (Task 2.3)
// ---------------------------------------------------------------------------
/**
* Update a filter's editable fields via the structured endpoint.
*
* Writes only the supplied fields to the ``.local`` override. Fields set
* to ``null`` are cleared; omitted fields are left unchanged.
*
* @param name - Filter base name (e.g. ``"sshd"``)
* @param req - Partial update payload.
*/
export async function updateFilter(
name: string,
req: FilterUpdateRequest
): Promise<void> {
await put<undefined>(ENDPOINTS.configFilter(name), req);
}
/**
* Create a brand-new user-defined filter in ``filter.d/{name}.local``.
*
* @param req - Name and optional regex patterns.
* @returns The newly created FilterConfig.
*/
export async function createFilter(
req: FilterCreateRequest
): Promise<FilterConfig> {
return post<FilterConfig>(ENDPOINTS.configFilters, req);
}
/**
* Delete a filter's ``.local`` override file.
*
* Only custom ``.local``-only filters can be deleted. Attempting to delete a
* filter that is backed by a shipped ``.conf`` file returns 409.
*
* @param name - Filter base name.
*/
export async function deleteFilter(name: string): Promise<void> {
await del<undefined>(ENDPOINTS.configFilter(name));
}
/**
* Assign a filter to a jail by writing ``filter = {filter_name}`` to the
* jail's ``.local`` config file.
*
* @param jailName - Jail name.
* @param req - The filter to assign.
* @param reload - When ``true``, trigger a fail2ban reload after writing.
*/
export async function assignFilterToJail(
jailName: string,
req: AssignFilterRequest,
reload = false
): Promise<void> {
const url = reload
? `${ENDPOINTS.configJailFilter(jailName)}?reload=true`
: ENDPOINTS.configJailFilter(jailName);
await post<undefined>(url, req);
}
// ---------------------------------------------------------------------------
// Filter discovery with active/inactive status (Task 2.1)
// ---------------------------------------------------------------------------