From 4dbe5ba6484748588156cfeb576d8df5eaab6aad Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 23 Apr 2026 08:50:50 +0000 Subject: [PATCH] feat(ui): migrate DebugWarningBanner + UIAccessControlForm to shadcn - DebugWarningBanner: antd Alert (banner mode) \u2192 shadcn Alert with amber palette + lucide AlertTriangle. Banner styling preserved via rounded-none + bordered classes; added to .eslintrc.json overrides for the amber categorical palette. - UIAccessControlForm: antd Form/Input/Select/Button + @tremor TextInput /Text \u2192 shadcn Input/Label/Select/Button + react-hook-form. Watch ui_access_mode_type to conditionally render the restricted_sso_group field (replaces antd Form.Item shouldUpdate render-prop). Gates: TS \u2713 | Vitest 12/12 \u2713 | Build \u2713. Co-authored-by: yuneng-jiang --- ui/litellm-dashboard/.eslintrc.json | 3 +- .../src/components/DebugWarningBanner.tsx | 37 ++- .../src/components/UIAccessControlForm.tsx | 238 ++++++++++-------- 3 files changed, 156 insertions(+), 122 deletions(-) diff --git a/ui/litellm-dashboard/.eslintrc.json b/ui/litellm-dashboard/.eslintrc.json index 0daae7d30b0..53e8a976bbe 100644 --- a/ui/litellm-dashboard/.eslintrc.json +++ b/ui/litellm-dashboard/.eslintrc.json @@ -29,7 +29,8 @@ "src/components/GuardrailsMonitor/LogViewer.tsx", "src/components/GuardrailsMonitor/GuardrailsOverview.tsx", "src/components/common_components/IconActionButton/TableIconActionButtons/TableIconActionButton.tsx", - "src/components/GuardrailSettingsView.tsx" + "src/components/GuardrailSettingsView.tsx", + "src/components/DebugWarningBanner.tsx" ], "rules": { "litellm-ui/no-banned-ui-imports": "off", diff --git a/ui/litellm-dashboard/src/components/DebugWarningBanner.tsx b/ui/litellm-dashboard/src/components/DebugWarningBanner.tsx index e4b2ab69a18..d2a7d3fc12c 100644 --- a/ui/litellm-dashboard/src/components/DebugWarningBanner.tsx +++ b/ui/litellm-dashboard/src/components/DebugWarningBanner.tsx @@ -1,32 +1,29 @@ "use client"; import React from "react"; -import { Alert } from "antd"; +import { + Alert, + AlertDescription, + AlertTitle, +} from "@/components/ui/alert"; +import { AlertTriangle } from "lucide-react"; import { useHealthReadiness } from "@/app/(dashboard)/hooks/healthReadiness/useHealthReadiness"; export const DebugWarningBanner: React.FC = () => { const { data: healthData } = useHealthReadiness(); - // Only show banner if detailed debug mode is explicitly enabled - if (!healthData?.is_detailed_debug) { - return null; - } + if (!healthData?.is_detailed_debug) return null; return ( - - Detailed debug logging (LITELLM_LOG=DEBUG) is currently - enabled. This mode logs extensive diagnostic information and will - significantly degrade performance. It should only be used for - troubleshooting and disabled in production environments. - - } - type="warning" - showIcon - banner - style={{ marginBottom: 0, borderRadius: 0 }} - /> + + + Performance Warning: Detailed Debug Mode Active + + Detailed debug logging (LITELLM_LOG=DEBUG) is currently + enabled. This mode logs extensive diagnostic information and will + significantly degrade performance. It should only be used for + troubleshooting and disabled in production environments. + + ); }; diff --git a/ui/litellm-dashboard/src/components/UIAccessControlForm.tsx b/ui/litellm-dashboard/src/components/UIAccessControlForm.tsx index 77bee2c1e52..054774d4c2e 100644 --- a/ui/litellm-dashboard/src/components/UIAccessControlForm.tsx +++ b/ui/litellm-dashboard/src/components/UIAccessControlForm.tsx @@ -1,6 +1,15 @@ import React, { useEffect, useState } from "react"; -import { Form, Button as Button2, Select } from "antd"; -import { Text, TextInput } from "@tremor/react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Controller, FormProvider, useForm } from "react-hook-form"; import { getSSOSettings, updateSSOSettings } from "./networking"; import NotificationManager from "./molecules/notifications_manager"; @@ -9,74 +18,81 @@ interface UIAccessControlFormProps { onSuccess: () => void; } -// Separate UI Access Control Form Component -const UIAccessControlForm: React.FC = ({ accessToken, onSuccess }) => { - const [form] = Form.useForm(); +interface FormValues { + ui_access_mode_type: string; + restricted_sso_group: string; + sso_group_jwt_field: string; +} + +const defaultValues: FormValues = { + ui_access_mode_type: "", + restricted_sso_group: "", + sso_group_jwt_field: "", +}; + +const UIAccessControlForm: React.FC = ({ + accessToken, + onSuccess, +}) => { + const form = useForm({ defaultValues, mode: "onSubmit" }); const [loading, setLoading] = useState(false); - // Load existing UI access control settings useEffect(() => { const loadUIAccessSettings = async () => { - if (accessToken) { - try { - const ssoData = await getSSOSettings(accessToken); - if (ssoData && ssoData.values) { - // Handle nested ui_access_mode structure - const uiAccessMode = ssoData.values.ui_access_mode; - let formValues = {}; + if (!accessToken) return; + try { + const ssoData = await getSSOSettings(accessToken); + if (ssoData && ssoData.values) { + const uiAccessMode = ssoData.values.ui_access_mode; + let formValues: FormValues = { ...defaultValues }; - if (uiAccessMode && typeof uiAccessMode === "object") { - formValues = { - ui_access_mode_type: uiAccessMode.type, - restricted_sso_group: uiAccessMode.restricted_sso_group, - sso_group_jwt_field: uiAccessMode.sso_group_jwt_field, - }; - } else if (typeof uiAccessMode === "string") { - // Handle legacy flat structure - formValues = { - ui_access_mode_type: uiAccessMode, - restricted_sso_group: ssoData.values.restricted_sso_group, - sso_group_jwt_field: ssoData.values.team_ids_jwt_field || ssoData.values.sso_group_jwt_field, - }; - } - - form.setFieldsValue(formValues); + if (uiAccessMode && typeof uiAccessMode === "object") { + formValues = { + ui_access_mode_type: uiAccessMode.type ?? "", + restricted_sso_group: uiAccessMode.restricted_sso_group ?? "", + sso_group_jwt_field: uiAccessMode.sso_group_jwt_field ?? "", + }; + } else if (typeof uiAccessMode === "string") { + formValues = { + ui_access_mode_type: uiAccessMode, + restricted_sso_group: + ssoData.values.restricted_sso_group ?? "", + sso_group_jwt_field: + ssoData.values.team_ids_jwt_field || + ssoData.values.sso_group_jwt_field || + "", + }; } - } catch (error) { - console.error("Failed to load UI access settings:", error); + form.reset(formValues); } + } catch (error) { + console.error("Failed to load UI access settings:", error); } }; - loadUIAccessSettings(); - }, [accessToken, form]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [accessToken]); - const handleUIAccessSubmit = async (formValues: Record) => { + const handleUIAccessSubmit = form.handleSubmit(async (values) => { if (!accessToken) { NotificationManager.fromBackend("No access token available"); return; } - setLoading(true); try { - // Transform form data to match API expected structure - let apiPayload; - - if (formValues.ui_access_mode_type === "all_authenticated_users") { - // Set ui_access_mode to none when all_authenticated_users is selected - apiPayload = { - ui_access_mode: "none", - }; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let apiPayload: any; + if (values.ui_access_mode_type === "all_authenticated_users") { + apiPayload = { ui_access_mode: "none" }; } else { apiPayload = { ui_access_mode: { - type: formValues.ui_access_mode_type, - restricted_sso_group: formValues.restricted_sso_group, - sso_group_jwt_field: formValues.sso_group_jwt_field, + type: values.ui_access_mode_type, + restricted_sso_group: values.restricted_sso_group, + sso_group_jwt_field: values.sso_group_jwt_field, }, }; } - await updateSSOSettings(accessToken, apiPayload); onSuccess(); } catch (error) { @@ -85,66 +101,86 @@ const UIAccessControlForm: React.FC = ({ accessToken, } finally { setLoading(false); } - }; + }); + + const accessModeType = form.watch("ui_access_mode_type"); return ( -
-
- - Configure who can access the UI interface and how group information is extracted from JWT tokens. - -
+
+

+ Configure who can access the UI interface and how group information is + extracted from JWT tokens. +

-
- - - + + +
+ + ( + + )} + /> +
- - prevValues.ui_access_mode_type !== currentValues.ui_access_mode_type - } - > - {({ getFieldValue }) => { - const uiAccessModeType = getFieldValue("ui_access_mode_type"); - return uiAccessModeType === "restricted_sso_group" ? ( - - - - ) : null; - }} - + {accessModeType === "restricted_sso_group" && ( +
+ + + {form.formState.errors.restricted_sso_group && ( +

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

+ )} +
+ )} - - - +
+ + +
-
- - Update UI Access Control - -
- +
+ +
+ +
); };