feat(ui): add health check endpoint (#11145)

This commit is contained in:
Alejandro Bailo
2026-05-14 13:47:48 +02:00
committed by GitHub
parent bf4fd8fabd
commit dcf91ef252
4 changed files with 91 additions and 1 deletions
+7 -1
View File
@@ -48,10 +48,16 @@ services:
- path: .env
required: false
ports:
- ${UI_PORT:-3000}:${UI_PORT:-3000}
- ${UI_PORT:-3000}:3000
depends_on:
mcp-server:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:3000/api/health || exit 1"]
interval: 10s
timeout: 5s
retries: 3
start_period: 60s
postgres:
image: postgres:16.3-alpine3.20
+1
View File
@@ -6,6 +6,7 @@ All notable changes to the **Prowler UI** are documented in this file.
### 🚀 Added
- UI health endpoint at `GET /api/health` for Docker Compose liveness checks [(#11145)](https://github.com/prowler-cloud/prowler/pull/11145)
- AWS findings and resource details now expose a "View in AWS Console" link that opens the resource directly in the AWS Console via the universal `/go/view` ARN resolver. The per-provider external link is rendered by a new shared `ExternalResourceLink` component, which also covers the existing IaC repository link [(#9172)](https://github.com/prowler-cloud/prowler/pull/9172)
### 🔄 Changed
+69
View File
@@ -0,0 +1,69 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { GET } from "./route";
interface HealthResponse {
status: "healthy";
service: "prowler-ui";
}
const parseHealthResponse = async (response: Response) =>
(await response.json()) as HealthResponse;
describe("GET /api/health", () => {
afterEach(() => {
vi.unstubAllEnvs();
vi.unstubAllGlobals();
});
it("should return a healthy response when the Next.js route handler responds", async () => {
// Given
const expectedBody: HealthResponse = {
status: "healthy",
service: "prowler-ui",
};
// When
const response = await GET();
const body = await parseHealthResponse(response);
// Then
expect(response.status).toBe(200);
expect(response.headers.get("Cache-Control")).toBe("no-store");
expect(body).toEqual(expectedBody);
});
it("should not call fetch while evaluating UI liveness", async () => {
// Given
const fetchMock = vi.fn();
vi.stubGlobal("fetch", fetchMock);
// When
await GET();
// Then
expect(fetchMock).not.toHaveBeenCalled();
});
it("should not depend on external health URLs", async () => {
// Given
vi.stubEnv(
"PROWLER_API_HEALTH_URL",
"https://api.example.com/health/ready",
);
const fetchMock = vi.fn();
vi.stubGlobal("fetch", fetchMock);
// When
const response = await GET();
const body = await parseHealthResponse(response);
// Then
expect(response.status).toBe(200);
expect(body).toEqual({
status: "healthy",
service: "prowler-ui",
});
expect(fetchMock).not.toHaveBeenCalled();
});
});
+14
View File
@@ -0,0 +1,14 @@
const healthResponse = {
status: "healthy",
service: "prowler-ui",
} as const;
export const dynamic = "force-dynamic";
export async function GET() {
return Response.json(healthResponse, {
headers: {
"Cache-Control": "no-store",
},
});
}