From 7c6e48c780a2114f196cfd17c40c1483f886ffc8 Mon Sep 17 00:00:00 2001 From: Achintya Rajan Date: Thu, 9 Oct 2025 11:09:08 -0700 Subject: [PATCH] useAuthorized fix, teams refactor progress --- .../app/(dashboard)/hooks/useAuthorized.ts | 31 +- .../src/app/(dashboard)/layout.tsx | 16 +- .../components/TeamsTable/ModelsCell.tsx | 101 +++++++ .../components/TeamsTable/TeamsTable.tsx | 172 +++++++++++ .../(dashboard)/teams/hooks/useFetchTeams.ts | 30 ++ .../src/app/(dashboard)/teams/page.tsx | 4 - .../src/app/(dashboard)/teams/teams.tsx | 274 ++---------------- ui/litellm-dashboard/src/app/page.tsx | 1 - 8 files changed, 358 insertions(+), 271 deletions(-) create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/teams/components/TeamsTable/ModelsCell.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/teams/components/TeamsTable/TeamsTable.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/teams/hooks/useFetchTeams.ts diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.ts index 435000f9c88..0ce0cab183f 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.ts @@ -5,6 +5,35 @@ import { useRouter } from "next/navigation"; import { jwtDecode } from "jwt-decode"; import { clearTokenCookies, getCookie } from "@/utils/cookieUtils"; +function formatUserRole(userRole: string) { + if (!userRole) { + return "Undefined Role"; + } + switch (userRole.toLowerCase()) { + case "app_owner": + return "App Owner"; + case "demo_app_owner": + return "App Owner"; + case "app_admin": + return "Admin"; + case "proxy_admin": + return "Admin"; + case "proxy_admin_viewer": + return "Admin Viewer"; + case "org_admin": + return "Org Admin"; + case "internal_user": + return "Internal User"; + case "internal_user_viewer": + case "internal_viewer": // TODO:remove if deprecated + return "Internal Viewer"; + case "app_user": + return "App User"; + default: + return "Unknown Role"; + } +} + const useAuthorized = () => { const router = useRouter(); @@ -35,7 +64,7 @@ const useAuthorized = () => { accessToken: decoded?.key ?? null, userId: decoded?.user_id ?? null, userEmail: decoded?.user_email ?? null, - userRole: decoded?.user_role ?? null, + userRole: formatUserRole(decoded?.user_role ?? null), premiumUser: decoded?.premium_user ?? null, disabledPersonalKeyCreation: decoded?.disabled_non_admin_personal_key_creation ?? null, showSSOBanner: decoded?.login_method === "username_password" ?? false, diff --git a/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx b/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx index 68ab361356f..97837ff8e0a 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx @@ -25,7 +25,7 @@ function withBase(path: string): string { export default function Layout({ children }: { children: React.ReactNode }) { const router = useRouter(); const searchParams = useSearchParams(); - const { accessToken, userRole } = useAuthorized(); + const { accessToken, userRole, userId, userEmail, premiumUser } = useAuthorized(); const [sidebarCollapsed, setSidebarCollapsed] = React.useState(false); const [page, setPage] = useState(() => { return searchParams.get("page") || "api-keys"; @@ -51,15 +51,13 @@ export default function Layout({ children }: { children: React.ReactNode }) { isPublicPage={false} sidebarCollapsed={sidebarCollapsed} onToggleSidebar={toggleSidebar} - userID={null} - userEmail={null} - userRole={null} - premiumUser={false} + userID={userId} + userEmail={userEmail} + userRole={userRole} + premiumUser={premiumUser} proxySettings={undefined} - setProxySettings={function (value: any): void { - throw new Error("Function not implemented."); - }} - accessToken={null} + setProxySettings={() => {}} + accessToken={accessToken} />
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/teams/components/TeamsTable/ModelsCell.tsx b/ui/litellm-dashboard/src/app/(dashboard)/teams/components/TeamsTable/ModelsCell.tsx new file mode 100644 index 00000000000..0ffd3ae22ea --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/teams/components/TeamsTable/ModelsCell.tsx @@ -0,0 +1,101 @@ +import { Badge, Icon, TableCell, Text } from "@tremor/react"; +import { ChevronDownIcon, ChevronRightIcon } from "@heroicons/react/outline"; +import { getModelDisplayName } from "@/components/key_team_helpers/fetch_available_models_team_key"; +import React, { useState } from "react"; +import { Team } from "@/components/key_team_helpers/key_list"; + +interface ModelsCellProps { + team: Team; +} + +const ModelsCell = ({ team }: ModelsCellProps) => { + const [expandedAccordions, setExpandedAccordions] = useState>({}); + + return ( + 3 ? "px-0" : ""} + > +
+ {Array.isArray(team.models) ? ( +
+ {team.models.length === 0 ? ( + + All Proxy Models + + ) : ( + <> +
+ {team.models.length > 3 && ( +
+ { + setExpandedAccordions((prev) => ({ + ...prev, + [team.team_id]: !prev[team.team_id], + })); + }} + /> +
+ )} +
+ {team.models.slice(0, 3).map((model: string, index: number) => + model === "all-proxy-models" ? ( + + All Proxy Models + + ) : ( + + + {model.length > 30 + ? `${getModelDisplayName(model).slice(0, 30)}...` + : getModelDisplayName(model)} + + + ), + )} + {team.models.length > 3 && !expandedAccordions[team.team_id] && ( + + + +{team.models.length - 3} {team.models.length - 3 === 1 ? "more model" : "more models"} + + + )} + {expandedAccordions[team.team_id] && ( +
+ {team.models.slice(3).map((model: string, index: number) => + model === "all-proxy-models" ? ( + + All Proxy Models + + ) : ( + + + {model.length > 30 + ? `${getModelDisplayName(model).slice(0, 30)}...` + : getModelDisplayName(model)} + + + ), + )} +
+ )} +
+
+ + )} +
+ ) : null} +
+
+ ); +}; + +export default ModelsCell; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/teams/components/TeamsTable/TeamsTable.tsx b/ui/litellm-dashboard/src/app/(dashboard)/teams/components/TeamsTable/TeamsTable.tsx new file mode 100644 index 00000000000..3ffdbc40629 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/teams/components/TeamsTable/TeamsTable.tsx @@ -0,0 +1,172 @@ +import { + Badge, + Button, + Icon, + Table, + TableBody, + TableCell, + TableHead, + TableHeaderCell, + TableRow, + Text, +} from "@tremor/react"; +import { Tooltip } from "antd"; +import { formatNumberWithCommas } from "@/utils/dataUtils"; +import { ChevronDownIcon, ChevronRightIcon, PencilAltIcon, TrashIcon } from "@heroicons/react/outline"; +import { getModelDisplayName } from "@/components/key_team_helpers/fetch_available_models_team_key"; +import React, { useState } from "react"; +import { type KeyResponse, Team } from "@/components/key_team_helpers/key_list"; +import { Member, Organization } from "@/components/networking"; +import ModelsCell from "@/app/(dashboard)/teams/components/TeamsTable/ModelsCell"; + +type TeamsTableProps = { + teams: Team[] | null; + currentOrg: Organization | null; + perTeamInfo: Record; + userRole: string | null; + setSelectedTeamId: (teamId: string) => void; + setEditTeam: (editTeam: boolean) => void; + onDeleteTeam: (teamId: string) => void; +}; + +interface TeamInfo { + members_with_roles: Member[]; +} + +interface PerTeamInfo { + keys: KeyResponse[]; + team_info: TeamInfo; +} + +const TeamsTable = ({ + teams, + currentOrg, + setSelectedTeamId, + perTeamInfo, + userRole, + setEditTeam, + onDeleteTeam, +}: TeamsTableProps) => { + return ( + + + + Team Name + Team ID + Created + Spend (USD) + Budget (USD) + Models + Organization + Info + + + + + {teams && teams.length > 0 + ? teams + .filter((team) => { + if (!currentOrg) return true; + return team.organization_id === currentOrg.organization_id; + }) + .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()) + .map((team: any) => ( + + + {team["team_alias"]} + + +
+ + + +
+
+ + {team.created_at ? new Date(team.created_at).toLocaleDateString() : "N/A"} + + + {formatNumberWithCommas(team["spend"], 4)} + + + {team["max_budget"] !== null && team["max_budget"] !== undefined ? team["max_budget"] : "No limit"} + + + {team.organization_id} + + + {perTeamInfo && + team.team_id && + perTeamInfo[team.team_id] && + perTeamInfo[team.team_id].keys && + perTeamInfo[team.team_id].keys.length}{" "} + Keys + + + {perTeamInfo && + team.team_id && + perTeamInfo[team.team_id] && + perTeamInfo[team.team_id].team_info && + perTeamInfo[team.team_id].team_info.members_with_roles && + perTeamInfo[team.team_id].team_info.members_with_roles.length}{" "} + Members + + + + {userRole == "Admin" ? ( + <> + { + setSelectedTeamId(team.team_id); + setEditTeam(true); + }} + /> + onDeleteTeam(team.team_id)} icon={TrashIcon} size="sm" /> + + ) : null} + +
+ )) + : null} +
+
+ ); +}; + +export default TeamsTable; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/teams/hooks/useFetchTeams.ts b/ui/litellm-dashboard/src/app/(dashboard)/teams/hooks/useFetchTeams.ts new file mode 100644 index 00000000000..c02787896f9 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/teams/hooks/useFetchTeams.ts @@ -0,0 +1,30 @@ +import { useCallback, useEffect, useState } from "react"; +import { fetchTeams } from "@/components/common_components/fetch_teams"; +import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; +import { Organization, Team } from "@/components/networking"; + +interface useFetchTeamsProps { + currentOrg: Organization | null; + setTeams: (teams: Team[] | null) => void; +} + +const useFetchTeams = ({ currentOrg, setTeams }: useFetchTeamsProps) => { + const [lastRefreshed, setLastRefreshed] = useState(""); + const { accessToken, userId, userRole } = useAuthorized(); + + const onRefreshClick = useCallback(() => { + const currentDate = new Date(); + setLastRefreshed(currentDate.toLocaleString()); + }, []); + + useEffect(() => { + if (accessToken) { + fetchTeams(accessToken, userId, userRole, currentOrg, setTeams).then(); + } + onRefreshClick(); + }, [accessToken, currentOrg, lastRefreshed, onRefreshClick, setTeams, userId, userRole]); + + return { lastRefreshed, setLastRefreshed, onRefreshClick }; +}; + +export default useFetchTeams; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/teams/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/teams/page.tsx index 00f482a6faf..531caabd12a 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/teams/page.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/teams/page.tsx @@ -10,9 +10,6 @@ import { fetchOrganizations } from "@/components/organizations"; const TeamsPage = () => { const { accessToken, userId, userRole } = useAuthorized(); const { teams, setTeams } = useTeams(); - const [searchParams, setSearchParams] = useState(() => - typeof window === "undefined" ? new URLSearchParams() : new URLSearchParams(window.location.search), - ); const [organizations, setOrganizations] = useState([]); useEffect(() => { @@ -22,7 +19,6 @@ const TeamsPage = () => { return ( >; userID: string | null; @@ -70,18 +60,6 @@ interface FilterState { sort_order: "asc" | "desc"; } -interface EditTeamModalProps { - visible: boolean; - onCancel: () => void; - team: any; // Assuming TeamType is a type representing your team object - onSubmit: (data: FormData) => void; // Assuming FormData is the type of data to be submitted -} - -import { teamCreateCall, Member, v2TeamListCall } from "@/components/networking"; -import { updateExistingKeys } from "@/utils/dataUtils"; -import TeamsHeaderTabs from "@/app/(dashboard)/teams/components/TeamsHeaderTabs"; -import TeamsFilters from "@/app/(dashboard)/teams/components/TeamsFilters"; - interface TeamInfo { members_with_roles: Member[]; } @@ -112,7 +90,6 @@ const getOrganizationModels = (organization: Organization | null, userModels: st const Teams: React.FC = ({ teams, - searchParams, accessToken, setTeams, userID, @@ -120,7 +97,6 @@ const Teams: React.FC = ({ organizations, premiumUser = false, }) => { - const [lastRefreshed, setLastRefreshed] = useState(""); const [currentOrg, setCurrentOrg] = useState(null); const [currentOrgForCreateTeam, setCurrentOrgForCreateTeam] = useState(null); const [showFilters, setShowFilters] = useState(false); @@ -132,15 +108,6 @@ const Teams: React.FC = ({ sort_order: "desc", }); - useEffect(() => { - console.log(`inside useeffect - ${lastRefreshed}`); - if (accessToken) { - // Call your function here - fetchTeams(accessToken, userID, userRole, currentOrg, setTeams); - } - handleRefreshClick(); - }, [lastRefreshed]); - const [form] = Form.useForm(); const [memberForm] = Form.useForm(); @@ -158,12 +125,12 @@ const Teams: React.FC = ({ // Add this state near the other useState declarations const [guardrailsList, setGuardrailsList] = useState([]); - const [expandedAccordions, setExpandedAccordions] = useState>({}); const [loggingSettings, setLoggingSettings] = useState([]); const [mcpAccessGroups, setMcpAccessGroups] = useState([]); const [mcpAccessGroupsLoaded, setMcpAccessGroupsLoaded] = useState(false); const [deleteConfirmInput, setDeleteConfirmInput] = useState(""); const [modelAliases, setModelAliases] = useState<{ [key: string]: string }>({}); + const { lastRefreshed, onRefreshClick: handleRefreshClick } = useFetchTeams({ currentOrg, setTeams }); useEffect(() => { console.log(`currentOrgForCreateTeam: ${currentOrgForCreateTeam}`); @@ -426,12 +393,6 @@ const Teams: React.FC = ({ return false; }; - const handleRefreshClick = () => { - // Update the 'lastRefreshed' state to the current date and time - const currentDate = new Date(); - setLastRefreshed(currentDate.toLocaleString()); - }; - const handleFilterChange = (key: keyof FilterState, value: string) => { const newFilters = { ...filters, [key]: value }; setFilters(newFilters); @@ -565,214 +526,15 @@ const Teams: React.FC = ({ />
- - - - Team Name - Team ID - Created - Spend (USD) - Budget (USD) - Models - Organization - Info - - - - - {teams && teams.length > 0 - ? teams - .filter((team) => { - if (!currentOrg) return true; - return team.organization_id === currentOrg.organization_id; - }) - .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()) - .map((team: any) => ( - - - {team["team_alias"]} - - -
- - - -
-
- - {team.created_at ? new Date(team.created_at).toLocaleDateString() : "N/A"} - - - {formatNumberWithCommas(team["spend"], 4)} - - - {team["max_budget"] !== null && team["max_budget"] !== undefined - ? team["max_budget"] - : "No limit"} - - 3 ? "px-0" : ""} - > -
- {Array.isArray(team.models) ? ( -
- {team.models.length === 0 ? ( - - All Proxy Models - - ) : ( - <> -
- {team.models.length > 3 && ( -
- { - setExpandedAccordions((prev) => ({ - ...prev, - [team.team_id]: !prev[team.team_id], - })); - }} - /> -
- )} -
- {team.models.slice(0, 3).map((model: string, index: number) => - model === "all-proxy-models" ? ( - - All Proxy Models - - ) : ( - - - {model.length > 30 - ? `${getModelDisplayName(model).slice(0, 30)}...` - : getModelDisplayName(model)} - - - ), - )} - {team.models.length > 3 && !expandedAccordions[team.team_id] && ( - - - +{team.models.length - 3}{" "} - {team.models.length - 3 === 1 ? "more model" : "more models"} - - - )} - {expandedAccordions[team.team_id] && ( -
- {team.models.slice(3).map((model: string, index: number) => - model === "all-proxy-models" ? ( - - All Proxy Models - - ) : ( - - - {model.length > 30 - ? `${getModelDisplayName(model).slice(0, 30)}...` - : getModelDisplayName(model)} - - - ), - )} -
- )} -
-
- - )} -
- ) : null} -
-
- - {team.organization_id} - - - {perTeamInfo && - team.team_id && - perTeamInfo[team.team_id] && - perTeamInfo[team.team_id].keys && - perTeamInfo[team.team_id].keys.length}{" "} - Keys - - - {perTeamInfo && - team.team_id && - perTeamInfo[team.team_id] && - perTeamInfo[team.team_id].team_info && - perTeamInfo[team.team_id].team_info.members_with_roles && - perTeamInfo[team.team_id].team_info.members_with_roles.length}{" "} - Members - - - - {userRole == "Admin" ? ( - <> - { - setSelectedTeamId(team.team_id); - setEditTeam(true); - }} - /> - handleDelete(team.team_id)} icon={TrashIcon} size="sm" /> - - ) : null} - -
- )) - : null} -
-
+ {isDeleteModalOpen && (() => { const team = teams?.find((t) => t.team_id === teamToDelete); diff --git a/ui/litellm-dashboard/src/app/page.tsx b/ui/litellm-dashboard/src/app/page.tsx index 4b5e19b60f9..d7262afefb9 100644 --- a/ui/litellm-dashboard/src/app/page.tsx +++ b/ui/litellm-dashboard/src/app/page.tsx @@ -379,7 +379,6 @@ export default function CreateKeyPage() {