diff --git a/ui/litellm-dashboard/src/components/TeamSSOSettings.tsx b/ui/litellm-dashboard/src/components/TeamSSOSettings.tsx index 98745d388a2..4a49ca1e678 100644 --- a/ui/litellm-dashboard/src/components/TeamSSOSettings.tsx +++ b/ui/litellm-dashboard/src/components/TeamSSOSettings.tsx @@ -1,14 +1,25 @@ -import React, { useState, useEffect } from "react"; -import { Card, Button, InputNumber, Typography, Spin, Select, Tag, Row, Col } from "antd"; -import { EditOutlined, SaveOutlined } from "@ant-design/icons"; +import React, { useState, useEffect, useMemo } from "react"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Card } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Skeleton } from "@/components/ui/skeleton"; +import { Edit, Save, X } from "lucide-react"; import { getDefaultTeamSettings, updateDefaultTeamSettings } from "./networking"; -import BudgetDurationDropdown, { getBudgetDurationLabel } from "./common_components/budget_duration_dropdown"; +import BudgetDurationDropdown, { + getBudgetDurationLabel, +} from "./common_components/budget_duration_dropdown"; import { getModelDisplayName } from "./key_team_helpers/fetch_available_models_team_key"; import NotificationsManager from "./molecules/notifications_manager"; import { ModelSelect } from "./ModelSelect/ModelSelect"; -const { Title, Text } = Typography; - interface TeamSSOSettingsProps { accessToken: string | null; userID: string; @@ -40,28 +51,38 @@ interface SettingRowProps { editContent: React.ReactNode; } -const SettingRow: React.FC = ({ label, description, isEditing, viewContent, editContent }) => ( - - -
{label}
-
{description}
- - +const SettingRow: React.FC = ({ + label, + description, + isEditing, + viewContent, + editContent, +}) => ( +
+
+
{label}
+
+ {description} +
+
+
{isEditing ? editContent : viewContent}
- - +
+
); -const NotSet = () => Not set; +const NotSet = () => ( + Not set +); const renderTags = (values: string[], displayFn?: (v: string) => string) => { if (!values || values.length === 0) return ; return (
{values.map((v) => ( - + {displayFn ? displayFn(v) : v} - + ))}
); @@ -89,7 +110,8 @@ const TeamSSOSettings: React.FC = ({ accessToken }) => { const [loading, setLoading] = useState(true); const [values, setValues] = useState(DEFAULT_VALUES); const [isEditing, setIsEditing] = useState(false); - const [editedValues, setEditedValues] = useState(DEFAULT_VALUES); + const [editedValues, setEditedValues] = + useState(DEFAULT_VALUES); const [saving, setSaving] = useState(false); const [fetchError, setFetchError] = useState(false); @@ -99,7 +121,6 @@ const TeamSSOSettings: React.FC = ({ accessToken }) => { setLoading(false); return; } - try { const data = await getDefaultTeamSettings(accessToken); const fetched = { ...DEFAULT_VALUES, ...(data.values || {}) }; @@ -113,21 +134,27 @@ const TeamSSOSettings: React.FC = ({ accessToken }) => { setLoading(false); } }; - fetchSettings(); }, [accessToken]); const handleSave = async () => { if (!accessToken) return; - setSaving(true); try { - const updatedSettings = await updateDefaultTeamSettings(accessToken, editedValues); - const newValues = { ...DEFAULT_VALUES, ...(updatedSettings.settings || {}) }; + const updatedSettings = await updateDefaultTeamSettings( + accessToken, + editedValues, + ); + const newValues = { + ...DEFAULT_VALUES, + ...(updatedSettings.settings || {}), + }; setValues(newValues); setEditedValues(newValues); setIsEditing(false); - NotificationsManager.success("Default team settings updated successfully"); + NotificationsManager.success( + "Default team settings updated successfully", + ); } catch (error) { console.error("Error updating team settings:", error); NotificationsManager.fromBackend("Failed to update team settings"); @@ -141,50 +168,68 @@ const TeamSSOSettings: React.FC = ({ accessToken }) => { setEditedValues(values); }; - const update = (key: K, value: SettingsValues[K]) => { + const update = ( + key: K, + value: SettingsValues[K], + ) => { setEditedValues((prev) => ({ ...prev, [key]: value })); }; + const remainingPermissions = useMemo( + () => + PERMISSION_OPTIONS.filter( + (p) => !(editedValues.team_member_permissions ?? []).includes(p), + ), + [editedValues.team_member_permissions], + ); + if (loading) { return (
- +
); } if (fetchError) { return ( - - No team settings available or you do not have permission to view them. + +

+ No team settings available or you do not have permission to view them. +

); } return ( - - {/* Header */} +
- + <h3 className="text-xl font-semibold m-0 text-foreground"> Default Team Settings - - + +

These settings will be applied by default when creating new teams. - +

{isEditing ? (
- -
) : ( - )} @@ -194,25 +239,41 @@ const TeamSSOSettings: React.FC = ({ accessToken }) => {
{/* Budget & Rate Limits */}
-
Budget & Rate Limits
-
+
+ Budget & Rate Limits +
+
${Number(values.max_budget).toLocaleString()} : + values.max_budget != null ? ( + ${Number(values.max_budget).toLocaleString()} + ) : ( + + ) } editContent={ - update("max_budget", v)} - placeholder="Not set" - prefix="$" - min={0} - /> +
+ + $ + + + update( + "max_budget", + e.target.value === "" ? null : Number(e.target.value), + ) + } + placeholder="Not set" + className="pl-6" + /> +
} /> @@ -221,14 +282,19 @@ const TeamSSOSettings: React.FC = ({ accessToken }) => { description="How frequently the team's budget resets." isEditing={isEditing} viewContent={ - values.budget_duration ? {getBudgetDurationLabel(values.budget_duration)} : + values.budget_duration ? ( + {getBudgetDurationLabel(values.budget_duration)} + ) : ( + + ) } editContent={ - update("budget_duration", v)} - style={{ maxWidth: 320 }} - /> +
+ update("budget_duration", v)} + /> +
} /> @@ -237,16 +303,25 @@ const TeamSSOSettings: React.FC = ({ accessToken }) => { description="Maximum tokens per minute allowed across all models." isEditing={isEditing} viewContent={ - values.tpm_limit != null ? {values.tpm_limit.toLocaleString()} : + values.tpm_limit != null ? ( + {values.tpm_limit.toLocaleString()} + ) : ( + + ) } editContent={ - update("tpm_limit", v)} - placeholder="Not set" + + update( + "tpm_limit", + e.target.value === "" ? null : Number(e.target.value), + ) + } + placeholder="Not set" /> } /> @@ -256,16 +331,25 @@ const TeamSSOSettings: React.FC = ({ accessToken }) => { description="Maximum requests per minute allowed across all models." isEditing={isEditing} viewContent={ - values.rpm_limit != null ? {values.rpm_limit.toLocaleString()} : + values.rpm_limit != null ? ( + {values.rpm_limit.toLocaleString()} + ) : ( + + ) } editContent={ - update("rpm_limit", v)} - placeholder="Not set" + + update( + "rpm_limit", + e.target.value === "" ? null : Number(e.target.value), + ) + } + placeholder="Not set" /> } /> @@ -274,8 +358,10 @@ const TeamSSOSettings: React.FC = ({ accessToken }) => { {/* Access & Permissions */}
-
Access & Permissions
-
+
+ Access & Permissions +
+
= ({ accessToken }) => { isEditing={isEditing} viewContent={renderTags(values.team_member_permissions)} editContent={ - { + if (v) + update("team_member_permissions", [ + ...(editedValues.team_member_permissions ?? []), + v, + ]); + }} + > + + + + + {remainingPermissions.length === 0 ? ( +
+ No more permissions available +
+ ) : ( + remainingPermissions.map((option) => ( + + {option} + + )) + )} +
+ + {(editedValues.team_member_permissions ?? []).length > 0 && ( +
+ {(editedValues.team_member_permissions ?? []).map( + (p) => ( + + {p} + + + ), + )} +
)} - > - {PERMISSION_OPTIONS.map((option) => ( - - {option} - - ))} - +
} />