mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): migrate mcp_hub_table_columns to shadcn
- @tremor Button/Badge/Text + antd Tooltip/Tag + @ant-design/icons CopyOutlined/InfoCircleOutlined \u2192 shadcn Badge/Button/Tooltip + lucide Copy/Info. CopyIconButton helper dedupes the icon+tooltip. - Status palette (active/healthy/inactive/unhealthy/unknown) uses the emerald/red/muted categorical tokens and is exempted from the raw-colors rule. - Tools column 'purple' tags \u2192 purple-100/800 Badge (kept as categorical tag color). Gates: TS \u2713 | Build \u2713. Co-authored-by: yuneng-jiang <yuneng-berri@users.noreply.github.com>
This commit is contained in:
parent
28e1cecc7e
commit
1a987d9a90
2 changed files with 131 additions and 105 deletions
|
|
@ -35,7 +35,8 @@
|
|||
"src/components/logging_settings_view.tsx",
|
||||
"src/components/model_group_alias_settings.tsx",
|
||||
"src/components/cache_dashboard.tsx",
|
||||
"src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterTestPanel.tsx"
|
||||
"src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterTestPanel.tsx",
|
||||
"src/components/mcp_hub_table_columns.tsx"
|
||||
],
|
||||
"rules": {
|
||||
"litellm-ui/no-banned-ui-imports": "off",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,14 @@
|
|||
import { ColumnDef } from "@tanstack/react-table";
|
||||
import { Button, Badge, Text } from "@tremor/react";
|
||||
import { Tooltip, Tag } from "antd";
|
||||
import { CopyOutlined, InfoCircleOutlined } from "@ant-design/icons";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { Copy, Info } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export interface MCPServerData {
|
||||
server_id: string;
|
||||
|
|
@ -11,6 +18,7 @@ export interface MCPServerData {
|
|||
url: string;
|
||||
transport: string;
|
||||
auth_type: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
credentials?: any;
|
||||
created_at: string;
|
||||
created_by: string;
|
||||
|
|
@ -19,24 +27,70 @@ export interface MCPServerData {
|
|||
teams: string[];
|
||||
mcp_access_groups: string[];
|
||||
allowed_tools: string[];
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
extra_headers: any[];
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
mcp_info: Record<string, any>;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
static_headers: Record<string, any>;
|
||||
status: string;
|
||||
last_health_check?: string | null;
|
||||
health_check_error?: string | null;
|
||||
command?: string | null;
|
||||
args: string[];
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
env: Record<string, any>;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
function CopyIconButton({
|
||||
value,
|
||||
onCopy,
|
||||
title,
|
||||
}: {
|
||||
value: string;
|
||||
onCopy: (text: string) => void;
|
||||
title: string;
|
||||
}) {
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onCopy(value)}
|
||||
className="cursor-pointer text-muted-foreground hover:text-primary"
|
||||
aria-label={title}
|
||||
>
|
||||
<Copy size={12} />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{title}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
|
||||
// Categorical status palette. Kept inline (not semantic-token-only) per the
|
||||
// DEVIATIONS.md categorical-palette policy.
|
||||
const statusClassMap: Record<string, string> = {
|
||||
active:
|
||||
"bg-emerald-100 text-emerald-800 dark:bg-emerald-950/40 dark:text-emerald-300",
|
||||
healthy:
|
||||
"bg-emerald-100 text-emerald-800 dark:bg-emerald-950/40 dark:text-emerald-300",
|
||||
inactive: "bg-red-100 text-red-800 dark:bg-red-950/40 dark:text-red-300",
|
||||
unhealthy: "bg-red-100 text-red-800 dark:bg-red-950/40 dark:text-red-300",
|
||||
unknown: "bg-muted text-muted-foreground",
|
||||
};
|
||||
|
||||
export const mcpHubColumns = (
|
||||
showModal: (server: MCPServerData) => void,
|
||||
copyToClipboard: (text: string) => void,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
publicPage: boolean = false,
|
||||
): ColumnDef<MCPServerData>[] => {
|
||||
const allColumns: ColumnDef<MCPServerData>[] = [
|
||||
return [
|
||||
{
|
||||
header: "Server Name",
|
||||
accessorKey: "server_name",
|
||||
|
|
@ -44,21 +98,20 @@ export const mcpHubColumns = (
|
|||
sortingFn: "alphanumeric",
|
||||
cell: ({ row }) => {
|
||||
const server = row.original;
|
||||
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Text className="font-medium text-sm">{server.server_name}</Text>
|
||||
<Tooltip title="Copy server name">
|
||||
<CopyOutlined
|
||||
onClick={() => copyToClipboard(server.server_name)}
|
||||
className="cursor-pointer text-gray-500 hover:text-blue-500 text-xs"
|
||||
/>
|
||||
</Tooltip>
|
||||
<span className="font-medium text-sm">{server.server_name}</span>
|
||||
<CopyIconButton
|
||||
value={server.server_name}
|
||||
onCopy={copyToClipboard}
|
||||
title="Copy server name"
|
||||
/>
|
||||
</div>
|
||||
{/* Show description on mobile */}
|
||||
<div className="md:hidden">
|
||||
<Text className="text-xs text-gray-600">{server.description || "-"}</Text>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{server.description || "-"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -71,16 +124,13 @@ export const mcpHubColumns = (
|
|||
sortingFn: "alphanumeric",
|
||||
cell: ({ row }) => {
|
||||
const server = row.original;
|
||||
|
||||
return (
|
||||
<Text className="text-xs line-clamp-2">
|
||||
<span className="text-xs line-clamp-2">
|
||||
{server.description || "-"}
|
||||
</Text>
|
||||
</span>
|
||||
);
|
||||
},
|
||||
meta: {
|
||||
className: "hidden md:table-cell",
|
||||
},
|
||||
meta: { className: "hidden md:table-cell" },
|
||||
},
|
||||
{
|
||||
header: "URL",
|
||||
|
|
@ -89,40 +139,30 @@ export const mcpHubColumns = (
|
|||
sortingFn: "alphanumeric",
|
||||
cell: ({ row }) => {
|
||||
const server = row.original;
|
||||
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Text className="text-xs truncate max-w-xs">{server.url}</Text>
|
||||
<Tooltip title="Copy URL">
|
||||
<CopyOutlined
|
||||
onClick={() => copyToClipboard(server.url)}
|
||||
className="cursor-pointer text-gray-500 hover:text-blue-500 text-xs flex-shrink-0"
|
||||
/>
|
||||
</Tooltip>
|
||||
<span className="text-xs truncate max-w-xs">{server.url}</span>
|
||||
<CopyIconButton
|
||||
value={server.url}
|
||||
onCopy={copyToClipboard}
|
||||
title="Copy URL"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
meta: {
|
||||
className: "hidden lg:table-cell",
|
||||
},
|
||||
meta: { className: "hidden lg:table-cell" },
|
||||
},
|
||||
{
|
||||
header: "Transport",
|
||||
accessorKey: "transport",
|
||||
enableSorting: true,
|
||||
sortingFn: "alphanumeric",
|
||||
cell: ({ row }) => {
|
||||
const server = row.original;
|
||||
|
||||
return (
|
||||
<Badge color="blue" size="sm">
|
||||
{server.transport}
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
meta: {
|
||||
className: "hidden md:table-cell",
|
||||
},
|
||||
cell: ({ row }) => (
|
||||
<Badge variant="default" className="text-xs">
|
||||
{row.original.transport}
|
||||
</Badge>
|
||||
),
|
||||
meta: { className: "hidden md:table-cell" },
|
||||
},
|
||||
{
|
||||
header: "Auth Type",
|
||||
|
|
@ -131,18 +171,16 @@ export const mcpHubColumns = (
|
|||
sortingFn: "alphanumeric",
|
||||
cell: ({ row }) => {
|
||||
const server = row.original;
|
||||
|
||||
const authColor = server.auth_type === "none" ? "gray" : "green";
|
||||
|
||||
return (
|
||||
<Badge color={authColor} size="sm">
|
||||
<Badge
|
||||
variant={server.auth_type === "none" ? "outline" : "default"}
|
||||
className="text-xs"
|
||||
>
|
||||
{server.auth_type}
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
meta: {
|
||||
className: "hidden md:table-cell",
|
||||
},
|
||||
meta: { className: "hidden md:table-cell" },
|
||||
},
|
||||
{
|
||||
header: "Status",
|
||||
|
|
@ -151,21 +189,16 @@ export const mcpHubColumns = (
|
|||
sortingFn: "alphanumeric",
|
||||
cell: ({ row }) => {
|
||||
const server = row.original;
|
||||
|
||||
const statusColors: Record<string, string> = {
|
||||
active: "green",
|
||||
inactive: "red",
|
||||
unknown: "gray",
|
||||
healthy: "green",
|
||||
unhealthy: "red",
|
||||
};
|
||||
|
||||
const color = statusColors[server.status] || "gray";
|
||||
|
||||
const cls = statusClassMap[server.status] ?? statusClassMap.unknown;
|
||||
return (
|
||||
<Badge color={color} size="sm">
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium",
|
||||
cls,
|
||||
)}
|
||||
>
|
||||
{server.status || "unknown"}
|
||||
</Badge>
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
|
@ -176,48 +209,45 @@ export const mcpHubColumns = (
|
|||
cell: ({ row }) => {
|
||||
const server = row.original;
|
||||
const tools = server.allowed_tools || [];
|
||||
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
<Text className="text-xs font-medium">
|
||||
{tools.length > 0 ? `${tools.length} tool${tools.length !== 1 ? "s" : ""}` : "All tools"}
|
||||
</Text>
|
||||
<span className="text-xs font-medium">
|
||||
{tools.length > 0
|
||||
? `${tools.length} tool${tools.length !== 1 ? "s" : ""}`
|
||||
: "All tools"}
|
||||
</span>
|
||||
{tools.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{tools.slice(0, 2).map((tool, idx) => (
|
||||
<Tag key={idx} color="purple" className="text-xs">
|
||||
<Badge
|
||||
key={idx}
|
||||
variant="outline"
|
||||
className="text-xs bg-purple-100 text-purple-800 dark:bg-purple-950/40 dark:text-purple-300 border-purple-200 dark:border-purple-800"
|
||||
>
|
||||
{tool}
|
||||
</Tag>
|
||||
</Badge>
|
||||
))}
|
||||
{tools.length > 2 && (
|
||||
<Text className="text-xs text-gray-500">+{tools.length - 2}</Text>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
+{tools.length - 2}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
meta: {
|
||||
className: "hidden lg:table-cell",
|
||||
},
|
||||
meta: { className: "hidden lg:table-cell" },
|
||||
},
|
||||
{
|
||||
header: "Created By",
|
||||
accessorKey: "created_by",
|
||||
enableSorting: true,
|
||||
sortingFn: "alphanumeric",
|
||||
cell: ({ row }) => {
|
||||
const server = row.original;
|
||||
|
||||
return (
|
||||
<Text className="text-xs">
|
||||
{server.created_by || "-"}
|
||||
</Text>
|
||||
);
|
||||
},
|
||||
meta: {
|
||||
className: "hidden xl:table-cell",
|
||||
},
|
||||
cell: ({ row }) => (
|
||||
<span className="text-xs">{row.original.created_by || "-"}</span>
|
||||
),
|
||||
meta: { className: "hidden xl:table-cell" },
|
||||
},
|
||||
{
|
||||
header: "Public",
|
||||
|
|
@ -230,38 +260,33 @@ export const mcpHubColumns = (
|
|||
},
|
||||
cell: ({ row }) => {
|
||||
const server = row.original;
|
||||
|
||||
return server.mcp_info?.is_public === true ? (
|
||||
<Badge color="green" size="xs">
|
||||
<Badge variant="default" className="text-xs">
|
||||
Yes
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge color="gray" size="xs">
|
||||
<Badge variant="outline" className="text-xs">
|
||||
No
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
meta: {
|
||||
className: "hidden md:table-cell",
|
||||
},
|
||||
meta: { className: "hidden md:table-cell" },
|
||||
},
|
||||
{
|
||||
header: "Details",
|
||||
id: "details",
|
||||
enableSorting: false,
|
||||
cell: ({ row }) => {
|
||||
const server = row.original;
|
||||
|
||||
return (
|
||||
<Button size="xs" variant="secondary" onClick={() => showModal(server)} icon={InfoCircleOutlined}>
|
||||
<span className="hidden lg:inline">Details</span>
|
||||
<span className="lg:hidden">Info</span>
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
cell: ({ row }) => (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
onClick={() => showModal(row.original)}
|
||||
>
|
||||
<Info className="h-3 w-3" />
|
||||
<span className="hidden lg:inline">Details</span>
|
||||
<span className="lg:hidden">Info</span>
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return allColumns;
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue