mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): policies/policy_templates to shadcn
- antd Card/Button/Spin/Checkbox + @heroicons ShieldCheck/ ShieldExclamation/Beaker/CurrencyDollar/CheckCircle \u2192 shadcn Card/Button/Checkbox + lucide Shield/ShieldAlert/Beaker/ DollarSign/CheckCircle/Loader2. - Kept iconMap contract (JSON templates reference 'ShieldCheckIcon' etc) but now maps to lucide equivalents. - Complexity badges keep categorical blue/purple palette; 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
bfc9d8f836
commit
12855c70da
2 changed files with 77 additions and 61 deletions
|
|
@ -69,6 +69,7 @@
|
|||
"src/components/model_add/credentials.tsx",
|
||||
"src/components/model_dashboard/health_check_columns.tsx",
|
||||
"src/components/UsagePage/components/EntityUsage/TopKeyView.tsx",
|
||||
"src/components/policies/policy_templates.tsx",
|
||||
"src/components/permissions/VectorStorePermissions.tsx",
|
||||
"src/components/permissions/AgentPermissions.tsx",
|
||||
"src/components/permissions/MCPServerPermissions.tsx",
|
||||
|
|
|
|||
|
|
@ -1,19 +1,23 @@
|
|||
import React, { useState, useEffect, useMemo } from "react";
|
||||
import { Card, Button, Spin, Checkbox } from "antd";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { cn } from "@/lib/utils";
|
||||
import MessageManager from "@/components/molecules/message_manager";
|
||||
import {
|
||||
ShieldCheckIcon,
|
||||
ShieldExclamationIcon,
|
||||
BeakerIcon,
|
||||
CurrencyDollarIcon,
|
||||
CheckCircleIcon,
|
||||
} from "@heroicons/react/outline";
|
||||
Beaker,
|
||||
CheckCircle,
|
||||
DollarSign,
|
||||
Loader2,
|
||||
Shield,
|
||||
ShieldAlert,
|
||||
} from "lucide-react";
|
||||
import { getPolicyTemplates } from "../networking";
|
||||
|
||||
interface PolicyTemplateCardProps {
|
||||
title: string;
|
||||
description: string;
|
||||
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
||||
icon: React.ComponentType<{ className?: string }>;
|
||||
iconColor: string;
|
||||
iconBg: string;
|
||||
guardrails: string[];
|
||||
|
|
@ -38,39 +42,41 @@ const PolicyTemplateCard: React.FC<PolicyTemplateCardProps> = ({
|
|||
const getComplexityStyle = () => {
|
||||
switch (complexity) {
|
||||
case "Low":
|
||||
return "bg-gray-50 text-gray-600 border-gray-200";
|
||||
return "bg-muted text-muted-foreground border-border";
|
||||
case "Medium":
|
||||
return "bg-blue-50 text-blue-600 border-blue-100";
|
||||
return "bg-blue-50 text-blue-600 border-blue-100 dark:bg-blue-950/30 dark:text-blue-300 dark:border-blue-900";
|
||||
case "High":
|
||||
return "bg-purple-50 text-purple-600 border-purple-100";
|
||||
return "bg-purple-50 text-purple-600 border-purple-100 dark:bg-purple-950/30 dark:text-purple-300 dark:border-purple-900";
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
className="h-full hover:shadow-md transition-shadow"
|
||||
bodyStyle={{ display: "flex", flexDirection: "column", height: "100%" }}
|
||||
>
|
||||
<Card className="h-full hover:shadow-md transition-shadow flex flex-col p-6">
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div className={`p-2 rounded-lg ${iconBg}`}>
|
||||
<Icon className={`h-6 w-6 ${iconColor}`} />
|
||||
<div className={cn("p-2 rounded-lg", iconBg)}>
|
||||
<Icon className={cn("h-6 w-6", iconColor)} />
|
||||
</div>
|
||||
<span
|
||||
className={`px-2.5 py-0.5 rounded-full text-xs font-medium border ${getComplexityStyle()}`}
|
||||
className={cn(
|
||||
"px-2.5 py-0.5 rounded-full text-xs font-medium border",
|
||||
getComplexityStyle(),
|
||||
)}
|
||||
>
|
||||
{complexity} Complexity
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h3 className="text-base font-semibold text-gray-900 mb-2">{title}</h3>
|
||||
<p className="text-sm text-gray-500 mb-4 flex-grow">{description}</p>
|
||||
<h3 className="text-base font-semibold text-foreground mb-2">{title}</h3>
|
||||
<p className="text-sm text-muted-foreground mb-4 flex-grow">
|
||||
{description}
|
||||
</p>
|
||||
|
||||
{tags.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1.5 mb-4">
|
||||
{tags.map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-50 text-blue-700 border border-blue-100"
|
||||
className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-50 text-blue-700 border border-blue-100 dark:bg-blue-950/30 dark:text-blue-300 dark:border-blue-900"
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
|
|
@ -80,22 +86,22 @@ const PolicyTemplateCard: React.FC<PolicyTemplateCardProps> = ({
|
|||
|
||||
{inherits && (
|
||||
<div className="mb-4 text-xs">
|
||||
<span className="text-gray-500">Inherits from: </span>
|
||||
<span className="font-medium text-gray-700 bg-gray-100 px-2 py-0.5 rounded">
|
||||
<span className="text-muted-foreground">Inherits from: </span>
|
||||
<span className="font-medium text-foreground bg-muted px-2 py-0.5 rounded">
|
||||
{inherits}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mb-6">
|
||||
<span className="text-xs font-medium text-gray-500 uppercase tracking-wider block mb-2">
|
||||
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wider block mb-2">
|
||||
Included Guardrails
|
||||
</span>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{guardrails.map((g) => (
|
||||
<span
|
||||
key={g}
|
||||
className="inline-flex items-center px-2 py-1 rounded text-xs font-medium bg-gray-50 text-gray-700 border border-gray-200"
|
||||
className="inline-flex items-center px-2 py-1 rounded text-xs font-medium bg-muted text-foreground border border-border"
|
||||
>
|
||||
{g}
|
||||
</span>
|
||||
|
|
@ -103,12 +109,7 @@ const PolicyTemplateCard: React.FC<PolicyTemplateCardProps> = ({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="primary"
|
||||
block
|
||||
className="mt-auto"
|
||||
onClick={onUseTemplate}
|
||||
>
|
||||
<Button className="mt-auto w-full" onClick={onUseTemplate}>
|
||||
Use Template
|
||||
</Button>
|
||||
</Card>
|
||||
|
|
@ -116,27 +117,34 @@ const PolicyTemplateCard: React.FC<PolicyTemplateCardProps> = ({
|
|||
};
|
||||
|
||||
interface PolicyTemplatesProps {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
onUseTemplate: (templateData: any) => void;
|
||||
onOpenAiSuggestion: () => void;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
onTemplatesLoaded?: (templates: any[]) => void;
|
||||
accessToken: string | null;
|
||||
}
|
||||
|
||||
// Map icon names from JSON to actual icon components
|
||||
const iconMap: Record<string, React.ComponentType<React.SVGProps<SVGSVGElement>>> = {
|
||||
ShieldCheckIcon: ShieldCheckIcon,
|
||||
ShieldExclamationIcon: ShieldExclamationIcon,
|
||||
BeakerIcon: BeakerIcon,
|
||||
CurrencyDollarIcon: CurrencyDollarIcon,
|
||||
CheckCircleIcon: CheckCircleIcon,
|
||||
// Map icon names from JSON to lucide icon components.
|
||||
const iconMap: Record<string, React.ComponentType<{ className?: string }>> = {
|
||||
ShieldCheckIcon: Shield,
|
||||
ShieldExclamationIcon: ShieldAlert,
|
||||
BeakerIcon: Beaker,
|
||||
CurrencyDollarIcon: DollarSign,
|
||||
CheckCircleIcon: CheckCircle,
|
||||
};
|
||||
|
||||
const PolicyTemplates: React.FC<PolicyTemplatesProps> = ({ onUseTemplate, onOpenAiSuggestion, onTemplatesLoaded, accessToken }) => {
|
||||
const PolicyTemplates: React.FC<PolicyTemplatesProps> = ({
|
||||
onUseTemplate,
|
||||
onOpenAiSuggestion,
|
||||
onTemplatesLoaded,
|
||||
accessToken,
|
||||
}) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const [templates, setTemplates] = useState<any[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [selectedTags, setSelectedTags] = useState<Set<string>>(new Set());
|
||||
|
||||
// Compute all unique tags with counts
|
||||
const tagCounts = useMemo(() => {
|
||||
const counts: Record<string, number> = {};
|
||||
templates.forEach((t) => {
|
||||
|
|
@ -145,16 +153,16 @@ const PolicyTemplates: React.FC<PolicyTemplatesProps> = ({ onUseTemplate, onOpen
|
|||
counts[tag] = (counts[tag] || 0) + 1;
|
||||
});
|
||||
});
|
||||
// Sort alphabetically
|
||||
return Object.entries(counts).sort(([a], [b]) => a.localeCompare(b));
|
||||
}, [templates]);
|
||||
|
||||
// Filter templates: show templates that have ALL selected tags (AND logic)
|
||||
const filteredTemplates = useMemo(() => {
|
||||
if (selectedTags.size === 0) return templates;
|
||||
return templates.filter((t) => {
|
||||
const tags: string[] = t.tags || [];
|
||||
return Array.from(selectedTags).every((selectedTag) => tags.includes(selectedTag));
|
||||
return Array.from(selectedTags).every((selectedTag) =>
|
||||
tags.includes(selectedTag),
|
||||
);
|
||||
});
|
||||
}, [templates, selectedTags]);
|
||||
|
||||
|
|
@ -192,12 +200,16 @@ const PolicyTemplates: React.FC<PolicyTemplatesProps> = ({ onUseTemplate, onOpen
|
|||
};
|
||||
|
||||
fetchTemplates();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [accessToken]);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex justify-center items-center py-20">
|
||||
<Spin size="large" tip="Loading policy templates..." />
|
||||
<div className="flex flex-col items-center justify-center py-20 gap-2">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-primary" />
|
||||
<span className="text-sm text-muted-foreground">
|
||||
Loading policy templates...
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -206,16 +218,16 @@ const PolicyTemplates: React.FC<PolicyTemplatesProps> = ({ onUseTemplate, onOpen
|
|||
<div className="space-y-6">
|
||||
<div className="flex justify-between items-end">
|
||||
<div>
|
||||
<h2 className="text-lg font-medium text-gray-900">
|
||||
<h2 className="text-lg font-medium text-foreground">
|
||||
Policy Templates
|
||||
</h2>
|
||||
<p className="text-sm text-gray-500 mt-1">
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Start with a pre-configured policy template to quickly set up
|
||||
guardrails for your organization.
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
type="default"
|
||||
variant="outline"
|
||||
onClick={onOpenAiSuggestion}
|
||||
className="flex items-center gap-1.5"
|
||||
>
|
||||
|
|
@ -232,13 +244,14 @@ const PolicyTemplates: React.FC<PolicyTemplatesProps> = ({ onUseTemplate, onOpen
|
|||
<div className="w-52 flex-shrink-0">
|
||||
<div className="sticky top-4">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<span className="text-sm font-semibold text-gray-900">
|
||||
<span className="text-sm font-semibold text-foreground">
|
||||
Categories
|
||||
</span>
|
||||
{selectedTags.size > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleClearAll}
|
||||
className="text-xs text-blue-600 hover:text-blue-800"
|
||||
className="text-xs text-blue-600 hover:text-blue-800 dark:text-blue-300 dark:hover:text-blue-200"
|
||||
>
|
||||
Clear all
|
||||
</button>
|
||||
|
|
@ -248,20 +261,21 @@ const PolicyTemplates: React.FC<PolicyTemplatesProps> = ({ onUseTemplate, onOpen
|
|||
{tagCounts.map(([tag, count]) => (
|
||||
<label
|
||||
key={tag}
|
||||
className={`flex items-center justify-between px-2 py-1.5 rounded-md cursor-pointer transition-colors ${
|
||||
className={cn(
|
||||
"flex items-center justify-between px-2 py-1.5 rounded-md cursor-pointer transition-colors",
|
||||
selectedTags.has(tag)
|
||||
? "bg-blue-50"
|
||||
: "hover:bg-gray-50"
|
||||
}`}
|
||||
? "bg-blue-50 dark:bg-blue-950/30"
|
||||
: "hover:bg-muted",
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={selectedTags.has(tag)}
|
||||
onChange={() => handleTagToggle(tag)}
|
||||
onCheckedChange={() => handleTagToggle(tag)}
|
||||
/>
|
||||
<span className="text-sm text-gray-700">{tag}</span>
|
||||
<span className="text-sm text-foreground">{tag}</span>
|
||||
</div>
|
||||
<span className="text-xs text-gray-400 font-medium">
|
||||
<span className="text-xs text-muted-foreground font-medium">
|
||||
{count}
|
||||
</span>
|
||||
</label>
|
||||
|
|
@ -274,7 +288,7 @@ const PolicyTemplates: React.FC<PolicyTemplatesProps> = ({ onUseTemplate, onOpen
|
|||
{/* Right content - template cards */}
|
||||
<div className="flex-1">
|
||||
{selectedTags.size > 0 && (
|
||||
<div className="mb-4 text-sm text-gray-500">
|
||||
<div className="mb-4 text-sm text-muted-foreground">
|
||||
Showing {filteredTemplates.length} of {templates.length} templates
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -284,7 +298,7 @@ const PolicyTemplates: React.FC<PolicyTemplatesProps> = ({ onUseTemplate, onOpen
|
|||
key={template.id || index}
|
||||
title={template.title}
|
||||
description={template.description}
|
||||
icon={iconMap[template.icon] || ShieldCheckIcon}
|
||||
icon={iconMap[template.icon] || Shield}
|
||||
iconColor={template.iconColor}
|
||||
iconBg={template.iconBg}
|
||||
guardrails={template.guardrails}
|
||||
|
|
@ -297,11 +311,12 @@ const PolicyTemplates: React.FC<PolicyTemplatesProps> = ({ onUseTemplate, onOpen
|
|||
</div>
|
||||
|
||||
{filteredTemplates.length === 0 && (
|
||||
<div className="text-center py-12 text-gray-500">
|
||||
<div className="text-center py-12 text-muted-foreground">
|
||||
<p>No templates match the selected filters.</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleClearAll}
|
||||
className="text-blue-600 hover:text-blue-800 mt-2 text-sm"
|
||||
className="text-blue-600 hover:text-blue-800 dark:text-blue-300 dark:hover:text-blue-200 mt-2 text-sm"
|
||||
>
|
||||
Clear all filters
|
||||
</button>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue