From 9894ce40d795cbfddcb9117ad1c9d97b9ea176f6 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 23 Apr 2026 08:54:18 +0000 Subject: [PATCH] feat(ui): migrate SCIM config view to shadcn - @tremor Card/Title/Text/Grid/TremorButton/Callout/TextInput/Divider + antd Form + @ant-design/icons LinkOutlined/KeyOutlined/CopyOutlined/ ExclamationCircleOutlined/PlusCircleOutlined \u2192 shadcn Card/Input/ Label/Button/Alert/Separator + react-hook-form + lucide Link/Key/Copy/AlertCircle/PlusCircle. - Token-revealed banner uses amber palette (categorical 'warning' color); added to .eslintrc.json overrides. CopyToClipboard wrappers preserved. Gates: TS \u2713 | Build \u2713. Co-authored-by: yuneng-jiang --- ui/litellm-dashboard/.eslintrc.json | 3 +- ui/litellm-dashboard/src/components/SCIM.tsx | 313 +++++++++++-------- 2 files changed, 182 insertions(+), 134 deletions(-) diff --git a/ui/litellm-dashboard/.eslintrc.json b/ui/litellm-dashboard/.eslintrc.json index 53e8a976bbe..0f6e76f71e3 100644 --- a/ui/litellm-dashboard/.eslintrc.json +++ b/ui/litellm-dashboard/.eslintrc.json @@ -30,7 +30,8 @@ "src/components/GuardrailsMonitor/GuardrailsOverview.tsx", "src/components/common_components/IconActionButton/TableIconActionButtons/TableIconActionButton.tsx", "src/components/GuardrailSettingsView.tsx", - "src/components/DebugWarningBanner.tsx" + "src/components/DebugWarningBanner.tsx", + "src/components/SCIM.tsx" ], "rules": { "litellm-ui/no-banned-ui-imports": "off", diff --git a/ui/litellm-dashboard/src/components/SCIM.tsx b/ui/litellm-dashboard/src/components/SCIM.tsx index c8da8f2c9c7..fefe5029944 100644 --- a/ui/litellm-dashboard/src/components/SCIM.tsx +++ b/ui/litellm-dashboard/src/components/SCIM.tsx @@ -1,190 +1,237 @@ import React, { useState, useEffect } from "react"; -import { Card, Title, Text, Grid, Button as TremorButton, Callout, TextInput, Divider } from "@tremor/react"; -import { Form } from "antd"; +import { + Alert, + AlertDescription, + AlertTitle, +} from "@/components/ui/alert"; +import { Button } from "@/components/ui/button"; +import { Card } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Separator } from "@/components/ui/separator"; import { keyCreateCall } from "./networking"; import { CopyToClipboard } from "react-copy-to-clipboard"; import { - LinkOutlined, - KeyOutlined, - CopyOutlined, - ExclamationCircleOutlined, - PlusCircleOutlined, -} from "@ant-design/icons"; + AlertCircle, + Copy, + Key, + Link as LinkIcon, + PlusCircle, +} from "lucide-react"; import { parseErrorMessage } from "./shared/errorUtils"; import NotificationsManager from "./molecules/notifications_manager"; +import { Controller, FormProvider, useForm } from "react-hook-form"; interface SCIMConfigProps { accessToken: string | null; userID: string | null; + // eslint-disable-next-line @typescript-eslint/no-explicit-any proxySettings: any; } -const SCIMConfig: React.FC = ({ accessToken, userID, proxySettings }) => { - const [form] = Form.useForm(); +interface SCIMFormValues { + key_alias: string; +} + +const SCIMConfig: React.FC = ({ + accessToken, + userID, + proxySettings, +}) => { + const form = useForm({ + defaultValues: { key_alias: "" }, + mode: "onSubmit", + }); const [isCreatingToken, setIsCreatingToken] = useState(false); + // eslint-disable-next-line @typescript-eslint/no-explicit-any const [tokenData, setTokenData] = useState(null); const [baseUrl, setBaseUrl] = useState(""); useEffect(() => { let url = ""; - - if (proxySettings && proxySettings.PROXY_BASE_URL && proxySettings.PROXY_BASE_URL !== undefined) { + if ( + proxySettings && + proxySettings.PROXY_BASE_URL && + proxySettings.PROXY_BASE_URL !== undefined + ) { url = proxySettings.PROXY_BASE_URL; } else if (typeof window !== "undefined") { - // Use the current origin as the base URL if no proxy URL is set url = window.location.origin; } - setBaseUrl(url); }, [proxySettings]); const scimBaseUrl = `${baseUrl}/scim/v2`; - const handleCreateSCIMToken = async (values: any) => { + const handleCreateSCIMToken = form.handleSubmit(async (values) => { if (!accessToken || !userID) { - NotificationsManager.fromBackend("You need to be logged in to create a SCIM token"); + NotificationsManager.fromBackend( + "You need to be logged in to create a SCIM token", + ); return; } - try { setIsCreatingToken(true); - const formData = { key_alias: values.key_alias || "SCIM Access Token", team_id: null, models: [], allowed_routes: ["/scim/*"], }; - const response = await keyCreateCall(accessToken, userID, formData); setTokenData(response); NotificationsManager.success("SCIM token created successfully"); + // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { console.error("Error creating SCIM token:", error); - NotificationsManager.fromBackend("Failed to create SCIM token: " + parseErrorMessage(error)); + NotificationsManager.fromBackend( + "Failed to create SCIM token: " + parseErrorMessage(error), + ); } finally { setIsCreatingToken(false); } - }; + }); return ( - - -
- SCIM Configuration -
- - System for Cross-domain Identity Management (SCIM) allows you to automatically provision and manage users and - groups in LiteLLM. - + +
+

SCIM Configuration

+
+

+ System for Cross-domain Identity Management (SCIM) allows you to + automatically provision and manage users and groups in LiteLLM. +

- + -
- {/* Step 1: SCIM URL */} -
-
-
- 1 -
- - <LinkOutlined className="h-5 w-5 mr-2" /> - SCIM Tenant URL - +
+ {/* Step 1: SCIM URL */} +
+
+
+ 1
- - Use this URL in your identity provider SCIM integration settings. - -
- - NotificationsManager.success("URL copied to clipboard")} +

