mirror of
https://github.com/BerriAI/litellm.git
synced 2025-04-27 11:43:54 +00:00
Merge pull request #3929 from BerriAI/litellm_edit_user_role_admin_ui
[UI] edit user role admin UI
This commit is contained in:
commit
b6ec312f60
4 changed files with 85 additions and 8 deletions
|
@ -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"],
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue