Add filter discovery endpoints with active/inactive status (Task 2.1)

- Add list_filters() and get_filter() to config_file_service.py:
  scans filter.d/, parses [Definition] + [Init] sections, merges .local
  overrides, and cross-references running jails to set active/used_by_jails
- Add FilterConfig.active, used_by_jails, source_file, has_local_override
  fields to the Pydantic model; add FilterListResponse and FilterNotFoundError
- Add GET /api/config/filters and GET /api/config/filters/{name} to config.py
- Remove the shadowed GET /api/config/filters list route from file_config.py;
  rename GET /api/config/filters/{name} raw variant to /filters/{name}/raw
- Update frontend: fetchFilterFiles() adapts FilterListResponse -> ConfFilesResponse;
  add fetchFilters() and fetchFilter() to api/config.ts; remove unused
  fetchFilterFiles/fetchActionFiles calls from useConfigActiveStatus
- Fix ConfigPageLogPath test mock to include fetchInactiveJails and related
  exports introduced by Stage 1
- Backend: 169 tests pass, mypy --strict clean, ruff clean
- Frontend: 63 tests pass, tsc --noEmit clean, eslint clean
This commit is contained in:
2026-03-13 16:48:27 +01:00
parent 8d9d63b866
commit 4c138424a5
14 changed files with 989 additions and 92 deletions

View File

@@ -15,6 +15,7 @@ import type {
ConfFileUpdateRequest,
FilterConfig,
FilterConfigUpdate,
FilterListResponse,
GlobalConfig,
GlobalConfigUpdate,
InactiveJailListResponse,
@@ -200,15 +201,27 @@ export async function setJailConfigFileEnabled(
}
// ---------------------------------------------------------------------------
// Filter files (Task 4d)
// Filter files (Task 4d) — raw file management
// ---------------------------------------------------------------------------
/**
* Return a lightweight name/filename list of all filter files.
*
* Internally calls the enriched ``GET /config/filters`` endpoint (which also
* returns active-status data) and maps the result down to the simpler
* ``ConfFilesResponse`` shape expected by the raw-file editor and export tab.
*/
export async function fetchFilterFiles(): Promise<ConfFilesResponse> {
return get<ConfFilesResponse>(ENDPOINTS.configFilters);
const result = await fetchFilters();
return {
files: result.filters.map((f) => ({ name: f.name, filename: f.filename })),
total: result.total,
};
}
/** Fetch the raw content of a filter definition file for the raw editor. */
export async function fetchFilterFile(name: string): Promise<ConfFileContent> {
return get<ConfFileContent>(ENDPOINTS.configFilter(name));
return get<ConfFileContent>(ENDPOINTS.configFilterRaw(name));
}
export async function updateFilterFile(
@@ -264,6 +277,32 @@ export async function updateParsedFilter(
await put<undefined>(ENDPOINTS.configFilterParsed(name), update);
}
// ---------------------------------------------------------------------------
// Filter discovery with active/inactive status (Task 2.1)
// ---------------------------------------------------------------------------
/**
* Fetch all filters from filter.d/ with active/inactive status.
*
* Active filters (those referenced by running jails) are returned first,
* followed by inactive ones. Both groups are sorted alphabetically.
*
* @returns FilterListResponse with all discovered filters and status.
*/
export async function fetchFilters(): Promise<FilterListResponse> {
return get<FilterListResponse>(ENDPOINTS.configFilters);
}
/**
* Fetch full parsed detail for a single filter with active/inactive status.
*
* @param name - Filter base name (e.g. "sshd" or "sshd.conf").
* @returns FilterConfig with active, used_by_jails, source_file populated.
*/
export async function fetchFilter(name: string): Promise<FilterConfig> {
return get<FilterConfig>(ENDPOINTS.configFilter(name));
}
// ---------------------------------------------------------------------------
// Parsed action config (Task 3.2)
// ---------------------------------------------------------------------------