From 372fdedf5bb5038bb822a17f76cc65e8a32bfcf1 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 23 Apr 2026 10:10:13 +0000 Subject: [PATCH] feat(ui): migrate mcp_tools/ByokCredentialModal to shadcn - antd Modal/Input/Input.Password/Switch + @ant-design/icons KeyOutlined/ LockOutlined/CheckOutlined/ArrowRight/ArrowLeft/CloseOutlined/ LinkOutlined \u2192 shadcn Dialog/Input/Button/Switch/Label + lucide Key/Lock/Check/ArrowRight/ArrowLeft/X/LinkIcon/Eye/EyeOff. - Two-step flow (service-icons \u2192 api-key-form) preserved. Preserve the teal/cyan \u2192 blue/indigo gradient logo tiles (intentional brand illustration); ByokCredentialModal added to .eslintrc.json overrides. Gates: TS \u2713 | Build \u2713. Co-authored-by: yuneng-jiang --- ui/litellm-dashboard/.eslintrc.json | 3 +- .../mcp_tools/ByokCredentialModal.tsx | 446 +++++++++++------- 2 files changed, 271 insertions(+), 178 deletions(-) diff --git a/ui/litellm-dashboard/.eslintrc.json b/ui/litellm-dashboard/.eslintrc.json index 51549f234e2..ca186e8bded 100644 --- a/ui/litellm-dashboard/.eslintrc.json +++ b/ui/litellm-dashboard/.eslintrc.json @@ -39,7 +39,8 @@ "src/components/mcp_hub_table_columns.tsx", "src/components/model_hub_table_columns.tsx", "src/components/mcp_server_management/MCPServerSelector.tsx", - "src/components/mcp_tools/MCPNetworkSettings.tsx" + "src/components/mcp_tools/MCPNetworkSettings.tsx", + "src/components/mcp_tools/ByokCredentialModal.tsx" ], "rules": { "litellm-ui/no-banned-ui-imports": "off", diff --git a/ui/litellm-dashboard/src/components/mcp_tools/ByokCredentialModal.tsx b/ui/litellm-dashboard/src/components/mcp_tools/ByokCredentialModal.tsx index 58c2a965e03..e0fef49ae15 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/ByokCredentialModal.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/ByokCredentialModal.tsx @@ -1,17 +1,27 @@ "use client"; import React, { useState } from "react"; -import { Modal, Input, Switch } from "antd"; +import { + Dialog, + DialogContent, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Switch } from "@/components/ui/switch"; import MessageManager from "@/components/molecules/message_manager"; import { - KeyOutlined, - LockOutlined, - CheckOutlined, - ArrowRightOutlined, - ArrowLeftOutlined, - CloseOutlined, - LinkOutlined, -} from "@ant-design/icons"; + ArrowLeft, + ArrowRight, + Check, + Eye, + EyeOff, + Key, + Link as LinkIcon, + Lock, + X, +} from "lucide-react"; +import { cn } from "@/lib/utils"; import { MCPServer } from "./types"; interface ByokCredentialModalProps { @@ -31,6 +41,7 @@ export const ByokCredentialModal: React.FC = ({ }) => { const [step, setStep] = useState<1 | 2>(1); const [apiKey, setApiKey] = useState(""); + const [showApiKey, setShowApiKey] = useState(false); const [saveKey, setSaveKey] = useState(true); const [loading, setLoading] = useState(false); @@ -40,6 +51,7 @@ export const ByokCredentialModal: React.FC = ({ const handleClose = () => { setStep(1); setApiKey(""); + setShowApiKey(false); setSaveKey(true); setLoading(false); onClose(); @@ -52,14 +64,17 @@ export const ByokCredentialModal: React.FC = ({ } setLoading(true); try { - const response = await fetch(`/v1/mcp/server/${server.server_id}/user-credential`, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${accessToken}`, + const response = await fetch( + `/v1/mcp/server/${server.server_id}/user-credential`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${accessToken}`, + }, + body: JSON.stringify({ credential: apiKey.trim(), save: saveKey }), }, - body: JSON.stringify({ credential: apiKey.trim(), save: saveKey }), - }); + ); if (!response.ok) { const err = await response.json(); throw new Error(err?.detail?.error || "Failed to save credential"); @@ -67,6 +82,7 @@ export const ByokCredentialModal: React.FC = ({ MessageManager.success(`Connected to ${serverDisplayName}`); onSuccess(server.server_id); handleClose(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (e: any) { MessageManager.error(e.message || "Failed to connect"); } finally { @@ -75,180 +91,256 @@ export const ByokCredentialModal: React.FC = ({ }; return ( - (!o ? handleClose() : undefined)} > -
- {/* Step dots + close */} -
- {step === 2 ? ( - - ) : ( -
- )} -
-
-
-
- -
- - {step === 1 ? ( -
- {/* Logos */} -
-
- L -
- -
- {firstLetter} -
-
- -

Connect {serverDisplayName}

-

- LiteLLM needs access to {serverDisplayName} to complete your request. -

- - {/* How it works */} -
-
-
- - - - -
-
-

How it works

-

- LiteLLM acts as a secure bridge. Your requests are routed through our MCP client directly to{" "} - {serverDisplayName}'s API. -

-
-
-
- - {/* Requested access */} - {server.byok_description && server.byok_description.length > 0 && ( -
-

- - - - - Requested Access -

-
    - {server.byok_description.map((item, i) => ( -
  • - - {item} -
  • - ))} -
-
+ +
+
+ {step === 2 ? ( + + ) : ( +
)} - - +
+
+
+
- ) : ( -
- {/* Key icon */} -
- -
-

Provide API Key

-

- Enter your {serverDisplayName} API key to authorize this connection. -

- -
- - setApiKey(e.target.value)} - size="large" - className="rounded-lg" - /> - {server.byok_api_key_help_url && ( - - Where do I find my API key? - - )} -
- - {/* Save toggle */} -
-
- - - - Save key for future use + {step === 1 ? ( +
+
+
+ L +
+ +
+ {firstLetter} +
- -
- {/* Security note */} -
- -

- Your key is stored securely and transmitted over HTTPS. It is never shared with third parties. +

+ Connect {serverDisplayName} +

+

+ LiteLLM needs access to {serverDisplayName} to complete your + request.

-
- -
- )} -
- +
+
+
+ + + + +
+
+

+ How it works +

+

+ LiteLLM acts as a secure bridge. Your requests are routed + through our MCP client directly to {serverDisplayName} + 's API. +

+
+
+
+ + {server.byok_description && + server.byok_description.length > 0 && ( +
+

+ + + + + Requested Access +

+
    + {server.byok_description.map((item, i) => ( +
  • + + {item} +
  • + ))} +
+
+ )} + + + +
+ ) : ( +
+
+ +
+ +

+ Provide API Key +

+

+ Enter your {serverDisplayName} API key to authorize this + connection. +

+ +
+ +
+ setApiKey(e.target.value)} + /> + +
+ {server.byok_api_key_help_url && ( + + Where do I find my API key?{" "} + + + )} +
+ +
+
+ + + + + Save key for future use + +
+ +
+ +
+ +

+ Your key is stored securely and transmitted over HTTPS. It is + never shared with third parties. +

+
+ + +
+ )} +
+ + ); };