From c23b2c502333236eb1ca6e8bb1aa6e66fc972726 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 12 Nov 2025 18:23:31 -0800 Subject: [PATCH] Config Guardrails should not be deletable from table (#16540) --- .../src/components/guardrails.tsx | 6 +- .../guardrails/guardrail_table.test.tsx | 56 ++++++++++++++++++ .../components/guardrails/guardrail_table.tsx | 58 +++++++++---------- .../guardrails/pii_components.test.tsx | 40 +++++++++++++ .../components/guardrails/pii_components.tsx | 2 + .../guardrails/pii_configuration.test.tsx | 20 +++++++ .../guardrails/pii_configuration.tsx | 15 ++--- .../src/components/guardrails/types.ts | 6 ++ 8 files changed, 161 insertions(+), 42 deletions(-) create mode 100644 ui/litellm-dashboard/src/components/guardrails/guardrail_table.test.tsx create mode 100644 ui/litellm-dashboard/src/components/guardrails/pii_components.test.tsx create mode 100644 ui/litellm-dashboard/src/components/guardrails/pii_configuration.test.tsx diff --git a/ui/litellm-dashboard/src/components/guardrails.tsx b/ui/litellm-dashboard/src/components/guardrails.tsx index 23aec34c40..3861545f8c 100644 --- a/ui/litellm-dashboard/src/components/guardrails.tsx +++ b/ui/litellm-dashboard/src/components/guardrails.tsx @@ -8,6 +8,7 @@ import { isAdminRole } from "@/utils/roles"; import GuardrailInfoView from "./guardrails/guardrail_info"; import GuardrailTestPlayground from "./guardrails/GuardrailTestPlayground"; import NotificationsManager from "./molecules/notifications_manager"; +import { Guardrail, GuardrailDefinitionLocation } from "./guardrails/types"; interface GuardrailsPanelProps { accessToken: string | null; @@ -25,14 +26,15 @@ interface GuardrailItem { guardrail_info: Record | null; created_at?: string; updated_at?: string; + guardrail_definition_location: GuardrailDefinitionLocation; } interface GuardrailsResponse { - guardrails: GuardrailItem[]; + guardrails: Guardrail[]; } const GuardrailsPanel: React.FC = ({ accessToken, userRole }) => { - const [guardrailsList, setGuardrailsList] = useState([]); + const [guardrailsList, setGuardrailsList] = useState([]); const [isAddModalVisible, setIsAddModalVisible] = useState(false); const [isLoading, setIsLoading] = useState(false); const [isDeleting, setIsDeleting] = useState(false); diff --git a/ui/litellm-dashboard/src/components/guardrails/guardrail_table.test.tsx b/ui/litellm-dashboard/src/components/guardrails/guardrail_table.test.tsx new file mode 100644 index 0000000000..dcc6767735 --- /dev/null +++ b/ui/litellm-dashboard/src/components/guardrails/guardrail_table.test.tsx @@ -0,0 +1,56 @@ +import GuardrailTable from "./guardrail_table"; +import { render } from "@testing-library/react"; +import { describe, it, expect } from "vitest"; +import { GuardrailDefinitionLocation } from "./types"; +describe("GuardrailTable", () => { + it("should render", () => { + const { getByText } = render( + {}} + accessToken={null} + onGuardrailUpdated={() => {}} + onGuardrailClick={() => {}} + />, + ); + expect(getByText("Guardrail ID")).toBeInTheDocument(); + expect(getByText("Name")).toBeInTheDocument(); + expect(getByText("Provider")).toBeInTheDocument(); + expect(getByText("Mode")).toBeInTheDocument(); + expect(getByText("Default On")).toBeInTheDocument(); + expect(getByText("Created At")).toBeInTheDocument(); + expect(getByText("Updated At")).toBeInTheDocument(); + }); + + it("should not allow deletion of config guardrails", () => { + const { getByTestId } = render( + {}} + accessToken={null} + onGuardrailUpdated={() => {}} + onGuardrailClick={() => {}} + />, + ); + + const deleteGuardrailButton = getByTestId("config-delete-icon"); + expect(deleteGuardrailButton).toBeInTheDocument(); + expect(deleteGuardrailButton).toHaveClass("cursor-not-allowed text-gray-400"); + expect(deleteGuardrailButton).toHaveAttribute( + "title", + "Config guardrail cannot be deleted on the dashboard. Please delete it from the config file.", + ); + }); +}); diff --git a/ui/litellm-dashboard/src/components/guardrails/guardrail_table.tsx b/ui/litellm-dashboard/src/components/guardrails/guardrail_table.tsx index 4857cdd31e..6a2476960f 100644 --- a/ui/litellm-dashboard/src/components/guardrails/guardrail_table.tsx +++ b/ui/litellm-dashboard/src/components/guardrails/guardrail_table.tsx @@ -13,24 +13,10 @@ import { } from "@tanstack/react-table"; import { getGuardrailLogoAndName, guardrail_provider_map } from "./guardrail_info_helpers"; import EditGuardrailForm from "./edit_guardrail_form"; - -interface GuardrailItem { - guardrail_id?: string; - guardrail_name: string | null; - litellm_params: { - guardrail: string; - mode: string; - default_on: boolean; - pii_entities_config?: { [key: string]: string }; - [key: string]: any; - }; - guardrail_info: Record | null; - created_at?: string; - updated_at?: string; -} +import { Guardrail, GuardrailDefinitionLocation } from "./types"; interface GuardrailTableProps { - guardrailsList: GuardrailItem[]; + guardrailsList: Guardrail[]; isLoading: boolean; onDeleteClick: (guardrailId: string, guardrailName: string) => void; accessToken: string | null; @@ -50,7 +36,7 @@ const GuardrailTable: React.FC = ({ }) => { const [sorting, setSorting] = useState([{ id: "created_at", desc: true }]); const [editModalVisible, setEditModalVisible] = useState(false); - const [selectedGuardrail, setSelectedGuardrail] = useState(null); + const [selectedGuardrail, setSelectedGuardrail] = useState(null); // Format date helper function const formatDate = (dateString?: string) => { @@ -59,7 +45,7 @@ const GuardrailTable: React.FC = ({ return date.toLocaleString(); }; - const handleEditClick = (guardrail: GuardrailItem) => { + const handleEditClick = (guardrail: Guardrail) => { setSelectedGuardrail(guardrail); setEditModalVisible(true); }; @@ -70,7 +56,7 @@ const GuardrailTable: React.FC = ({ onGuardrailUpdated(); }; - const columns: ColumnDef[] = [ + const columns: ColumnDef[] = [ { header: "Guardrail ID", accessorKey: "guardrail_id", @@ -176,18 +162,32 @@ const GuardrailTable: React.FC = ({ header: "", cell: ({ row }) => { const guardrail = row.original; + const isConfigGuardrail = guardrail.guardrail_definition_location === GuardrailDefinitionLocation.CONFIG; return (
- - guardrail.guardrail_id && - onDeleteClick(guardrail.guardrail_id, guardrail.guardrail_name || "Unnamed Guardrail") - } - className="cursor-pointer hover:text-red-500" - tooltip="Delete guardrail" - /> + {isConfigGuardrail ? ( + + + + ) : ( + + guardrail.guardrail_id && + onDeleteClick(guardrail.guardrail_id, guardrail.guardrail_name || "Unnamed Guardrail") + } + className="cursor-pointer hover:text-red-500" + tooltip="Delete guardrail" + /> + )}
); }, diff --git a/ui/litellm-dashboard/src/components/guardrails/pii_components.test.tsx b/ui/litellm-dashboard/src/components/guardrails/pii_components.test.tsx new file mode 100644 index 0000000000..3accbf13bd --- /dev/null +++ b/ui/litellm-dashboard/src/components/guardrails/pii_components.test.tsx @@ -0,0 +1,40 @@ +import { render } from "@testing-library/react"; +import { describe, it, expect } from "vitest"; +import { CategoryFilter, QuickActions, PiiEntityList } from "./pii_components"; +import type { PiiEntityCategory } from "./types"; + +describe("CategoryFilter", () => { + it("should render", () => { + const emptyCategories: PiiEntityCategory[] = []; + const { getByText } = render( + {}} />, + ); + expect(getByText("Filter by category")).toBeInTheDocument(); + }); +}); + +describe("QuickActions", () => { + it("should render", () => { + const { getByText } = render( + {}} onUnselectAll={() => {}} hasSelectedEntities={false} />, + ); + expect(getByText("Quick Actions")).toBeInTheDocument(); + }); +}); + +describe("PiiEntityList", () => { + it("should render", () => { + const { getByText } = render( + {}} + onActionSelect={() => {}} + entityToCategoryMap={new Map()} + />, + ); + expect(getByText("No PII types match your filter criteria")).toBeInTheDocument(); + }); +}); diff --git a/ui/litellm-dashboard/src/components/guardrails/pii_components.tsx b/ui/litellm-dashboard/src/components/guardrails/pii_components.tsx index 1b744cad31..e3b3926b99 100644 --- a/ui/litellm-dashboard/src/components/guardrails/pii_components.tsx +++ b/ui/litellm-dashboard/src/components/guardrails/pii_components.tsx @@ -83,6 +83,7 @@ export const QuickActions: React.FC = ({ onSelectAll, onUnsel