From ce9ab69be2f5a6406a4f6ebd4b4fab7f9e23f833 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 17 Mar 2025 15:09:54 -0700 Subject: [PATCH 01/23] add UISSOSettings --- litellm/proxy/_types.py | 68 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 645346811d0..a75a2f1583b 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -2609,3 +2609,71 @@ class SpecialManagementEndpointEnums(enum.Enum): class TransformRequestBody(BaseModel): call_type: CallTypes request_body: dict + + +class DefaultInternalUserParams(LiteLLMPydanticObjectBase): + """ + Default parameters to apply when a new user signs in via SSO + """ + + user_role: Optional[ + Literal[ + LitellmUserRoles.INTERNAL_USER, + LitellmUserRoles.INTERNAL_USER_VIEW_ONLY, + LitellmUserRoles.PROXY_ADMIN, + LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY, + ] + ] = LitellmUserRoles.INTERNAL_USER + max_budget: Optional[float] = None + budget_duration: Optional[str] = None + models: Optional[List[str]] = None + + +class UpperboundKeyGenerateParams(LiteLLMPydanticObjectBase): + """ + Upperbound limits for /key/generate requests when self-serve flow is enabled + """ + + max_budget: Optional[float] = None + budget_duration: Optional[str] = None + duration: Optional[str] = None + max_parallel_requests: Optional[int] = None + tpm_limit: Optional[int] = None + rpm_limit: Optional[int] = None + + +class TeamKeyGenerationSettings(LiteLLMPydanticObjectBase): + """ + Settings for team-based key generation + """ + + allowed_team_member_roles: Optional[List[Literal["admin", "user"]]] = ["admin"] + + +class PersonalKeyGenerationSettings(LiteLLMPydanticObjectBase): + """ + Settings for personal key generation (maps to 'Default Team' on UI) + """ + + allowed_user_roles: Optional[List[str]] = [LitellmUserRoles.PROXY_ADMIN] + + +class KeyGenerationSettings(LiteLLMPydanticObjectBase): + """ + Settings that restrict who can generate keys + """ + + team_key_generation: Optional[TeamKeyGenerationSettings] = None + personal_key_generation: Optional[PersonalKeyGenerationSettings] = None + + +class UISSOSettings(LiteLLMPydanticObjectBase): + """ + Configuration for SSO integration with the LiteLLM proxy UI + """ + + max_internal_user_budget: Optional[float] = None + internal_user_budget_duration: Optional[str] = None + default_internal_user_params: Optional[DefaultInternalUserParams] = None + upperbound_key_generate_params: Optional[UpperboundKeyGenerateParams] = None + key_generation_settings: Optional[KeyGenerationSettings] = None From 401630833f021520b6647735d9077381b3eb318a Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 17 Mar 2025 15:52:01 -0700 Subject: [PATCH 02/23] add types for internal user settings --- litellm/proxy/_types.py | 149 +++++++++++++++++++++++++++------------- 1 file changed, 102 insertions(+), 47 deletions(-) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index a75a2f1583b..b18a1932ade 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -2611,6 +2611,74 @@ class TransformRequestBody(BaseModel): request_body: dict +class UpperboundKeyGenerateParams(LiteLLMPydanticObjectBase): + """ + Upperbound limits for /key/generate requests when self-serve flow is enabled + """ + + max_budget: Optional[float] = Field( + default=None, + description="Maximum budget (in USD) that can be set for any generated key", + ) + budget_duration: Optional[str] = Field( + default=None, + description="Maximum budget duration that can be set (e.g. '30d', '1mo')", + ) + duration: Optional[str] = Field( + default=None, + description="Maximum key lifetime duration that can be set (e.g. '30d', '1mo')", + ) + max_parallel_requests: Optional[int] = Field( + default=None, + description="Maximum parallel requests limit that can be set for any generated key", + ) + tpm_limit: Optional[int] = Field( + default=None, + description="Maximum tokens per minute limit that can be set for any generated key", + ) + rpm_limit: Optional[int] = Field( + default=None, + description="Maximum requests per minute limit that can be set for any generated key", + ) + + +class TeamKeyGenerationSettings(LiteLLMPydanticObjectBase): + """ + Settings for team-based key generation + """ + + allowed_team_member_roles: Optional[List[Literal["admin", "user"]]] = Field( + default=["admin"], + description="Team member roles that are allowed to generate keys for their team", + ) + + +class PersonalKeyGenerationSettings(LiteLLMPydanticObjectBase): + """ + Settings for personal key generation (maps to 'Default Team' on UI) + """ + + allowed_user_roles: Optional[List[str]] = Field( + default=[LitellmUserRoles.PROXY_ADMIN], + description="User roles that are allowed to generate personal keys (for the 'Default Team')", + ) + + +class KeyGenerationSettings(LiteLLMPydanticObjectBase): + """ + Settings that restrict who can generate keys + """ + + team_key_generation: Optional[TeamKeyGenerationSettings] = Field( + default=None, + description="Settings that control which team members can generate keys for their team", + ) + personal_key_generation: Optional[PersonalKeyGenerationSettings] = Field( + default=None, + description="Settings that control which users can generate personal keys", + ) + + class DefaultInternalUserParams(LiteLLMPydanticObjectBase): """ Default parameters to apply when a new user signs in via SSO @@ -2623,48 +2691,21 @@ class DefaultInternalUserParams(LiteLLMPydanticObjectBase): LitellmUserRoles.PROXY_ADMIN, LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY, ] - ] = LitellmUserRoles.INTERNAL_USER - max_budget: Optional[float] = None - budget_duration: Optional[str] = None - models: Optional[List[str]] = None - - -class UpperboundKeyGenerateParams(LiteLLMPydanticObjectBase): - """ - Upperbound limits for /key/generate requests when self-serve flow is enabled - """ - - max_budget: Optional[float] = None - budget_duration: Optional[str] = None - duration: Optional[str] = None - max_parallel_requests: Optional[int] = None - tpm_limit: Optional[int] = None - rpm_limit: Optional[int] = None - - -class TeamKeyGenerationSettings(LiteLLMPydanticObjectBase): - """ - Settings for team-based key generation - """ - - allowed_team_member_roles: Optional[List[Literal["admin", "user"]]] = ["admin"] - - -class PersonalKeyGenerationSettings(LiteLLMPydanticObjectBase): - """ - Settings for personal key generation (maps to 'Default Team' on UI) - """ - - allowed_user_roles: Optional[List[str]] = [LitellmUserRoles.PROXY_ADMIN] - - -class KeyGenerationSettings(LiteLLMPydanticObjectBase): - """ - Settings that restrict who can generate keys - """ - - team_key_generation: Optional[TeamKeyGenerationSettings] = None - personal_key_generation: Optional[PersonalKeyGenerationSettings] = None + ] = Field( + default=LitellmUserRoles.INTERNAL_USER, + description="Default role assigned to new users signing in via SSO", + ) + max_budget: Optional[float] = Field( + default=None, + description="Default maximum budget (in USD) for new users signing in via SSO", + ) + budget_duration: Optional[str] = Field( + default=None, + description="Default budget duration for new users (e.g. '30d', '1mo')", + ) + models: Optional[List[str]] = Field( + default=None, description="Default list of models that new users can access" + ) class UISSOSettings(LiteLLMPydanticObjectBase): @@ -2672,8 +2713,22 @@ class UISSOSettings(LiteLLMPydanticObjectBase): Configuration for SSO integration with the LiteLLM proxy UI """ - max_internal_user_budget: Optional[float] = None - internal_user_budget_duration: Optional[str] = None - default_internal_user_params: Optional[DefaultInternalUserParams] = None - upperbound_key_generate_params: Optional[UpperboundKeyGenerateParams] = None - key_generation_settings: Optional[KeyGenerationSettings] = None + max_internal_user_budget: Optional[float] = Field( + default=None, + description="Global maximum budget (in USD) for all internal users", + ) + internal_user_budget_duration: Optional[str] = Field( + default=None, + description="Global budget duration for internal users (e.g. '30d', '1mo')", + ) + default_internal_user_params: Optional[DefaultInternalUserParams] = Field( + default=None, + description="Default parameters applied to new users signing in via SSO", + ) + upperbound_key_generate_params: Optional[UpperboundKeyGenerateParams] = Field( + default=None, + description="Maximum limits for key generation when self-serve flow is enabled", + ) + key_generation_settings: Optional[KeyGenerationSettings] = Field( + default=None, description="Settings that control which users can generate keys" + ) From 0af0de8a96342061e2fe1b63769eddf8ff28f426 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 17 Mar 2025 15:53:36 -0700 Subject: [PATCH 03/23] model_dump() --- .../proxy_setting_endpoints.py | 67 ++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py b/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py index 8f91f9bbc99..1f5dcd82409 100644 --- a/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py +++ b/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py @@ -1,8 +1,9 @@ #### CRUD ENDPOINTS for UI Settings ##### -from typing import List +from typing import Any, Dict, List, Optional from fastapi import APIRouter, Depends, HTTPException +import litellm from litellm._logging import verbose_proxy_logger from litellm.proxy._types import * from litellm.proxy.auth.user_api_key_auth import user_api_key_auth @@ -108,3 +109,67 @@ async def delete_allowed_ip(ip_address: IPAddress): await proxy_config.save_config(new_config=config) return {"message": f"IP {ip_address.ip} deleted successfully", "status": "success"} + + +@router.get( + "/sso_settings", + tags=["SSO Settings"], + dependencies=[Depends(user_api_key_auth)], +) +async def get_sso_settings(): + """ + Get all SSO settings from the litellm_settings configuration. + Returns a structured object with values and descriptions for UI display. + """ + from pydantic import TypeAdapter + + # Create the settings object first + sso_settings = UISSOSettings( + max_internal_user_budget=litellm.max_internal_user_budget, + internal_user_budget_duration=litellm.internal_user_budget_duration, + default_internal_user_params=DefaultInternalUserParams( + **( + litellm.default_internal_user_params + if isinstance(litellm.default_internal_user_params, dict) + else {} + ) + ), + upperbound_key_generate_params=UpperboundKeyGenerateParams( + **( + litellm.upperbound_key_generate_params + if isinstance(litellm.upperbound_key_generate_params, dict) + else {} + ) + ), + ) + + # Get the schema for UISSOSettings + schema = TypeAdapter(UISSOSettings).json_schema(by_alias=True) + + # Convert to dict for response + settings_dict = sso_settings.model_dump() + + # Add descriptions to the response + result = { + "values": settings_dict, + "schema": {"description": schema.get("description", ""), "properties": {}}, + } + + # Add property descriptions + for field_name, field_info in schema["properties"].items(): + result["schema"]["properties"][field_name] = { + "description": field_info.get("description", ""), + "type": field_info.get("type", "string"), + } + + # Add nested object descriptions + for def_name, def_schema in schema.get("definitions", {}).items(): + result["schema"][def_name] = { + "description": def_schema.get("description", ""), + "properties": { + prop_name: {"description": prop_info.get("description", "")} + for prop_name, prop_info in def_schema.get("properties", {}).items() + }, + } + + return result From 0771d49ea7978f4a8bb21d91ff3212882a2023b5 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 17 Mar 2025 15:54:20 -0700 Subject: [PATCH 04/23] ui networking getSSOSettingsCall --- .../src/components/networking.tsx | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index 70e84dc9167..b798d74fb49 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -3970,6 +3970,38 @@ export const uiSpendLogDetailsCall = async ( return data; } catch (error) { console.error("Failed to fetch log details:", error); + throw error; + } +}; + +export const getSSOSettingsCall = async (accessToken: string) => { + try { + // Construct base URL + let url = proxyBaseUrl + ? `${proxyBaseUrl}/sso_settings` + : `/sso_settings`; + + console.log("Fetching SSO settings from:", url); + + const response = await fetch(url, { + method: "GET", + headers: { + [globalLitellmHeaderName]: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + }); + + if (!response.ok) { + const errorData = await response.text(); + handleError(errorData); + throw new Error("Network response was not ok"); + } + + const data = await response.json(); + console.log("Fetched SSO settings:", data); + return data; + } catch (error) { + console.error("Failed to fetch SSO settings:", error); throw error; } }; From 0f44b33e0fab5b7ce394bb713e6a92cfd5631c4f Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 17 Mar 2025 15:54:41 -0700 Subject: [PATCH 05/23] ui sso settings --- .../src/components/SSOSettings.tsx | 107 ++++++++++++++++++ .../src/components/admins.tsx | 7 ++ 2 files changed, 114 insertions(+) create mode 100644 ui/litellm-dashboard/src/components/SSOSettings.tsx diff --git a/ui/litellm-dashboard/src/components/SSOSettings.tsx b/ui/litellm-dashboard/src/components/SSOSettings.tsx new file mode 100644 index 00000000000..8e6b31d3164 --- /dev/null +++ b/ui/litellm-dashboard/src/components/SSOSettings.tsx @@ -0,0 +1,107 @@ +import React, { useState, useEffect } from "react"; +import { Card, Title, Text, Divider } from "@tremor/react"; +import { Typography, Spin, message } from "antd"; +import { getSSOSettingsCall } from "./networking"; + +interface SSOSettingsProps { + accessToken: string | null; +} + +const SSOSettings: React.FC = ({ accessToken }) => { + const [loading, setLoading] = useState(true); + const [settings, setSettings] = useState(null); + const { Paragraph } = Typography; + + useEffect(() => { + const fetchSSOSettings = async () => { + if (!accessToken) { + setLoading(false); + return; + } + + try { + const data = await getSSOSettingsCall(accessToken); + setSettings(data); + } catch (error) { + console.error("Error fetching SSO settings:", error); + message.error("Failed to fetch SSO settings"); + } finally { + setLoading(false); + } + }; + + fetchSSOSettings(); + }, [accessToken]); + + const renderValue = (value: any): JSX.Element => { + if (value === null) return Not set; + + if (typeof value === "object") { + return ( +
+          {JSON.stringify(value, null, 2)}
+        
+ ); + } + + return {String(value)}; + }; + + if (loading) { + return ( +
+ +
+ ); + } + + if (!settings) { + return ( + + SSO Settings + No settings available or you don't have permission to view them. + + ); + } + + // Dynamically render settings based on the schema + const renderSettings = () => { + const { values, schema } = settings; + + if (!schema || !schema.properties) { + return No schema information available; + } + + return Object.entries(schema.properties).map(([key, property]: [string, any]) => { + const value = values[key]; + + return ( +
+ {key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())} + + {property.description || "No description available"} + +
+ {renderValue(value)} +
+
+ ); + }); + }; + + return ( + + SSO Settings + {settings.schema?.description && ( + {settings.schema.description} + )} + + +
+ {renderSettings()} +
+
+ ); +}; + +export default SSOSettings; \ No newline at end of file diff --git a/ui/litellm-dashboard/src/components/admins.tsx b/ui/litellm-dashboard/src/components/admins.tsx index cd0f34b7060..059933f6849 100644 --- a/ui/litellm-dashboard/src/components/admins.tsx +++ b/ui/litellm-dashboard/src/components/admins.tsx @@ -38,6 +38,7 @@ import OnboardingModal from "./onboarding_link"; import { InvitationLink } from "./onboarding_link"; import SSOModals from "./SSOModals"; import { ssoProviderConfigs } from './SSOModals'; +import SSOSettings from "./SSOSettings"; interface AdminPanelProps { searchParams: any; @@ -621,6 +622,12 @@ const AdminPanel: React.FC = ({ {nonSssoUrl}{" "} + {/* Add SSO Settings Component */} + {premiumUser && accessToken && ( +
+ +
+ )} ); From 2607676e3f89f9ac9814f791ddede9b8d7f88c3c Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 17 Mar 2025 16:29:32 -0700 Subject: [PATCH 06/23] UISSOSettings --- litellm/proxy/_types.py | 15 --- .../src/components/SSOSettings.tsx | 2 +- .../src/components/admins.tsx | 7 -- .../src/components/view_users.tsx | 113 ++++++++++-------- 4 files changed, 66 insertions(+), 71 deletions(-) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index b18a1932ade..8b63f5b7f52 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -2713,22 +2713,7 @@ class UISSOSettings(LiteLLMPydanticObjectBase): Configuration for SSO integration with the LiteLLM proxy UI """ - max_internal_user_budget: Optional[float] = Field( - default=None, - description="Global maximum budget (in USD) for all internal users", - ) - internal_user_budget_duration: Optional[str] = Field( - default=None, - description="Global budget duration for internal users (e.g. '30d', '1mo')", - ) default_internal_user_params: Optional[DefaultInternalUserParams] = Field( default=None, description="Default parameters applied to new users signing in via SSO", ) - upperbound_key_generate_params: Optional[UpperboundKeyGenerateParams] = Field( - default=None, - description="Maximum limits for key generation when self-serve flow is enabled", - ) - key_generation_settings: Optional[KeyGenerationSettings] = Field( - default=None, description="Settings that control which users can generate keys" - ) diff --git a/ui/litellm-dashboard/src/components/SSOSettings.tsx b/ui/litellm-dashboard/src/components/SSOSettings.tsx index 8e6b31d3164..d1b29b22425 100644 --- a/ui/litellm-dashboard/src/components/SSOSettings.tsx +++ b/ui/litellm-dashboard/src/components/SSOSettings.tsx @@ -58,7 +58,7 @@ const SSOSettings: React.FC = ({ accessToken }) => { if (!settings) { return ( - SSO Settings + Personal Key Creation No settings available or you don't have permission to view them. ); diff --git a/ui/litellm-dashboard/src/components/admins.tsx b/ui/litellm-dashboard/src/components/admins.tsx index 059933f6849..cd0f34b7060 100644 --- a/ui/litellm-dashboard/src/components/admins.tsx +++ b/ui/litellm-dashboard/src/components/admins.tsx @@ -38,7 +38,6 @@ import OnboardingModal from "./onboarding_link"; import { InvitationLink } from "./onboarding_link"; import SSOModals from "./SSOModals"; import { ssoProviderConfigs } from './SSOModals'; -import SSOSettings from "./SSOSettings"; interface AdminPanelProps { searchParams: any; @@ -622,12 +621,6 @@ const AdminPanel: React.FC = ({ {nonSssoUrl}{" "} - {/* Add SSO Settings Component */} - {premiumUser && accessToken && ( -
- -
- )} ); diff --git a/ui/litellm-dashboard/src/components/view_users.tsx b/ui/litellm-dashboard/src/components/view_users.tsx index d97acbce6d1..6c2db92d1c2 100644 --- a/ui/litellm-dashboard/src/components/view_users.tsx +++ b/ui/litellm-dashboard/src/components/view_users.tsx @@ -49,6 +49,7 @@ import { columns } from "./view_users/columns"; import { UserDataTable } from "./view_users/table"; import { UserInfo } from "./view_users/types"; import BulkCreateUsers from "./bulk_create_users_button"; +import SSOSettings from "./SSOSettings"; interface ViewUserDashboardProps { accessToken: string | null; @@ -106,6 +107,7 @@ const ViewUserDashboard: React.FC = ({ >({}); const defaultPageSize = 25; const [searchTerm, setSearchTerm] = useState(""); + const [activeTab, setActiveTab] = useState("users"); // check if window is not undefined if (typeof window !== "undefined") { @@ -285,56 +287,71 @@ const ViewUserDashboard: React.FC = ({ -
-
-
-
- - Showing{" "} - {userListResponse && userListResponse.users && userListResponse.users.length > 0 - ? (userListResponse.page - 1) * userListResponse.page_size + 1 - : 0}{" "} - -{" "} - {userListResponse && userListResponse.users - ? Math.min( - userListResponse.page * userListResponse.page_size, - userListResponse.total - ) - : 0}{" "} - of {userListResponse ? userListResponse.total : 0} results - -
- - - Page {userListResponse ? userListResponse.page : "-"} of{" "} - {userListResponse ? userListResponse.total_pages : "-"} - - + setActiveTab(index === 0 ? "users" : "settings")}> + + Users + Personal Key Settings + + + + +
+
+
+
+ + Showing{" "} + {userListResponse && userListResponse.users && userListResponse.users.length > 0 + ? (userListResponse.page - 1) * userListResponse.page_size + 1 + : 0}{" "} + -{" "} + {userListResponse && userListResponse.users + ? Math.min( + userListResponse.page * userListResponse.page_size, + userListResponse.total + ) + : 0}{" "} + of {userListResponse ? userListResponse.total : 0} results + +
+ + + Page {userListResponse ? userListResponse.page : "-"} of{" "} + {userListResponse ? userListResponse.total_pages : "-"} + + +
+
+
+
-
-
- -
+ + + + + + + {/* Existing Modals */} Date: Mon, 17 Mar 2025 16:56:49 -0700 Subject: [PATCH 07/23] fix display of settings --- litellm/proxy/_types.py | 37 ---- .../proxy_setting_endpoints.py | 28 +-- .../src/components/SSOSettings.tsx | 171 ++++++++++++++++-- .../src/components/networking.tsx | 6 +- 4 files changed, 166 insertions(+), 76 deletions(-) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 8b63f5b7f52..f4464eed2b1 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -2653,32 +2653,6 @@ class TeamKeyGenerationSettings(LiteLLMPydanticObjectBase): ) -class PersonalKeyGenerationSettings(LiteLLMPydanticObjectBase): - """ - Settings for personal key generation (maps to 'Default Team' on UI) - """ - - allowed_user_roles: Optional[List[str]] = Field( - default=[LitellmUserRoles.PROXY_ADMIN], - description="User roles that are allowed to generate personal keys (for the 'Default Team')", - ) - - -class KeyGenerationSettings(LiteLLMPydanticObjectBase): - """ - Settings that restrict who can generate keys - """ - - team_key_generation: Optional[TeamKeyGenerationSettings] = Field( - default=None, - description="Settings that control which team members can generate keys for their team", - ) - personal_key_generation: Optional[PersonalKeyGenerationSettings] = Field( - default=None, - description="Settings that control which users can generate personal keys", - ) - - class DefaultInternalUserParams(LiteLLMPydanticObjectBase): """ Default parameters to apply when a new user signs in via SSO @@ -2706,14 +2680,3 @@ class DefaultInternalUserParams(LiteLLMPydanticObjectBase): models: Optional[List[str]] = Field( default=None, description="Default list of models that new users can access" ) - - -class UISSOSettings(LiteLLMPydanticObjectBase): - """ - Configuration for SSO integration with the LiteLLM proxy UI - """ - - default_internal_user_params: Optional[DefaultInternalUserParams] = Field( - default=None, - description="Default parameters applied to new users signing in via SSO", - ) diff --git a/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py b/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py index 1f5dcd82409..294a580a7f8 100644 --- a/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py +++ b/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py @@ -112,7 +112,7 @@ async def delete_allowed_ip(ip_address: IPAddress): @router.get( - "/sso_settings", + "/get/internal_user_settings", tags=["SSO Settings"], dependencies=[Depends(user_api_key_auth)], ) @@ -124,27 +124,15 @@ async def get_sso_settings(): from pydantic import TypeAdapter # Create the settings object first - sso_settings = UISSOSettings( - max_internal_user_budget=litellm.max_internal_user_budget, - internal_user_budget_duration=litellm.internal_user_budget_duration, - default_internal_user_params=DefaultInternalUserParams( - **( - litellm.default_internal_user_params - if isinstance(litellm.default_internal_user_params, dict) - else {} - ) - ), - upperbound_key_generate_params=UpperboundKeyGenerateParams( - **( - litellm.upperbound_key_generate_params - if isinstance(litellm.upperbound_key_generate_params, dict) - else {} - ) - ), + sso_settings = DefaultInternalUserParams( + **( + litellm.default_internal_user_params + if isinstance(litellm.default_internal_user_params, dict) + else {} + ) ) - # Get the schema for UISSOSettings - schema = TypeAdapter(UISSOSettings).json_schema(by_alias=True) + schema = TypeAdapter(DefaultInternalUserParams).json_schema(by_alias=True) # Convert to dict for response settings_dict = sso_settings.model_dump() diff --git a/ui/litellm-dashboard/src/components/SSOSettings.tsx b/ui/litellm-dashboard/src/components/SSOSettings.tsx index d1b29b22425..21d44c93d8d 100644 --- a/ui/litellm-dashboard/src/components/SSOSettings.tsx +++ b/ui/litellm-dashboard/src/components/SSOSettings.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from "react"; -import { Card, Title, Text, Divider } from "@tremor/react"; -import { Typography, Spin, message } from "antd"; -import { getSSOSettingsCall } from "./networking"; +import { Card, Title, Text, Divider, Button, TextInput } from "@tremor/react"; +import { Typography, Spin, message, Switch, Select, Form } from "antd"; +import { getInternalUserSettings, updateInternalUserSettings } from "./networking"; interface SSOSettingsProps { accessToken: string | null; @@ -10,7 +10,11 @@ interface SSOSettingsProps { const SSOSettings: React.FC = ({ accessToken }) => { const [loading, setLoading] = useState(true); const [settings, setSettings] = useState(null); + const [isEditing, setIsEditing] = useState(false); + const [editedValues, setEditedValues] = useState({}); + const [saving, setSaving] = useState(false); const { Paragraph } = Typography; + const { Option } = Select; useEffect(() => { const fetchSSOSettings = async () => { @@ -20,8 +24,9 @@ const SSOSettings: React.FC = ({ accessToken }) => { } try { - const data = await getSSOSettingsCall(accessToken); + const data = await getInternalUserSettings(accessToken); setSettings(data); + setEditedValues(data.values || {}); } catch (error) { console.error("Error fetching SSO settings:", error); message.error("Failed to fetch SSO settings"); @@ -33,12 +38,105 @@ const SSOSettings: React.FC = ({ accessToken }) => { fetchSSOSettings(); }, [accessToken]); + const handleSaveSettings = async () => { + if (!accessToken) return; + + setSaving(true); + try { + await updateInternalUserSettings(accessToken, editedValues); + setSettings({...settings, values: editedValues}); + setIsEditing(false); + message.success("Settings updated successfully"); + } catch (error) { + console.error("Error updating SSO settings:", error); + message.error("Failed to update settings"); + } finally { + setSaving(false); + } + }; + + const handleTextInputChange = (key: string, value: any) => { + setEditedValues(prev => ({ + ...prev, + [key]: value + })); + }; + + const renderEditableField = (key: string, property: any, value: any) => { + const type = property.type; + + if (type === "boolean") { + return ( +
+ handleTextInputChange(key, checked)} + /> +
+ ); + } else if (type === "array" && property.items?.enum) { + return ( + + ); + } else if (type === "string" && property.enum) { + return ( + + ); + } else { + return ( + handleTextInputChange(key, e.target.value)} + placeholder={property.description || ""} + className="mt-2" + /> + ); + } + }; + const renderValue = (value: any): JSX.Element => { - if (value === null) return Not set; + if (value === null || value === undefined) return Not set; + + if (typeof value === "boolean") { + return {value ? "Enabled" : "Disabled"}; + } if (typeof value === "object") { + if (Array.isArray(value)) { + if (value.length === 0) return None; + + return ( +
+ {value.map((item, index) => ( + + {typeof item === "object" ? JSON.stringify(item) : String(item)} + + ))} +
+ ); + } + return ( -
+        
           {JSON.stringify(value, null, 2)}
         
); @@ -58,7 +156,7 @@ const SSOSettings: React.FC = ({ accessToken }) => { if (!settings) { return ( - Personal Key Creation + SSO Settings No settings available or you don't have permission to view them. ); @@ -74,16 +172,24 @@ const SSOSettings: React.FC = ({ accessToken }) => { return Object.entries(schema.properties).map(([key, property]: [string, any]) => { const value = values[key]; + const displayName = key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()); return ( -
- {key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())} +
+ {displayName} {property.description || "No description available"} -
- {renderValue(value)} -
+ + {isEditing ? ( +
+ {renderEditableField(key, property, value)} +
+ ) : ( +
+ {renderValue(value)} +
+ )}
); }); @@ -91,13 +197,46 @@ const SSOSettings: React.FC = ({ accessToken }) => { return ( - SSO Settings - {settings.schema?.description && ( - {settings.schema.description} +
+ SSO Settings + {!loading && settings && ( + isEditing ? ( +
+ + +
+ ) : ( + + ) + )} +
+ + {settings?.schema?.description && ( + {settings.schema.description} )} -
+
{renderSettings()}
diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index b798d74fb49..255a72fca26 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -3974,12 +3974,12 @@ export const uiSpendLogDetailsCall = async ( } }; -export const getSSOSettingsCall = async (accessToken: string) => { +export const getInternalUserSettings = async (accessToken: string) => { try { // Construct base URL let url = proxyBaseUrl - ? `${proxyBaseUrl}/sso_settings` - : `/sso_settings`; + ? `${proxyBaseUrl}/get/internal_user_settings` + : `/get/internal_user_settings`; console.log("Fetching SSO settings from:", url); From 508b474a1f6c8c9c38371f806f5730533cfae692 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 17 Mar 2025 17:03:06 -0700 Subject: [PATCH 08/23] update_internal_user_settings --- .../proxy_setting_endpoints.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py b/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py index 294a580a7f8..8d476d74dd7 100644 --- a/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py +++ b/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py @@ -161,3 +161,39 @@ async def get_sso_settings(): } return result + + +@router.patch( + "/update/internal_user_settings", + tags=["SSO Settings"], + dependencies=[Depends(user_api_key_auth)], +) +async def update_internal_user_settings(settings: DefaultInternalUserParams): + """ + Update the default internal user parameters for SSO users. + These settings will be applied to new users who sign in via SSO. + """ + from litellm.proxy.proxy_server import proxy_config + + # Update the in-memory settings + litellm.default_internal_user_params = settings.model_dump(exclude_none=True) + + # Load existing config + config = await proxy_config.get_config() + + # Update config with new settings + if "litellm_settings" not in config: + config["litellm_settings"] = {} + + config["litellm_settings"]["default_internal_user_params"] = settings.model_dump( + exclude_none=True + ) + + # Save the updated config + await proxy_config.save_config(new_config=config) + + return { + "message": "Internal user settings updated successfully", + "status": "success", + "settings": litellm.default_internal_user_params, + } From 30add0e64c6d446c7943371aa94794ea757d909a Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 17 Mar 2025 17:11:16 -0700 Subject: [PATCH 09/23] update internal user settings on ui --- .../src/components/SSOSettings.tsx | 5 ++- .../src/components/networking.tsx | 34 +++++++++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/ui/litellm-dashboard/src/components/SSOSettings.tsx b/ui/litellm-dashboard/src/components/SSOSettings.tsx index 21d44c93d8d..21c10b7cec7 100644 --- a/ui/litellm-dashboard/src/components/SSOSettings.tsx +++ b/ui/litellm-dashboard/src/components/SSOSettings.tsx @@ -43,10 +43,9 @@ const SSOSettings: React.FC = ({ accessToken }) => { setSaving(true); try { - await updateInternalUserSettings(accessToken, editedValues); - setSettings({...settings, values: editedValues}); + const updatedSettings = await updateInternalUserSettings(accessToken, editedValues); + setSettings({...settings, values: updatedSettings.settings}); setIsEditing(false); - message.success("Settings updated successfully"); } catch (error) { console.error("Error updating SSO settings:", error); message.error("Failed to update settings"); diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index 255a72fca26..1079b5b2787 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -4006,3 +4006,37 @@ export const getInternalUserSettings = async (accessToken: string) => { } }; + +export const updateInternalUserSettings = async (accessToken: string, settings: Record) => { + try { + // Construct base URL + let url = proxyBaseUrl + ? `${proxyBaseUrl}/update/internal_user_settings` + : `/update/internal_user_settings`; + + console.log("Updating internal user settings:", settings); + + const response = await fetch(url, { + method: "PATCH", + headers: { + [globalLitellmHeaderName]: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(settings), + }); + + if (!response.ok) { + const errorData = await response.text(); + handleError(errorData); + throw new Error("Network response was not ok"); + } + + const data = await response.json(); + console.log("Updated internal user settings:", data); + message.success("Internal user settings updated successfully"); + return data; + } catch (error) { + console.error("Failed to update internal user settings:", error); + throw error; + } +}; From ba7891d3322477f658b2f902d12287b5660b43dd Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 17 Mar 2025 17:26:51 -0700 Subject: [PATCH 10/23] ui fix rendering budget duration settings on ui --- .../src/components/SSOSettings.tsx | 52 +++++++++++++++++-- .../budget_duration_dropdown.tsx | 46 ++++++++++++++++ .../src/components/create_key_button.tsx | 7 +-- .../src/components/view_users.tsx | 4 +- 4 files changed, 98 insertions(+), 11 deletions(-) create mode 100644 ui/litellm-dashboard/src/components/common_components/budget_duration_dropdown.tsx diff --git a/ui/litellm-dashboard/src/components/SSOSettings.tsx b/ui/litellm-dashboard/src/components/SSOSettings.tsx index 21c10b7cec7..7b8470538a2 100644 --- a/ui/litellm-dashboard/src/components/SSOSettings.tsx +++ b/ui/litellm-dashboard/src/components/SSOSettings.tsx @@ -2,12 +2,14 @@ import React, { useState, useEffect } from "react"; import { Card, Title, Text, Divider, Button, TextInput } from "@tremor/react"; import { Typography, Spin, message, Switch, Select, Form } from "antd"; import { getInternalUserSettings, updateInternalUserSettings } from "./networking"; +import BudgetDurationDropdown, { getBudgetDurationLabel } from "./common_components/budget_duration_dropdown"; interface SSOSettingsProps { accessToken: string | null; + possibleUIRoles?: Record> | null; } -const SSOSettings: React.FC = ({ accessToken }) => { +const SSOSettings: React.FC = ({ accessToken, possibleUIRoles }) => { const [loading, setLoading] = useState(true); const [settings, setSettings] = useState(null); const [isEditing, setIsEditing] = useState(false); @@ -64,7 +66,35 @@ const SSOSettings: React.FC = ({ accessToken }) => { const renderEditableField = (key: string, property: any, value: any) => { const type = property.type; - if (type === "boolean") { + if (key === "user_role" && possibleUIRoles) { + return ( + + ); + } else if (key === "budget_duration") { + return ( + handleTextInputChange(key, value)} + className="mt-2" + /> + ); + } else if (type === "boolean") { return (
= ({ accessToken }) => { } }; - const renderValue = (value: any): JSX.Element => { + const renderValue = (key: string, value: any): JSX.Element => { if (value === null || value === undefined) return Not set; + if (key === "user_role" && possibleUIRoles && possibleUIRoles[value]) { + const { ui_label, description } = possibleUIRoles[value]; + return ( +
+ {ui_label} + {description &&

{description}

} +
+ ); + } + + if (key === "budget_duration") { + return {getBudgetDurationLabel(value)}; + } + if (typeof value === "boolean") { return {value ? "Enabled" : "Disabled"}; } @@ -186,7 +230,7 @@ const SSOSettings: React.FC = ({ accessToken }) => {
) : (
- {renderValue(value)} + {renderValue(key, value)}
)}
diff --git a/ui/litellm-dashboard/src/components/common_components/budget_duration_dropdown.tsx b/ui/litellm-dashboard/src/components/common_components/budget_duration_dropdown.tsx new file mode 100644 index 00000000000..9171e13381c --- /dev/null +++ b/ui/litellm-dashboard/src/components/common_components/budget_duration_dropdown.tsx @@ -0,0 +1,46 @@ +import React from "react"; +import { Select } from "antd"; + +const { Option } = Select; + +interface BudgetDurationDropdownProps { + value?: string | null; + onChange: (value: string) => void; + className?: string; + style?: React.CSSProperties; +} + +const BudgetDurationDropdown: React.FC = ({ + value, + onChange, + className = "", + style = {} +}) => { + return ( + + ); +}; + +export const getBudgetDurationLabel = (value: string | null | undefined): string => { + if (!value) return "Not set"; + + const budgetDurationMap: Record = { + "24h": "daily", + "7d": "weekly", + "30d": "monthly" + }; + + return budgetDurationMap[value] || value; +}; + +export default BudgetDurationDropdown; \ No newline at end of file diff --git a/ui/litellm-dashboard/src/components/create_key_button.tsx b/ui/litellm-dashboard/src/components/create_key_button.tsx index 0f93f61ff77..cc0a3899289 100644 --- a/ui/litellm-dashboard/src/components/create_key_button.tsx +++ b/ui/litellm-dashboard/src/components/create_key_button.tsx @@ -40,6 +40,7 @@ import { Tooltip } from 'antd'; import Createuser from "./create_user_button"; import debounce from 'lodash/debounce'; import { rolesWithWriteAccess } from '../utils/roles'; +import BudgetDurationDropdown from "./common_components/budget_duration_dropdown"; @@ -572,11 +573,7 @@ const CreateKey: React.FC = ({ name="budget_duration" help={`Team Reset Budget: ${team?.budget_duration !== null && team?.budget_duration !== undefined ? team?.budget_duration : "None"}`} > - + form.setFieldValue('budget_duration', value)} /> = ({ setActiveTab(index === 0 ? "users" : "settings")}> Users - Personal Key Settings + Default User Settings @@ -348,7 +348,7 @@ const ViewUserDashboard: React.FC = ({ - + From c2db7fb28b9cccf72baa58b3ffd1355f36e4a919 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 17 Mar 2025 17:31:31 -0700 Subject: [PATCH 11/23] fix internal user sso settings --- .../src/components/SSOSettings.tsx | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/ui/litellm-dashboard/src/components/SSOSettings.tsx b/ui/litellm-dashboard/src/components/SSOSettings.tsx index 7b8470538a2..c39cd46142b 100644 --- a/ui/litellm-dashboard/src/components/SSOSettings.tsx +++ b/ui/litellm-dashboard/src/components/SSOSettings.tsx @@ -1,8 +1,9 @@ import React, { useState, useEffect } from "react"; import { Card, Title, Text, Divider, Button, TextInput } from "@tremor/react"; import { Typography, Spin, message, Switch, Select, Form } from "antd"; -import { getInternalUserSettings, updateInternalUserSettings } from "./networking"; +import { getInternalUserSettings, updateInternalUserSettings, modelAvailableCall } from "./networking"; import BudgetDurationDropdown, { getBudgetDurationLabel } from "./common_components/budget_duration_dropdown"; +import { getModelDisplayName } from "./key_team_helpers/fetch_available_models_team_key"; interface SSOSettingsProps { accessToken: string | null; @@ -15,6 +16,7 @@ const SSOSettings: React.FC = ({ accessToken, possibleUIRoles const [isEditing, setIsEditing] = useState(false); const [editedValues, setEditedValues] = useState({}); const [saving, setSaving] = useState(false); + const [availableModels, setAvailableModels] = useState([]); const { Paragraph } = Typography; const { Option } = Select; @@ -29,6 +31,19 @@ const SSOSettings: React.FC = ({ accessToken, possibleUIRoles const data = await getInternalUserSettings(accessToken); setSettings(data); setEditedValues(data.values || {}); + + // Fetch available models + if (accessToken) { + try { + const modelResponse = await modelAvailableCall(accessToken); + if (modelResponse && modelResponse.data) { + const modelNames = modelResponse.data.map((model: { id: string }) => model.id); + setAvailableModels(modelNames); + } + } catch (error) { + console.error("Error fetching available models:", error); + } + } } catch (error) { console.error("Error fetching SSO settings:", error); message.error("Failed to fetch SSO settings"); @@ -117,6 +132,22 @@ const SSOSettings: React.FC = ({ accessToken, possibleUIRoles ))} ); + } else if (key === "models") { + return ( + + ); } else if (type === "string" && property.enum) { return (