forked from phoenix/litellm-mirror
Merge pull request #2101 from BerriAI/litellm_allow_admin_create_users
[FEAT] UI - View all users
This commit is contained in:
commit
c656eaf7a4
5 changed files with 126 additions and 6 deletions
|
@ -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 ##
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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",
|
||||
|
|
88
ui/litellm-dashboard/src/components/view_users.tsx
Normal file
88
ui/litellm-dashboard/src/components/view_users.tsx
Normal 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;
|
Loading…
Add table
Add a link
Reference in a new issue