diff --git a/Docs/Tasks.md b/Docs/Tasks.md index 916816d..9e63297 100644 --- a/Docs/Tasks.md +++ b/Docs/Tasks.md @@ -189,6 +189,8 @@ Add a `bangui_version` field to every API response that already carries the fail - All existing backend tests pass. - Add one test per endpoint asserting that `bangui_version` matches `app.__version__`. +**Status:** ✅ Completed (2026-03-19) + --- ### Task GV-3 — Display the BanGUI version on Dashboard and Configuration → Server @@ -215,4 +217,6 @@ After GV-2 the API delivers `bangui_version`; this task makes the frontend show - No TypeScript compile errors (`tsc --noEmit`). - Both values originate from the same API field (`bangui_version`) and therefore always match the backend version. +**Status:** ✅ Completed (2026-03-19) + --- diff --git a/frontend/src/components/ServerStatusBar.tsx b/frontend/src/components/ServerStatusBar.tsx index c0851e8..5def4cb 100644 --- a/frontend/src/components/ServerStatusBar.tsx +++ b/frontend/src/components/ServerStatusBar.tsx @@ -83,7 +83,7 @@ const useStyles = makeStyles({ */ export function ServerStatusBar(): React.JSX.Element { const styles = useStyles(); - const { status, loading, error, refresh } = useServerStatus(); + const { status, banguiVersion, loading, error, refresh } = useServerStatus(); return (
@@ -116,6 +116,14 @@ export function ServerStatusBar(): React.JSX.Element { )} + {banguiVersion != null && ( + + + BanGUI v{banguiVersion} + + + )} + {/* ---------------------------------------------------------------- */} {/* Stats (only when online) */} {/* ---------------------------------------------------------------- */} diff --git a/frontend/src/components/__tests__/ServerStatusBar.test.tsx b/frontend/src/components/__tests__/ServerStatusBar.test.tsx index a827bf8..a70ab47 100644 --- a/frontend/src/components/__tests__/ServerStatusBar.test.tsx +++ b/frontend/src/components/__tests__/ServerStatusBar.test.tsx @@ -41,6 +41,7 @@ describe("ServerStatusBar", () => { it("shows a spinner while the initial load is in progress", () => { mockedUseServerStatus.mockReturnValue({ status: null, + banguiVersion: null, loading: true, error: null, refresh: vi.fn(), @@ -59,6 +60,7 @@ describe("ServerStatusBar", () => { total_bans: 10, total_failures: 5, }, + banguiVersion: "1.1.0", loading: false, error: null, refresh: vi.fn(), @@ -76,6 +78,7 @@ describe("ServerStatusBar", () => { total_bans: 0, total_failures: 0, }, + banguiVersion: "1.1.0", loading: false, error: null, refresh: vi.fn(), @@ -93,6 +96,7 @@ describe("ServerStatusBar", () => { total_bans: 0, total_failures: 0, }, + banguiVersion: "1.2.3", loading: false, error: null, refresh: vi.fn(), @@ -101,6 +105,24 @@ describe("ServerStatusBar", () => { expect(screen.getByText("v1.2.3")).toBeInTheDocument(); }); + it("renders a BanGUI version badge", () => { + mockedUseServerStatus.mockReturnValue({ + status: { + online: true, + version: "1.2.3", + active_jails: 1, + total_bans: 0, + total_failures: 0, + }, + banguiVersion: "9.9.9", + loading: false, + error: null, + refresh: vi.fn(), + }); + renderBar(); + expect(screen.getByText("BanGUI v9.9.9")).toBeInTheDocument(); + }); + it("does not render the version element when version is null", () => { mockedUseServerStatus.mockReturnValue({ status: { @@ -110,6 +132,7 @@ describe("ServerStatusBar", () => { total_bans: 0, total_failures: 0, }, + banguiVersion: "1.2.3", loading: false, error: null, refresh: vi.fn(), @@ -128,6 +151,7 @@ describe("ServerStatusBar", () => { total_bans: 21, total_failures: 99, }, + banguiVersion: "1.0.0", loading: false, error: null, refresh: vi.fn(), @@ -143,6 +167,7 @@ describe("ServerStatusBar", () => { it("renders an error message when the status fetch fails", () => { mockedUseServerStatus.mockReturnValue({ status: null, + banguiVersion: null, loading: false, error: "Network error", refresh: vi.fn(), diff --git a/frontend/src/components/config/ServerHealthSection.tsx b/frontend/src/components/config/ServerHealthSection.tsx index 65c352e..039ac0d 100644 --- a/frontend/src/components/config/ServerHealthSection.tsx +++ b/frontend/src/components/config/ServerHealthSection.tsx @@ -352,6 +352,12 @@ export function ServerHealthSection(): React.JSX.Element { {status.version}
)} + {status.bangui_version && ( +
+ BanGUI + {status.bangui_version} +
+ )}
Active Jails {status.jail_count} diff --git a/frontend/src/components/config/__tests__/ServerHealthSection.test.tsx b/frontend/src/components/config/__tests__/ServerHealthSection.test.tsx new file mode 100644 index 0000000..45575c9 --- /dev/null +++ b/frontend/src/components/config/__tests__/ServerHealthSection.test.tsx @@ -0,0 +1,51 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { render, screen } from "@testing-library/react"; +import { FluentProvider, webLightTheme } from "@fluentui/react-components"; +import { ServerHealthSection } from "../ServerHealthSection"; + +vi.mock("../../../api/config"); + +import { fetchFail2BanLog, fetchServiceStatus } from "../../../api/config"; + +const mockedFetchServiceStatus = vi.mocked(fetchServiceStatus); +const mockedFetchFail2BanLog = vi.mocked(fetchFail2BanLog); + +describe("ServerHealthSection", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("shows the BanGUI version in the service health panel", async () => { + mockedFetchServiceStatus.mockResolvedValue({ + online: true, + version: "1.2.3", + bangui_version: "1.2.3", + jail_count: 2, + total_bans: 5, + total_failures: 1, + log_level: "INFO", + log_target: "STDOUT", + }); + + mockedFetchFail2BanLog.mockResolvedValue({ + log_path: "/var/log/fail2ban.log", + lines: ["2026-01-01 fail2ban[123]: INFO Test"], + total_lines: 1, + log_level: "INFO", + log_target: "STDOUT", + }); + + render( + + + , + ); + + // The service health panel should render and include the BanGUI version. + const banGuiLabel = await screen.findByText("BanGUI"); + expect(banGuiLabel).toBeInTheDocument(); + + const banGuiCard = banGuiLabel.closest("div"); + expect(banGuiCard).toHaveTextContent("1.2.3"); + }); +}); diff --git a/frontend/src/hooks/useServerStatus.ts b/frontend/src/hooks/useServerStatus.ts index fbbe43f..f4a37fd 100644 --- a/frontend/src/hooks/useServerStatus.ts +++ b/frontend/src/hooks/useServerStatus.ts @@ -17,6 +17,8 @@ const POLL_INTERVAL_MS = 30_000; export interface UseServerStatusResult { /** The most recent server status snapshot, or `null` before the first fetch. */ status: ServerStatus | null; + /** BanGUI application version string. */ + banguiVersion: string | null; /** Whether a fetch is currently in flight. */ loading: boolean; /** Error message string when the last fetch failed, otherwise `null`. */ @@ -32,6 +34,7 @@ export interface UseServerStatusResult { */ export function useServerStatus(): UseServerStatusResult { const [status, setStatus] = useState(null); + const [banguiVersion, setBanguiVersion] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -43,6 +46,7 @@ export function useServerStatus(): UseServerStatusResult { try { const data = await fetchServerStatus(); setStatus(data.status); + setBanguiVersion(data.bangui_version); setError(null); } catch (err: unknown) { setError(err instanceof Error ? err.message : "Failed to fetch server status"); @@ -77,5 +81,5 @@ export function useServerStatus(): UseServerStatusResult { void doFetch().catch((): void => undefined); }, [doFetch]); - return { status, loading, error, refresh }; + return { status, banguiVersion, loading, error, refresh }; } diff --git a/frontend/src/layouts/MainLayout.tsx b/frontend/src/layouts/MainLayout.tsx index 305a476..833875e 100644 --- a/frontend/src/layouts/MainLayout.tsx +++ b/frontend/src/layouts/MainLayout.tsx @@ -313,7 +313,7 @@ export function MainLayout(): React.JSX.Element {
{!collapsed && ( - BanGUI v{__APP_VERSION__} + BanGUI )} { expect(screen.getByRole("navigation", { name: "Main navigation" })).toBeInTheDocument(); }); - it("shows the BanGUI version in the sidebar footer when expanded", () => { + it("does not show the BanGUI application version in the sidebar footer", () => { renderLayout(); // __APP_VERSION__ is stubbed to "0.0.0-test" via vitest.config.ts define. - expect(screen.getByText("BanGUI v0.0.0-test")).toBeInTheDocument(); + expect(screen.queryByText(/BanGUI v/)).not.toBeInTheDocument(); }); - it("hides the BanGUI version text when the sidebar is collapsed", async () => { + it("hides the logo text when the sidebar is collapsed", async () => { renderLayout(); const toggleButton = screen.getByRole("button", { name: /collapse sidebar/i }); await userEvent.click(toggleButton); - expect(screen.queryByText("BanGUI v0.0.0-test")).not.toBeInTheDocument(); + expect(screen.queryByText("BanGUI")).not.toBeInTheDocument(); }); }); diff --git a/frontend/src/types/config.ts b/frontend/src/types/config.ts index 372c772..ec47f65 100644 --- a/frontend/src/types/config.ts +++ b/frontend/src/types/config.ts @@ -661,6 +661,8 @@ export interface ServiceStatusResponse { online: boolean; /** fail2ban version string, or null when offline. */ version: string | null; + /** BanGUI application version (from the API). */ + bangui_version: string; /** Number of currently active jails. */ jail_count: number; /** Aggregated current ban count across all jails. */ diff --git a/frontend/src/types/server.ts b/frontend/src/types/server.ts index 7c9525f..272c538 100644 --- a/frontend/src/types/server.ts +++ b/frontend/src/types/server.ts @@ -21,4 +21,6 @@ export interface ServerStatus { /** Response shape for ``GET /api/dashboard/status``. */ export interface ServerStatusResponse { status: ServerStatus; + /** BanGUI application version (from the API). */ + bangui_version: string; }