From c79b511c6921d56dfdd40c127b1f779c202c9d1d Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 23 Apr 2026 19:21:16 +0000 Subject: [PATCH] feat(ui): mcp_tools/OAuthFormFields to shadcn - @tremor Button/TextInput + antd Tooltip/Input/Input.TextArea + @ant-design InfoCircleOutlined \u2192 shadcn Button/Input/Textarea/ Tooltip + lucide Info. - antd Form/Form.Item/Select/InputNumber preserved (form context + mode='tags' + tokenSeparators + rules validator). - text-gray-400/600 \u2192 text-muted-foreground; text-red-500 \u2192 text-destructive; text-green-600 stays categorical (success indicator). File added to .eslintrc overrides. Gates: TS \u2713 | Build \u2713. Co-authored-by: yuneng-jiang --- ui/litellm-dashboard/.eslintrc.json | 1 + .../components/mcp_tools/OAuthFormFields.tsx | 181 ++++++++++++++---- 2 files changed, 141 insertions(+), 41 deletions(-) diff --git a/ui/litellm-dashboard/.eslintrc.json b/ui/litellm-dashboard/.eslintrc.json index bca9af3333c..af7cc8f07cd 100644 --- a/ui/litellm-dashboard/.eslintrc.json +++ b/ui/litellm-dashboard/.eslintrc.json @@ -87,6 +87,7 @@ "src/components/AIHub/forms/MakeModelPublicForm.tsx", "src/components/policies/policy_test_panel.tsx", "src/components/mcp_tools/McpCrudPermissionPanel.tsx", + "src/components/mcp_tools/OAuthFormFields.tsx", "src/components/permissions/VectorStorePermissions.tsx", "src/components/permissions/AgentPermissions.tsx", "src/components/permissions/MCPServerPermissions.tsx", diff --git a/ui/litellm-dashboard/src/components/mcp_tools/OAuthFormFields.tsx b/ui/litellm-dashboard/src/components/mcp_tools/OAuthFormFields.tsx index 4a808ca489d..a3080bad6d3 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/OAuthFormFields.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/OAuthFormFields.tsx @@ -1,7 +1,15 @@ import React from "react"; -import { Form, Input, InputNumber, Select, Tooltip } from "antd"; -import { InfoCircleOutlined } from "@ant-design/icons"; -import { Button, TextInput } from "@tremor/react"; +import { Form, InputNumber, Select } from "antd"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { Info } from "lucide-react"; import { OAUTH_FLOW } from "./types"; interface OAuthFlowStatus { @@ -20,14 +28,22 @@ interface OAuthFormFieldsProps { docsUrl?: string | null; } -const fieldClassName = "rounded-lg border-gray-300 focus:border-blue-500 focus:ring-blue-500"; +const fieldClassName = "rounded-lg"; -const FieldLabel: React.FC<{ label: string; tooltip: string }> = ({ label, tooltip }) => ( - +const FieldLabel: React.FC<{ label: string; tooltip: string }> = ({ + label, + tooltip, +}) => ( + {label} - - - + + + + + + {tooltip} + + ); @@ -56,13 +72,17 @@ const OAuthFormFields: React.FC = ({
Machine-to-Machine (M2M) - server-to-server, no user interaction + + server-to-server, no user interaction +
Interactive (PKCE) - browser-based user authorization + + browser-based user authorization +
@@ -73,23 +93,53 @@ const OAuthFormFields: React.FC = ({ } name={["credentials", "client_id"]} - rules={[{ required: true, message: "Client ID is required for M2M OAuth" }]} + rules={[ + { required: true, message: "Client ID is required for M2M OAuth" }, + ]} > - + } + label={ + + } name={["credentials", "client_secret"]} - rules={[{ required: true, message: "Client Secret is required for M2M OAuth" }]} + rules={[ + { + required: true, + message: "Client Secret is required for M2M OAuth", + }, + ]} > - + } + label={ + + } name="token_url" - rules={[{ required: true, message: "Token URL is required for M2M OAuth" }]} + rules={[ + { required: true, message: "Token URL is required for M2M OAuth" }, + ]} > - + } @@ -109,7 +159,7 @@ const OAuthFormFields: React.FC = ({ href={docsUrl} target="_blank" rel="noopener noreferrer" - className="text-xs text-blue-500 hover:text-blue-700 ml-2 font-normal" + className="text-xs text-primary hover:text-primary/80 ml-2 font-normal" onClick={(e) => e.stopPropagation()} > Create OAuth App → @@ -119,13 +169,26 @@ const OAuthFormFields: React.FC = ({ } name={["credentials", "client_id"]} > - + } + label={ + + } name={["credentials", "client_secret"]} > - + } @@ -134,22 +197,46 @@ const OAuthFormFields: React.FC = ({ } + label={ + + } name="token_url" > - + } + label={ + + } name="registration_url" > - + = ({ name="token_validation_json" rules={[ { + // eslint-disable-next-line @typescript-eslint/no-explicit-any validator: (_: any, value: string) => { if (!value || value.trim() === "") return Promise.resolve(); try { @@ -173,10 +261,12 @@ const OAuthFormFields: React.FC = ({ }, ]} > - = ({ /> {oauthFlow && ( -
-

- Use OAuth to fetch a fresh access token and temporarily save it in the session as the authentication value. +

+

+ Use OAuth to fetch a fresh access token and temporarily save it + in the session as the authentication value.

- {oauthFlow.error &&

{oauthFlow.error}

} - {oauthFlow.status === "success" && oauthFlow.tokenResponse?.access_token && ( -

- Token fetched. Expires in {oauthFlow.tokenResponse.expires_in ?? "?"} seconds. -

+ {oauthFlow.error && ( +

{oauthFlow.error}

)} + {oauthFlow.status === "success" && + oauthFlow.tokenResponse?.access_token && ( +

+ Token fetched. Expires in{" "} + {oauthFlow.tokenResponse.expires_in ?? "?"} seconds. +

+ )}
)}