diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py index c7213d465c..04a692e8f0 100644 --- a/litellm/proxy/utils.py +++ b/litellm/proxy/utils.py @@ -601,7 +601,7 @@ class PrismaClient: except Exception as e: sql_query = """ CREATE VIEW "Last30dTopEndUsersSpend" AS - SELECT end_user, SUM(spend) AS total_spend + SELECT end_user, COUNT(*) AS total_events, SUM(spend) AS total_spend FROM "LiteLLM_SpendLogs" WHERE end_user <> '' AND end_user <> user AND "startTime" >= CURRENT_DATE - INTERVAL '30 days' diff --git a/ui/litellm-dashboard/src/components/view_users.tsx b/ui/litellm-dashboard/src/components/view_users.tsx index 763176a308..00329e206e 100644 --- a/ui/litellm-dashboard/src/components/view_users.tsx +++ b/ui/litellm-dashboard/src/components/view_users.tsx @@ -5,16 +5,19 @@ import { Subtitle, Table, TableHead, + TableHeaderCell, TableRow, TableCell, TableBody, Tab, TabGroup, TabList, + TabPanels, Metric, Grid, + TabPanel, } from "@tremor/react"; -import { userInfoCall } from "./networking"; +import { userInfoCall, adminTopEndUsersCall } from "./networking"; import { Badge, BadgeDelta, Button } from "@tremor/react"; import RequestAccess from "./request_model_access"; import CreateUser from "./create_user_button"; @@ -33,6 +36,7 @@ const ViewUserDashboard: React.FC = ({ userID, }) => { const [userData, setUserData] = useState(null); + const [endUsers, setEndUsers] = useState(null); const [currentPage, setCurrentPage] = useState(1); const defaultPageSize = 25; @@ -56,9 +60,26 @@ const ViewUserDashboard: React.FC = ({ } }; - if (accessToken && token && userRole && userID) { + if (accessToken && token && userRole && userID && !userData) { fetchData(); } + + const fetchEndUserSpend = async () => { + try { + const topEndUsers = await adminTopEndUsersCall(accessToken); + console.log("user data response:", topEndUsers); + setEndUsers(topEndUsers); + } catch (error) { + console.error("There was an error fetching the model data", error); + } + }; + if ( + userRole && + (userRole == "Admin" || userRole == "Admin Viewer") && + !endUsers + ) { + fetchEndUserSpend(); + } }, [accessToken, token, userRole, userID]); if (!userData) { @@ -105,53 +126,68 @@ const ViewUserDashboard: React.FC = ({
- + Key Owners End-Users + + + + + + User ID + User Role + User Models + User Spend ($ USD) + User Max Budget ($ USD) + + + + {userData.map((user: any) => ( + + {user.user_id} + + {user.user_role ? user.user_role : "app_owner"} + + + {user.models && user.models.length > 0 + ? user.models + : "All Models"} + + {user.spend ? user.spend : 0} + + {user.max_budget ? user.max_budget : "Unlimited"} + + + ))} + +
+
+ + + + + End User + Spend + Total Events + + + + + {endUsers?.map((user: any, index: number) => ( + + {user.end_user} + {user.total_spend} + {user.total_events} + + ))} + +
+
+
- - - - - User ID - - - User Role - - - User Models - - - User Spend ($ USD) - - - User Max Budget ($ USD) - - - - - {userData.map((user: any) => ( - - {user.user_id} - - {user.user_role ? user.user_role : "app_owner"} - - - {user.models && user.models.length > 0 - ? user.models - : "All Models"} - - {user.spend ? user.spend : 0} - - {user.max_budget ? user.max_budget : "Unlimited"} - - - ))} - -
{renderPagination()}