forked from phoenix/litellm-mirror
Merge pull request #3904 from BerriAI/litellm_ui_edit_users
[Admin UI] Edit `Internal Users`
This commit is contained in:
commit
40e7560d6b
3 changed files with 207 additions and 31 deletions
136
ui/litellm-dashboard/src/components/edit_user.tsx
Normal file
136
ui/litellm-dashboard/src/components/edit_user.tsx
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogPanel,
|
||||||
|
TextInput,
|
||||||
|
Button,
|
||||||
|
Select,
|
||||||
|
SelectItem,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
Subtitle,
|
||||||
|
} from '@tremor/react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button as Button2,
|
||||||
|
Modal,
|
||||||
|
Form,
|
||||||
|
Input,
|
||||||
|
Select as Select2,
|
||||||
|
InputNumber,
|
||||||
|
message,
|
||||||
|
} from "antd";
|
||||||
|
|
||||||
|
interface EditUserModalProps {
|
||||||
|
visible: boolean;
|
||||||
|
onCancel: () => void;
|
||||||
|
user: any;
|
||||||
|
onSubmit: (data: any) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EditUserModal: React.FC<EditUserModalProps> = ({ visible, onCancel, user, onSubmit }) => {
|
||||||
|
const [editedUser, setEditedUser] = useState(user);
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
form.resetFields();
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
|
const handleChange = (e) => {
|
||||||
|
setEditedUser({ ...editedUser, [e.target.name]: e.target.value });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = async () => {
|
||||||
|
form.resetFields();
|
||||||
|
onCancel();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditSubmit = async (formValues: Record<string, any>) => {
|
||||||
|
// Call API to update team with teamId and values
|
||||||
|
form.resetFields();
|
||||||
|
|
||||||
|
onSubmit(formValues);
|
||||||
|
onCancel();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
visible={visible}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
footer={null}
|
||||||
|
title={"Edit User " + user.user_id}
|
||||||
|
width={1000}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
onFinish={handleEditSubmit}
|
||||||
|
initialValues={user} // Pass initial values here
|
||||||
|
labelCol={{ span: 8 }}
|
||||||
|
wrapperCol={{ span: 16 }}
|
||||||
|
labelAlign="left"
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
<Form.Item
|
||||||
|
className="mt-8"
|
||||||
|
label="User Email"
|
||||||
|
tooltip="Email of the User"
|
||||||
|
name="user_email">
|
||||||
|
<TextInput />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label="user_id"
|
||||||
|
name="user_id"
|
||||||
|
hidden={true}
|
||||||
|
>
|
||||||
|
<TextInput />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label="User Role"
|
||||||
|
name="user_role"
|
||||||
|
>
|
||||||
|
<Select2>
|
||||||
|
<Select2.Option value="proxy_admin">Proxy Admin (Can create, edit, delete keys, teams)</Select2.Option>
|
||||||
|
<Select2.Option value="proxy_admin_viewer">Proxy Viewer (Can just view spend, cannot created keys, teams)</Select2.Option>
|
||||||
|
</Select2>
|
||||||
|
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label="Spend (USD)"
|
||||||
|
name="spend"
|
||||||
|
tooltip="(float) - Spend of all LLM calls completed by this user"
|
||||||
|
>
|
||||||
|
<InputNumber min={0} step={1} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label="User Budget (USD)"
|
||||||
|
name="max_budget"
|
||||||
|
tooltip="(float) - Maximum budget of this user"
|
||||||
|
>
|
||||||
|
<InputNumber min={0} step={1} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<div style={{ textAlign: "right", marginTop: "10px" }}>
|
||||||
|
<Button2 htmlType="submit">Save</Button2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</>
|
||||||
|
|
||||||
|
</Form>
|
||||||
|
|
||||||
|
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditUserModal;
|
|
@ -79,7 +79,7 @@ const Sidebar: React.FC<SidebarProps> = ({
|
||||||
|
|
||||||
{userRole == "Admin" ? (
|
{userRole == "Admin" ? (
|
||||||
<Menu.Item key="5" onClick={() => setPage("users")}>
|
<Menu.Item key="5" onClick={() => setPage("users")}>
|
||||||
<Text>Users</Text>
|
<Text>Internal Users</Text>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
|
|
@ -24,12 +24,18 @@ import {
|
||||||
Icon,
|
Icon,
|
||||||
TextInput,
|
TextInput,
|
||||||
} from "@tremor/react";
|
} from "@tremor/react";
|
||||||
import { userInfoCall } from "./networking";
|
import { userInfoCall, userUpdateUserCall } 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";
|
||||||
|
import EditUserModal from "./edit_user";
|
||||||
import Paragraph from "antd/es/skeleton/Paragraph";
|
import Paragraph from "antd/es/skeleton/Paragraph";
|
||||||
import InformationCircleIcon from "@heroicons/react/outline/InformationCircleIcon";
|
import {
|
||||||
|
PencilAltIcon,
|
||||||
|
InformationCircleIcon,
|
||||||
|
TrashIcon,
|
||||||
|
} from "@heroicons/react/outline";
|
||||||
|
|
||||||
|
|
||||||
interface ViewUserDashboardProps {
|
interface ViewUserDashboardProps {
|
||||||
accessToken: string | null;
|
accessToken: string | null;
|
||||||
|
@ -55,8 +61,35 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
|
||||||
const [currentPage, setCurrentPage] = useState(0);
|
const [currentPage, setCurrentPage] = useState(0);
|
||||||
const [openDialogId, setOpenDialogId] = React.useState<null | number>(null);
|
const [openDialogId, setOpenDialogId] = React.useState<null | number>(null);
|
||||||
const [selectedItem, setSelectedItem] = useState<null | any>(null);
|
const [selectedItem, setSelectedItem] = useState<null | any>(null);
|
||||||
|
const [editModalVisible, setEditModalVisible] = useState(false);
|
||||||
|
const [selectedUser, setSelectedUser] = useState(null);
|
||||||
const defaultPageSize = 25;
|
const defaultPageSize = 25;
|
||||||
|
|
||||||
|
const handleEditCancel = async () => {
|
||||||
|
setSelectedUser(null);
|
||||||
|
setEditModalVisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditSubmit = async (editedUser: any) => {
|
||||||
|
console.log("inside handleEditSubmit:", editedUser);
|
||||||
|
|
||||||
|
if (!accessToken || !token || !userRole || !userID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
userUpdateUserCall(accessToken, editedUser, userRole);
|
||||||
|
|
||||||
|
if (userData) {
|
||||||
|
const updatedUserData = userData.map((user) =>
|
||||||
|
user.user_id === editedUser.user_id ? editedUser : user
|
||||||
|
);
|
||||||
|
setUserData(updatedUserData);
|
||||||
|
}
|
||||||
|
setSelectedUser(null);
|
||||||
|
setEditModalVisible(false);
|
||||||
|
// Close the modal
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!accessToken || !token || !userRole || !userID) {
|
if (!accessToken || !token || !userRole || !userID) {
|
||||||
return;
|
return;
|
||||||
|
@ -146,7 +179,8 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
|
||||||
<TableHeaderCell>User Models</TableHeaderCell>
|
<TableHeaderCell>User Models</TableHeaderCell>
|
||||||
<TableHeaderCell>User Spend ($ USD)</TableHeaderCell>
|
<TableHeaderCell>User Spend ($ USD)</TableHeaderCell>
|
||||||
<TableHeaderCell>User Max Budget ($ USD)</TableHeaderCell>
|
<TableHeaderCell>User Max Budget ($ USD)</TableHeaderCell>
|
||||||
<TableHeaderCell>User API Key Aliases</TableHeaderCell>
|
<TableHeaderCell>API Keys</TableHeaderCell>
|
||||||
|
<TableHeaderCell></TableHeaderCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
|
@ -173,9 +207,13 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
|
||||||
(key: any) => key !== null
|
(key: any) => key !== null
|
||||||
).length > 0 ? (
|
).length > 0 ? (
|
||||||
<Badge size={"xs"} color={"indigo"}>
|
<Badge size={"xs"} color={"indigo"}>
|
||||||
{user.key_aliases
|
{
|
||||||
.filter((key: any) => key !== null)
|
user.key_aliases.filter(
|
||||||
.join(", ")}
|
(key: any) => key !== null
|
||||||
|
).length
|
||||||
|
|
||||||
|
}
|
||||||
|
Keys
|
||||||
</Badge>
|
</Badge>
|
||||||
) : (
|
) : (
|
||||||
<Badge size={"xs"} color={"gray"}>
|
<Badge size={"xs"} color={"gray"}>
|
||||||
|
@ -188,12 +226,29 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
{/* <Text>{user.key_aliases.filter(key => key !== null).length} Keys</Text> */}
|
{/* <Text>{user.key_aliases.filter(key => key !== null).length} Keys</Text> */}
|
||||||
{/* <Icon icon={InformationCircleIcon} onClick= {() => {
|
</Grid>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
|
||||||
|
<Icon icon={InformationCircleIcon} onClick= {() => {
|
||||||
setOpenDialogId(user.user_id)
|
setOpenDialogId(user.user_id)
|
||||||
setSelectedItem(user)
|
setSelectedItem(user)
|
||||||
}}>View Keys</Icon> */}
|
}}>View Keys</Icon>
|
||||||
</Grid>
|
|
||||||
|
|
||||||
|
<Icon icon={PencilAltIcon} onClick= {() => {
|
||||||
|
setSelectedUser(user)
|
||||||
|
setEditModalVisible(true)
|
||||||
|
}}>View Keys</Icon>
|
||||||
|
|
||||||
|
<Icon icon={TrashIcon} onClick= {() => {
|
||||||
|
setOpenDialogId(user.user_id)
|
||||||
|
setSelectedItem(user)
|
||||||
|
}}>View Keys</Icon>
|
||||||
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
|
@ -226,30 +281,15 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabPanels>
|
</TabPanels>
|
||||||
</TabGroup>
|
</TabGroup>
|
||||||
|
<EditUserModal
|
||||||
|
visible={editModalVisible}
|
||||||
|
onCancel={handleEditCancel}
|
||||||
|
user={selectedUser}
|
||||||
|
onSubmit={handleEditSubmit}
|
||||||
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
{renderPagination()}
|
{renderPagination()}
|
||||||
</Grid>
|
</Grid>
|
||||||
{/* <Dialog
|
|
||||||
open={openDialogId !== null}
|
|
||||||
onClose={() => {
|
|
||||||
setOpenDialogId(null);
|
|
||||||
}}
|
|
||||||
|
|
||||||
>
|
|
||||||
<DialogPanel>
|
|
||||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
|
||||||
<Title>Key Aliases</Title>
|
|
||||||
|
|
||||||
<Text>
|
|
||||||
{selectedItem && selectedItem.key_aliases
|
|
||||||
? selectedItem.key_aliases.filter(key => key !== null).length > 0
|
|
||||||
? selectedItem.key_aliases.filter(key => key !== null).join(', ')
|
|
||||||
: 'No Keys'
|
|
||||||
: "No Keys"}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
</DialogPanel>
|
|
||||||
</Dialog> */}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue