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

@@ -88,7 +88,7 @@ export async function updateGlobalConfig(
}
// ---------------------------------------------------------------------------
// Reload
// Reload and Restart
// ---------------------------------------------------------------------------
export async function reloadConfig(
@@ -96,6 +96,11 @@ export async function reloadConfig(
await post<undefined>(ENDPOINTS.configReload, undefined);
}
export async function restartFail2Ban(
): Promise<void> {
await post<undefined>(ENDPOINTS.configRestart, undefined);
}
// ---------------------------------------------------------------------------
// Regex tester
// ---------------------------------------------------------------------------

View File

@@ -78,6 +78,7 @@ export const ENDPOINTS = {
configPendingRecovery: "/config/pending-recovery" as string,
configGlobal: "/config/global",
configReload: "/config/reload",
configRestart: "/config/restart",
configRegexTest: "/config/regex-test",
configPreviewLog: "/config/preview-log",
configMapColorThresholds: "/config/map-color-thresholds",

View File

@@ -2,8 +2,9 @@
* ServerTab — fail2ban server-level settings editor.
*
* Provides form fields for live server settings (log level, log target,
* DB purge age, DB max matches), a "Flush Logs" action button,
* world map color threshold configuration, and service health + log viewer.
* DB purge age, DB max matches), action buttons (flush logs, reload fail2ban,
* restart fail2ban), world map color threshold configuration, and service
* health + log viewer.
*/
import { useCallback, useEffect, useMemo, useState } from "react";
@@ -21,6 +22,7 @@ import {
} from "@fluentui/react-components";
import {
DocumentArrowDown24Regular,
ArrowSync24Regular,
} from "@fluentui/react-icons";
import { ApiError } from "../../api/client";
import type { ServerSettingsUpdate, MapColorThresholdsResponse, MapColorThresholdsUpdate } from "../../types/config";
@@ -29,6 +31,8 @@ import { useAutoSave } from "../../hooks/useAutoSave";
import {
fetchMapColorThresholds,
updateMapColorThresholds,
reloadConfig,
restartFail2Ban,
} from "../../api/config";
import { AutoSaveIndicator } from "./AutoSaveIndicator";
import { ServerHealthSection } from "./ServerHealthSection";
@@ -53,6 +57,10 @@ export function ServerTab(): React.JSX.Element {
const [flushing, setFlushing] = useState(false);
const [msg, setMsg] = useState<{ text: string; ok: boolean } | null>(null);
// Reload/Restart state
const [isReloading, setIsReloading] = useState(false);
const [isRestarting, setIsRestarting] = useState(false);
// Map color thresholds
const [mapThresholds, setMapThresholds] = useState<MapColorThresholdsResponse | null>(null);
const [mapThresholdHigh, setMapThresholdHigh] = useState("");
@@ -97,6 +105,38 @@ export function ServerTab(): React.JSX.Element {
}
}, [flush]);
const handleReload = useCallback(async () => {
setIsReloading(true);
setMsg(null);
try {
await reloadConfig();
setMsg({ text: "fail2ban reloaded successfully", ok: true });
} catch (err: unknown) {
setMsg({
text: err instanceof ApiError ? err.message : "Reload failed.",
ok: false,
});
} finally {
setIsReloading(false);
}
}, []);
const handleRestart = useCallback(async () => {
setIsRestarting(true);
setMsg(null);
try {
await restartFail2Ban();
setMsg({ text: "fail2ban restart initiated", ok: true });
} catch (err: unknown) {
setMsg({
text: err instanceof ApiError ? err.message : "Restart failed.",
ok: false,
});
} finally {
setIsRestarting(false);
}
}, []);
// Load map color thresholds on mount.
const loadMapThresholds = useCallback(async (): Promise<void> => {
try {
@@ -263,6 +303,22 @@ export function ServerTab(): React.JSX.Element {
>
{flushing ? "Flushing…" : "Flush Logs"}
</Button>
<Button
appearance="secondary"
icon={<ArrowSync24Regular />}
disabled={isReloading}
onClick={() => void handleReload()}
>
{isReloading ? "Reloading…" : "Reload fail2ban"}
</Button>
<Button
appearance="secondary"
icon={<ArrowSync24Regular />}
disabled={isRestarting}
onClick={() => void handleRestart()}
>
{isRestarting ? "Restarting…" : "Restart fail2ban"}
</Button>
</div>
{msg && (
<MessageBar intent={msg.ok ? "success" : "error"}>