Add fail2ban log viewer and service health to Config page
Task 2: adds a new Log tab to the Configuration page.
Backend:
- New Pydantic models: Fail2BanLogResponse, ServiceStatusResponse
(backend/app/models/config.py)
- New service methods in config_service.py:
read_fail2ban_log() — queries socket for log target/level, validates the
resolved path against a safe-prefix allowlist (/var/log) to prevent
path traversal, then reads the tail of the file via the existing
_read_tail_lines() helper; optional substring filter applied server-side.
get_service_status() — delegates to health_service.probe() and appends
log level/target from the socket.
- New endpoints in routers/config.py:
GET /api/config/fail2ban-log?lines=200&filter=...
GET /api/config/service-status
Both require authentication; log endpoint returns 400 for non-file log
targets or path-traversal attempts, 502 when fail2ban is unreachable.
Frontend:
- New LogTab.tsx component:
Service Health panel (Running/Offline badge, version, jail count, bans,
failures, log level/target, offline warning banner).
Log viewer with color-coded lines (error=red, warning=yellow,
debug=grey), toolbar (filter input + debounce, lines selector, manual
refresh, auto-refresh with interval selector), truncation notice, and
auto-scroll to bottom on data updates.
fetchData uses Promise.allSettled so a log-read failure never hides the
service-health panel.
- Types: Fail2BanLogResponse, ServiceStatusResponse (types/config.ts)
- API functions: fetchFail2BanLog, fetchServiceStatus (api/config.ts)
- Endpoint constants (api/endpoints.ts)
- ConfigPage.tsx: Log tab added after existing tabs
Tests:
- Backend service tests: TestReadFail2BanLog (6), TestGetServiceStatus (2)
- Backend router tests: TestGetFail2BanLog (8), TestGetServiceStatus (3)
- Frontend: LogTab.test.tsx (8 tests)
Docs:
- Features.md: Log section added under Configuration View
- Architekture.md: config.py router and config_service.py descriptions updated
- Tasks.md: Task 2 marked done
This commit is contained in:
@@ -18,6 +18,7 @@ import type {
|
||||
ConfFileCreateRequest,
|
||||
ConfFilesResponse,
|
||||
ConfFileUpdateRequest,
|
||||
Fail2BanLogResponse,
|
||||
FilterConfig,
|
||||
FilterConfigUpdate,
|
||||
FilterCreateRequest,
|
||||
@@ -43,6 +44,7 @@ import type {
|
||||
ServerSettingsUpdate,
|
||||
JailFileConfig,
|
||||
JailFileConfigUpdate,
|
||||
ServiceStatusResponse,
|
||||
} from "../types/config";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -541,3 +543,29 @@ export async function deactivateJail(
|
||||
undefined
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// fail2ban log viewer (Task 2)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Fetch the tail of the fail2ban daemon log file.
|
||||
*
|
||||
* @param lines - Number of tail lines to return (1–2000, 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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user