From 3705fb5ef516cf8665b13f9215470b0665a5ef3a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 23 Apr 2026 19:11:11 +0000 Subject: [PATCH] feat(ui): policies/policy_test_panel to shadcn - antd Alert/Tag/Empty/Typography + @tremor Button \u2192 shadcn Badge/ Button + plain inline bg-red-50 error callout + lucide AlertCircle/Info. antd Form/Form.Item/Select preserved (mode=tags + filterOption). - loadOptions wrapped in useCallback with full deps. Categorical emerald guardrail / blue matched-via badge; file added to .eslintrc overrides. Gates: TS \u2713 | Build \u2713. Co-authored-by: yuneng-jiang --- ui/litellm-dashboard/.eslintrc.json | 1 + .../components/policies/policy_test_panel.tsx | 166 ++++++++++++------ 2 files changed, 114 insertions(+), 53 deletions(-) diff --git a/ui/litellm-dashboard/.eslintrc.json b/ui/litellm-dashboard/.eslintrc.json index c1b974f1f0a..adc55a0e362 100644 --- a/ui/litellm-dashboard/.eslintrc.json +++ b/ui/litellm-dashboard/.eslintrc.json @@ -85,6 +85,7 @@ "src/components/AIHub/forms/MakeAgentPublicForm.tsx", "src/components/AIHub/forms/MakeMCPPublicForm.tsx", "src/components/AIHub/forms/MakeModelPublicForm.tsx", + "src/components/policies/policy_test_panel.tsx", "src/components/permissions/VectorStorePermissions.tsx", "src/components/permissions/AgentPermissions.tsx", "src/components/permissions/MCPServerPermissions.tsx", diff --git a/ui/litellm-dashboard/src/components/policies/policy_test_panel.tsx b/ui/litellm-dashboard/src/components/policies/policy_test_panel.tsx index 055c206ae44..c243a990c76 100644 --- a/ui/litellm-dashboard/src/components/policies/policy_test_panel.tsx +++ b/ui/litellm-dashboard/src/components/policies/policy_test_panel.tsx @@ -1,11 +1,17 @@ -import React, { useState, useEffect } from "react"; -import { Form, Select, Alert, Tag, Empty, Typography } from "antd"; -import { Button } from "@tremor/react"; -import { resolvePoliciesCall, teamListCall, keyListCall, modelAvailableCall } from "../networking"; +import React, { useCallback, useState, useEffect } from "react"; +import { Form, Select } from "antd"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; +import { AlertCircle, Info } from "lucide-react"; +import { + resolvePoliciesCall, + teamListCall, + keyListCall, + modelAvailableCall, +} from "../networking"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; -const { Text } = Typography; - interface PolicyTestPanelProps { accessToken: string | null; } @@ -31,46 +37,65 @@ const PolicyTestPanel: React.FC = ({ accessToken }) => { const [availableModels, setAvailableModels] = useState([]); const { userId, userRole } = useAuthorized(); - useEffect(() => { - if (accessToken) { - loadOptions(); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [accessToken]); - - const loadOptions = async () => { + const loadOptions = useCallback(async () => { if (!accessToken) return; try { const teamsResponse = await teamListCall(accessToken, null, userId); - const teamsArray = Array.isArray(teamsResponse) ? teamsResponse : (teamsResponse?.data || []); + const teamsArray = Array.isArray(teamsResponse) + ? teamsResponse + : teamsResponse?.data || []; setAvailableTeams( - teamsArray.map((t: any) => t.team_alias).filter(Boolean) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + teamsArray.map((t: any) => t.team_alias).filter(Boolean), ); } catch (error) { console.error("Failed to load teams:", error); } try { - const keysResponse = await keyListCall(accessToken, null, null, null, null, null, 1, 100); + const keysResponse = await keyListCall( + accessToken, + null, + null, + null, + null, + null, + 1, + 100, + ); const keysArray = keysResponse?.keys || keysResponse?.data || []; setAvailableKeys( - keysArray.map((k: any) => k.key_alias).filter(Boolean) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + keysArray.map((k: any) => k.key_alias).filter(Boolean), ); } catch (error) { console.error("Failed to load keys:", error); } try { - const modelsResponse = await modelAvailableCall(accessToken, userId || "", userRole || ""); - const modelsArray = modelsResponse?.data || (Array.isArray(modelsResponse) ? modelsResponse : []); + const modelsResponse = await modelAvailableCall( + accessToken, + userId || "", + userRole || "", + ); + const modelsArray = + modelsResponse?.data || + (Array.isArray(modelsResponse) ? modelsResponse : []); setAvailableModels( - modelsArray.map((m: any) => m.id || m.model_name).filter(Boolean) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + modelsArray.map((m: any) => m.id || m.model_name).filter(Boolean), ); } catch (error) { console.error("Failed to load models:", error); } - }; + }, [accessToken, userId, userRole]); + + useEffect(() => { + if (accessToken) { + loadOptions(); + } + }, [accessToken, loadOptions]); const handleTest = async () => { if (!accessToken) return; @@ -79,6 +104,7 @@ const PolicyTestPanel: React.FC = ({ accessToken }) => { setHasSearched(true); try { const values = form.getFieldsValue(true); + // eslint-disable-next-line @typescript-eslint/no-explicit-any const context: any = {}; if (values.team_alias) context.team_alias = values.team_alias; if (values.key_alias) context.key_alias = values.key_alias; @@ -103,12 +129,14 @@ const PolicyTestPanel: React.FC = ({ accessToken }) => { return (
-
+

Policy Simulator

- - Simulate a request to see which policies and guardrails would apply. Select a team, key, model, or tags below and click "Simulate" to see the results. - +

+ Simulate a request to see which policies and guardrails would + apply. Select a team, key, model, or tags below and click + "Simulate" to see the results. +

@@ -158,8 +186,11 @@ const PolicyTestPanel: React.FC = ({ accessToken }) => {
-
{!hasSearched && ( -
-
- - - +
+
+
-

No simulation run yet

-

- Fill in one or more fields above and click "Simulate" to see which policies and guardrails would apply to that request. +

+ No simulation run yet +

+

+ Fill in one or more fields above and click "Simulate" to + see which policies and guardrails would apply to that request.

)} {hasSearched && result && ( -
+
{result.matched_policies.length === 0 ? ( - +
+

No policies matched this context

+
) : ( <>
-

Effective Guardrails

+

+ Effective Guardrails +

{result.effective_guardrails.length > 0 ? ( result.effective_guardrails.map((g) => ( - {g} + + {g} + )) ) : ( - None + None )}
@@ -205,7 +246,7 @@ const PolicyTestPanel: React.FC = ({ accessToken }) => {

Matched Policies

- + @@ -213,20 +254,32 @@ const PolicyTestPanel: React.FC = ({ accessToken }) => { {result.matched_policies.map((p) => ( - - + + @@ -240,12 +293,19 @@ const PolicyTestPanel: React.FC = ({ accessToken }) => { )} {hasSearched && !result && !isLoading && ( - +
+ +
+
Error
+
+ Failed to resolve policies. Check the proxy logs. +
+
+
)} );
Policy Matched Via Guardrails Added
{p.policy_name}
+ {p.policy_name} + - {p.matched_via} + + {p.matched_via} + {p.guardrails_added.length > 0 ? (
{p.guardrails_added.map((g) => ( - {g} + + {g} + ))}
) : ( - None + None )}