From 948b05da8646ba15f26bc2355f60d1b17c5ace33 Mon Sep 17 00:00:00 2001 From: tanjiro <56165694+NANDINI-star@users.noreply.github.com> Date: Wed, 27 Aug 2025 16:33:19 +0900 Subject: [PATCH 1/3] added badge --- .../src/components/view_users/user_info_view.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) 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 d416df2d9b..823baaf64e 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 @@ -284,7 +284,17 @@ export default function UserInfoView({ Teams
- {userData.teams?.length || 0} teams + {userData.teams?.length && userData.teams?.length > 0 ? ( +
+ {userData.teams?.map((team, index) => ( + + {team.team_alias} + + ))} +
+ ) : ( + No teams + )}
From 51f44c0419bf338c8104d08a13ec62835d45a5b7 Mon Sep 17 00:00:00 2001 From: tanjiro <56165694+NANDINI-star@users.noreply.github.com> Date: Tue, 2 Sep 2025 19:26:09 +0900 Subject: [PATCH 2/3] limit to 20 teams and make it expandable after that --- .../components/view_users/user_info_view.tsx | 133 +++++++++++++----- 1 file changed, 99 insertions(+), 34 deletions(-) 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 823baaf64e..59d2b8e386 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 )} From 80970951c500ec1a61bfa8546d9fd3d472a08c9f Mon Sep 17 00:00:00 2001 From: tanjiro <56165694+NANDINI-star@users.noreply.github.com> Date: Tue, 2 Sep 2025 19:36:06 +0900 Subject: [PATCH 3/3] fix ui for expandable badge --- .../components/view_users/user_info_view.tsx | 90 +++++++------------ 1 file changed, 33 insertions(+), 57 deletions(-) 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 59d2b8e386..c36bde78a7 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, ChevronDownIcon, ChevronUpIcon } from "@heroicons/react/outline" +import { ArrowLeftIcon, TrashIcon, RefreshIcon } from "@heroicons/react/outline" import { userInfoCall, userDeleteCall, @@ -283,19 +283,7 @@ export default function UserInfoView({ -
- Teams - {userData.teams?.length && userData.teams?.length > 20 && ( - - )} -
+ Teams
{userData.teams?.length && userData.teams?.length > 0 ? (
@@ -305,21 +293,22 @@ export default function UserInfoView({ ))} {!isTeamsExpanded && userData.teams?.length > 20 && ( -
- - +{userData.teams.length - 20} more - -
-
- {userData.teams?.slice(20).map((team, index) => ( -
- {team.team_alias} -
- ))} -
-
-
-
+ setIsTeamsExpanded(true)} + > + +{userData.teams.length - 20} more + + )} + {isTeamsExpanded && userData.teams?.length > 20 && ( + setIsTeamsExpanded(false)} + > + Show Less + )}
) : ( @@ -421,19 +410,7 @@ export default function UserInfoView({
-
- Teams - {userData.teams?.length && userData.teams?.length > 20 && ( - - )} -
+ Teams
{userData.teams?.length && userData.teams?.length > 0 ? ( <> @@ -447,21 +424,20 @@ export default function UserInfoView({ ))} {!isTeamsExpanded && userData.teams?.length > 20 && ( -
- - +{userData.teams.length - 20} more - -
-
- {userData.teams?.slice(20).map((team, index) => ( -
- {team.team_alias || team.team_id} -
- ))} -
-
-
-
+ setIsTeamsExpanded(true)} + > + +{userData.teams.length - 20} more + + )} + {isTeamsExpanded && userData.teams?.length > 20 && ( + setIsTeamsExpanded(false)} + > + Show Less + )} ) : (