mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
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 <yuneng-berri@users.noreply.github.com>
This commit is contained in:
parent
8b0dfae348
commit
3705fb5ef5
2 changed files with 114 additions and 53 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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<PolicyTestPanelProps> = ({ accessToken }) => {
|
|||
const [availableModels, setAvailableModels] = useState<string[]>([]);
|
||||
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<PolicyTestPanelProps> = ({ 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<PolicyTestPanelProps> = ({ accessToken }) => {
|
|||
|
||||
return (
|
||||
<div>
|
||||
<div className="bg-white border rounded-lg p-6 mb-6">
|
||||
<div className="bg-background border border-border rounded-lg p-6 mb-6">
|
||||
<div className="mb-5">
|
||||
<h3 className="text-base font-semibold mb-1">Policy Simulator</h3>
|
||||
<Text type="secondary">
|
||||
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.
|
||||
</Text>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Form form={form} layout="vertical">
|
||||
|
|
@ -158,8 +186,11 @@ const PolicyTestPanel: React.FC<PolicyTestPanelProps> = ({ accessToken }) => {
|
|||
</Form.Item>
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<Button onClick={handleTest} loading={isLoading} disabled={!accessToken}>
|
||||
Simulate
|
||||
<Button
|
||||
onClick={handleTest}
|
||||
disabled={!accessToken || isLoading}
|
||||
>
|
||||
{isLoading ? "Simulating..." : "Simulate"}
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={handleReset}>
|
||||
Reset
|
||||
|
|
@ -169,34 +200,44 @@ const PolicyTestPanel: React.FC<PolicyTestPanelProps> = ({ accessToken }) => {
|
|||
</div>
|
||||
|
||||
{!hasSearched && (
|
||||
<div className="bg-white border rounded-lg p-8 text-center">
|
||||
<div className="text-gray-400 mb-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-10 w-10 mx-auto mb-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4" />
|
||||
</svg>
|
||||
<div className="bg-background border border-border rounded-lg p-8 text-center">
|
||||
<div className="text-muted-foreground/80 mb-2">
|
||||
<Info className="h-10 w-10 mx-auto mb-3" />
|
||||
</div>
|
||||
<p className="text-sm font-medium text-gray-600 mb-1">No simulation run yet</p>
|
||||
<p className="text-xs text-gray-400">
|
||||
Fill in one or more fields above and click "Simulate" to see which policies and guardrails would apply to that request.
|
||||
<p className="text-sm font-medium text-muted-foreground mb-1">
|
||||
No simulation run yet
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground/80">
|
||||
Fill in one or more fields above and click "Simulate" to
|
||||
see which policies and guardrails would apply to that request.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasSearched && result && (
|
||||
<div className="bg-white border rounded-lg p-6">
|
||||
<div className="bg-background border border-border rounded-lg p-6">
|
||||
{result.matched_policies.length === 0 ? (
|
||||
<Empty description="No policies matched this context" />
|
||||
<div className="flex flex-col items-center justify-center py-8 text-muted-foreground">
|
||||
<p>No policies matched this context</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="mb-4">
|
||||
<p className="text-sm font-semibold mb-2">Effective Guardrails</p>
|
||||
<p className="text-sm font-semibold mb-2">
|
||||
Effective Guardrails
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{result.effective_guardrails.length > 0 ? (
|
||||
result.effective_guardrails.map((g) => (
|
||||
<Tag key={g} color="green">{g}</Tag>
|
||||
<Badge
|
||||
key={g}
|
||||
className="bg-emerald-100 text-emerald-700 dark:bg-emerald-950 dark:text-emerald-300"
|
||||
>
|
||||
{g}
|
||||
</Badge>
|
||||
))
|
||||
) : (
|
||||
<span className="text-gray-400 text-sm">None</span>
|
||||
<span className="text-muted-foreground text-sm">None</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -205,7 +246,7 @@ const PolicyTestPanel: React.FC<PolicyTestPanelProps> = ({ accessToken }) => {
|
|||
<p className="text-sm font-semibold mb-2">Matched Policies</p>
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b">
|
||||
<tr className="border-b border-border">
|
||||
<th className="text-left py-2 pr-4">Policy</th>
|
||||
<th className="text-left py-2 pr-4">Matched Via</th>
|
||||
<th className="text-left py-2">Guardrails Added</th>
|
||||
|
|
@ -213,20 +254,32 @@ const PolicyTestPanel: React.FC<PolicyTestPanelProps> = ({ accessToken }) => {
|
|||
</thead>
|
||||
<tbody>
|
||||
{result.matched_policies.map((p) => (
|
||||
<tr key={p.policy_name} className="border-b last:border-0">
|
||||
<td className="py-2 pr-4 font-medium">{p.policy_name}</td>
|
||||
<tr
|
||||
key={p.policy_name}
|
||||
className="border-b border-border last:border-0"
|
||||
>
|
||||
<td className="py-2 pr-4 font-medium">
|
||||
{p.policy_name}
|
||||
</td>
|
||||
<td className="py-2 pr-4">
|
||||
<Tag color="blue">{p.matched_via}</Tag>
|
||||
<Badge className="bg-blue-100 text-blue-700 dark:bg-blue-950 dark:text-blue-300">
|
||||
{p.matched_via}
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="py-2">
|
||||
{p.guardrails_added.length > 0 ? (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{p.guardrails_added.map((g) => (
|
||||
<Tag key={g} color="green">{g}</Tag>
|
||||
<Badge
|
||||
key={g}
|
||||
className="bg-emerald-100 text-emerald-700 dark:bg-emerald-950 dark:text-emerald-300"
|
||||
>
|
||||
{g}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-gray-400">None</span>
|
||||
<span className="text-muted-foreground">None</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -240,12 +293,19 @@ const PolicyTestPanel: React.FC<PolicyTestPanelProps> = ({ accessToken }) => {
|
|||
)}
|
||||
|
||||
{hasSearched && !result && !isLoading && (
|
||||
<Alert
|
||||
message="Error"
|
||||
description="Failed to resolve policies. Check the proxy logs."
|
||||
type="error"
|
||||
showIcon
|
||||
/>
|
||||
<div
|
||||
className={cn(
|
||||
"flex gap-2 items-start p-3 rounded-md bg-red-50 dark:bg-red-950/30 border border-red-200 dark:border-red-900 text-red-800 dark:text-red-200",
|
||||
)}
|
||||
>
|
||||
<AlertCircle className="h-4 w-4 mt-0.5 shrink-0" />
|
||||
<div>
|
||||
<div className="font-semibold">Error</div>
|
||||
<div className="text-sm">
|
||||
Failed to resolve policies. Check the proxy logs.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue