From b10006876d34610e9ae17ab400ecc4f818301e1e Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 30 May 2024 16:11:58 -0700 Subject: [PATCH 1/3] fix - edit user role on admin ui --- .../src/components/edit_user.tsx | 15 +++++++--- .../src/components/networking.tsx | 28 +++++++++++++++++++ .../src/components/view_users.tsx | 9 +++++- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/ui/litellm-dashboard/src/components/edit_user.tsx b/ui/litellm-dashboard/src/components/edit_user.tsx index 850793d01b..4932abe192 100644 --- a/ui/litellm-dashboard/src/components/edit_user.tsx +++ b/ui/litellm-dashboard/src/components/edit_user.tsx @@ -23,12 +23,13 @@ import { interface EditUserModalProps { visible: boolean; + possibleUIRoles: null | Record>; onCancel: () => void; user: any; onSubmit: (data: any) => void; } -const EditUserModal: React.FC = ({ visible, onCancel, user, onSubmit }) => { +const EditUserModal: React.FC = ({ visible, possibleUIRoles, onCancel, user, onSubmit }) => { const [editedUser, setEditedUser] = useState(user); const [form] = Form.useForm(); @@ -94,9 +95,15 @@ const EditUserModal: React.FC = ({ visible, onCancel, user, name="user_role" > - Proxy Admin (Can create, edit, delete keys, teams) - Proxy Viewer (Can just view spend, cannot created keys, teams) - + {possibleUIRoles && + Object.entries(possibleUIRoles).map(([role, { ui_label, description }]) => ( + +
+ {ui_label}

{description}

+
+
+ ))} + diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index 523c951b59..5299c0392a 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -1390,6 +1390,34 @@ export const userGetAllUsersCall = async ( } }; +export const getPossibleUserRoles = async ( + accessToken: String, +) => { + try { + const url = proxyBaseUrl + ? `${proxyBaseUrl}/user/available_roles` + : `/user/available_roles`; + const response = await fetch(url, { + method: "GET", + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + }); + + if (!response.ok) { + const errorData = await response.text(); + throw new Error("Network response was not ok"); + } + const data = await response.json(); + console.log("response from user/available_role", data); + return data; + // Handle success - you might want to update some state or UI based on the created key + } catch (error) { + throw error; + } +}; + export const teamCreateCall = async ( accessToken: string, formValues: Record // Assuming formValues is an object diff --git a/ui/litellm-dashboard/src/components/view_users.tsx b/ui/litellm-dashboard/src/components/view_users.tsx index b87524bc6e..9e28913285 100644 --- a/ui/litellm-dashboard/src/components/view_users.tsx +++ b/ui/litellm-dashboard/src/components/view_users.tsx @@ -24,7 +24,7 @@ import { Icon, TextInput, } from "@tremor/react"; -import { userInfoCall, userUpdateUserCall } from "./networking"; +import { userInfoCall, userUpdateUserCall, getPossibleUserRoles } from "./networking"; import { Badge, BadgeDelta, Button } from "@tremor/react"; import RequestAccess from "./request_model_access"; import CreateUser from "./create_user_button"; @@ -63,6 +63,7 @@ const ViewUserDashboard: React.FC = ({ const [selectedItem, setSelectedItem] = useState(null); const [editModalVisible, setEditModalVisible] = useState(false); const [selectedUser, setSelectedUser] = useState(null); + const [possibleUIRoles, setPossibleUIRoles] = useState>>({}); const defaultPageSize = 25; const handleEditCancel = async () => { @@ -107,11 +108,16 @@ const ViewUserDashboard: React.FC = ({ ); console.log("user data response:", userDataResponse); setUserData(userDataResponse); + + const availableUserRoles = await getPossibleUserRoles(accessToken); + setPossibleUIRoles(availableUserRoles); + } catch (error) { console.error("There was an error fetching the model data", error); } }; + if (accessToken && token && userRole && userID) { fetchData(); } @@ -283,6 +289,7 @@ const ViewUserDashboard: React.FC = ({ Date: Thu, 30 May 2024 16:12:41 -0700 Subject: [PATCH 2/3] fix ui_get_available_role --- litellm/proxy/proxy_server.py | 37 ++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 45c2abdf02..a13e730029 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -7791,7 +7791,6 @@ async def user_auth(request: Request): params = { "sender_name": "LiteLLM Proxy", - "sender_email": os.getenv("SMTP_SENDER_EMAIL"), "receiver_email": user_email, "subject": "Your Magic Link", "html": f" Follow this link, to login:\n\n{base_url}user/?token={response['token']}&user_id={response['user_id']}&page={page_params}", @@ -7801,6 +7800,42 @@ async def user_auth(request: Request): return "Email sent!" +@router.get( + "/user/available_roles", + tags=["Internal User management"], + include_in_schema=False, + dependencies=[Depends(user_api_key_auth)], +) +async def ui_get_available_role( + user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), +): + """ + Endpoint used by Admin UI to show all available roles to assign a user + return { + "proxy_admin": { + "description": "Proxy Admin role", + "ui_label": "Admin" + } + } + """ + + _data_to_return = {} + for role in LitellmUserRoles: + + # We only show a subset of roles on UI + if role in [ + LitellmUserRoles.PROXY_ADMIN, + LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY, + LitellmUserRoles.INTERNAL_USER, + LitellmUserRoles.INTERNAL_USER_VIEW_ONLY, + ]: + _data_to_return[role.value] = { + "description": role.description, + "ui_label": role.ui_label, + } + return _data_to_return + + @router.get( "/user/info", tags=["Internal User management"], From a5fbc0bd3effc1609dccba2e6a18483e3ce4783a Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 30 May 2024 16:28:50 -0700 Subject: [PATCH 3/3] fix edit user roles on admin ui --- .../src/components/edit_user.tsx | 22 +++++++++---------- .../src/components/view_users.tsx | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ui/litellm-dashboard/src/components/edit_user.tsx b/ui/litellm-dashboard/src/components/edit_user.tsx index 4932abe192..0d5d14e64d 100644 --- a/ui/litellm-dashboard/src/components/edit_user.tsx +++ b/ui/litellm-dashboard/src/components/edit_user.tsx @@ -44,9 +44,9 @@ const EditUserModal: React.FC = ({ visible, possibleUIRoles, const handleEditSubmit = async (formValues: Record) => { // Call API to update team with teamId and values - form.resetFields(); - onSubmit(formValues); + + form.resetFields(); onCancel(); }; @@ -95,15 +95,15 @@ const EditUserModal: React.FC = ({ visible, possibleUIRoles, name="user_role" > - {possibleUIRoles && - Object.entries(possibleUIRoles).map(([role, { ui_label, description }]) => ( - -
- {ui_label}

{description}

-
-
- ))} -
+ {possibleUIRoles && + Object.entries(possibleUIRoles).map(([role, { ui_label, description }]) => ( + +
+ {ui_label}

{description}

+
+
+ ))} + diff --git a/ui/litellm-dashboard/src/components/view_users.tsx b/ui/litellm-dashboard/src/components/view_users.tsx index 9e28913285..ea1625514c 100644 --- a/ui/litellm-dashboard/src/components/view_users.tsx +++ b/ui/litellm-dashboard/src/components/view_users.tsx @@ -78,7 +78,7 @@ const ViewUserDashboard: React.FC = ({ return; } - userUpdateUserCall(accessToken, editedUser, userRole); + userUpdateUserCall(accessToken, editedUser, null); if (userData) { const updatedUserData = userData.map((user) =>