From 83b376bc92729de6754c60709d3a29c37ec0829b Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Wed, 4 Feb 2026 11:53:43 -0800 Subject: [PATCH] feat: new interface on guardrails to view guardrails by team --- .../src/app/(dashboard)/guardrails/page.tsx | 4 +- ui/litellm-dashboard/src/app/page.tsx | 2 +- .../src/components/guardrails.test.tsx | 2 + .../src/components/guardrails.tsx | 135 +++++++++++++++++- ui/litellm-dashboard/tsconfig.tsbuildinfo | 2 +- 5 files changed, 134 insertions(+), 11 deletions(-) diff --git a/ui/litellm-dashboard/src/app/(dashboard)/guardrails/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/guardrails/page.tsx index 50cee215eb9..0f26472d484 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/guardrails/page.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/guardrails/page.tsx @@ -4,9 +4,9 @@ import GuardrailsPanel from "@/components/guardrails"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; const GuardrailsPage = () => { - const { accessToken } = useAuthorized(); + const { accessToken, userId, userRole } = useAuthorized(); - return ; + return ; }; export default GuardrailsPage; diff --git a/ui/litellm-dashboard/src/app/page.tsx b/ui/litellm-dashboard/src/app/page.tsx index 5f94db7e9f4..d8a1c0f3250 100644 --- a/ui/litellm-dashboard/src/app/page.tsx +++ b/ui/litellm-dashboard/src/app/page.tsx @@ -484,7 +484,7 @@ function CreateKeyPageContent() { ) : page == "budgets" ? ( ) : page == "guardrails" ? ( - + ) : page == "policies" ? ( ) : page == "agents" ? ( diff --git a/ui/litellm-dashboard/src/components/guardrails.test.tsx b/ui/litellm-dashboard/src/components/guardrails.test.tsx index 8cafc18eb9a..48221d2ec6f 100644 --- a/ui/litellm-dashboard/src/components/guardrails.test.tsx +++ b/ui/litellm-dashboard/src/components/guardrails.test.tsx @@ -6,6 +6,7 @@ import { getGuardrailsList } from "./networking"; vi.mock("./networking", () => ({ getGuardrailsList: vi.fn(), deleteGuardrailCall: vi.fn(), + teamListCall: vi.fn().mockResolvedValue([]), })); vi.mock("./guardrails/add_guardrail_form", () => ({ @@ -71,6 +72,7 @@ describe("GuardrailsPanel", () => { const defaultProps = { accessToken: "test-token", userRole: "admin", + userID: "test-user-id", }; const mockGetGuardrailsList = vi.mocked(getGuardrailsList); diff --git a/ui/litellm-dashboard/src/components/guardrails.tsx b/ui/litellm-dashboard/src/components/guardrails.tsx index d0031b872f7..0add74cd598 100644 --- a/ui/litellm-dashboard/src/components/guardrails.tsx +++ b/ui/litellm-dashboard/src/components/guardrails.tsx @@ -1,8 +1,8 @@ -import React, { useState, useEffect } from "react"; -import { Button, TabGroup, TabList, Tab, TabPanels, TabPanel } from "@tremor/react"; -import { Dropdown } from "antd"; -import { DownOutlined, PlusOutlined, CodeOutlined } from "@ant-design/icons"; -import { getGuardrailsList, deleteGuardrailCall } from "./networking"; +import React, { useState, useEffect, useMemo, useCallback } from "react"; +import { Button, TabGroup, TabList, Tab, TabPanels, TabPanel, Text } from "@tremor/react"; +import { Dropdown, Select, Tooltip } from "antd"; +import { DownOutlined, PlusOutlined, CodeOutlined, QuestionCircleOutlined } from "@ant-design/icons"; +import { getGuardrailsList, deleteGuardrailCall, teamListCall } from "./networking"; import AddGuardrailForm from "./guardrails/add_guardrail_form"; import GuardrailTable from "./guardrails/guardrail_table"; import { isAdminRole } from "@/utils/roles"; @@ -14,9 +14,21 @@ import DeleteResourceModal from "./common_components/DeleteResourceModal"; import { getGuardrailLogoAndName } from "./guardrails/guardrail_info_helpers"; import { CustomCodeModal } from "./guardrails/custom_code"; +const { Option } = Select; + interface GuardrailsPanelProps { accessToken: string | null; userRole?: string; + userID?: string | null; +} + +interface Team { + team_id: string; + team_alias: string; + metadata?: { + guardrails?: string[]; + [key: string]: any; + }; } interface GuardrailItem { @@ -37,7 +49,7 @@ interface GuardrailsResponse { guardrails: Guardrail[]; } -const GuardrailsPanel: React.FC = ({ accessToken, userRole }) => { +const GuardrailsPanel: React.FC = ({ accessToken, userRole, userID }) => { const [guardrailsList, setGuardrailsList] = useState([]); const [isAddModalVisible, setIsAddModalVisible] = useState(false); const [isCustomCodeModalVisible, setIsCustomCodeModalVisible] = useState(false); @@ -47,6 +59,11 @@ const GuardrailsPanel: React.FC = ({ accessToken, userRole const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [selectedGuardrailId, setSelectedGuardrailId] = useState(null); const [activeTab, setActiveTab] = useState(0); + + // Team filtering state + const [teams, setTeams] = useState([]); + const [selectedTeam, setSelectedTeam] = useState("all"); + const [filteredGuardrails, setFilteredGuardrails] = useState([]); const isAdmin = userRole ? isAdminRole(userRole) : false; @@ -67,10 +84,69 @@ const GuardrailsPanel: React.FC = ({ accessToken, userRole } }; + const fetchTeams = async () => { + if (!accessToken) { + return; + } + + try { + const teamsResponse = await teamListCall(accessToken, null, isAdmin ? null : userID); + setTeams(teamsResponse || []); + } catch (error) { + console.error("Error fetching teams:", error); + } + }; + useEffect(() => { fetchGuardrails(); + fetchTeams(); }, [accessToken]); + // Get teams that have guardrails configured + const teamsWithGuardrails = useMemo(() => { + return teams.filter(team => + team.metadata?.guardrails && + Array.isArray(team.metadata.guardrails) && + team.metadata.guardrails.length > 0 + ); + }, [teams]); + + // Filter guardrails based on selected team + const filterGuardrails = useCallback((teamId: string) => { + if (!guardrailsList) { + setFilteredGuardrails([]); + return; + } + + if (teamId === "all") { + // Show all guardrails + setFilteredGuardrails(guardrailsList); + } else if (teamId === "global") { + // Show only guardrails with default_on = true (global guardrails) + setFilteredGuardrails(guardrailsList.filter(g => g.litellm_params?.default_on === true)); + } else { + // Show guardrails assigned to the selected team + const team = teams.find(t => t.team_id === teamId); + const teamGuardrailNames = team?.metadata?.guardrails || []; + setFilteredGuardrails( + guardrailsList.filter(g => + g.guardrail_name && teamGuardrailNames.includes(g.guardrail_name) + ) + ); + } + }, [guardrailsList, teams]); + + // Handle team filter change + const handleTeamChange = (teamId: string) => { + setSelectedTeam(teamId); + filterGuardrails(teamId); + }; + + // Initial and effect-based filtering + useEffect(() => { + filterGuardrails(selectedTeam); + }, [guardrailsList, selectedTeam, filterGuardrails]); + const handleAddGuardrail = () => { if (selectedGuardrailId) { setSelectedGuardrailId(null); @@ -132,6 +208,12 @@ const GuardrailsPanel: React.FC = ({ accessToken, userRole ? getGuardrailLogoAndName(guardrailToDelete.litellm_params.guardrail).displayName : undefined; + // Count guardrails for display + const globalGuardrailsCount = useMemo(() => + guardrailsList.filter(g => g.litellm_params?.default_on === true).length, + [guardrailsList] + ); + return (
@@ -169,6 +251,45 @@ const GuardrailsPanel: React.FC = ({ accessToken, userRole
+ {/* Team Filter Section */} +
+
+
+
+ Current Team: + +
+
+
+
+ {selectedGuardrailId ? ( = ({ accessToken, userRole /> ) : (