From 16364d3879d8a7d7d71a19c7b67346850898f319 Mon Sep 17 00:00:00 2001 From: ishaan-jaff Date: Sat, 3 Feb 2024 14:12:18 -0800 Subject: [PATCH 1/4] (feat) update max_budget in /user/update --- litellm/proxy/_types.py | 1 + 1 file changed, 1 insertion(+) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index e8e01602097..ed7237141e0 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -208,6 +208,7 @@ class UpdateUserRequest(GenerateRequestBase): spend: Optional[float] = None metadata: Optional[dict] = None user_role: Optional[str] = None + max_budget: Optional[float] = None class KeyManagementSystem(enum.Enum): From 78b899dc1defd561f8439a2477aadc0dbe392b09 Mon Sep 17 00:00:00 2001 From: ishaan-jaff Date: Sat, 3 Feb 2024 14:15:56 -0800 Subject: [PATCH 2/4] (feat) only allow master key to update users --- ui/litellm-dashboard/src/components/user_dashboard.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ui/litellm-dashboard/src/components/user_dashboard.tsx b/ui/litellm-dashboard/src/components/user_dashboard.tsx index f8054751eb6..a3af0580374 100644 --- a/ui/litellm-dashboard/src/components/user_dashboard.tsx +++ b/ui/litellm-dashboard/src/components/user_dashboard.tsx @@ -4,6 +4,7 @@ import { userInfoCall } from "./networking"; import { Grid, Col, Card, Text } from "@tremor/react"; import CreateKey from "./create_key_button"; import ViewKeyTable from "./view_key_table"; +import ViewUserSpend from "./view_user_spend"; import EnterProxyUrl from "./enter_proxy_url"; import Navbar from "./navbar"; import { useSearchParams } from "next/navigation"; @@ -14,6 +15,7 @@ const proxyBaseUrl = "http://localhost:4000" // http://localhost:4000 const UserDashboard = () => { const [data, setData] = useState(null); // Keep the initialization of state here + const [userSpendData, setUserSpendData] = useState(null); // Assuming useSearchParams() hook exists and works in your setup const searchParams = useSearchParams(); const userID = searchParams.get("userID"); @@ -71,6 +73,8 @@ const UserDashboard = () => { accessToken, userID ); + console.log("Response:", response); + setUserSpendData(response["user_info"]) setData(response["keys"]); // Assuming this is the correct path to your data } catch (error) { console.error("There was an error fetching the data", error); @@ -104,6 +108,10 @@ const UserDashboard = () => { /> + Date: Sat, 3 Feb 2024 14:16:14 -0800 Subject: [PATCH 3/4] (feat) only allow master key to update users --- litellm/proxy/proxy_server.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 7679d873264..1a5417d7c20 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -554,7 +554,6 @@ async def user_api_key_auth( db=custom_db_client, ) ) - if ( route.startswith("/key/") or route.startswith("/user/") @@ -589,6 +588,11 @@ async def user_api_key_auth( status_code=status.HTTP_403_FORBIDDEN, detail="user not allowed to access this key's info", ) + elif route == "/user/update": + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="only proxy admin can update user settings. Tried calling `/user/update`", + ) elif route == "/model/info": # /model/info just shows models user has access to pass From dcc6ddcecfe81f5b2dc772697f6a8ae28d7d8f5b Mon Sep 17 00:00:00 2001 From: ishaan-jaff Date: Sat, 3 Feb 2024 14:37:19 -0800 Subject: [PATCH 4/4] (feat) - ui view spend/budget for your user --- .../src/components/view_user_spend.tsx | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 ui/litellm-dashboard/src/components/view_user_spend.tsx diff --git a/ui/litellm-dashboard/src/components/view_user_spend.tsx b/ui/litellm-dashboard/src/components/view_user_spend.tsx new file mode 100644 index 00000000000..70431c9ae04 --- /dev/null +++ b/ui/litellm-dashboard/src/components/view_user_spend.tsx @@ -0,0 +1,54 @@ +"use client"; +import React, { useEffect } from "react"; +import { keyDeleteCall } from "./networking"; +import { StatusOnlineIcon, TrashIcon } from "@heroicons/react/outline"; +import { DonutChart } from "@tremor/react"; +import { + Badge, + Card, + Table, + Metric, + TableBody, + TableCell, + TableHead, + TableHeaderCell, + TableRow, + Text, + Title, + Icon, +} from "@tremor/react"; + + +// Define the props type +interface UserSpendData { + spend: number; // Adjust the type accordingly based on your data + max_budget?: number | null; // Optional property with a default of null + // Add other properties if needed + } +interface ViewUserSpendProps { + userID: string | null; + userSpendData: UserSpendData | null; // Use the UserSpendData interface here +} +const ViewUserSpend: React.FC = ({ userID, userSpendData }) => { + console.log("User SpendData:", userSpendData); + const spend = userSpendData?.spend; + const maxBudget = userSpendData?.max_budget || null; + const displayMaxBudget = maxBudget !== null ? `$${maxBudget} limit` : "No limit"; + const displayText = `$${spend} / ${displayMaxBudget}`; + + return ( + <> + + + ${spend} + + + / {displayMaxBudget} + + + + ) +} + +export default ViewUserSpend; +