diff --git a/ui/litellm-dashboard/eslint-suppressions.json b/ui/litellm-dashboard/eslint-suppressions.json index c6f600d90ec..25778401efa 100644 --- a/ui/litellm-dashboard/eslint-suppressions.json +++ b/ui/litellm-dashboard/eslint-suppressions.json @@ -2055,9 +2055,6 @@ "src/components/GuardrailSettingsView.tsx": { "no-nested-ternary": { "count": 1 - }, - "no-restricted-imports": { - "count": 1 } }, "src/components/GuardrailsMonitor/LogViewer.tsx": { @@ -2649,11 +2646,6 @@ "count": 1 } }, - "src/components/common_components/DurationSelect.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "src/components/common_components/Filters/FilterInput.tsx": { "react-hooks/set-state-in-effect": { "count": 1 diff --git a/ui/litellm-dashboard/src/components/GuardrailSettingsView.test.tsx b/ui/litellm-dashboard/src/components/GuardrailSettingsView.test.tsx new file mode 100644 index 00000000000..1c206b9c3b7 --- /dev/null +++ b/ui/litellm-dashboard/src/components/GuardrailSettingsView.test.tsx @@ -0,0 +1,31 @@ +import { renderWithProviders, screen } from "../../tests/test-utils"; +import { describe, expect, it } from "vitest"; +import GuardrailSettingsView from "./GuardrailSettingsView"; + +describe("GuardrailSettingsView", () => { + it("should render", () => { + renderWithProviders(); + + expect(screen.getByText("Guardrails Settings")).toBeInTheDocument(); + }); + + it("should separate active global and team-specific guardrails", () => { + renderWithProviders( + , + ); + + expect(screen.getByText("global-one")).toBeInTheDocument(); + expect(screen.getByText("team-one")).toBeInTheDocument(); + expect(screen.queryByText("global-two")).not.toBeInTheDocument(); + }); + + it("should show when global guardrails are bypassed", () => { + renderWithProviders(); + + expect(screen.getByText("Bypassed for this team")).toBeInTheDocument(); + }); +}); diff --git a/ui/litellm-dashboard/src/components/GuardrailSettingsView.tsx b/ui/litellm-dashboard/src/components/GuardrailSettingsView.tsx index 95957e845d9..5510eb62bdb 100644 --- a/ui/litellm-dashboard/src/components/GuardrailSettingsView.tsx +++ b/ui/litellm-dashboard/src/components/GuardrailSettingsView.tsx @@ -1,6 +1,8 @@ import React from "react"; -import { Tag } from "antd"; -import { GlobalOutlined } from "@ant-design/icons"; +import { Globe2 } from "lucide-react"; +import { Badge } from "@/components/ui/badge"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { cn } from "@/lib/cva.config"; interface GuardrailSettingsViewProps { globalGuardrailNames: Set; @@ -26,40 +28,36 @@ export function GuardrailSettingsView({ const isEmpty = !killSwitchOn && globalsRunning.length === 0 && nonGlobalOptIns.length === 0; const content = isEmpty ? ( - No guardrails configured + No guardrails configured ) : (
- - + + Global {killSwitchOn ? ( - Bypassed for this team + Bypassed for this team ) : globalsRunning.length > 0 ? (
{globalsRunning.map((name) => ( - - {name} - + {name} ))}
) : ( - None configured + None configured )}
- Team-specific + Team-specific {nonGlobalOptIns.length > 0 ? (
{nonGlobalOptIns.map((name) => ( - - {name} - + {name} ))}
) : ( - None configured + None configured )}
@@ -67,23 +65,19 @@ export function GuardrailSettingsView({ if (variant === "card") { return ( -
-
-
- Guardrails Settings - - Global and team-specific guardrails applied to this team - -
-
- {content} -
+ + + Guardrails Settings + Global and team-specific guardrails applied to this team + + {content} + ); } return ( -
- Guardrails Settings +
+ Guardrails Settings {content}
); diff --git a/ui/litellm-dashboard/src/components/common_components/DurationSelect.test.tsx b/ui/litellm-dashboard/src/components/common_components/DurationSelect.test.tsx index 296ef1ae632..bce1093d211 100644 --- a/ui/litellm-dashboard/src/components/common_components/DurationSelect.test.tsx +++ b/ui/litellm-dashboard/src/components/common_components/DurationSelect.test.tsx @@ -1,5 +1,5 @@ import { render, screen } from "@testing-library/react"; -import userEvent from "@testing-library/user-event"; +import userEvent, { PointerEventsCheckLevel } from "@testing-library/user-event"; import { describe, it, expect, vi } from "vitest"; import DurationSelect from "./DurationSelect"; @@ -19,6 +19,9 @@ describe("DurationSelect", () => { expect(screen.getByText("Daily")).toBeInTheDocument(); expect(screen.getByText("Weekly")).toBeInTheDocument(); expect(screen.getByText("Monthly")).toBeInTheDocument(); + const dailyLabel = screen.getByText("Daily"); + const dailyOption = dailyLabel.closest('[role="option"]') ?? dailyLabel; + await user.click(dailyOption); }); it("should apply className prop", () => { @@ -28,14 +31,15 @@ describe("DurationSelect", () => { }); it("should call onChange when an option is selected", async () => { - const user = userEvent.setup(); + const user = userEvent.setup({ pointerEventsCheck: PointerEventsCheckLevel.Never }); const onChange = vi.fn(); render(); const select = screen.getByRole("combobox"); await user.click(select); - const dailyOption = screen.getByText("Daily"); + const dailyLabel = screen.getByText("Daily"); + const dailyOption = dailyLabel.closest('[role="option"]') ?? dailyLabel; await user.click(dailyOption); expect(onChange).toHaveBeenCalledWith("24h", expect.any(Object)); diff --git a/ui/litellm-dashboard/src/components/common_components/DurationSelect.tsx b/ui/litellm-dashboard/src/components/common_components/DurationSelect.tsx index a84e8aeb110..cd5f6f4ffdc 100644 --- a/ui/litellm-dashboard/src/components/common_components/DurationSelect.tsx +++ b/ui/litellm-dashboard/src/components/common_components/DurationSelect.tsx @@ -1,17 +1,38 @@ -import { Select } from "antd"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; interface DurationSelectProps { className?: string; value?: string; - onChange?: (value: string) => void; + onChange?: (value: string, option: { value: string; label: string }) => void; } +const DURATION_OPTIONS = [ + { value: "24h", label: "Daily" }, + { value: "7d", label: "Weekly" }, + { value: "30d", label: "Monthly" }, +]; + export default function DurationSelect({ className, value, onChange }: DurationSelectProps) { return ( - { + const selectedOption = DURATION_OPTIONS.find((option) => option.value === nextValue); + if (selectedOption) { + onChange?.(selectedOption.value, selectedOption); + } + }} + > + + + + + {DURATION_OPTIONS.map((option) => ( + + {option.label} + + ))} + ); }