diff --git a/ui/litellm-dashboard/.eslintrc.json b/ui/litellm-dashboard/.eslintrc.json
index 19a56de12d3..8648c7a1ff1 100644
--- a/ui/litellm-dashboard/.eslintrc.json
+++ b/ui/litellm-dashboard/.eslintrc.json
@@ -57,6 +57,7 @@
"src/components/pass_through_settings.tsx",
"src/components/policies/attachment_table.tsx",
"src/components/claude_code_plugins/plugin_table.tsx",
+ "src/components/policies/policy_table.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_table.tsx b/ui/litellm-dashboard/src/components/policies/policy_table.tsx
index 9a3796c2395..8d5c47d4f2b 100644
--- a/ui/litellm-dashboard/src/components/policies/policy_table.tsx
+++ b/ui/litellm-dashboard/src/components/policies/policy_table.tsx
@@ -1,7 +1,28 @@
import React, { useMemo, useState } from "react";
-import { Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow, Icon, Button, Badge } from "@tremor/react";
-import { TrashIcon, PencilIcon, SwitchVerticalIcon, ChevronUpIcon, ChevronDownIcon } from "@heroicons/react/outline";
-import { Tooltip, Tag } from "antd";
+// eslint-disable-next-line litellm-ui/no-banned-ui-imports
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeaderCell,
+ TableRow,
+} from "@tremor/react";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@/components/ui/tooltip";
+import {
+ ArrowUpDown,
+ ChevronDown,
+ ChevronUp,
+ Pencil,
+ Trash2,
+} from "lucide-react";
import {
ColumnDef,
flexRender,
@@ -12,7 +33,6 @@ import {
} from "@tanstack/react-table";
import { Policy } from "./types";
-/** One row per policy name; primaryPolicy is used for display and for Edit (FlowBuilder loads all versions) */
interface PolicyRow {
policy_name: string;
primaryPolicy: Policy;
@@ -28,12 +48,17 @@ function groupPoliciesByName(policies: Policy[]): PolicyRow[] {
}
const rows: PolicyRow[] = [];
for (const [policyName, versions] of byName) {
- // Prefer production, then highest version_number
const primary =
versions.find((v) => v.version_status === "production") ??
- [...versions].sort((a, b) => (b.version_number ?? 0) - (a.version_number ?? 0))[0] ??
+ [...versions].sort(
+ (a, b) => (b.version_number ?? 0) - (a.version_number ?? 0),
+ )[0] ??
versions[0];
- rows.push({ policy_name: policyName, primaryPolicy: primary, versionCount: versions.length });
+ rows.push({
+ policy_name: policyName,
+ primaryPolicy: primary,
+ versionCount: versions.length,
+ });
}
return rows.sort((a, b) => a.policy_name.localeCompare(b.policy_name));
}
@@ -47,6 +72,32 @@ interface PolicyTableProps {
isAdmin?: boolean;
}
+const chipList = (items: string[], classes: string) => {
+ if (items.length === 0)
+ return -;
+ return (
+
+ {items.slice(0, 2).map((g, i) => (
+
+ {g}
+
+ ))}
+ {items.length > 2 && (
+
+
+
+
+ +{items.length - 2}
+
+
+ {items.slice(2).join(", ")}
+
+
+ )}
+
+ );
+};
+
const PolicyTable: React.FC = ({
policies,
isLoading,
@@ -55,7 +106,9 @@ const PolicyTable: React.FC = ({
onViewClick,
isAdmin = false,
}) => {
- const [sorting, setSorting] = useState([{ id: "policy_name", desc: false }]);
+ const [sorting, setSorting] = useState([
+ { id: "policy_name", desc: false },
+ ]);
const rows = useMemo(() => groupPoliciesByName(policies), [policies]);
@@ -73,18 +126,28 @@ const PolicyTable: React.FC = ({
const { primaryPolicy, versionCount } = row.original;
return (
-
1 ? ` (${versionCount} versions)` : ""}`}>
-
-
+
+
+
+
+
+
+ {primaryPolicy.policy_name || "-"}
+ {versionCount > 1 ? ` (${versionCount} versions)` : ""}
+
+
+
{versionCount > 1 && (
-
+
{versionCount} version{versionCount !== 1 ? "s" : ""}
)}
@@ -98,11 +161,16 @@ const PolicyTable: React.FC = ({
cell: ({ row }) => {
const policy = row.original.primaryPolicy;
return (
-
-
- {policy.description || "-"}
-
-
+
+
+
+
+ {policy.description || "-"}
+
+
+ {policy.description || "-"}
+
+
);
},
},
@@ -112,63 +180,32 @@ const PolicyTable: React.FC = ({
cell: ({ row }) => {
const policy = row.original.primaryPolicy;
return policy.inherit ? (
-
+
{policy.inherit}
) : (
- -
+ -
);
},
},
{
header: "Guardrails (Add)",
accessorFn: (row) => (row.primaryPolicy.guardrails_add ?? []).join(", "),
- cell: ({ row }) => {
- const policy = row.original.primaryPolicy;
- const guardrails = policy.guardrails_add || [];
- if (guardrails.length === 0) {
- return -;
- }
- return (
-
- {guardrails.slice(0, 2).map((g, i) => (
-
- {g}
-
- ))}
- {guardrails.length > 2 && (
-
- +{guardrails.length - 2}
-
- )}
-
- );
- },
+ cell: ({ row }) =>
+ chipList(
+ row.original.primaryPolicy.guardrails_add || [],
+ "bg-emerald-100 text-emerald-700 dark:bg-emerald-950 dark:text-emerald-300",
+ ),
},
{
header: "Guardrails (Remove)",
- accessorFn: (row) => (row.primaryPolicy.guardrails_remove ?? []).join(", "),
- cell: ({ row }) => {
- const policy = row.original.primaryPolicy;
- const guardrails = policy.guardrails_remove || [];
- if (guardrails.length === 0) {
- return -;
- }
- return (
-
- {guardrails.slice(0, 2).map((g, i) => (
-
- {g}
-
- ))}
- {guardrails.length > 2 && (
-
- +{guardrails.length - 2}
-
- )}
-
- );
- },
+ accessorFn: (row) =>
+ (row.primaryPolicy.guardrails_remove ?? []).join(", "),
+ cell: ({ row }) =>
+ chipList(
+ row.original.primaryPolicy.guardrails_remove || [],
+ "bg-red-100 text-red-700 dark:bg-red-950 dark:text-red-300",
+ ),
},
{
header: "Model Condition",
@@ -180,18 +217,27 @@ const PolicyTable: React.FC = ({
const policy = row.original.primaryPolicy;
const modelCondition = policy.condition?.model;
if (!modelCondition) {
- return -;
+ return -;
}
+ const asString =
+ typeof modelCondition === "string"
+ ? modelCondition
+ : JSON.stringify(modelCondition);
return (
-
-
- {typeof modelCondition === "string"
- ? modelCondition.length > 20
- ? modelCondition.slice(0, 20) + "..."
- : modelCondition
- : "Multiple"}
-
-
+
+
+
+
+ {typeof modelCondition === "string"
+ ? modelCondition.length > 20
+ ? modelCondition.slice(0, 20) + "..."
+ : modelCondition
+ : "Multiple"}
+
+
+ {asString}
+
+
);
},
},
@@ -202,9 +248,14 @@ const PolicyTable: React.FC = ({
cell: ({ row }) => {
const policy = row.original.primaryPolicy;
return (
-
- {formatDate(policy.created_at)}
-
+
+
+
+ {formatDate(policy.created_at)}
+
+ {policy.created_at}
+
+
);
},
},
@@ -212,31 +263,49 @@ const PolicyTable: React.FC = ({
id: "actions",
header: "Actions",
cell: ({ row }) => {
- const { primaryPolicy } = row.original;
- const policy = primaryPolicy;
+ const policy = row.original.primaryPolicy;
return (
-
+
{isAdmin && (
<>
-
- onEditClick(policy)}
- className="cursor-pointer hover:text-blue-500"
- />
-
-
-
- policy.policy_id &&
- onDeleteClick(policy.policy_id, policy.policy_name || "Unnamed Policy")
- }
- className="cursor-pointer hover:text-red-500"
- />
-
+
+
+
+
+
+ Edit policy
+
+
+
+
+
+
+
+ Delete policy
+
+
>
)}
@@ -248,9 +317,7 @@ const PolicyTable: React.FC
= ({
const table = useReactTable({
data: rows,
columns,
- state: {
- sorting,
- },
+ state: { sorting },
onSortingChange: setSorting,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
@@ -268,23 +335,34 @@ const PolicyTable: React.FC = ({
- {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
+ {header.isPlaceholder
+ ? null
+ : flexRender(
+ header.column.columnDef.header,
+ header.getContext(),
+ )}
{header.id !== "actions" && (
{header.column.getIsSorted() ? (
{
- asc:
,
- desc:
,
+ asc: (
+
+ ),
+ desc: (
+
+ ),
}[header.column.getIsSorted() as string]
) : (
-
+
)}
)}
@@ -297,8 +375,11 @@ const PolicyTable: React.FC
= ({
{isLoading ? (
-
-
+
+
@@ -311,19 +392,25 @@ const PolicyTable: React.FC
= ({
key={cell.id}
className={`py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap ${
cell.column.id === "actions"
- ? "sticky right-0 bg-white shadow-[-4px_0_8px_-6px_rgba(0,0,0,0.1)]"
+ ? "sticky right-0 bg-background shadow-[-4px_0_8px_-6px_rgba(0,0,0,0.1)]"
: ""
}`}
>
- {flexRender(cell.column.columnDef.cell, cell.getContext())}
+ {flexRender(
+ cell.column.columnDef.cell,
+ cell.getContext(),
+ )}
))}
))
) : (
-
-