Merge pull request #3929 from BerriAI/litellm_edit_user_role_admin_ui

[UI] edit user role admin UI
This commit is contained in:
Ishaan Jaff 2024-05-30 16:29:52 -07:00 committed by GitHub
commit b6ec312f60
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 85 additions and 8 deletions

View file

@ -7791,7 +7791,6 @@ async def user_auth(request: Request):
params = { params = {
"sender_name": "LiteLLM Proxy", "sender_name": "LiteLLM Proxy",
"sender_email": os.getenv("SMTP_SENDER_EMAIL"),
"receiver_email": user_email, "receiver_email": user_email,
"subject": "Your Magic Link", "subject": "Your Magic Link",
"html": f"<strong> Follow this link, to login:\n\n{base_url}user/?token={response['token']}&user_id={response['user_id']}&page={page_params}</strong>", "html": f"<strong> Follow this link, to login:\n\n{base_url}user/?token={response['token']}&user_id={response['user_id']}&page={page_params}</strong>",
@ -7801,6 +7800,42 @@ async def user_auth(request: Request):
return "Email sent!" 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( @router.get(
"/user/info", "/user/info",
tags=["Internal User management"], tags=["Internal User management"],

View file

@ -23,12 +23,13 @@ import {
interface EditUserModalProps { interface EditUserModalProps {
visible: boolean; visible: boolean;
possibleUIRoles: null | Record<string, Record<string, string>>;
onCancel: () => void; onCancel: () => void;
user: any; user: any;
onSubmit: (data: any) => void; onSubmit: (data: any) => void;
} }
const EditUserModal: React.FC<EditUserModalProps> = ({ visible, onCancel, user, onSubmit }) => { const EditUserModal: React.FC<EditUserModalProps> = ({ visible, possibleUIRoles, onCancel, user, onSubmit }) => {
const [editedUser, setEditedUser] = useState(user); const [editedUser, setEditedUser] = useState(user);
const [form] = Form.useForm(); const [form] = Form.useForm();
@ -43,9 +44,9 @@ const EditUserModal: React.FC<EditUserModalProps> = ({ visible, onCancel, user,
const handleEditSubmit = async (formValues: Record<string, any>) => { const handleEditSubmit = async (formValues: Record<string, any>) => {
// Call API to update team with teamId and values // Call API to update team with teamId and values
form.resetFields();
onSubmit(formValues); onSubmit(formValues);
form.resetFields();
onCancel(); onCancel();
}; };
@ -94,8 +95,14 @@ const EditUserModal: React.FC<EditUserModalProps> = ({ visible, onCancel, user,
name="user_role" name="user_role"
> >
<Select2> <Select2>
<Select2.Option value="proxy_admin">Proxy Admin (Can create, edit, delete keys, teams)</Select2.Option> {possibleUIRoles &&
<Select2.Option value="proxy_admin_viewer">Proxy Viewer (Can just view spend, cannot created keys, teams)</Select2.Option> Object.entries(possibleUIRoles).map(([role, { ui_label, description }]) => (
<SelectItem key={role} value={role} title={ui_label}>
<div className='flex'>
{ui_label} <p className="ml-2" style={{ color: "gray", fontSize: "12px" }}>{description}</p>
</div>
</SelectItem>
))}
</Select2> </Select2>
</Form.Item> </Form.Item>

View file

@ -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 ( export const teamCreateCall = async (
accessToken: string, accessToken: string,
formValues: Record<string, any> // Assuming formValues is an object formValues: Record<string, any> // Assuming formValues is an object

View file

@ -24,7 +24,7 @@ import {
Icon, Icon,
TextInput, TextInput,
} from "@tremor/react"; } from "@tremor/react";
import { userInfoCall, userUpdateUserCall } from "./networking"; import { userInfoCall, userUpdateUserCall, getPossibleUserRoles } from "./networking";
import { Badge, BadgeDelta, Button } from "@tremor/react"; import { Badge, BadgeDelta, Button } from "@tremor/react";
import RequestAccess from "./request_model_access"; import RequestAccess from "./request_model_access";
import CreateUser from "./create_user_button"; import CreateUser from "./create_user_button";
@ -62,6 +62,7 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
const [selectedItem, setSelectedItem] = useState<null | any>(null); const [selectedItem, setSelectedItem] = useState<null | any>(null);
const [editModalVisible, setEditModalVisible] = useState(false); const [editModalVisible, setEditModalVisible] = useState(false);
const [selectedUser, setSelectedUser] = useState(null); const [selectedUser, setSelectedUser] = useState(null);
const [possibleUIRoles, setPossibleUIRoles] = useState<Record<string, Record<string, string>>>({});
const defaultPageSize = 25; const defaultPageSize = 25;
const handleEditCancel = async () => { const handleEditCancel = async () => {
@ -76,7 +77,7 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
return; return;
} }
userUpdateUserCall(accessToken, editedUser, userRole); userUpdateUserCall(accessToken, editedUser, null);
if (userData) { if (userData) {
const updatedUserData = userData.map((user) => const updatedUserData = userData.map((user) =>
@ -106,11 +107,16 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
); );
console.log("user data response:", userDataResponse); console.log("user data response:", userDataResponse);
setUserData(userDataResponse); setUserData(userDataResponse);
const availableUserRoles = await getPossibleUserRoles(accessToken);
setPossibleUIRoles(availableUserRoles);
} catch (error) { } catch (error) {
console.error("There was an error fetching the model data", error); console.error("There was an error fetching the model data", error);
} }
}; };
if (accessToken && token && userRole && userID) { if (accessToken && token && userRole && userID) {
fetchData(); fetchData();
} }
@ -273,6 +279,7 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
</TabGroup> </TabGroup>
<EditUserModal <EditUserModal
visible={editModalVisible} visible={editModalVisible}
possibleUIRoles={possibleUIRoles}
onCancel={handleEditCancel} onCancel={handleEditCancel}
user={selectedUser} user={selectedUser}
onSubmit={handleEditSubmit} onSubmit={handleEditSubmit}