Refactor service status response: migrate bangui_version into version field
This commit is contained in:
@@ -1001,8 +1001,7 @@ class ServiceStatusResponse(BaseModel):
|
|||||||
model_config = ConfigDict(strict=True)
|
model_config = ConfigDict(strict=True)
|
||||||
|
|
||||||
online: bool = Field(..., description="Whether fail2ban is reachable via its socket.")
|
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.")
|
version: str | None = Field(default=None, description="BanGUI application version (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.")
|
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_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.")
|
total_failures: int = Field(default=0, ge=0, description="Aggregated current failure count across all jails.")
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ class ServerStatusResponse(BaseModel):
|
|||||||
model_config = ConfigDict(strict=True)
|
model_config = ConfigDict(strict=True)
|
||||||
|
|
||||||
status: ServerStatus
|
status: ServerStatus
|
||||||
bangui_version: str = Field(..., description="BanGUI application version.")
|
|
||||||
|
|
||||||
|
|
||||||
class ServerSettings(BaseModel):
|
class ServerSettings(BaseModel):
|
||||||
|
|||||||
@@ -70,7 +70,8 @@ async def get_server_status(
|
|||||||
"server_status",
|
"server_status",
|
||||||
ServerStatus(online=False),
|
ServerStatus(online=False),
|
||||||
)
|
)
|
||||||
return ServerStatusResponse(status=cached, bangui_version=__version__)
|
cached.version = __version__
|
||||||
|
return ServerStatusResponse(status=cached)
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
|
|||||||
@@ -819,8 +819,7 @@ async def get_service_status(
|
|||||||
|
|
||||||
return ServiceStatusResponse(
|
return ServiceStatusResponse(
|
||||||
online=server_status.online,
|
online=server_status.online,
|
||||||
version=server_status.version,
|
version=__version__,
|
||||||
bangui_version=__version__,
|
|
||||||
jail_count=server_status.active_jails,
|
jail_count=server_status.active_jails,
|
||||||
total_bans=server_status.total_bans,
|
total_bans=server_status.total_bans,
|
||||||
total_failures=server_status.total_failures,
|
total_failures=server_status.total_failures,
|
||||||
|
|||||||
@@ -221,8 +221,8 @@ class TestFilterConfigImports:
|
|||||||
|
|
||||||
|
|
||||||
class TestServiceStatusBanguiVersion:
|
class TestServiceStatusBanguiVersion:
|
||||||
"""Bug 4: ``get_service_status`` must include ``bangui_version``
|
"""Bug 4: ``get_service_status`` must include application version
|
||||||
in the ``ServiceStatusResponse`` it returns."""
|
in the ``version`` field of the ``ServiceStatusResponse``."""
|
||||||
|
|
||||||
async def test_online_response_contains_bangui_version(self) -> None:
|
async def test_online_response_contains_bangui_version(self) -> None:
|
||||||
"""The returned model must contain the ``bangui_version`` field."""
|
"""The returned model must contain the ``bangui_version`` field."""
|
||||||
@@ -256,11 +256,9 @@ class TestServiceStatusBanguiVersion:
|
|||||||
probe_fn=AsyncMock(return_value=online_status),
|
probe_fn=AsyncMock(return_value=online_status),
|
||||||
)
|
)
|
||||||
|
|
||||||
assert hasattr(result, "bangui_version"), (
|
assert result.version == app.__version__, (
|
||||||
"ServiceStatusResponse is missing bangui_version — "
|
"ServiceStatusResponse must expose BanGUI version in version field"
|
||||||
"Pydantic will raise ValidationError → 500"
|
|
||||||
)
|
)
|
||||||
assert result.bangui_version == app.__version__
|
|
||||||
|
|
||||||
async def test_offline_response_contains_bangui_version(self) -> None:
|
async def test_offline_response_contains_bangui_version(self) -> None:
|
||||||
"""Even when fail2ban is offline, ``bangui_version`` must be present."""
|
"""Even when fail2ban is offline, ``bangui_version`` must be present."""
|
||||||
@@ -275,4 +273,4 @@ class TestServiceStatusBanguiVersion:
|
|||||||
probe_fn=AsyncMock(return_value=offline_status),
|
probe_fn=AsyncMock(return_value=offline_status),
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result.bangui_version == app.__version__
|
assert result.version == app.__version__
|
||||||
|
|||||||
@@ -2001,8 +2001,7 @@ class TestGetServiceStatus:
|
|||||||
def _mock_status(self, online: bool = True) -> ServiceStatusResponse:
|
def _mock_status(self, online: bool = True) -> ServiceStatusResponse:
|
||||||
return ServiceStatusResponse(
|
return ServiceStatusResponse(
|
||||||
online=online,
|
online=online,
|
||||||
version="1.0.0" if online else None,
|
version=app.__version__,
|
||||||
bangui_version=app.__version__,
|
|
||||||
jail_count=2 if online else 0,
|
jail_count=2 if online else 0,
|
||||||
total_bans=10 if online else 0,
|
total_bans=10 if online else 0,
|
||||||
total_failures=3 if online else 0,
|
total_failures=3 if online else 0,
|
||||||
@@ -2021,7 +2020,7 @@ class TestGetServiceStatus:
|
|||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
assert data["online"] is True
|
assert data["online"] is True
|
||||||
assert data["bangui_version"] == app.__version__
|
assert data["version"] == app.__version__
|
||||||
assert data["jail_count"] == 2
|
assert data["jail_count"] == 2
|
||||||
assert data["log_level"] == "INFO"
|
assert data["log_level"] == "INFO"
|
||||||
|
|
||||||
@@ -2035,7 +2034,7 @@ class TestGetServiceStatus:
|
|||||||
|
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
assert data["bangui_version"] == app.__version__
|
assert data["version"] == app.__version__
|
||||||
assert data["online"] is False
|
assert data["online"] is False
|
||||||
assert data["log_level"] == "UNKNOWN"
|
assert data["log_level"] == "UNKNOWN"
|
||||||
|
|
||||||
|
|||||||
@@ -153,8 +153,6 @@ class TestDashboardStatus:
|
|||||||
body = response.json()
|
body = response.json()
|
||||||
|
|
||||||
assert "status" in body
|
assert "status" in body
|
||||||
assert "bangui_version" in body
|
|
||||||
assert body["bangui_version"] == app.__version__
|
|
||||||
|
|
||||||
status = body["status"]
|
status = body["status"]
|
||||||
assert "online" in status
|
assert "online" in status
|
||||||
@@ -171,9 +169,8 @@ class TestDashboardStatus:
|
|||||||
body = response.json()
|
body = response.json()
|
||||||
status = body["status"]
|
status = body["status"]
|
||||||
|
|
||||||
assert body["bangui_version"] == app.__version__
|
|
||||||
assert status["online"] is True
|
assert status["online"] is True
|
||||||
assert status["version"] == "1.0.2"
|
assert status["version"] == app.__version__
|
||||||
assert status["active_jails"] == 2
|
assert status["active_jails"] == 2
|
||||||
assert status["total_bans"] == 10
|
assert status["total_bans"] == 10
|
||||||
assert status["total_failures"] == 5
|
assert status["total_failures"] == 5
|
||||||
@@ -187,9 +184,8 @@ class TestDashboardStatus:
|
|||||||
body = response.json()
|
body = response.json()
|
||||||
status = body["status"]
|
status = body["status"]
|
||||||
|
|
||||||
assert body["bangui_version"] == app.__version__
|
|
||||||
assert status["online"] is False
|
assert status["online"] is False
|
||||||
assert status["version"] is None
|
assert status["version"] == app.__version__
|
||||||
assert status["active_jails"] == 0
|
assert status["active_jails"] == 0
|
||||||
assert status["total_bans"] == 0
|
assert status["total_bans"] == 0
|
||||||
assert status["total_failures"] == 0
|
assert status["total_failures"] == 0
|
||||||
|
|||||||
@@ -752,8 +752,7 @@ class TestGetServiceStatus:
|
|||||||
from app import __version__
|
from app import __version__
|
||||||
|
|
||||||
assert result.online is True
|
assert result.online is True
|
||||||
assert result.version == "1.0.0"
|
assert result.version == __version__
|
||||||
assert result.bangui_version == __version__
|
|
||||||
assert result.jail_count == 2
|
assert result.jail_count == 2
|
||||||
assert result.total_bans == 5
|
assert result.total_bans == 5
|
||||||
assert result.total_failures == 3
|
assert result.total_failures == 3
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ const useStyles = makeStyles({
|
|||||||
*/
|
*/
|
||||||
export function ServerStatusBar(): React.JSX.Element {
|
export function ServerStatusBar(): React.JSX.Element {
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
const { status, banguiVersion, loading, error, refresh } = useServerStatus();
|
const { status, loading, error, refresh } = useServerStatus();
|
||||||
|
|
||||||
const cardStyles = useCardStyles();
|
const cardStyles = useCardStyles();
|
||||||
|
|
||||||
@@ -98,21 +98,13 @@ export function ServerStatusBar(): React.JSX.Element {
|
|||||||
{/* Version */}
|
{/* Version */}
|
||||||
{/* ---------------------------------------------------------------- */}
|
{/* ---------------------------------------------------------------- */}
|
||||||
{status?.version != null && (
|
{status?.version != null && (
|
||||||
<Tooltip content="fail2ban daemon version" relationship="description">
|
<Tooltip content="BanGUI version" relationship="description">
|
||||||
<Text size={200} className={styles.statValue}>
|
<Text size={200} className={styles.statValue}>
|
||||||
v{status.version}
|
v{status.version}
|
||||||
</Text>
|
</Text>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{banguiVersion != null && (
|
|
||||||
<Tooltip content="BanGUI version" relationship="description">
|
|
||||||
<Badge appearance="filled" size="small">
|
|
||||||
BanGUI v{banguiVersion}
|
|
||||||
</Badge>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* ---------------------------------------------------------------- */}
|
{/* ---------------------------------------------------------------- */}
|
||||||
{/* Stats (only when online) */}
|
{/* Stats (only when online) */}
|
||||||
{/* ---------------------------------------------------------------- */}
|
{/* ---------------------------------------------------------------- */}
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ describe("ServerStatusBar", () => {
|
|||||||
it("shows a spinner while the initial load is in progress", () => {
|
it("shows a spinner while the initial load is in progress", () => {
|
||||||
mockedUseServerStatus.mockReturnValue({
|
mockedUseServerStatus.mockReturnValue({
|
||||||
status: null,
|
status: null,
|
||||||
banguiVersion: null,
|
|
||||||
loading: true,
|
loading: true,
|
||||||
error: null,
|
error: null,
|
||||||
refresh: vi.fn(),
|
refresh: vi.fn(),
|
||||||
@@ -60,7 +59,6 @@ describe("ServerStatusBar", () => {
|
|||||||
total_bans: 10,
|
total_bans: 10,
|
||||||
total_failures: 5,
|
total_failures: 5,
|
||||||
},
|
},
|
||||||
banguiVersion: "1.1.0",
|
|
||||||
loading: false,
|
loading: false,
|
||||||
error: null,
|
error: null,
|
||||||
refresh: vi.fn(),
|
refresh: vi.fn(),
|
||||||
@@ -78,7 +76,6 @@ describe("ServerStatusBar", () => {
|
|||||||
total_bans: 0,
|
total_bans: 0,
|
||||||
total_failures: 0,
|
total_failures: 0,
|
||||||
},
|
},
|
||||||
banguiVersion: "1.1.0",
|
|
||||||
loading: false,
|
loading: false,
|
||||||
error: null,
|
error: null,
|
||||||
refresh: vi.fn(),
|
refresh: vi.fn(),
|
||||||
@@ -96,7 +93,6 @@ describe("ServerStatusBar", () => {
|
|||||||
total_bans: 0,
|
total_bans: 0,
|
||||||
total_failures: 0,
|
total_failures: 0,
|
||||||
},
|
},
|
||||||
banguiVersion: "1.2.3",
|
|
||||||
loading: false,
|
loading: false,
|
||||||
error: null,
|
error: null,
|
||||||
refresh: vi.fn(),
|
refresh: vi.fn(),
|
||||||
@@ -105,7 +101,7 @@ describe("ServerStatusBar", () => {
|
|||||||
expect(screen.getByText("v1.2.3")).toBeInTheDocument();
|
expect(screen.getByText("v1.2.3")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders a BanGUI version badge", () => {
|
it("does not render a separate BanGUI version badge", () => {
|
||||||
mockedUseServerStatus.mockReturnValue({
|
mockedUseServerStatus.mockReturnValue({
|
||||||
status: {
|
status: {
|
||||||
online: true,
|
online: true,
|
||||||
@@ -114,13 +110,12 @@ describe("ServerStatusBar", () => {
|
|||||||
total_bans: 0,
|
total_bans: 0,
|
||||||
total_failures: 0,
|
total_failures: 0,
|
||||||
},
|
},
|
||||||
banguiVersion: "9.9.9",
|
|
||||||
loading: false,
|
loading: false,
|
||||||
error: null,
|
error: null,
|
||||||
refresh: vi.fn(),
|
refresh: vi.fn(),
|
||||||
});
|
});
|
||||||
renderBar();
|
renderBar();
|
||||||
expect(screen.getByText("BanGUI v9.9.9")).toBeInTheDocument();
|
expect(screen.queryByText("BanGUI v9.9.9")).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not render the version element when version is null", () => {
|
it("does not render the version element when version is null", () => {
|
||||||
@@ -132,7 +127,6 @@ describe("ServerStatusBar", () => {
|
|||||||
total_bans: 0,
|
total_bans: 0,
|
||||||
total_failures: 0,
|
total_failures: 0,
|
||||||
},
|
},
|
||||||
banguiVersion: "1.2.3",
|
|
||||||
loading: false,
|
loading: false,
|
||||||
error: null,
|
error: null,
|
||||||
refresh: vi.fn(),
|
refresh: vi.fn(),
|
||||||
@@ -151,7 +145,6 @@ describe("ServerStatusBar", () => {
|
|||||||
total_bans: 21,
|
total_bans: 21,
|
||||||
total_failures: 99,
|
total_failures: 99,
|
||||||
},
|
},
|
||||||
banguiVersion: "1.0.0",
|
|
||||||
loading: false,
|
loading: false,
|
||||||
error: null,
|
error: null,
|
||||||
refresh: vi.fn(),
|
refresh: vi.fn(),
|
||||||
@@ -167,7 +160,6 @@ describe("ServerStatusBar", () => {
|
|||||||
it("renders an error message when the status fetch fails", () => {
|
it("renders an error message when the status fetch fails", () => {
|
||||||
mockedUseServerStatus.mockReturnValue({
|
mockedUseServerStatus.mockReturnValue({
|
||||||
status: null,
|
status: null,
|
||||||
banguiVersion: null,
|
|
||||||
loading: false,
|
loading: false,
|
||||||
error: "Network error",
|
error: "Network error",
|
||||||
refresh: vi.fn(),
|
refresh: vi.fn(),
|
||||||
|
|||||||
@@ -352,12 +352,6 @@ export function ServerHealthSection(): React.JSX.Element {
|
|||||||
<Text className={styles.statValue}>{status.version}</Text>
|
<Text className={styles.statValue}>{status.version}</Text>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{status.bangui_version && (
|
|
||||||
<div className={styles.statCard}>
|
|
||||||
<Text className={styles.statLabel}>BanGUI</Text>
|
|
||||||
<Text className={styles.statValue}>{status.bangui_version}</Text>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className={styles.statCard}>
|
<div className={styles.statCard}>
|
||||||
<Text className={styles.statLabel}>Active Jails</Text>
|
<Text className={styles.statLabel}>Active Jails</Text>
|
||||||
<Text className={styles.statValue}>{status.jail_count}</Text>
|
<Text className={styles.statValue}>{status.jail_count}</Text>
|
||||||
|
|||||||
@@ -15,11 +15,10 @@ describe("ServerHealthSection", () => {
|
|||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows the BanGUI version in the service health panel", async () => {
|
it("shows the version in the service health panel", async () => {
|
||||||
mockedFetchServiceStatus.mockResolvedValue({
|
mockedFetchServiceStatus.mockResolvedValue({
|
||||||
online: true,
|
online: true,
|
||||||
version: "1.2.3",
|
version: "1.2.3",
|
||||||
bangui_version: "1.2.3",
|
|
||||||
jail_count: 2,
|
jail_count: 2,
|
||||||
total_bans: 5,
|
total_bans: 5,
|
||||||
total_failures: 1,
|
total_failures: 1,
|
||||||
@@ -41,11 +40,11 @@ describe("ServerHealthSection", () => {
|
|||||||
</FluentProvider>,
|
</FluentProvider>,
|
||||||
);
|
);
|
||||||
|
|
||||||
// The service health panel should render and include the BanGUI version.
|
// The service health panel should render and include the version.
|
||||||
const banGuiLabel = await screen.findByText("BanGUI");
|
const versionLabel = await screen.findByText("Version");
|
||||||
expect(banGuiLabel).toBeInTheDocument();
|
expect(versionLabel).toBeInTheDocument();
|
||||||
|
|
||||||
const banGuiCard = banGuiLabel.closest("div");
|
const versionCard = versionLabel.closest("div");
|
||||||
expect(banGuiCard).toHaveTextContent("1.2.3");
|
expect(versionCard).toHaveTextContent("1.2.3");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ const POLL_INTERVAL_MS = 30_000;
|
|||||||
export interface UseServerStatusResult {
|
export interface UseServerStatusResult {
|
||||||
/** The most recent server status snapshot, or `null` before the first fetch. */
|
/** The most recent server status snapshot, or `null` before the first fetch. */
|
||||||
status: ServerStatus | null;
|
status: ServerStatus | null;
|
||||||
/** BanGUI application version string. */
|
|
||||||
banguiVersion: string | null;
|
|
||||||
/** Whether a fetch is currently in flight. */
|
/** Whether a fetch is currently in flight. */
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
/** Error message string when the last fetch failed, otherwise `null`. */
|
/** Error message string when the last fetch failed, otherwise `null`. */
|
||||||
@@ -35,7 +33,6 @@ export interface UseServerStatusResult {
|
|||||||
*/
|
*/
|
||||||
export function useServerStatus(): UseServerStatusResult {
|
export function useServerStatus(): UseServerStatusResult {
|
||||||
const [status, setStatus] = useState<ServerStatus | null>(null);
|
const [status, setStatus] = useState<ServerStatus | null>(null);
|
||||||
const [banguiVersion, setBanguiVersion] = useState<string | null>(null);
|
|
||||||
const [loading, setLoading] = useState<boolean>(true);
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
@@ -47,7 +44,6 @@ export function useServerStatus(): UseServerStatusResult {
|
|||||||
try {
|
try {
|
||||||
const data = await fetchServerStatus();
|
const data = await fetchServerStatus();
|
||||||
setStatus(data.status);
|
setStatus(data.status);
|
||||||
setBanguiVersion(data.bangui_version);
|
|
||||||
setError(null);
|
setError(null);
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
handleFetchError(err, setError, "Failed to fetch server status");
|
handleFetchError(err, setError, "Failed to fetch server status");
|
||||||
@@ -82,5 +78,5 @@ export function useServerStatus(): UseServerStatusResult {
|
|||||||
void doFetch().catch((): void => undefined);
|
void doFetch().catch((): void => undefined);
|
||||||
}, [doFetch]);
|
}, [doFetch]);
|
||||||
|
|
||||||
return { status, banguiVersion, loading, error, refresh };
|
return { status, loading, error, refresh };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -659,10 +659,8 @@ export interface Fail2BanLogResponse {
|
|||||||
export interface ServiceStatusResponse {
|
export interface ServiceStatusResponse {
|
||||||
/** Whether fail2ban is reachable via its socket. */
|
/** Whether fail2ban is reachable via its socket. */
|
||||||
online: boolean;
|
online: boolean;
|
||||||
/** fail2ban version string, or null when offline. */
|
/** BanGUI application version (or null when offline). */
|
||||||
version: string | null;
|
version: string | null;
|
||||||
/** BanGUI application version (from the API). */
|
|
||||||
bangui_version: string;
|
|
||||||
/** Number of currently active jails. */
|
/** Number of currently active jails. */
|
||||||
jail_count: number;
|
jail_count: number;
|
||||||
/** Aggregated current ban count across all jails. */
|
/** Aggregated current ban count across all jails. */
|
||||||
|
|||||||
@@ -21,6 +21,4 @@ export interface ServerStatus {
|
|||||||
/** Response shape for ``GET /api/dashboard/status``. */
|
/** Response shape for ``GET /api/dashboard/status``. */
|
||||||
export interface ServerStatusResponse {
|
export interface ServerStatusResponse {
|
||||||
status: ServerStatus;
|
status: ServerStatus;
|
||||||
/** BanGUI application version (from the API). */
|
|
||||||
bangui_version: string;
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user