From dfdd74f9ac0a2186f2a2b53e9b10080c4f181b45 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Thu, 11 Dec 2025 17:37:09 -0800 Subject: [PATCH] Usage View Select --- .../UsagePage/components/UsagePageView.tsx | 826 +++++++++--------- .../UsageViewSelect/UsageViewSelect.test.tsx | 0 .../UsageViewSelect/UsageViewSelect.tsx | 171 ++++ 3 files changed, 586 insertions(+), 411 deletions(-) create mode 100644 ui/litellm-dashboard/src/components/UsagePage/components/UsageViewSelect/UsageViewSelect.test.tsx create mode 100644 ui/litellm-dashboard/src/components/UsagePage/components/UsageViewSelect/UsageViewSelect.tsx diff --git a/ui/litellm-dashboard/src/components/UsagePage/components/UsagePageView.tsx b/ui/litellm-dashboard/src/components/UsagePage/components/UsagePageView.tsx index d52e9d7a94..1c4dba8458 100644 --- a/ui/litellm-dashboard/src/components/UsagePage/components/UsagePageView.tsx +++ b/ui/litellm-dashboard/src/components/UsagePage/components/UsagePageView.tsx @@ -51,6 +51,7 @@ import UserAgentActivity from "../../user_agent_activity"; import ViewUserSpend from "../../view_user_spend"; import { useAgents } from "@/app/(dashboard)/hooks/agents/useAgents"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; +import { UsageViewSelect, UsageOption } from "./UsageViewSelect/UsageViewSelect"; interface UsagePageProps { teams: Team[]; @@ -86,7 +87,8 @@ const UsagePage: React.FC = ({ teams, organizations }) => { const [isGlobalExportModalOpen, setIsGlobalExportModalOpen] = useState(false); const [showOrganizationBanner, setShowOrganizationBanner] = useState(true); const [showCustomerBanner, setShowCustomerBanner] = useState(true); - + const [usageView, setUsageView] = useState("global"); + const [showAgentBanner, setShowAgentBanner] = useState(true); const getAllTags = async () => { if (!accessToken) { return; @@ -416,431 +418,433 @@ const UsagePage: React.FC = ({ teams, organizations }) => { {/* Global Date Picker and Tabs - Single Row */}
- -
- - {all_admin_roles.includes(userRole || "") ? Global Usage : Your Usage} - {all_admin_roles.includes(userRole || "") ? ( - Organization Usage - ) : ( - Your Organization Usage - )} - Team Usage - {all_admin_roles.includes(userRole || "") ? Customer Usage : <>} - {all_admin_roles.includes(userRole || "") ? Tag Usage : <>} - {all_admin_roles.includes(userRole || "") ? Agent Usage : <>} - {all_admin_roles.includes(userRole || "") ? User Agent Activity : <>} - - -
- - {/* Your Usage Panel */} - - -
- - Cost - Model Activity - Key Activity - MCP Server Activity - -
+ {/* Your Usage Panel */} + {usageView === "global" && ( + +
+ + Cost + Model Activity + Key Activity + MCP Server Activity + + +
+ + {/* Cost Panel */} + + + {/* Total Spend Card */} + + + Project Spend{" "} + {dateValue.from && dateValue.to && ( + <> + {dateValue.from.toLocaleDateString("en-US", { + month: "short", + day: "numeric", + year: dateValue.from.getFullYear() !== dateValue.to.getFullYear() ? "numeric" : undefined, + })} + {" - "} + {dateValue.to.toLocaleDateString("en-US", { + month: "short", + day: "numeric", + year: "numeric", + })} + + )} + + + + + + + + Usage Metrics + + + Total Requests + + {userSpendData.metadata?.total_api_requests?.toLocaleString() || 0} + + + + Successful Requests + + {userSpendData.metadata?.total_successful_requests?.toLocaleString() || 0} + + + + Failed Requests + + {userSpendData.metadata?.total_failed_requests?.toLocaleString() || 0} + + + + Total Tokens + + {userSpendData.metadata?.total_tokens?.toLocaleString() || 0} + + + + Average Cost per Request + + $ + {formatNumberWithCommas( + (totalSpend || 0) / (userSpendData.metadata?.total_api_requests || 1), + 4, + )} + + + + + + + {/* Daily Spend Chart */} + + + Daily Spend + {loading ? ( + + ) : ( + new Date(a.date).getTime() - new Date(b.date).getTime(), + )} + index="date" + categories={["metrics.spend"]} + colors={["cyan"]} + valueFormatter={valueFormatterSpend} + yAxisWidth={100} + showLegend={false} + customTooltip={({ payload, active }) => { + if (!active || !payload?.[0]) return null; + const data = payload[0].payload; + return ( +
+

{data.date}

+

+ Spend: ${formatNumberWithCommas(data.metrics.spend, 2)} +

+

Requests: {data.metrics.api_requests}

+

Successful: {data.metrics.successful_requests}

+

Failed: {data.metrics.failed_requests}

+

Tokens: {data.metrics.total_tokens}

+
+ ); + }} /> - - )} - > - Export Data - -
- - {/* Cost Panel */} - - - {/* Total Spend Card */} - - - Project Spend{" "} - {dateValue.from && dateValue.to && ( - <> - {dateValue.from.toLocaleDateString("en-US", { - month: "short", - day: "numeric", - year: - dateValue.from.getFullYear() !== dateValue.to.getFullYear() ? "numeric" : undefined, - })} - {" - "} - {dateValue.to.toLocaleDateString("en-US", { - month: "short", - day: "numeric", - year: "numeric", - })} - - )} - + )} + + + {/* Top API Keys */} + + + Top Virtual Keys + + + - - + {/* Top Models */} + + +
+ {modelViewType === "groups" ? "Top Public Model Names" : "Top Litellm Models"} +
+ + +
+
+ {loading ? ( + + ) : ( + { + if (!active || !payload?.[0]) return null; + const data = payload[0].payload; + return ( +
+

{data.key}

+

Spend: ${formatNumberWithCommas(data.spend, 2)}

+

Total Requests: {data.requests.toLocaleString()}

+

+ Successful: {data.successful_requests.toLocaleString()} +

+

Failed: {data.failed_requests.toLocaleString()}

+

Tokens: {data.tokens.toLocaleString()}

+
+ ); + }} + /> + )} +
+ - - - Usage Metrics - - - Total Requests - - {userSpendData.metadata?.total_api_requests?.toLocaleString() || 0} - - - - Successful Requests - - {userSpendData.metadata?.total_successful_requests?.toLocaleString() || 0} - - - - Failed Requests - - {userSpendData.metadata?.total_failed_requests?.toLocaleString() || 0} - - - - Total Tokens - - {userSpendData.metadata?.total_tokens?.toLocaleString() || 0} - - - - Average Cost per Request - - $ - {formatNumberWithCommas( - (totalSpend || 0) / (userSpendData.metadata?.total_api_requests || 1), - 4, - )} - - - - - - - {/* Daily Spend Chart */} - - - Daily Spend - {loading ? ( - - ) : ( - new Date(a.date).getTime() - new Date(b.date).getTime(), - )} - index="date" - categories={["metrics.spend"]} - colors={["cyan"]} - valueFormatter={valueFormatterSpend} - yAxisWidth={100} - showLegend={false} - customTooltip={({ payload, active }) => { - if (!active || !payload?.[0]) return null; - const data = payload[0].payload; - return ( -
-

{data.date}

-

- Spend: ${formatNumberWithCommas(data.metrics.spend, 2)} -

-

Requests: {data.metrics.api_requests}

-

Successful: {data.metrics.successful_requests}

-

Failed: {data.metrics.failed_requests}

-

Tokens: {data.metrics.total_tokens}

-
- ); - }} - /> - )} -
- - {/* Top API Keys */} - - - Top Virtual Keys - - - - - {/* Top Models */} - - -
- - {modelViewType === "groups" ? "Top Public Model Names" : "Top Litellm Models"} - -
- - -
-
- {loading ? ( - - ) : ( - + +
+ Spend by Provider +
+ {loading ? ( + + ) : ( + + + `$${formatNumberWithCommas(value, 2)}`} colors={["cyan"]} - valueFormatter={valueFormatterSpend} - layout="vertical" - yAxisWidth={200} - showLegend={false} - customTooltip={({ payload, active }) => { - if (!active || !payload?.[0]) return null; - const data = payload[0].payload; - return ( -
-

{data.key}

-

Spend: ${formatNumberWithCommas(data.spend, 2)}

-

Total Requests: {data.requests.toLocaleString()}

-

- Successful: {data.successful_requests.toLocaleString()} -

-

Failed: {data.failed_requests.toLocaleString()}

-

Tokens: {data.tokens.toLocaleString()}

-
- ); - }} /> - )} -
- - - {/* Spend by Provider */} - - -
- Spend by Provider -
- {loading ? ( - - ) : ( - - - `$${formatNumberWithCommas(value, 2)}`} - colors={["cyan"]} - /> - - - - - - Provider - Spend - Successful - Failed - Tokens + + +
+ + + Provider + Spend + Successful + Failed + Tokens + + + + {getProviderSpend() + .filter((provider) => provider.spend > 0) + .map((provider) => ( + + +
+ {provider.provider && ( + {`${provider.provider} { + const target = e.target as HTMLImageElement; + const parent = target.parentElement; + if (parent) { + const fallbackDiv = document.createElement("div"); + fallbackDiv.className = + "w-4 h-4 rounded-full bg-gray-200 flex items-center justify-center text-xs"; + fallbackDiv.textContent = provider.provider?.charAt(0) || "-"; + parent.replaceChild(fallbackDiv, target); + } + }} + /> + )} + {provider.provider} +
+
+ ${formatNumberWithCommas(provider.spend, 2)} + + {provider.successful_requests.toLocaleString()} + + + {provider.failed_requests.toLocaleString()} + + {provider.tokens.toLocaleString()}
- - - {getProviderSpend() - .filter((provider) => provider.spend > 0) - .map((provider) => ( - - -
- {provider.provider && ( - {`${provider.provider} { - const target = e.target as HTMLImageElement; - const parent = target.parentElement; - if (parent) { - const fallbackDiv = document.createElement("div"); - fallbackDiv.className = - "w-4 h-4 rounded-full bg-gray-200 flex items-center justify-center text-xs"; - fallbackDiv.textContent = provider.provider?.charAt(0) || "-"; - parent.replaceChild(fallbackDiv, target); - } - }} - /> - )} - {provider.provider} -
-
- ${formatNumberWithCommas(provider.spend, 2)} - - {provider.successful_requests.toLocaleString()} - - - {provider.failed_requests.toLocaleString()} - - {provider.tokens.toLocaleString()} -
- ))} -
-
- -
- )} -
- + ))} + + + +
+ )} + + - {/* Usage Metrics */} - -
+ {/* Usage Metrics */} + + - {/* Activity Panel */} - - - - - - - - - -
- - + {/* Activity Panel */} + + + + + + + + + + + + )} + {/* Organization Usage Panel */} - {/* Organization Usage Panel */} - - {showOrganizationBanner && ( - setShowOrganizationBanner(false)} - className="mb-5" - /> - )} - ({ - label: organization.organization_alias, - value: organization.organization_id, - })) || null - } - premiumUser={premiumUser} + {usageView === "organization" && ( + <> + {showOrganizationBanner && ( + setShowOrganizationBanner(false)} + className="mb-5" /> - + )} + ({ + label: organization.organization_alias, + value: organization.organization_id, + })) || null + } + premiumUser={premiumUser} + /> + + )} - {/* Team Usage Panel */} - - ({ - label: team.team_alias, - value: team.team_id, - })) || null - } - premiumUser={premiumUser} - dateValue={dateValue} - /> - + {/* Team Usage Panel */} + {usageView === "team" && ( + ({ + label: team.team_alias, + value: team.team_id, + })) || null + } + premiumUser={premiumUser} + dateValue={dateValue} + /> + )} - {/* Customer Usage Panel */} - - {showCustomerBanner && ( - setShowCustomerBanner(false)} - className="mb-5" - /> - )} - ({ - label: customer.alias || customer.user_id, - value: customer.user_id, - })) || null - } - premiumUser={premiumUser} - dateValue={dateValue} + {/* Customer Usage Panel */} + {usageView === "customer" && ( + <> + {showCustomerBanner && ( + setShowCustomerBanner(false)} + className="mb-5" /> - - {/* Tag Usage Panel */} - - ({ + label: customer.alias || customer.user_id, + value: customer.user_id, + })) || null + } + premiumUser={premiumUser} + dateValue={dateValue} + /> + + )} + {/* Tag Usage Panel */} + {usageView === "tag" && ( + + )} + {usageView === "agent" && ( + <> + {showAgentBanner && ( + setShowAgentBanner(false)} + className="mb-5" /> - - - ({ label: agent.agent_name, value: agent.agent_id })) || null - } - premiumUser={premiumUser} - dateValue={dateValue} - /> - - {/* User Agent Activity Panel */} - - - - - + )} + ({ label: agent.agent_name, value: agent.agent_id })) || null + } + premiumUser={premiumUser} + dateValue={dateValue} + />{" "} + + )} + {/* User Agent Activity Panel */} + {usageView === "user-agent-activity" && ( + + )}
diff --git a/ui/litellm-dashboard/src/components/UsagePage/components/UsageViewSelect/UsageViewSelect.test.tsx b/ui/litellm-dashboard/src/components/UsagePage/components/UsageViewSelect/UsageViewSelect.test.tsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ui/litellm-dashboard/src/components/UsagePage/components/UsageViewSelect/UsageViewSelect.tsx b/ui/litellm-dashboard/src/components/UsagePage/components/UsageViewSelect/UsageViewSelect.tsx new file mode 100644 index 0000000000..1cbe30ebcf --- /dev/null +++ b/ui/litellm-dashboard/src/components/UsagePage/components/UsageViewSelect/UsageViewSelect.tsx @@ -0,0 +1,171 @@ +import React from "react"; +import { Select } from "antd"; +import { + GlobalOutlined, + BankOutlined, + TeamOutlined, + ShoppingCartOutlined, + TagsOutlined, + RobotOutlined, + LineChartOutlined, + BarChartOutlined, +} from "@ant-design/icons"; +export type UsageOption = "global" | "organization" | "team" | "customer" | "tag" | "agent" | "user-agent-activity"; +export interface UsageViewSelectProps { + value: UsageOption; + onChange: (value: UsageOption) => void; + isAdmin: boolean; + title?: string; + description?: string; + "data-id"?: string; +} +interface OptionConfig { + value: UsageOption; + label: string; + description: string; + icon: React.ReactNode; + adminOnly?: boolean; + showForAdmin?: string; + showForNonAdmin?: string; + descriptionForAdmin?: string; + descriptionForNonAdmin?: string; +} +const OPTIONS: OptionConfig[] = [ + { + value: "global", + label: "Global Usage", + showForAdmin: "Global Usage", + showForNonAdmin: "Your Usage", + description: "View usage across all resources", + descriptionForAdmin: "View usage across all resources and users", + descriptionForNonAdmin: "View your personal usage statistics", + icon: , + }, + { + value: "organization", + label: "Organization Usage", + showForAdmin: "Organization Usage", + showForNonAdmin: "Your Organization Usage", + description: "View organization-level usage", + descriptionForAdmin: "View usage across all organizations", + descriptionForNonAdmin: "View your organization's usage statistics", + icon: , + }, + { + value: "team", + label: "Team Usage", + description: "View usage by team", + icon: , + }, + { + value: "customer", + label: "Customer Usage", + description: "View usage by customer accounts", + icon: , + adminOnly: true, + }, + { + value: "tag", + label: "Tag Usage", + description: "View usage grouped by tags", + icon: , + adminOnly: true, + }, + { + value: "agent", + label: "Agent Usage (A2A)", + description: "View usage by AI agents", + icon: , + adminOnly: true, + }, + { + value: "user-agent-activity", + label: "User Agent Activity", + description: "View detailed user agent activity logs", + icon: , + adminOnly: true, + }, +]; +export const UsageViewSelect: React.FC = ({ + value, + onChange, + isAdmin, + title = "Usage View", + description = "Select the usage data you want to view", + "data-id": dataId, +}) => { + const getFilteredOptions = () => { + return OPTIONS.filter((option) => { + if (option.adminOnly && !isAdmin) { + return false; + } + return true; + }).map((option) => { + let label = option.label; + let desc = option.description; + if (option.showForAdmin && option.showForNonAdmin) { + label = isAdmin ? option.showForAdmin : option.showForNonAdmin; + } + if (option.descriptionForAdmin && option.descriptionForNonAdmin) { + desc = isAdmin ? option.descriptionForAdmin : option.descriptionForNonAdmin; + } + return { + value: option.value, + label, + description: desc, + icon: option.icon, + }; + }); + }; + const filteredOptions = getFilteredOptions(); + return ( +
+
+
+
+ +
+
+

{title}

+

{description}

+
+
+
+