From 1cc9968d312321ee5e402bde0296cf572933e8a8 Mon Sep 17 00:00:00 2001 From: Lukas Date: Thu, 19 Mar 2026 19:19:42 +0100 Subject: [PATCH] Expose BanGUI version in API responses (dashboard + config) --- backend/app/models/config.py | 1 + backend/app/models/server.py | 1 + backend/app/routers/dashboard.py | 3 ++- backend/app/services/config_service.py | 2 ++ backend/tests/test_routers/test_config.py | 5 +++++ backend/tests/test_routers/test_dashboard.py | 13 +++++++++++-- 6 files changed, 22 insertions(+), 3 deletions(-) diff --git a/backend/app/models/config.py b/backend/app/models/config.py index 3f2569a..0aba7be 100644 --- a/backend/app/models/config.py +++ b/backend/app/models/config.py @@ -1002,6 +1002,7 @@ class ServiceStatusResponse(BaseModel): online: bool = Field(..., description="Whether fail2ban is reachable via its socket.") version: str | None = Field(default=None, description="fail2ban version string, or None when offline.") + bangui_version: str = Field(..., description="BanGUI application version.") jail_count: int = Field(default=0, ge=0, description="Number of currently active jails.") total_bans: int = Field(default=0, ge=0, description="Aggregated current ban count across all jails.") total_failures: int = Field(default=0, ge=0, description="Aggregated current failure count across all jails.") diff --git a/backend/app/models/server.py b/backend/app/models/server.py index 0e572a0..9ebcb36 100644 --- a/backend/app/models/server.py +++ b/backend/app/models/server.py @@ -24,6 +24,7 @@ class ServerStatusResponse(BaseModel): model_config = ConfigDict(strict=True) status: ServerStatus + bangui_version: str = Field(..., description="BanGUI application version.") class ServerSettings(BaseModel): diff --git a/backend/app/routers/dashboard.py b/backend/app/routers/dashboard.py index 5d77858..8ce593f 100644 --- a/backend/app/routers/dashboard.py +++ b/backend/app/routers/dashboard.py @@ -19,6 +19,7 @@ if TYPE_CHECKING: from fastapi import APIRouter, Query, Request +from app import __version__ from app.dependencies import AuthDep from app.models.ban import ( BanOrigin, @@ -69,7 +70,7 @@ async def get_server_status( "server_status", ServerStatus(online=False), ) - return ServerStatusResponse(status=cached) + return ServerStatusResponse(status=cached, bangui_version=__version__) @router.get( diff --git a/backend/app/services/config_service.py b/backend/app/services/config_service.py index 5baa492..d791061 100644 --- a/backend/app/services/config_service.py +++ b/backend/app/services/config_service.py @@ -897,6 +897,7 @@ async def get_service_status(socket_path: str) -> ServiceStatusResponse: Returns: :class:`~app.models.config.ServiceStatusResponse`. """ + from app import __version__ # noqa: TCH001 - expose the app release version from app.services.health_service import probe # lazy import avoids circular dep server_status = await probe(socket_path) @@ -922,6 +923,7 @@ async def get_service_status(socket_path: str) -> ServiceStatusResponse: return ServiceStatusResponse( online=server_status.online, version=server_status.version, + bangui_version=__version__, jail_count=server_status.active_jails, total_bans=server_status.total_bans, total_failures=server_status.total_failures, diff --git a/backend/tests/test_routers/test_config.py b/backend/tests/test_routers/test_config.py index f3f32fb..646b064 100644 --- a/backend/tests/test_routers/test_config.py +++ b/backend/tests/test_routers/test_config.py @@ -9,6 +9,8 @@ import aiosqlite import pytest from httpx import ASGITransport, AsyncClient +import app + from app.config import Settings from app.db import init_db from app.main import create_app @@ -2000,6 +2002,7 @@ class TestGetServiceStatus: return ServiceStatusResponse( online=online, version="1.0.0" if online else None, + bangui_version=app.__version__, jail_count=2 if online else 0, total_bans=10 if online else 0, total_failures=3 if online else 0, @@ -2018,6 +2021,7 @@ class TestGetServiceStatus: assert resp.status_code == 200 data = resp.json() assert data["online"] is True + assert data["bangui_version"] == app.__version__ assert data["jail_count"] == 2 assert data["log_level"] == "INFO" @@ -2031,6 +2035,7 @@ class TestGetServiceStatus: assert resp.status_code == 200 data = resp.json() + assert data["bangui_version"] == app.__version__ assert data["online"] is False assert data["log_level"] == "UNKNOWN" diff --git a/backend/tests/test_routers/test_dashboard.py b/backend/tests/test_routers/test_dashboard.py index d89c9f6..33b3c93 100644 --- a/backend/tests/test_routers/test_dashboard.py +++ b/backend/tests/test_routers/test_dashboard.py @@ -9,6 +9,8 @@ import aiosqlite import pytest from httpx import ASGITransport, AsyncClient +import app + from app.config import Settings from app.db import init_db from app.main import create_app @@ -151,6 +153,9 @@ class TestDashboardStatus: body = response.json() assert "status" in body + assert "bangui_version" in body + assert body["bangui_version"] == app.__version__ + status = body["status"] assert "online" in status assert "version" in status @@ -163,8 +168,10 @@ class TestDashboardStatus: ) -> None: """Endpoint returns the exact values from ``app.state.server_status``.""" response = await dashboard_client.get("/api/dashboard/status") - status = response.json()["status"] + body = response.json() + status = body["status"] + assert body["bangui_version"] == app.__version__ assert status["online"] is True assert status["version"] == "1.0.2" assert status["active_jails"] == 2 @@ -177,8 +184,10 @@ class TestDashboardStatus: """Endpoint returns online=False when the cache holds an offline snapshot.""" response = await offline_dashboard_client.get("/api/dashboard/status") assert response.status_code == 200 - status = response.json()["status"] + body = response.json() + status = body["status"] + assert body["bangui_version"] == app.__version__ assert status["online"] is False assert status["version"] is None assert status["active_jails"] == 0