mirror of
https://github.com/BerriAI/litellm.git
synced 2025-04-27 03:34:10 +00:00
fix: enable end user tracking on ui
This commit is contained in:
parent
c371009faf
commit
7f2e12cd10
4 changed files with 130 additions and 25 deletions
|
@ -4113,6 +4113,28 @@ async def global_spend_keys(
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/global/spend/end_users",
|
||||||
|
tags=["Budget & Spend Tracking"],
|
||||||
|
dependencies=[Depends(user_api_key_auth)],
|
||||||
|
)
|
||||||
|
async def global_spend_end_users():
|
||||||
|
"""
|
||||||
|
[BETA] This is a beta endpoint. It will change.
|
||||||
|
|
||||||
|
Use this to get the top 'n' keys with the highest spend, ordered by spend.
|
||||||
|
"""
|
||||||
|
global prisma_client
|
||||||
|
|
||||||
|
if prisma_client is None:
|
||||||
|
raise HTTPException(status_code=500, detail={"error": "No db connected"})
|
||||||
|
sql_query = f"""SELECT * FROM "Last30dTopEndUsersSpend";"""
|
||||||
|
|
||||||
|
response = await prisma_client.db.query_raw(query=sql_query)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
"/global/spend/models",
|
"/global/spend/models",
|
||||||
tags=["Budget & Spend Tracking"],
|
tags=["Budget & Spend Tracking"],
|
||||||
|
|
|
@ -593,6 +593,26 @@ class PrismaClient:
|
||||||
|
|
||||||
print("Last30dModelsBySpend Created!") # noqa
|
print("Last30dModelsBySpend Created!") # noqa
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self.db.query_raw(
|
||||||
|
"""SELECT 1 FROM "Last30dTopEndUsersSpend" LIMIT 1"""
|
||||||
|
)
|
||||||
|
print("Last30dTopEndUsersSpend Exists!") # noqa
|
||||||
|
except Exception as e:
|
||||||
|
sql_query = """
|
||||||
|
CREATE VIEW "Last30dTopEndUsersSpend" AS
|
||||||
|
SELECT end_user, SUM(spend) AS total_spend
|
||||||
|
FROM "LiteLLM_SpendLogs"
|
||||||
|
WHERE end_user <> '' AND end_user <> user
|
||||||
|
AND "startTime" >= CURRENT_DATE - INTERVAL '30 days'
|
||||||
|
GROUP BY end_user
|
||||||
|
ORDER BY total_spend DESC
|
||||||
|
LIMIT 100;
|
||||||
|
"""
|
||||||
|
await self.db.execute_raw(query=sql_query)
|
||||||
|
|
||||||
|
print("Last30dTopEndUsersSpend Created!") # noqa
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@backoff.on_exception(
|
@backoff.on_exception(
|
||||||
|
|
|
@ -404,13 +404,13 @@ export const adminTopKeysCall = async (accessToken: String) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const adminTopModelsCall = async (accessToken: String) => {
|
export const adminTopEndUsersCall = async (accessToken: String) => {
|
||||||
try {
|
try {
|
||||||
let url = proxyBaseUrl
|
let url = proxyBaseUrl
|
||||||
? `${proxyBaseUrl}/global/spend/models?limit=5`
|
? `${proxyBaseUrl}/global/spend/end_users`
|
||||||
: `/global/spend/models?limit=5`;
|
: `/global/spend/end_users`;
|
||||||
|
|
||||||
message.info("Making spend models request");
|
message.info("Making top end users request");
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -426,7 +426,37 @@ export const adminTopModelsCall = async (accessToken: String) => {
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
console.log(data);
|
console.log(data);
|
||||||
message.success("Spend Logs received");
|
message.success("Top End users received");
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to create key:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const adminTopModelsCall = async (accessToken: String) => {
|
||||||
|
try {
|
||||||
|
let url = proxyBaseUrl
|
||||||
|
? `${proxyBaseUrl}/global/spend/models?limit=5`
|
||||||
|
: `/global/spend/models?limit=5`;
|
||||||
|
|
||||||
|
message.info("Making top models request");
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.text();
|
||||||
|
message.error(errorData);
|
||||||
|
throw new Error("Network response was not ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
console.log(data);
|
||||||
|
message.success("Top Models received");
|
||||||
return data;
|
return data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to create key:", error);
|
console.error("Failed to create key:", error);
|
||||||
|
|
|
@ -8,6 +8,9 @@ import {
|
||||||
TableRow,
|
TableRow,
|
||||||
TableCell,
|
TableCell,
|
||||||
TableBody,
|
TableBody,
|
||||||
|
Tab,
|
||||||
|
TabGroup,
|
||||||
|
TabList,
|
||||||
Metric,
|
Metric,
|
||||||
Grid,
|
Grid,
|
||||||
} from "@tremor/react";
|
} from "@tremor/react";
|
||||||
|
@ -29,8 +32,9 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
|
||||||
userRole,
|
userRole,
|
||||||
userID,
|
userID,
|
||||||
}) => {
|
}) => {
|
||||||
const [userData, setuserData] = useState<null | any[]>(null);
|
const [userData, setUserData] = useState<null | any[]>(null);
|
||||||
const [pendingRequests, setPendingRequests] = useState<any[]>([]);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const defaultPageSize = 25;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!accessToken || !token || !userRole || !userID) {
|
if (!accessToken || !token || !userRole || !userID) {
|
||||||
|
@ -46,7 +50,7 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
console.log("user data response:", userDataResponse);
|
console.log("user data response:", userDataResponse);
|
||||||
setuserData(userDataResponse);
|
setUserData(userDataResponse);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("There was an error fetching the model data", error);
|
console.error("There was an error fetching the model data", error);
|
||||||
}
|
}
|
||||||
|
@ -65,11 +69,49 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
|
||||||
return <div>Loading...</div>;
|
return <div>Loading...</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderPagination() {
|
||||||
|
if (!userData) return null;
|
||||||
|
|
||||||
|
const totalPages = Math.ceil(userData.length / defaultPageSize);
|
||||||
|
const startItem = (currentPage - 1) * defaultPageSize + 1;
|
||||||
|
const endItem = Math.min(currentPage * defaultPageSize, userData.length);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<div>
|
||||||
|
Showing {startItem} – {endItem} of {userData.length}
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
<button
|
||||||
|
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-l focus:outline-none"
|
||||||
|
disabled={currentPage === 1}
|
||||||
|
onClick={() => setCurrentPage(currentPage - 1)}
|
||||||
|
>
|
||||||
|
← Prev
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-r focus:outline-none"
|
||||||
|
disabled={currentPage === totalPages}
|
||||||
|
onClick={() => setCurrentPage(currentPage + 1)}
|
||||||
|
>
|
||||||
|
Next →
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ width: "100%" }}>
|
<div style={{ width: "100%" }}>
|
||||||
<Grid className="gap-2 p-10 h-[75vh] w-full">
|
<Grid className="gap-2 p-10 h-[75vh] w-full">
|
||||||
<CreateUser userID={userID} accessToken={accessToken} />
|
<CreateUser userID={userID} accessToken={accessToken} />
|
||||||
<Card>
|
<Card>
|
||||||
|
<TabGroup>
|
||||||
|
<TabList variant="line" defaultValue="1">
|
||||||
|
<Tab value="1">Key Owners</Tab>
|
||||||
|
<Tab value="2">End-Users</Tab>
|
||||||
|
</TabList>
|
||||||
|
</TabGroup>
|
||||||
<Table className="mt-5">
|
<Table className="mt-5">
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
|
@ -93,34 +135,25 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{userData.map((user: any) => (
|
{userData.map((user: any) => (
|
||||||
<TableRow key={user.user_id}>
|
<TableRow key={user.user_id}>
|
||||||
|
<TableCell>{user.user_id}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Title>{user.user_id}</Title>
|
{user.user_role ? user.user_role : "app_owner"}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Title>
|
{user.models && user.models.length > 0
|
||||||
{user.user_role ? user.user_role : "app_user"}
|
? user.models
|
||||||
</Title>
|
: "All Models"}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell>{user.spend ? user.spend : 0}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Title>
|
{user.max_budget ? user.max_budget : "Unlimited"}
|
||||||
{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>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</Card>
|
</Card>
|
||||||
|
{renderPagination()}
|
||||||
</Grid>
|
</Grid>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue