Add reload and restart buttons to Server tab

Adds ability to reload or restart fail2ban service from the Server tab UI.

Backend changes:
- Add new restart() method to jail_service.py that sends 'restart' command
- Add new POST /api/config/restart endpoint in config router
- Endpoint returns 204 on success, 502 if fail2ban unreachable
- Includes structured logging via 'fail2ban_restarted' log entry

Frontend changes:
- Add configRestart endpoint to endpoints.ts
- Add restartFail2Ban() API function in config.ts API module
- Import ArrowSync24Regular icon from Fluent UI
- Add reload and restart button handlers to ServerTab
- Display 'Reload fail2ban' and 'Restart fail2ban' buttons in action row
- Show loading spinner during operation
- Display success/error MessageBar with appropriate feedback
- Update ServerTab docstring to document new buttons

All 115 frontend tests pass.
This commit is contained in:
2026-03-14 22:03:58 +01:00
parent 1da38361a9
commit 1e33220f59
5 changed files with 122 additions and 3 deletions

View File

@@ -366,6 +366,40 @@ async def reload_fail2ban(
raise _bad_gateway(exc) from exc
# Restart endpoint
# ---------------------------------------------------------------------------
@router.post(
"/restart",
status_code=status.HTTP_204_NO_CONTENT,
summary="Restart the fail2ban service",
)
async def restart_fail2ban(
request: Request,
_auth: AuthDep,
) -> None:
"""Trigger a full fail2ban service restart.
The fail2ban daemon is completely stopped and then started again,
re-reading all configuration files in the process.
Args:
request: Incoming request.
_auth: Validated session.
Raises:
HTTPException: 502 when fail2ban is unreachable.
"""
socket_path: str = request.app.state.settings.fail2ban_socket
try:
# Perform restart by sending the restart command via the fail2ban socket.
# If fail2ban is not running, this will raise an exception, and we return 502.
await jail_service.restart(socket_path)
except Fail2BanConnectionError as exc:
raise _bad_gateway(exc) from exc
# ---------------------------------------------------------------------------
# Regex tester (stateless)
# ---------------------------------------------------------------------------

View File

@@ -596,6 +596,29 @@ async def reload_all(
raise JailOperationError(str(exc)) from exc
async def restart(socket_path: str) -> None:
"""Restart the fail2ban service (daemon).
Sends the 'restart' command to the fail2ban daemon via the Unix socket.
All jails are stopped and the daemon is restarted, re-reading all
configuration from scratch.
Args:
socket_path: Path to the fail2ban Unix domain socket.
Raises:
JailOperationError: If fail2ban reports the operation failed.
~app.utils.fail2ban_client.Fail2BanConnectionError: If the socket
cannot be reached.
"""
client = Fail2BanClient(socket_path=socket_path, timeout=_SOCKET_TIMEOUT)
try:
_ok(await client.send(["restart"]))
log.info("fail2ban_restarted")
except ValueError as exc:
raise JailOperationError(str(exc)) from exc
# ---------------------------------------------------------------------------
# Public API — Ban / Unban
# ---------------------------------------------------------------------------