From dcf91ef252cbe45d41c2ad86656f672c42a81df5 Mon Sep 17 00:00:00 2001 From: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com> Date: Thu, 14 May 2026 13:47:48 +0200 Subject: [PATCH] feat(ui): add health check endpoint (#11145) --- docker-compose.yml | 8 +++- ui/CHANGELOG.md | 1 + ui/app/api/health/route.test.ts | 69 +++++++++++++++++++++++++++++++++ ui/app/api/health/route.ts | 14 +++++++ 4 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 ui/app/api/health/route.test.ts create mode 100644 ui/app/api/health/route.ts diff --git a/docker-compose.yml b/docker-compose.yml index 5a4514546a..210eac093f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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 diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md index bfd3863f78..2cf613fa60 100644 --- a/ui/CHANGELOG.md +++ b/ui/CHANGELOG.md @@ -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 diff --git a/ui/app/api/health/route.test.ts b/ui/app/api/health/route.test.ts new file mode 100644 index 0000000000..4f893c2574 --- /dev/null +++ b/ui/app/api/health/route.test.ts @@ -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(); + }); +}); diff --git a/ui/app/api/health/route.ts b/ui/app/api/health/route.ts new file mode 100644 index 0000000000..0b6730dd1f --- /dev/null +++ b/ui/app/api/health/route.ts @@ -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", + }, + }); +}