diff --git a/ui/litellm-dashboard/src/components/view_users/user_info_view.tsx b/ui/litellm-dashboard/src/components/view_users/user_info_view.tsx index 823baaf64ef..59d2b8e3867 100644 --- a/ui/litellm-dashboard/src/components/view_users/user_info_view.tsx +++ b/ui/litellm-dashboard/src/components/view_users/user_info_view.tsx @@ -1,6 +1,6 @@ import React, { useState } from "react" import { Card, Text, Button, Grid, Col, Tab, TabList, TabGroup, TabPanel, TabPanels, Title, Badge } from "@tremor/react" -import { ArrowLeftIcon, TrashIcon, RefreshIcon } from "@heroicons/react/outline" +import { ArrowLeftIcon, TrashIcon, RefreshIcon, ChevronDownIcon, ChevronUpIcon } from "@heroicons/react/outline" import { userInfoCall, userDeleteCall, @@ -14,9 +14,9 @@ import { rolesWithWriteAccess } from "../../utils/roles" import { UserEditView } from "../user_edit_view" import OnboardingModal, { InvitationLink } from "../onboarding_link" import { formatNumberWithCommas, copyToClipboard as utilCopyToClipboard } from "@/utils/dataUtils" -import { CopyIcon, CheckIcon } from "lucide-react"; -import NotificationsManager from "../molecules/notifications_manager"; -import { getBudgetDurationLabel } from "../common_components/budget_duration_dropdown"; +import { CopyIcon, CheckIcon } from "lucide-react" +import NotificationsManager from "../molecules/notifications_manager" +import { getBudgetDurationLabel } from "../common_components/budget_duration_dropdown" interface UserInfoViewProps { userId: string @@ -57,16 +57,17 @@ export default function UserInfoView({ initialTab = 0, startInEditMode = false, }: UserInfoViewProps) { - const [userData, setUserData] = useState(null); - const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); - const [isLoading, setIsLoading] = useState(true); - const [isEditing, setIsEditing] = useState(startInEditMode); - const [userModels, setUserModels] = useState([]); - const [isInvitationLinkModalVisible, setIsInvitationLinkModalVisible] = useState(false); - const [invitationLinkData, setInvitationLinkData] = useState(null); - const [baseUrl, setBaseUrl] = useState(null); - const [activeTab, setActiveTab] = useState(initialTab); - const [copiedStates, setCopiedStates] = useState>({}); + const [userData, setUserData] = useState(null) + const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false) + const [isLoading, setIsLoading] = useState(true) + const [isEditing, setIsEditing] = useState(startInEditMode) + const [userModels, setUserModels] = useState([]) + const [isInvitationLinkModalVisible, setIsInvitationLinkModalVisible] = useState(false) + const [invitationLinkData, setInvitationLinkData] = useState(null) + const [baseUrl, setBaseUrl] = useState(null) + const [activeTab, setActiveTab] = useState(initialTab) + const [copiedStates, setCopiedStates] = useState>({}) + const [isTeamsExpanded, setIsTeamsExpanded] = useState(false) React.useEffect(() => { setBaseUrl(getProxyBaseUrl()) @@ -175,14 +176,14 @@ export default function UserInfoView({ } const copyToClipboard = async (text: string, key: string) => { - const success = await utilCopyToClipboard(text); + const success = await utilCopyToClipboard(text) if (success) { - setCopiedStates((prev) => ({ ...prev, [key]: true })); + setCopiedStates((prev) => ({ ...prev, [key]: true })) setTimeout(() => { - setCopiedStates((prev) => ({ ...prev, [key]: false })); - }, 2000); + setCopiedStates((prev) => ({ ...prev, [key]: false })) + }, 2000) } - }; + } return (
@@ -200,9 +201,9 @@ export default function UserInfoView({ icon={copiedStates["user-id"] ? : } onClick={() => copyToClipboard(userData.user_id, "user-id")} className={`left-2 z-10 transition-all duration-200 ${ - copiedStates["user-id"] - ? 'text-green-600 bg-green-50 border-green-200' - : 'text-gray-500 hover:text-gray-700 hover:bg-gray-100' + copiedStates["user-id"] + ? "text-green-600 bg-green-50 border-green-200" + : "text-gray-500 hover:text-gray-700 hover:bg-gray-100" }`} />
@@ -282,15 +283,44 @@ export default function UserInfoView({ - Teams +
+ Teams + {userData.teams?.length && userData.teams?.length > 20 && ( + + )} +
{userData.teams?.length && userData.teams?.length > 0 ? (
- {userData.teams?.map((team, index) => ( - + {userData.teams?.slice(0, isTeamsExpanded ? userData.teams.length : 20).map((team, index) => ( + {team.team_alias} ))} + {!isTeamsExpanded && userData.teams?.length > 20 && ( +
+ + +{userData.teams.length - 20} more + +
+
+ {userData.teams?.slice(20).map((team, index) => ( +
+ {team.team_alias} +
+ ))} +
+
+
+
+ )}
) : ( No teams @@ -354,9 +384,9 @@ export default function UserInfoView({ icon={copiedStates["user-id"] ? : } onClick={() => copyToClipboard(userData.user_id, "user-id")} className={`left-2 z-10 transition-all duration-200 ${ - copiedStates["user-id"] - ? 'text-green-600 bg-green-50 border-green-200' - : 'text-gray-500 hover:text-gray-700 hover:bg-gray-100' + copiedStates["user-id"] + ? "text-green-600 bg-green-50 border-green-200" + : "text-gray-500 hover:text-gray-700 hover:bg-gray-100" }`} />
@@ -391,14 +421,49 @@ export default function UserInfoView({
- Teams +
+ Teams + {userData.teams?.length && userData.teams?.length > 20 && ( + + )} +
{userData.teams?.length && userData.teams?.length > 0 ? ( - userData.teams?.map((team, index) => ( - - {team.team_alias || team.team_id} - - )) + <> + {userData.teams?.slice(0, isTeamsExpanded ? userData.teams.length : 20).map((team, index) => ( + + {team.team_alias || team.team_id} + + ))} + {!isTeamsExpanded && userData.teams?.length > 20 && ( +
+ + +{userData.teams.length - 20} more + +
+
+ {userData.teams?.slice(20).map((team, index) => ( +
+ {team.team_alias || team.team_id} +
+ ))} +
+
+
+
+ )} + ) : ( No teams )}