Files
BanGUI/frontend/src/api/config.ts
Lukas d4d04491d2 Add Deactivate Jail button for inactive jails with local override
- Add has_local_override field to InactiveJail model
- Update _build_inactive_jail and list_inactive_jails to compute the field
- Add delete_jail_local_override() service function
- Add DELETE /api/config/jails/{name}/local router endpoint
- Surface has_local_override in frontend InactiveJail type
- Show Deactivate Jail button in JailsTab when has_local_override is true
- Add tests: TestBuildInactiveJail, TestListInactiveJails, TestDeleteJailLocalOverride
2026-03-15 13:41:00 +01:00

606 lines
19 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* API functions for the configuration and server settings endpoints.
*/
import { del, get, post, put } from "./client";
import { ENDPOINTS } from "./endpoints";
import type {
ActionConfig,
ActionConfigUpdate,
ActionCreateRequest,
ActionListResponse,
ActionUpdateRequest,
ActivateJailRequest,
AddLogPathRequest,
AssignActionRequest,
AssignFilterRequest,
ConfFileContent,
ConfFileCreateRequest,
ConfFilesResponse,
ConfFileUpdateRequest,
Fail2BanLogResponse,
FilterConfig,
FilterConfigUpdate,
FilterCreateRequest,
FilterListResponse,
FilterUpdateRequest,
GlobalConfig,
GlobalConfigUpdate,
InactiveJailListResponse,
JailActivationResponse,
JailConfigFileContent,
JailConfigFileEnabledUpdate,
JailConfigFilesResponse,
JailConfigListResponse,
JailConfigResponse,
JailConfigUpdate,
JailValidationResult,
LogPreviewRequest,
LogPreviewResponse,
MapColorThresholdsResponse,
MapColorThresholdsUpdate,
RegexTestRequest,
RegexTestResponse,
ServerSettingsResponse,
ServerSettingsUpdate,
JailFileConfig,
JailFileConfigUpdate,
ServiceStatusResponse,
} from "../types/config";
// ---------------------------------------------------------------------------
// Jail configuration
// ---------------------------------------------------------------------------
export async function fetchJailConfigs(
): Promise<JailConfigListResponse> {
return get<JailConfigListResponse>(ENDPOINTS.configJails);
}
export async function fetchJailConfig(
name: string
): Promise<JailConfigResponse> {
return get<JailConfigResponse>(ENDPOINTS.configJail(name));
}
export async function updateJailConfig(
name: string,
update: JailConfigUpdate
): Promise<void> {
await put<undefined>(ENDPOINTS.configJail(name), update);
}
// ---------------------------------------------------------------------------
// Global configuration
// ---------------------------------------------------------------------------
export async function fetchGlobalConfig(
): Promise<GlobalConfig> {
return get<GlobalConfig>(ENDPOINTS.configGlobal);
}
export async function updateGlobalConfig(
update: GlobalConfigUpdate
): Promise<void> {
await put<undefined>(ENDPOINTS.configGlobal, update);
}
// ---------------------------------------------------------------------------
// Reload and Restart
// ---------------------------------------------------------------------------
export async function reloadConfig(
): Promise<void> {
await post<undefined>(ENDPOINTS.configReload, undefined);
}
export async function restartFail2Ban(
): Promise<void> {
await post<undefined>(ENDPOINTS.configRestart, undefined);
}
// ---------------------------------------------------------------------------
// Regex tester
// ---------------------------------------------------------------------------
export async function testRegex(
req: RegexTestRequest
): Promise<RegexTestResponse> {
return post<RegexTestResponse>(ENDPOINTS.configRegexTest, req);
}
// ---------------------------------------------------------------------------
// Log path management
// ---------------------------------------------------------------------------
export async function addLogPath(
jailName: string,
req: AddLogPathRequest
): Promise<void> {
await post<undefined>(ENDPOINTS.configJailLogPath(jailName), req);
}
export async function deleteLogPath(
jailName: string,
logPath: string
): Promise<void> {
await del<undefined>(
`${ENDPOINTS.configJailLogPath(jailName)}?log_path=${encodeURIComponent(logPath)}`
);
}
// ---------------------------------------------------------------------------
// Log preview
// ---------------------------------------------------------------------------
export async function previewLog(
req: LogPreviewRequest
): Promise<LogPreviewResponse> {
return post<LogPreviewResponse>(ENDPOINTS.configPreviewLog, req);
}
// ---------------------------------------------------------------------------
// Server settings
// ---------------------------------------------------------------------------
export async function fetchServerSettings(
): Promise<ServerSettingsResponse> {
return get<ServerSettingsResponse>(ENDPOINTS.serverSettings);
}
export async function updateServerSettings(
update: ServerSettingsUpdate
): Promise<void> {
await put<undefined>(ENDPOINTS.serverSettings, update);
}
export async function flushLogs(
): Promise<string> {
const resp = await post<{ message: string }>(
ENDPOINTS.serverFlushLogs,
undefined,
);
return resp.message;
}
// ---------------------------------------------------------------------------
// Map color thresholds
// ---------------------------------------------------------------------------
export async function fetchMapColorThresholds(
): Promise<MapColorThresholdsResponse> {
return get<MapColorThresholdsResponse>(ENDPOINTS.configMapColorThresholds);
}
export async function updateMapColorThresholds(
update: MapColorThresholdsUpdate
): Promise<MapColorThresholdsResponse> {
return put<MapColorThresholdsResponse>(
ENDPOINTS.configMapColorThresholds,
update,
);
}
// ---------------------------------------------------------------------------
// Jail config files (Task 4a)
// ---------------------------------------------------------------------------
export async function fetchJailConfigFiles(): Promise<JailConfigFilesResponse> {
return get<JailConfigFilesResponse>(ENDPOINTS.configJailFiles);
}
export async function createJailConfigFile(
req: ConfFileCreateRequest
): Promise<ConfFileContent> {
return post<ConfFileContent>(ENDPOINTS.configJailFiles, req);
}
export async function fetchJailConfigFileContent(
filename: string
): Promise<JailConfigFileContent> {
return get<JailConfigFileContent>(ENDPOINTS.configJailFile(filename));
}
export async function updateJailConfigFile(
filename: string,
req: ConfFileUpdateRequest
): Promise<void> {
await put<undefined>(ENDPOINTS.configJailFile(filename), req);
}
export async function setJailConfigFileEnabled(
filename: string,
update: JailConfigFileEnabledUpdate
): Promise<void> {
await put<undefined>(ENDPOINTS.configJailFileEnabled(filename), update);
}
// ---------------------------------------------------------------------------
// 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> {
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.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.configFilterRaw(name), req);
}
/** Create a new raw filter file (``POST /filters/raw``). */
export async function createFilterFile(
req: ConfFileCreateRequest
): Promise<ConfFileContent> {
return post<ConfFileContent>(ENDPOINTS.configFiltersRaw, req);
}
// ---------------------------------------------------------------------------
// Action files (Task 4e)
// ---------------------------------------------------------------------------
export async function fetchActionFiles(): Promise<ConfFilesResponse> {
return get<ConfFilesResponse>(ENDPOINTS.configActions);
}
export async function fetchActionFile(name: string): Promise<ConfFileContent> {
return get<ConfFileContent>(ENDPOINTS.configAction(name));
}
export async function updateActionFile(
name: string,
req: ConfFileUpdateRequest
): Promise<void> {
await put<undefined>(ENDPOINTS.configAction(name), req);
}
export async function createActionFile(
req: ConfFileCreateRequest
): Promise<ConfFileContent> {
return post<ConfFileContent>(ENDPOINTS.configActions, req);
}
// ---------------------------------------------------------------------------
// Parsed filter config (Task 2.2 / legacy /parsed endpoint)
// ---------------------------------------------------------------------------
export async function fetchParsedFilter(name: string): Promise<FilterConfig> {
return get<FilterConfig>(ENDPOINTS.configFilterParsed(name));
}
export async function updateParsedFilter(
name: string,
update: FilterConfigUpdate
): Promise<void> {
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)
// ---------------------------------------------------------------------------
/**
* 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)
// ---------------------------------------------------------------------------
export async function fetchParsedAction(name: string): Promise<ActionConfig> {
return get<ActionConfig>(ENDPOINTS.configActionParsed(name));
}
export async function updateParsedAction(
name: string,
update: ActionConfigUpdate
): Promise<void> {
await put<undefined>(ENDPOINTS.configActionParsed(name), update);
}
// ---------------------------------------------------------------------------
// Action discovery with active/inactive status (Task 3.1 / 3.2)
// ---------------------------------------------------------------------------
/**
* Fetch all actions from action.d/ with active/inactive status.
*
* Active actions (those referenced by running jails) are returned first,
* followed by inactive ones. Both groups are sorted alphabetically.
*
* @returns ActionListResponse with all discovered actions and status.
*/
export async function fetchActions(): Promise<ActionListResponse> {
return get<ActionListResponse>(ENDPOINTS.configActions);
}
/**
* Fetch full parsed detail for a single action.
*
* @param name - Action base name (e.g. "iptables" or "iptables.conf").
* @returns ActionConfig with active, used_by_jails, source_file populated.
*/
export async function fetchAction(name: string): Promise<ActionConfig> {
return get<ActionConfig>(ENDPOINTS.configAction(name));
}
/**
* Update an action'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 - Action base name (e.g. ``"iptables"``)
* @param req - Partial update payload.
*/
export async function updateAction(
name: string,
req: ActionUpdateRequest
): Promise<void> {
await put<undefined>(ENDPOINTS.configAction(name), req);
}
/**
* Create a brand-new user-defined action in ``action.d/{name}.local``.
*
* @param req - Name and optional lifecycle commands.
* @returns The newly created ActionConfig.
*/
export async function createAction(
req: ActionCreateRequest
): Promise<ActionConfig> {
return post<ActionConfig>(ENDPOINTS.configActions, req);
}
/**
* Delete an action's ``.local`` override file.
*
* Only custom ``.local``-only actions can be deleted. Attempting to delete an
* action backed by a shipped ``.conf`` file returns 409.
*
* @param name - Action base name.
*/
export async function deleteAction(name: string): Promise<void> {
await del<undefined>(ENDPOINTS.configAction(name));
}
/**
* Assign an action to a jail by appending it to the jail's action list.
*
* @param jailName - Jail name.
* @param req - The action to assign with optional parameters.
* @param reload - When ``true``, trigger a fail2ban reload after writing.
*/
export async function assignActionToJail(
jailName: string,
req: AssignActionRequest,
reload = false
): Promise<void> {
const url = reload
? `${ENDPOINTS.configJailAction(jailName)}?reload=true`
: ENDPOINTS.configJailAction(jailName);
await post<undefined>(url, req);
}
/**
* Remove an action from a jail's action list.
*
* @param jailName - Jail name.
* @param actionName - Action base name to remove.
* @param reload - When ``true``, trigger a fail2ban reload after writing.
*/
export async function removeActionFromJail(
jailName: string,
actionName: string,
reload = false
): Promise<void> {
const url = reload
? `${ENDPOINTS.configJailActionName(jailName, actionName)}?reload=true`
: ENDPOINTS.configJailActionName(jailName, actionName);
await del<undefined>(url);
}
// ---------------------------------------------------------------------------
// Parsed jail file config (Task 6.1 / 6.2)
// ---------------------------------------------------------------------------
export async function fetchParsedJailFile(filename: string): Promise<JailFileConfig> {
return get<JailFileConfig>(ENDPOINTS.configJailFileParsed(filename));
}
export async function updateParsedJailFile(
filename: string,
update: JailFileConfigUpdate
): Promise<void> {
await put<undefined>(ENDPOINTS.configJailFileParsed(filename), update);
}
// ---------------------------------------------------------------------------
// Inactive jails (Stage 1)
// ---------------------------------------------------------------------------
/** Fetch all inactive jails from config files. */
export async function fetchInactiveJails(): Promise<InactiveJailListResponse> {
return get<InactiveJailListResponse>(ENDPOINTS.configJailsInactive);
}
/**
* Activate an inactive jail, optionally providing override values.
*
* @param name - The jail name.
* @param overrides - Optional parameter overrides (bantime, findtime, etc.).
*/
export async function activateJail(
name: string,
overrides?: ActivateJailRequest
): Promise<JailActivationResponse> {
return post<JailActivationResponse>(
ENDPOINTS.configJailActivate(name),
overrides ?? {}
);
}
/** Deactivate an active jail. */
export async function deactivateJail(
name: string
): Promise<JailActivationResponse> {
return post<JailActivationResponse>(
ENDPOINTS.configJailDeactivate(name),
undefined
);
}
/**
* Delete the ``jail.d/{name}.local`` override file for an inactive jail.
*
* Only valid when the jail is **not** currently active. Use this to clean up
* leftover ``.local`` files after a jail has been fully deactivated.
*
* @param name - The jail name.
*/
export async function deleteJailLocalOverride(name: string): Promise<void> {
await del<undefined>(ENDPOINTS.configJailLocalOverride(name));
}
// ---------------------------------------------------------------------------
// fail2ban log viewer (Task 2)
// ---------------------------------------------------------------------------
/**
* Fetch the tail of the fail2ban daemon log file.
*
* @param lines - Number of tail lines to return (12000, default 200).
* @param filter - Optional plain-text substring; only matching lines returned.
*/
export async function fetchFail2BanLog(
lines?: number,
filter?: string,
): Promise<Fail2BanLogResponse> {
const params = new URLSearchParams();
if (lines !== undefined) params.set("lines", String(lines));
if (filter !== undefined && filter !== "") params.set("filter", filter);
const query = params.toString() ? `?${params.toString()}` : "";
return get<Fail2BanLogResponse>(`${ENDPOINTS.configFail2BanLog}${query}`);
}
/** Fetch fail2ban service health status with current log configuration. */
export async function fetchServiceStatus(): Promise<ServiceStatusResponse> {
return get<ServiceStatusResponse>(ENDPOINTS.configServiceStatus);
}
// ---------------------------------------------------------------------------
// Jail config recovery (Task 3)
// ---------------------------------------------------------------------------
/**
* Run pre-activation validation on a jail's config.
*
* Checks that referenced filter/action files exist, that all regex patterns
* compile, and that log paths are accessible on the server.
*/
export async function validateJailConfig(
name: string,
): Promise<JailValidationResult> {
return post<JailValidationResult>(ENDPOINTS.configJailValidate(name), undefined);
}