+ + SCIM Tenant URL +

+
+

+ Use this URL in your identity provider SCIM integration settings. +

+
+ + + NotificationsManager.success("URL copied to clipboard") + } + > + + +
+
+ + {/* Step 2: SCIM Token */} +
+
+
+ 2 +
+

+ + Authentication Token +

+
+ + + Using SCIM + + You need a SCIM token to authenticate with the SCIM API. Create + one below and use it in your SCIM provider configuration. + + + + {!tokenData ? ( +
+ +
+
+ + ( + + )} + /> + {form.formState.errors.key_alias && ( +

+ {form.formState.errors.key_alias.message as string} +

+ )} +
+ +
+
+
+ ) : ( + +
+ +

Your SCIM Token

+
+

+ Make sure to copy this token now. You will not be able to see + it again. +

+
+ + + NotificationsManager.success("Token copied to clipboard") + } + > + + +
+
-
- - {/* Step 2: SCIM Token */} -
-
-
- 2 -
- - <KeyOutlined className="h-5 w-5 mr-2" /> - Authentication Token - -
- - - You need a SCIM token to authenticate with the SCIM API. Create one below and use it in your SCIM provider - configuration. - - - {!tokenData ? ( -
-
- - - - - - - Create SCIM Token - - -
-
- ) : ( - -
- - Your SCIM Token -
- - Make sure to copy this token now. You will not be able to see it again. - -
- - NotificationsManager.success("Token copied to clipboard")} - > - - - Copy - - -
- setTokenData(null)}> - - Create Another Token - -
- )} -
+ + Create Another Token + + + )}
- - +
+ ); };