From 1080c7014ed98b617061cb26a51c525dbf3f24cb Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Fri, 2 Aug 2024 16:19:40 -0700 Subject: [PATCH] build(ui): allow admin_viewer to view teams tab Allows admin viewe role to see available teams on proxy ui --- litellm/proxy/auth/user_api_key_auth.py | 16 +++------ .../management_endpoints/team_endpoints.py | 11 +++++-- .../src/components/leftnav.tsx | 3 ++ .../src/components/networking.tsx | 33 +++++++++++++++++++ ui/litellm-dashboard/src/components/teams.tsx | 22 +++++++++++++ 5 files changed, 72 insertions(+), 13 deletions(-) diff --git a/litellm/proxy/auth/user_api_key_auth.py b/litellm/proxy/auth/user_api_key_auth.py index 8f78857d5..693fda2cb 100644 --- a/litellm/proxy/auth/user_api_key_auth.py +++ b/litellm/proxy/auth/user_api_key_auth.py @@ -1037,14 +1037,7 @@ async def user_api_key_auth( # /model/info just shows models user has access to pass elif route == "/team/info": - # check if key can access this team's info - query_params = request.query_params - team_id = query_params.get("team_id") - if team_id != valid_token.team_id: - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, - detail="key not allowed to access this team's info", - ) + pass # handled by function itself elif ( _has_user_setup_sso() and route in LiteLLMRoutes.sso_only_routes.value @@ -1171,6 +1164,7 @@ async def user_api_key_auth( # No token was found when looking up in the DB raise Exception("Invalid proxy server token passed") if valid_token_dict is not None: + user_role = _get_user_role(user_id_information=user_id_information) if user_id_information is not None and _is_user_proxy_admin( user_id_information ): @@ -1183,14 +1177,14 @@ async def user_api_key_auth( elif _has_user_setup_sso() and route in LiteLLMRoutes.sso_only_routes.value: return UserAPIKeyAuth( api_key=api_key, - user_role=LitellmUserRoles.INTERNAL_USER, + user_role=user_role or LitellmUserRoles.INTERNAL_USER, parent_otel_span=parent_otel_span, **valid_token_dict, ) else: return UserAPIKeyAuth( api_key=api_key, - user_role=LitellmUserRoles.INTERNAL_USER, + user_role=user_role or LitellmUserRoles.INTERNAL_USER, parent_otel_span=parent_otel_span, **valid_token_dict, ) @@ -1282,7 +1276,7 @@ def _is_user_proxy_admin(user_id_information: Optional[list]): return False -def _get_user_role(user_id_information: Optional[list]): +def _get_user_role(user_id_information: Optional[list]) -> Optional[str]: if user_id_information is None: return None diff --git a/litellm/proxy/management_endpoints/team_endpoints.py b/litellm/proxy/management_endpoints/team_endpoints.py index fd46dbd89..2c19bc25b 100644 --- a/litellm/proxy/management_endpoints/team_endpoints.py +++ b/litellm/proxy/management_endpoints/team_endpoints.py @@ -766,7 +766,11 @@ async def team_info( detail={"message": "Malformed request. No team id passed in."}, ) - if user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN.value: + if ( + user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN.value + or user_api_key_dict.user_role + == LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY.value + ): pass elif user_api_key_dict.team_id is None or ( team_id != user_api_key_dict.team_id @@ -916,7 +920,10 @@ async def list_team( prisma_client, ) - if user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN: + if ( + user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN + and user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY + ): raise HTTPException( status_code=401, detail={ diff --git a/ui/litellm-dashboard/src/components/leftnav.tsx b/ui/litellm-dashboard/src/components/leftnav.tsx index 7d2de838a..b33dda982 100644 --- a/ui/litellm-dashboard/src/components/leftnav.tsx +++ b/ui/litellm-dashboard/src/components/leftnav.tsx @@ -31,6 +31,9 @@ const Sidebar: React.FC = ({ setPage("usage")}> Usage + setPage("teams")}> + Teams + setPage("caching")}> Caching diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index d85f73c3a..eed406b59 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -636,6 +636,39 @@ export const teamInfoCall = async ( } }; +export const teamListCall = async ( + accessToken: String, +) => { + /** + * Get all available teams on proxy + */ + try { + let url = proxyBaseUrl ? `${proxyBaseUrl}/team/list` : `/team/list`; + console.log("in teamInfoCall"); + 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("API Response:", data); + return data; + // Handle success - you might want to update some state or UI based on the created key + } catch (error) { + console.error("Failed to create key:", error); + throw error; + } +}; + export const getTotalSpendCall = async (accessToken: String) => { /** * Get all models on proxy diff --git a/ui/litellm-dashboard/src/components/teams.tsx b/ui/litellm-dashboard/src/components/teams.tsx index bd8a8d095..6f7180f7f 100644 --- a/ui/litellm-dashboard/src/components/teams.tsx +++ b/ui/litellm-dashboard/src/components/teams.tsx @@ -62,6 +62,7 @@ import { teamMemberAddCall, Member, modelAvailableCall, + teamListCall } from "./networking"; const Team: React.FC = ({ @@ -72,6 +73,27 @@ const Team: React.FC = ({ userID, userRole, }) => { + + if (teams && teams.length > 0) { + console.log(`Received teams: ${JSON.stringify(teams, null, 2)}`); + } else { + console.log("No teams received or teams array is empty."); + } + + useEffect(() => { + console.log(`inside useeffect - ${teams}`) + if (teams === null && accessToken) { + // Call your function here + const fetchData = async () => { + const givenTeams = await teamListCall(accessToken) + console.log(`givenTeams: ${givenTeams}`) + + setTeams(givenTeams) + } + fetchData() + } + }, [teams]); + const [form] = Form.useForm(); const [memberForm] = Form.useForm(); const { Title, Paragraph } = Typography;