diff --git a/ui/litellm-dashboard/.eslintrc.json b/ui/litellm-dashboard/.eslintrc.json index 390b215ef7f..5299c64ccd0 100644 --- a/ui/litellm-dashboard/.eslintrc.json +++ b/ui/litellm-dashboard/.eslintrc.json @@ -71,6 +71,7 @@ "src/components/UsagePage/components/EntityUsage/TopKeyView.tsx", "src/components/policies/policy_templates.tsx", "src/components/mcp_tools/mcp_server_view.tsx", + "src/components/claude_code_plugins/plugin_info.tsx", "src/components/permissions/VectorStorePermissions.tsx", "src/components/permissions/AgentPermissions.tsx", "src/components/permissions/MCPServerPermissions.tsx", diff --git a/ui/litellm-dashboard/src/components/claude_code_plugins/plugin_info.tsx b/ui/litellm-dashboard/src/components/claude_code_plugins/plugin_info.tsx index 142d7757711..1444af03973 100644 --- a/ui/litellm-dashboard/src/components/claude_code_plugins/plugin_info.tsx +++ b/ui/litellm-dashboard/src/components/claude_code_plugins/plugin_info.tsx @@ -1,15 +1,21 @@ -import { CopyOutlined } from "@ant-design/icons"; -import { ArrowLeftIcon, ExternalLinkIcon } from "@heroicons/react/outline"; import { - Badge, - Button, - Card, - Grid, - Text, - Title, -} from "@tremor/react"; -import { Spin, Switch, Tooltip } from "antd"; -import React, { useEffect, useState } from "react"; + ArrowLeft, + Copy, + ExternalLink, + Loader2, +} from "lucide-react"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Card } from "@/components/ui/card"; +import { Switch } from "@/components/ui/switch"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { cn } from "@/lib/utils"; +import React, { useCallback, useEffect, useState } from "react"; import NotificationsManager from "../molecules/notifications_manager"; import { disableClaudeCodePlugin, @@ -21,10 +27,34 @@ import { formatInstallCommand, getCategoryBadgeColor, getSourceDisplayText, - getSourceLink + getSourceLink, } from "./helpers"; import { Plugin } from "./types"; +// Map tremor-style color tokens to categorical Tailwind palette classes. +const BADGE_COLOR_CLASSES: Record = { + gray: "bg-muted text-muted-foreground", + green: + "bg-emerald-100 text-emerald-700 dark:bg-emerald-950 dark:text-emerald-300", + red: "bg-red-100 text-red-700 dark:bg-red-950 dark:text-red-300", + blue: "bg-blue-100 text-blue-700 dark:bg-blue-950 dark:text-blue-300", + indigo: + "bg-indigo-100 text-indigo-700 dark:bg-indigo-950 dark:text-indigo-300", + purple: + "bg-purple-100 text-purple-700 dark:bg-purple-950 dark:text-purple-300", + orange: + "bg-orange-100 text-orange-700 dark:bg-orange-950 dark:text-orange-300", + amber: "bg-amber-100 text-amber-700 dark:bg-amber-950 dark:text-amber-300", + yellow: "bg-amber-100 text-amber-700 dark:bg-amber-950 dark:text-amber-300", + cyan: "bg-cyan-100 text-cyan-700 dark:bg-cyan-950 dark:text-cyan-300", + pink: "bg-pink-100 text-pink-700 dark:bg-pink-950 dark:text-pink-300", + rose: "bg-rose-100 text-rose-700 dark:bg-rose-950 dark:text-rose-300", + teal: "bg-teal-100 text-teal-700 dark:bg-teal-950 dark:text-teal-300", +}; + +const badgeClasses = (color: string): string => + BADGE_COLOR_CLASSES[color] || BADGE_COLOR_CLASSES.gray; + interface PluginInfoViewProps { pluginId: string; onClose: () => void; @@ -44,21 +74,14 @@ const PluginInfoView: React.FC = ({ const [isLoading, setIsLoading] = useState(true); const [isToggling, setIsToggling] = useState(false); - useEffect(() => { - fetchPluginInfo(); - }, [pluginId, accessToken]); - - const fetchPluginInfo = async () => { + const fetchPluginInfo = useCallback(async () => { if (!accessToken) return; setIsLoading(true); try { - // The backend expects plugin name, not ID - // We'll need to find the plugin by ID from the list - // For now, assume pluginId is actually the plugin name const data = await getClaudeCodePluginDetails( accessToken, - pluginId as string + pluginId as string, ); setPlugin(data.plugin); } catch (error) { @@ -67,7 +90,11 @@ const PluginInfoView: React.FC = ({ } finally { setIsLoading(false); } - }; + }, [accessToken, pluginId]); + + useEffect(() => { + fetchPluginInfo(); + }, [fetchPluginInfo]); const handleToggleEnabled = async () => { if (!accessToken || !plugin) return; @@ -83,7 +110,8 @@ const PluginInfoView: React.FC = ({ } onPluginUpdated(); fetchPluginInfo(); - } catch (error) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (_error) { NotificationsManager.error("Failed to toggle plugin status"); } finally { setIsToggling(false); @@ -98,14 +126,14 @@ const PluginInfoView: React.FC = ({ if (isLoading) { return (
- +
); } if (!plugin) { return ( -
+

Plugin not found

- + + + + + + Copy install command + +
{/* Plugin Details */} - - Plugin Details - - {/* Plugin ID */} + +

Plugin Details

+
- Plugin ID +

Plugin ID

- {plugin.id} - {plugin.id} + copyToClipboard(plugin.id)} />
- {/* Name */}
- Name - {plugin.name} +

Name

+

{plugin.name}

- {/* Version */}
- Version - - {plugin.version || "N/A"} - +

Version

+

{plugin.version || "N/A"}

- {/* Source */}
- Source +

Source

- +

{getSourceDisplayText(plugin.source)} - +

{sourceLink && ( - + )}
- {/* Category */}
- Category +

Category

{plugin.category ? ( - + {plugin.category} ) : ( - Uncategorized +

Uncategorized

)}
- {/* Enabled Status */} {isAdmin && (
- Status +

Status

- +

{plugin.enabled ? "Plugin is enabled and visible in marketplace" : "Plugin is disabled and hidden from marketplace"} - +

)} - +
- {/* Description */} {plugin.description && ( - - Description - {plugin.description} + +

Description

+

{plugin.description}

)} - {/* Keywords */} {plugin.keywords && plugin.keywords.length > 0 && ( - - Keywords + +

Keywords

{plugin.keywords.map((keyword, index) => ( - + {keyword} ))} @@ -274,75 +306,70 @@ const PluginInfoView: React.FC = ({ )} - {/* Author Information */} {plugin.author && ( - - Author Information - + +

Author Information

+
{plugin.author.name && (
- Name - - {plugin.author.name} - +

Name

+

{plugin.author.name}

)} {plugin.author.email && (
- Email - +

Email

+

{plugin.author.email} - +

)} - +
)} - {/* Additional Links */} {plugin.homepage && ( - - Homepage + +

Homepage

{plugin.homepage} - +
)} - {/* Timestamps */} - - Metadata - + +

Metadata

+
- Created At - +

Created At

+

{formatDateString(plugin.created_at)} - +

- Updated At - +

Updated At

+

{formatDateString(plugin.updated_at)} - +

{plugin.created_by && (
- Created By - {plugin.created_by} +

Created By

+

{plugin.created_by}

)} - +
);