diff --git a/ui/litellm-dashboard/eslint-suppressions.json b/ui/litellm-dashboard/eslint-suppressions.json
index 042570aff0a..063aee5f08f 100644
--- a/ui/litellm-dashboard/eslint-suppressions.json
+++ b/ui/litellm-dashboard/eslint-suppressions.json
@@ -321,9 +321,6 @@
"src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.tsx": {
"no-nested-ternary": {
"count": 5
- },
- "no-restricted-imports": {
- "count": 1
}
},
"src/app/(dashboard)/guardrails/_components/GuardrailTestPanel.tsx": {
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.test.tsx
index 9bfa71f77be..b616982d69b 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.test.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.test.tsx
@@ -1,4 +1,4 @@
-import { render, screen } from "@testing-library/react";
+import { render, screen, waitFor } from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import userEvent from "@testing-library/user-event";
import { beforeEach, describe, expect, it, vi } from "vitest";
@@ -14,7 +14,7 @@ vi.mock("./ScoreChart", () => ({
}));
vi.mock("./EvaluationSettingsModal", () => ({
- EvaluationSettingsModal: () => null,
+ EvaluationSettingsModal: ({ open }: { open: boolean }) => (open ?
Evaluation settings modal
: null),
}));
const mockGetGuardrailsUsageOverview = vi.mocked(networking.getGuardrailsUsageOverview);
@@ -28,6 +28,18 @@ function wrapper({ children }: { children: React.ReactNode }) {
return {children};
}
+function renderOverview(onSelectGuardrail = vi.fn()) {
+ return render(
+ ,
+ { wrapper },
+ );
+}
+
describe("GuardrailsOverview", () => {
beforeEach(() => {
vi.clearAllMocks();
@@ -92,4 +104,58 @@ describe("GuardrailsOverview", () => {
expect(onSelectGuardrail).toHaveBeenCalledWith("guardrail-low");
});
+
+ it("renders the page header and the export action", async () => {
+ renderOverview();
+
+ expect(await screen.findByRole("heading", { name: "Guardrails Monitor", level: 1 })).toBeInTheDocument();
+ expect(screen.getByText("Monitor guardrail performance across all requests")).toBeInTheDocument();
+ expect(screen.getByRole("button", { name: /Export Data/i })).toBeInTheDocument();
+ });
+
+ it("renders every summary metric card", async () => {
+ renderOverview();
+
+ expect(await screen.findByText("1,500")).toBeInTheDocument();
+ expect(screen.getByText("Total Evaluations")).toBeInTheDocument();
+ expect(screen.getByText("Blocked Requests")).toBeInTheDocument();
+ expect(screen.getByText("84")).toBeInTheDocument();
+ expect(screen.getByText("Pass Rate")).toBeInTheDocument();
+ expect(screen.getByText("94.4%")).toBeInTheDocument();
+ expect(screen.getByText("23ms")).toBeInTheDocument();
+ expect(screen.getByText("Active Guardrails")).toBeInTheDocument();
+ expect(screen.getByText("2")).toBeInTheDocument();
+ });
+
+ it("renders the table toolbar heading and its description", async () => {
+ renderOverview();
+
+ expect(await screen.findByRole("heading", { name: "Guardrail Performance", level: 5 })).toBeInTheDocument();
+ expect(screen.getByText("Click a guardrail to view details, logs, and configuration")).toBeInTheDocument();
+ });
+
+ it("opens the evaluation settings modal from the toolbar action", async () => {
+ const user = userEvent.setup();
+ renderOverview();
+
+ expect(screen.queryByText("Evaluation settings modal")).not.toBeInTheDocument();
+
+ await user.click(await screen.findByTitle("Evaluation settings"));
+
+ expect(await screen.findByText("Evaluation settings modal")).toBeInTheDocument();
+ });
+
+ it("marks the overview busy while the usage request is in flight", async () => {
+ mockGetGuardrailsUsageOverview.mockReturnValue(new Promise(() => {}));
+ renderOverview();
+
+ await waitFor(() => expect(document.querySelector('[aria-busy="true"]')).toBeInTheDocument());
+ });
+
+ it("shows a failure message when the usage request rejects", async () => {
+ mockGetGuardrailsUsageOverview.mockRejectedValue(new Error("network down"));
+ renderOverview();
+
+ expect(await screen.findByText("Failed to load data. Try again.")).toBeInTheDocument();
+ });
});
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.tsx b/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.tsx
index 8d45b4a4ee8..e1048c5322f 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.tsx
@@ -1,11 +1,12 @@
-import { DownloadOutlined, RiseOutlined, SafetyOutlined, SettingOutlined, WarningOutlined } from "@ant-design/icons";
import { useQuery } from "@tanstack/react-query";
import type { ColumnDef, OnChangeFn, SortingState } from "@tanstack/react-table";
-import { Button, Col, Row, Spin, Typography } from "antd";
+import { Download, Settings, Shield, TrendingUp, TriangleAlert } from "lucide-react";
import React, { useMemo, useState } from "react";
import { DataTable, DataTableSortHeader } from "@/components/shared/DataTable";
import { getGuardrailsUsageOverview } from "@/components/networking";
import { type PerformanceRow } from "@/components/GuardrailsMonitor/mockData";
+import { Button } from "@/components/ui/button";
+import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner";
import { EvaluationSettingsModal } from "./EvaluationSettingsModal";
import { MetricCard } from "@/components/GuardrailsMonitor/MetricCard";
import { ScoreChart } from "./ScoreChart";
@@ -197,51 +198,42 @@ export function GuardrailsOverview({
-
+
Guardrails Monitor
Monitor guardrail performance across all requests
- } title="Coming soon">
+
-
-
-
-
-
- }
- />
-
-
- }
- />
-
-
- 150 ? "text-red-600" : metrics.avgLatency > 50 ? "text-amber-600" : "text-green-600"
- }
- />
-
-
-
-
-
+
+
+ }
+ />
+ }
+ />
+ 150 ? "text-red-600" : metrics.avgLatency > 50 ? "text-amber-600" : "text-green-600"
+ }
+ />
+
+
@@ -250,7 +242,11 @@ export function GuardrailsOverview({
{(isLoading || error) && (
- {isLoading && }
+ {isLoading && (
+
+
+
+ )}
{error && Failed to load data. Try again.}
)}
@@ -270,20 +266,20 @@ export function GuardrailsOverview({
toolbar={() => (
-
- Guardrail Performance
-
+
Guardrail Performance
Click a guardrail to view details, logs, and configuration
}
+ variant="outline"
+ size="icon"
onClick={() => setEvaluationModalOpen(true)}
title="Evaluation settings"
- />
+ >
+
+
)}