diff --git a/ui/litellm-dashboard/src/components/TeamSSOSettings.tsx b/ui/litellm-dashboard/src/components/TeamSSOSettings.tsx new file mode 100644 index 0000000000..4ee4aae565 --- /dev/null +++ b/ui/litellm-dashboard/src/components/TeamSSOSettings.tsx @@ -0,0 +1,307 @@ +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 { getDefaultTeamSettings, updateDefaultTeamSettings, modelAvailableCall } from "./networking"; +import BudgetDurationDropdown, { getBudgetDurationLabel } from "./common_components/budget_duration_dropdown"; +import { getModelDisplayName } from "./key_team_helpers/fetch_available_models_team_key"; + +interface TeamSSOSettingsProps { + accessToken: string | null; + userID: string; + userRole: string; +} + +const TeamSSOSettings: React.FC = ({ accessToken, userID, userRole }) => { + 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 [availableModels, setAvailableModels] = useState([]); + const { Paragraph } = Typography; + const { Option } = Select; + + useEffect(() => { + const fetchTeamSSOSettings = async () => { + if (!accessToken) { + setLoading(false); + return; + } + + try { + const data = await getDefaultTeamSettings(accessToken); + setSettings(data); + setEditedValues(data.values || {}); + + // Fetch available models + if (accessToken) { + try { + const modelResponse = await modelAvailableCall(accessToken, userID, userRole); + 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 team SSO settings:", error); + message.error("Failed to fetch team settings"); + } finally { + setLoading(false); + } + }; + + fetchTeamSSOSettings(); + }, [accessToken]); + + const handleSaveSettings = async () => { + if (!accessToken) return; + + setSaving(true); + try { + const updatedSettings = await updateDefaultTeamSettings(accessToken, editedValues); + setSettings({...settings, values: updatedSettings.settings}); + setIsEditing(false); + message.success("Default team settings updated successfully"); + } catch (error) { + console.error("Error updating team settings:", error); + message.error("Failed to update team settings"); + } finally { + setSaving(false); + } + }; + + const handleTextInputChange = (key: string, value: any) => { + setEditedValues((prev: Record) => ({ + ...prev, + [key]: value + })); + }; + + const renderEditableField = (key: string, property: any, value: any) => { + const type = property.type; + + if (key === "budget_duration") { + return ( + handleTextInputChange(key, value)} + className="mt-2" + /> + ); + } else if (type === "boolean") { + return ( +
+ handleTextInputChange(key, checked)} + /> +
+ ); + } else if (type === "array" && property.items?.enum) { + return ( + + ); + } else if (key === "models") { + return ( + + ); + } else if (type === "string" && property.enum) { + return ( + + ); + } else { + return ( + handleTextInputChange(key, e.target.value)} + placeholder={property.description || ""} + className="mt-2" + /> + ); + } + }; + + const renderValue = (key: string, value: any): JSX.Element => { + if (value === null || value === undefined) return Not set; + + if (key === "budget_duration") { + return {getBudgetDurationLabel(value)}; + } + + if (typeof value === "boolean") { + return {value ? "Enabled" : "Disabled"}; + } + + if (key === "models" && Array.isArray(value)) { + if (value.length === 0) return None; + + return ( +
+ {value.map((model, index) => ( + + {getModelDisplayName(model)} + + ))} +
+ ); + } + + 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)}
+        
+ ); + } + + return {String(value)}; + }; + + if (loading) { + return ( +
+ +
+ ); + } + + if (!settings) { + return ( + + No team settings available or you do not 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]; + const displayName = key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()); + + return ( +
+ {displayName} + + {property.description || "No description available"} + + + {isEditing ? ( +
+ {renderEditableField(key, property, value)} +
+ ) : ( +
+ {renderValue(key, value)} +
+ )} +
+ ); + }); + }; + + return ( + +
+ Default Team Settings + {!loading && settings && ( + isEditing ? ( +
+ + +
+ ) : ( + + ) + )} +
+ + + These settings will be applied by default when creating new teams. + + + {settings?.schema?.description && ( + {settings.schema.description} + )} + + +
+ {renderSettings()} +
+
+ ); +}; + +export default TeamSSOSettings; \ No newline at end of file diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index f41222f357..9fbff17aa5 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -4344,4 +4344,71 @@ export const tagDeleteCall = async ( console.error("Error deleting tag:", error); throw error; } +}; + +export const getDefaultTeamSettings = async (accessToken: string) => { + try { + // Construct base URL + let url = proxyBaseUrl + ? `${proxyBaseUrl}/get/default_team_settings` + : `/get/default_team_settings`; + + console.log("Fetching default team 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 default team settings:", data); + return data; + } catch (error) { + console.error("Failed to fetch default team settings:", error); + throw error; + } +}; + + +export const updateDefaultTeamSettings = async (accessToken: string, settings: Record) => { + try { + // Construct base URL + let url = proxyBaseUrl + ? `${proxyBaseUrl}/update/default_team_settings` + : `/update/default_team_settings`; + + console.log("Updating default team 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 default team settings:", data); + message.success("Default team settings updated successfully"); + return data; + } catch (error) { + console.error("Failed to update default team settings:", error); + throw error; + } }; \ No newline at end of file diff --git a/ui/litellm-dashboard/src/components/teams.tsx b/ui/litellm-dashboard/src/components/teams.tsx index 7e3b607267..4f38972363 100644 --- a/ui/litellm-dashboard/src/components/teams.tsx +++ b/ui/litellm-dashboard/src/components/teams.tsx @@ -27,6 +27,7 @@ import { Select, SelectItem } from "@tremor/react"; import { InfoCircleOutlined } from '@ant-design/icons'; import { getGuardrailsList } from "./networking"; import TeamInfoView from "@/components/team/team_info"; +import TeamSSOSettings from "@/components/TeamSSOSettings"; import { Table, TableBody, @@ -354,6 +355,7 @@ const Teams: React.FC = ({
Your Teams Available Teams + Default Team Settings
{lastRefreshed && Last Refreshed: {lastRefreshed}} @@ -797,6 +799,13 @@ const Teams: React.FC = ({ userID={userID} /> + + + )}