Merge pull request #2101 from BerriAI/litellm_allow_admin_create_users

[FEAT] UI - View all users
This commit is contained in:
Ishaan Jaff 2024-02-20 15:42:04 -08:00 committed by GitHub
commit c656eaf7a4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 126 additions and 6 deletions

View file

@ -4020,7 +4020,11 @@ async def user_auth(request: Request):
async def user_info(
user_id: Optional[str] = fastapi.Query(
default=None, description="User ID in the request parameters"
)
),
view_all: bool = fastapi.Query(
default=False,
description="set to true to View all users. When using view_all, don't pass user_id",
),
):
"""
Use this to get user information. (user row + all user key info)
@ -4040,6 +4044,11 @@ async def user_info(
## GET USER ROW ##
if user_id is not None:
user_info = await prisma_client.get_data(user_id=user_id)
elif view_all == True:
user_info = await prisma_client.get_data(
table_name="user", query_type="find_all"
)
return user_info
else:
user_info = None
## GET ALL TEAMS ##

View file

@ -4,6 +4,7 @@ import { useSearchParams } from "next/navigation";
import Navbar from "../components/navbar";
import UserDashboard from "../components/user_dashboard";
import ModelDashboard from "@/components/model_dashboard";
import ViewUserDashboard from "@/components/view_users";
import ChatUI from "@/components/chat_ui";
import Sidebar from "../components/leftnav";
import Usage from "../components/usage";
@ -68,12 +69,13 @@ const CreateKeyPage = () => {
}
}
return (
<Suspense fallback={<div>Loading...</div>}>
<div className="flex flex-col min-h-screen">
<Navbar userID={userID} userRole={userRole} userEmail={userEmail} />
<div className="flex flex-1 overflow-auto">
<Sidebar setPage={setPage} />
<Sidebar setPage={setPage} userRole={userRole}/>
{page == "api-keys" ? (
<UserDashboard
userID={userID}
@ -97,6 +99,14 @@ const CreateKeyPage = () => {
accessToken={accessToken}
/>
)
: page == "users" ? (
<ViewUserDashboard
userID={userID}
userRole={userRole}
token={token}
accessToken={accessToken}
/>
)
: (
<Usage
userID={userID}

View file

@ -6,9 +6,10 @@ const { Sider } = Layout;
// Define the props type
interface SidebarProps {
setPage: React.Dispatch<React.SetStateAction<string>>;
userRole: string;
}
const Sidebar: React.FC<SidebarProps> = ({ setPage }) => {
const Sidebar: React.FC<SidebarProps> = ({ setPage, userRole }) => {
return (
<Layout style={{ minHeight: "100vh", maxWidth: "120px" }}>
<Sider width={120}>
@ -29,6 +30,13 @@ const Sidebar: React.FC<SidebarProps> = ({ setPage }) => {
<Menu.Item key="4" onClick={() => setPage("usage")}>
Usage
</Menu.Item>
{
userRole == "Admin" ?
<Menu.Item key="5" onClick={() => setPage("users")}>
Users
</Menu.Item>
: null
}
</Menu>
</Sider>
</Layout>

View file

@ -104,14 +104,19 @@ export const keyDeleteCall = async (accessToken: String, user_key: String) => {
export const userInfoCall = async (
accessToken: String,
userID: String,
userRole: String
userID: String | null,
userRole: String,
viewAll: Boolean = false
) => {
try {
let url = proxyBaseUrl ? `${proxyBaseUrl}/user/info` : `/user/info`;
if (userRole == "App Owner") {
if (userRole == "App Owner" && userID) {
url = `${url}/?user_id=${userID}`;
}
console.log("in userInfoCall viewAll=", viewAll);
if (viewAll) {
url = `${url}/?view_all=true`;
}
message.info("Requesting user data");
const response = await fetch(url, {
method: "GET",

View file

@ -0,0 +1,88 @@
import React, { useState, useEffect } from "react";
import { Card, Title, Subtitle, Table, TableHead, TableRow, TableCell, TableBody, Metric, Grid } from "@tremor/react";
import { userInfoCall } from "./networking";
import { Badge, BadgeDelta, Button } from '@tremor/react';
import RequestAccess from "./request_model_access";
interface ViewUserDashboardProps {
accessToken: string | null;
token: string | null;
userRole: string | null;
userID: string | null;
}
const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
accessToken,
token,
userRole,
userID,
}) => {
const [userData, setuserData] = useState<null | any[]>(null);
const [pendingRequests, setPendingRequests] = useState<any[]>([]);
useEffect(() => {
if (!accessToken || !token || !userRole || !userID) {
return;
}
const fetchData = async () => {
try {
// Replace with your actual API call for model data
const userDataResponse = await userInfoCall(accessToken, null, userRole, true);
console.log("user data response:", userDataResponse);
setuserData(userDataResponse);
} catch (error) {
console.error("There was an error fetching the model data", error);
}
};
if (accessToken && token && userRole && userID) {
fetchData();
}
}, [accessToken, token, userRole, userID]);
if (!userData) {
return <div>Loading...</div>;
}
if (!accessToken || !token || !userRole || !userID) {
return <div>Loading...</div>;
}
// when users click request access show pop up to allow them to request access
return (
<div style={{ width: "100%" }}>
<Grid className="gap-2 p-10 h-[75vh] w-full">
<Card>
<Table className="mt-5">
<TableHead>
<TableRow>
<TableCell><Title>User ID </Title></TableCell>
<TableCell><Title>User Role</Title></TableCell>
<TableCell><Title>User Models</Title></TableCell>
<TableCell><Title>User Spend ($ USD)</Title></TableCell>
<TableCell><Title>User Max Budget ($ USD)</Title></TableCell>
</TableRow>
</TableHead>
<TableBody>
{userData.map((user: any) => (
<TableRow key={user.user_id}>
<TableCell><Title>{user.user_id}</Title></TableCell>
<TableCell><Title>{user.user_role ? user.user_role : "app_user"}</Title></TableCell>
<TableCell><Title>{user.models && user.models.length > 0 ? user.models : "All Models"}</Title></TableCell>
<TableCell><Title>{user.spend ? user.spend : 0}</Title></TableCell>
<TableCell><Title>{user.max_budget ? user.max_budget : "Unlimited"}</Title></TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
</Grid>
</div>
);
};
export default ViewUserDashboard;