refactor(ui): remove the deprecated Old Usage page (#49284)

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Kerry Lu 2026-10-30 11:43:13 -07:00 • committed by GitHub
parent 24b8889965
commit 1ccb970d52
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 4 additions and 1453 deletions

View file

@ -32,7 +32,6 @@ export const menuLabelToPage: Record<string, Page> = {
Budgets: Page.Budgets,
"API Playground": Page.TransformRequest,
"Tag Management": Page.TagManagement,
"Old Usage": Page.Usage,
// Tools submenu items
"MCP Servers": Page.McpServers,
"Vector Stores": Page.VectorStores,

View file

@ -118,12 +118,6 @@ export const MIGRATED_E2E_PAGES: Readonly<Record<string, MigratedPage>> = {
content: { role: "heading", name: "AI Hub" },
},
new_usage: { segment: "usage", linkName: "Usage", content: { role: "heading", name: "Usage" } },
usage: {
segment: "old-usage",
linkName: "Old Usage",
group: "Experimental",
content: { role: "tab", name: "All Up" },
},
agents: { segment: "agents", linkName: "Agents", group: "Agentic", content: { role: "heading", name: "Agents" } },
"router-settings": {
segment: "router-settings",

View file

@ -30,5 +30,4 @@ export enum Page {
TagManagement = "tag-management",
VectorStores = "vector-stores",
NewUsage = "new_usage",
Usage = "usage",
}

View file

@ -84,7 +84,7 @@ test.describe("Logs pagination with Aria in the header", () => {
await expect(aria).toBeFocused();
await visibleTestId(page, "pagination-first").click();
await expect(visibleTestId(page, "pagination-range")).toHaveText("Showing 1-25 of 26");
for (const destination of [Page.Usage, Page.LlmPlayground, Page.ApiKeys]) {
for (const destination of [Page.NewUsage, Page.LlmPlayground, Page.ApiKeys]) {
await navigateToPage(page, destination);
await aria.click();
await expect(page.getByRole("dialog", { name: "Aria Agent", exact: true })).toBeVisible();

View file

@ -11,7 +11,7 @@ import {
waitForSpendLog,
} from "../../helpers/traffic";
/** Covers /ui/usage. The legacy /ui/old-usage view is deprecated and deliberately not covered. */
/** Covers /ui/usage. */
const topKeysCard = (page: PlaywrightPage): Locator =>
page.locator("section").filter({ has: page.getByRole("heading", { name: "Top Virtual Keys", exact: true }) });

View file

@ -625,23 +625,6 @@
"count": 1
}
},
"src/app/(dashboard)/old-usage/_components/usage.tsx": {
"local/filename-pascal-case": {
"count": 1
},
"max-lines": {
"count": 1
},
"prefer-const": {
"count": 6
},
"react-hooks/immutability": {
"count": 1
},
"react-hooks/purity": {
"count": 1
}
},
"src/app/(dashboard)/playground/components/chat_ui/AdditionalModelSettings.tsx": {
"react-hooks/set-state-in-effect": {
"count": 2
@ -2090,17 +2073,6 @@
"count": 1
}
},
"src/components/view_user_spend.tsx": {
"local/filename-pascal-case": {
"count": 1
},
"prefer-const": {
"count": 3
},
"react-hooks/set-state-in-effect": {
"count": 2
}
},
"src/contexts/AuthContext.tsx": {
"react-hooks/set-state-in-effect": {
"count": 1

View file

@ -10,7 +10,7 @@ describe("legacyPageRedirectHref", () => {
expect(redirect("page=models")).toBe("/ui/models-and-endpoints");
expect(redirect("page=llm-playground")).toBe("/ui/playground");
expect(redirect("page=new_usage")).toBe("/ui/usage");
expect(redirect("page=usage")).toBe("/ui/old-usage");
expect(redirect("page=usage")).toBe("/ui/usage");
});
it("keeps the older aliases for renamed pages", () => {

View file

@ -36,7 +36,7 @@ const LEGACY_PAGE_ROUTES: ReadonlyMap<string, string> = new Map(
"logging-and-alerts": "logging-and-alerts",
"model-hub-table": "model-hub-table",
new_usage: "usage",
usage: "old-usage",
usage: "usage",
"cost-optimization": "cost-optimization",
"model-insights": "model-insights",
agents: "agents",

View file

@ -1,257 +0,0 @@
import React from "react";
import { describe, it, expect, vi, beforeEach } from "vitest";
import { act, screen, waitFor, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { renderWithProviders } from "../../../../../tests/test-utils";
import UsagePage from "./usage";
const networking = vi.hoisted(() => ({
adminSpendLogsCall: vi.fn(),
adminTopKeysCall: vi.fn(),
adminTopModelsCall: vi.fn(),
adminTopEndUsersCall: vi.fn(),
teamSpendLogsCall: vi.fn(),
tagsSpendLogsCall: vi.fn(),
allTagNamesCall: vi.fn(),
adminspendByProvider: vi.fn(),
adminGlobalActivity: vi.fn(),
adminGlobalActivityPerModel: vi.fn(),
getProxyUISettings: vi.fn(),
modelAvailableCall: vi.fn(),
keyInfoV1Call: vi.fn(),
}));
vi.mock("@/components/networking", () => networking);
vi.mock("../../../../components/networking", () => networking);
vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
default: () => ({
accessToken: "sk-test",
token: "tok",
userRole: "Admin",
userId: "u1",
premiumUser: true,
}),
}));
const UNLIMITED_SETTINGS = { DISABLE_EXPENSIVE_DB_QUERIES: false, NUM_SPEND_LOGS_ROWS: 10 };
const renderUsage = (overrides: Partial<React.ComponentProps<typeof UsagePage>> = {}) =>
renderWithProviders(
<UsagePage
accessToken="sk-test"
token="tok"
userRole="Admin"
userID="u1"
keys={null}
premiumUser={true}
{...overrides}
/>,
);
// Width of this window is guarded by "proves the flush window is wide enough".
const flushPendingRequests = async () => {
await act(async () => {
await new Promise((resolve) => setTimeout(resolve, 0));
await new Promise((resolve) => setTimeout(resolve, 0));
});
};
beforeEach(() => {
vi.clearAllMocks();
networking.getProxyUISettings.mockResolvedValue(UNLIMITED_SETTINGS);
networking.adminSpendLogsCall.mockResolvedValue([{ date: "2026-07-01", spend: 12.5 }]);
networking.adminTopKeysCall.mockResolvedValue([
{ api_key: "sk-abcdefghijk", key_alias: "prod-key", total_spend: 9.5 },
]);
networking.adminTopModelsCall.mockResolvedValue([{ model: "gpt-5.1", total_spend: 7.25 }]);
networking.adminTopEndUsersCall.mockResolvedValue([
{ end_user: "customer-alpha", total_spend: 3.5, total_count: 42 },
]);
networking.teamSpendLogsCall.mockResolvedValue({
daily_spend: [{ date: "2026-07-01", "team-a": 5 }],
teams: ["team-a"],
total_spend_per_team: [{ team_id: "team-a", total_spend: 5 }],
});
networking.tagsSpendLogsCall.mockResolvedValue({ spend_per_tag: [{ name: "prod", spend: 4 }] });
networking.allTagNamesCall.mockResolvedValue({ tag_names: ["prod", "staging"] });
networking.adminspendByProvider.mockResolvedValue([{ provider: "openai", spend: 6.75 }]);
networking.adminGlobalActivity.mockResolvedValue({
sum_api_requests: 120,
sum_total_tokens: 4500,
daily_data: [{ date: "2026-07-01", api_requests: 120, total_tokens: 4500 }],
});
networking.adminGlobalActivityPerModel.mockResolvedValue([]);
networking.modelAvailableCall.mockResolvedValue({ data: [] });
networking.keyInfoV1Call.mockResolvedValue({ info: {} });
});
describe("old usage page", () => {
describe("when the proxy has disabled expensive DB queries", () => {
beforeEach(() => {
networking.getProxyUISettings.mockResolvedValue({
DISABLE_EXPENSIVE_DB_QUERIES: true,
NUM_SPEND_LOGS_ROWS: 2500000,
});
});
it("shows the database query limit warning instead of the usage dashboard", async () => {
renderUsage();
expect(await screen.findByText("Database Query Limit Reached")).toBeInTheDocument();
expect(screen.getByText(/SpendLogs in DB has/)).toHaveTextContent("2500000");
expect(screen.getByText(/Please follow our guide to view usage when SpendLogs has more than 1M rows/i));
expect(screen.queryByRole("tab", { name: "All Up" })).not.toBeInTheDocument();
});
it("links to the cost tracking guide in a new tab", async () => {
renderUsage();
const link = await screen.findByRole("link", { name: "View Usage Guide" });
expect(link).toHaveAttribute("href", "https://docs.litellm.ai/docs/proxy/cost_tracking");
expect(link).toHaveAttribute("target", "_blank");
});
it("skips every expensive usage query", async () => {
renderUsage();
await screen.findByText("Database Query Limit Reached");
await waitFor(() => expect(networking.getProxyUISettings).toHaveBeenCalled());
expect(networking.adminSpendLogsCall).not.toHaveBeenCalled();
expect(networking.adminspendByProvider).not.toHaveBeenCalled();
expect(networking.adminTopKeysCall).not.toHaveBeenCalled();
expect(networking.adminTopModelsCall).not.toHaveBeenCalled();
expect(networking.adminGlobalActivity).not.toHaveBeenCalled();
expect(networking.adminGlobalActivityPerModel).not.toHaveBeenCalled();
expect(networking.teamSpendLogsCall).not.toHaveBeenCalled();
expect(networking.adminTopEndUsersCall).not.toHaveBeenCalled();
expect(networking.tagsSpendLogsCall).not.toHaveBeenCalled();
});
});
describe("as an admin", () => {
it("renders the admin tabs", async () => {
renderUsage();
expect(await screen.findByRole("tab", { name: "All Up" })).toBeInTheDocument();
expect(screen.getByRole("tab", { name: "Team Based Usage" })).toBeInTheDocument();
expect(screen.getByRole("tab", { name: "Customer Usage" })).toBeInTheDocument();
expect(screen.getByRole("tab", { name: "Tag Based Usage" })).toBeInTheDocument();
});
it("renders the cost panel cards", async () => {
renderUsage();
expect(await screen.findByText("Monthly Spend")).toBeInTheDocument();
expect(screen.getByText("Top Virtual Keys")).toBeInTheDocument();
expect(screen.getByText("Top Models")).toBeInTheDocument();
expect(screen.getByText("Spend by Provider")).toBeInTheDocument();
});
it("lists spend by provider in a table", async () => {
renderUsage();
const providerCell = await screen.findByText("openai");
const row = providerCell.closest("tr");
expect(row).not.toBeNull();
expect(within(row as HTMLElement).getByText("$6.75")).toBeInTheDocument();
expect(screen.getByRole("columnheader", { name: "Provider" })).toBeInTheDocument();
});
it("shows the customer usage table when its tab is selected", async () => {
const user = userEvent.setup();
renderUsage();
await user.click(await screen.findByRole("tab", { name: "Customer Usage" }));
const customerCell = await screen.findByText("customer-alpha");
const row = customerCell.closest("tr");
expect(row).not.toBeNull();
expect(within(row as HTMLElement).getByText("$3.50")).toBeInTheDocument();
expect(within(row as HTMLElement).getByText("42")).toBeInTheDocument();
expect(screen.getByRole("columnheader", { name: "Total Events" })).toBeInTheDocument();
});
it("shows the tag spend panel when its tab is selected", async () => {
const user = userEvent.setup();
renderUsage();
await user.click(await screen.findByRole("tab", { name: "Tag Based Usage" }));
expect(await screen.findByText("Spend Per Tag")).toBeInTheDocument();
});
it("shows the team spend panel when its tab is selected", async () => {
const user = userEvent.setup();
renderUsage();
await user.click(await screen.findByRole("tab", { name: "Team Based Usage" }));
expect(await screen.findByText("Total Spend Per Team")).toBeInTheDocument();
expect(screen.getByText("Daily Spend Per Team")).toBeInTheDocument();
});
});
// org_admin is an organization membership role; those users reach the UI as "Internal User".
describe.each(["Internal User", "Internal Viewer", "internal_user", "internal_user_viewer", "Org Admin"])(
"as %s",
(userRole) => {
it("shows the admin-only notice instead of the usage dashboard", async () => {
renderUsage({ userRole });
expect(await screen.findByText(/Proxy-wide usage is only available to admin users/i)).toBeInTheDocument();
expect(screen.queryByRole("tab", { name: "All Up" })).not.toBeInTheDocument();
});
it("fires no /global/spend or /global/activity request", async () => {
renderUsage({ userRole });
await screen.findByText(/Proxy-wide usage is only available to admin users/i);
await flushPendingRequests();
expect(networking.getProxyUISettings).not.toHaveBeenCalled();
expect(networking.adminSpendLogsCall).not.toHaveBeenCalled();
expect(networking.adminTopKeysCall).not.toHaveBeenCalled();
expect(networking.adminTopModelsCall).not.toHaveBeenCalled();
expect(networking.adminTopEndUsersCall).not.toHaveBeenCalled();
expect(networking.teamSpendLogsCall).not.toHaveBeenCalled();
expect(networking.tagsSpendLogsCall).not.toHaveBeenCalled();
expect(networking.allTagNamesCall).not.toHaveBeenCalled();
expect(networking.adminspendByProvider).not.toHaveBeenCalled();
expect(networking.adminGlobalActivity).not.toHaveBeenCalled();
expect(networking.adminGlobalActivityPerModel).not.toHaveBeenCalled();
});
},
);
describe("the admin-only gate", () => {
it("proves the flush window is wide enough to catch a leaked request", async () => {
renderUsage({ userRole: "Admin" });
await flushPendingRequests();
expect(networking.getProxyUISettings).toHaveBeenCalled();
expect(networking.adminSpendLogsCall).toHaveBeenCalled();
expect(networking.tagsSpendLogsCall).toHaveBeenCalled();
expect(networking.adminGlobalActivity).toHaveBeenCalled();
});
it("still lets an admin through, so the notice is a real gate and not a dead branch", async () => {
renderUsage({ userRole: "Admin" });
expect(await screen.findByRole("tab", { name: "All Up" })).toBeInTheDocument();
expect(screen.queryByText(/Proxy-wide usage is only available to admin users/i)).not.toBeInTheDocument();
await waitFor(() => expect(networking.adminSpendLogsCall).toHaveBeenCalled());
});
it("does not put the session token in the provider spend query", async () => {
renderUsage({ userRole: "Admin", token: "session-jwt-value" });
await waitFor(() => expect(networking.adminspendByProvider).toHaveBeenCalled());
const callArgs = networking.adminspendByProvider.mock.calls[0];
expect(callArgs).not.toContain("session-jwt-value");
expect(callArgs[0]).toBe("sk-test");
});
});
});

View file

@ -1,950 +0,0 @@
import React, { useState, useEffect } from "react";
import ViewUserSpend from "@/components/view_user_spend";
import { ProxySettings } from "@/app/(dashboard)/hooks/proxySettings/useProxySettings";
import AdvancedDatePicker from "@/components/shared/advanced_date_picker";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
Combobox,
ComboboxChip,
ComboboxChips,
ComboboxChipsInput,
ComboboxContent,
ComboboxEmpty,
ComboboxItem,
ComboboxList,
ComboboxValue,
useComboboxAnchor,
} from "@/components/ui/combobox";
import { Meter, MeterIndicator, MeterTrack } from "@/components/shared/Meter";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import {
NUMERIC_CELL_CLASS,
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { AreaChart, BarChart, DonutChart } from "@/components/shared/charts";
import {
adminSpendLogsCall,
adminTopKeysCall,
adminTopModelsCall,
adminTopEndUsersCall,
teamSpendLogsCall,
tagsSpendLogsCall,
allTagNamesCall,
adminspendByProvider,
adminGlobalActivity,
adminGlobalActivityPerModel,
getProxyUISettings,
} from "@/components/networking";
import TopKeyView from "@/components/UsagePage/components/EntityUsage/TopKeyView";
import { MoneyCell } from "@/components/shared/table_cells";
import { hasCapability } from "@/utils/capabilities";
import { formatNumberWithCommas } from "@/utils/dataUtils";
interface UsagePageProps {
accessToken: string | null;
token: string | null;
userRole: string | null;
userID: string | null;
keys: any[] | null;
premiumUser: boolean;
}
interface GlobalActivityData {
sum_api_requests: number;
sum_total_tokens: number;
daily_data: { date: string; api_requests: number; total_tokens: number }[];
}
const EMPTY_GLOBAL_ACTIVITY: GlobalActivityData = { sum_api_requests: 0, sum_total_tokens: 0, daily_data: [] };
type UsageDateRange = { from?: Date; to?: Date };
type TeamSpendTotal = { name: string; value: number };
type TagOption = { value: string; label: string; disabled: boolean };
const ALL_TAGS = "all-tags";
const isAdminOrAdminViewer = (role: string | null): boolean => {
if (role === null) return false;
return role === "Admin" || role === "Admin Viewer";
};
const TeamSpendBarList: React.FC<{ data: TeamSpendTotal[] }> = ({ data }) => {
const max = Math.max(0, ...data.map((team) => team.value));
return (
<div className="flex flex-col gap-3">
{data.map((team) => (
<div key={team.name} className="flex items-center gap-4">
<p className="w-1/3 truncate text-sm text-foreground">{team.name}</p>
<Meter value={team.value} max={max === 0 ? 1 : max} className="flex-1">
<MeterTrack>
<MeterIndicator />
</MeterTrack>
</Meter>
<p className="w-24 shrink-0 text-right text-sm tabular-nums text-foreground">
{formatNumberWithCommas(team.value, 2)}
</p>
</div>
))}
</div>
);
};
const UsagePage: React.FC<UsagePageProps> = ({ accessToken, token, userRole, userID, keys, premiumUser }) => {
const anchor = useComboboxAnchor();
const canViewGlobalSpend = hasCapability(userRole, "viewGlobalSpend");
const currentDate = new Date();
const [keySpendData, setKeySpendData] = useState<any[]>([]);
const [topKeys, setTopKeys] = useState<any[]>([]);
const [topModels, setTopModels] = useState<any[]>([]);
const [topUsers, setTopUsers] = useState<any[]>([]);
const [teamSpendData, setTeamSpendData] = useState<any[]>([]);
const [topTagsData, setTopTagsData] = useState<any[]>([]);
const [allTagNames, setAllTagNames] = useState<string[]>([]);
const [uniqueTeamIds, setUniqueTeamIds] = useState<any[]>([]);
const [totalSpendPerTeam, setTotalSpendPerTeam] = useState<TeamSpendTotal[]>([]);
const [spendByProvider, setSpendByProvider] = useState<any[]>([]);
const [globalActivity, setGlobalActivity] = useState<GlobalActivityData>(EMPTY_GLOBAL_ACTIVITY);
const [globalActivityPerModel, setGlobalActivityPerModel] = useState<any[]>([]);
const [selectedKeyToken, setSelectedKeyToken] = useState<string | null>(null);
const [selectedTags, setSelectedTags] = useState<string[]>([ALL_TAGS]);
const [dateValue, setDateValue] = useState<UsageDateRange>({
from: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
to: new Date(),
});
const [proxySettings, setProxySettings] = useState<ProxySettings | null>(null);
const [totalMonthlySpend, setTotalMonthlySpend] = useState<number>(0);
const firstDay = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1);
const lastDay = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0);
let startTime = formatDate(firstDay);
let endTime = formatDate(lastDay);
const selectableKeys: { token: string; alias: string }[] = (keys ?? [])
.filter((key: any) => key && typeof key["key_alias"] === "string" && key["key_alias"].length > 0)
.map((key: any) => ({ token: String(key["token"]), alias: String(key["key_alias"]) }));
const tagOptions: TagOption[] = [
{ value: ALL_TAGS, label: "All Tags", disabled: false },
...allTagNames
.filter((tag) => tag !== ALL_TAGS)
.map((tag) => ({
value: tag,
label: premiumUser ? tag : `✨ ${tag} (Enterprise only Feature)`,
disabled: !premiumUser,
})),
];
function valueFormatterNumbers(number: number) {
const formatter = new Intl.NumberFormat("en-US", {
maximumFractionDigits: 0,
notation: "compact",
compactDisplay: "short",
});
return formatter.format(number);
}
const fetchProxySettings = async () => {
if (accessToken) {
try {
const proxy_settings: ProxySettings = await getProxyUISettings(accessToken);
return proxy_settings;
} catch (error) {
console.error("Error fetching proxy settings:", error);
}
}
};
useEffect(() => {
if (!canViewGlobalSpend) {
return;
}
updateTagSpendData(dateValue.from, dateValue.to);
}, [canViewGlobalSpend, dateValue, selectedTags]);
const updateEndUserData = async (
startTime: Date | undefined,
endTime: Date | undefined,
uiSelectedKey: string | null,
) => {
if (!startTime || !endTime || !accessToken) {
return;
}
let newTopUserData = await adminTopEndUsersCall(
accessToken,
uiSelectedKey,
startTime.toISOString(),
endTime.toISOString(),
);
setTopUsers(newTopUserData);
};
const updateTagSpendData = async (startTime: Date | undefined, endTime: Date | undefined) => {
if (!startTime || !endTime || !accessToken) {
return;
}
// we refetch because the state variable can be None when the user refreshes the page
const proxy_settings: ProxySettings | undefined = await fetchProxySettings();
if (proxy_settings?.DISABLE_EXPENSIVE_DB_QUERIES) {
return; // Don't run expensive DB queries - return out when SpendLogs has more than 1M rows
}
let top_tags = await tagsSpendLogsCall(
accessToken,
startTime.toISOString(),
endTime.toISOString(),
selectedTags.length === 0 ? undefined : selectedTags,
);
setTopTagsData(top_tags.spend_per_tag);
};
function formatDate(date: Date) {
const year = date.getFullYear();
let month = date.getMonth() + 1; // JS month index starts from 0
let day = date.getDate();
// Pad with 0 if month or day is less than 10
const monthStr = month < 10 ? "0" + month : month;
const dayStr = day < 10 ? "0" + day : day;
return `${year}-${monthStr}-${dayStr}`;
}
const valueFormatter = (number: number) => `$ ${formatNumberWithCommas(number, 2)}`;
const fetchAndSetData = async (
fetchFunction: () => Promise<any>,
setStateFunction: React.Dispatch<React.SetStateAction<any>>,
errorMessage: string,
) => {
try {
const data = await fetchFunction();
setStateFunction(data);
} catch (error) {
console.error(errorMessage, error);
// Optionally, update UI to reflect error state for this specific data
}
};
// Update the fillMissingDates function to handle different date formats
const fillMissingDates = (data: any[], startDate: Date, endDate: Date, categories: string[]) => {
const filledData = [];
const currentDate = new Date(startDate);
// Helper function to standardize date format
const standardizeDate = (dateStr: string) => {
if (dateStr.includes("-")) {
// Already in YYYY-MM-DD format
return dateStr;
} else {
// Convert "Jan 06" format
const [month, day] = dateStr.split(" ");
const year = new Date().getFullYear();
const monthIndex = new Date(`${month} 01 2024`).getMonth();
const fullDate = new Date(year, monthIndex, parseInt(day));
return fullDate.toISOString().split("T")[0];
}
};
// Create a map of existing dates for quick lookup
const existingDates = new Map(
data.map((item) => {
const standardizedDate = standardizeDate(item.date);
return [
standardizedDate,
{
...item,
date: standardizedDate, // Store standardized date format
},
];
}),
);
// Iterate through each date in the range
while (currentDate <= endDate) {
const dateStr = currentDate.toISOString().split("T")[0];
if (existingDates.has(dateStr)) {
// Use existing data if we have it
filledData.push(existingDates.get(dateStr));
} else {
// Create an entry with zero values
const emptyEntry: any = {
date: dateStr,
api_requests: 0,
total_tokens: 0,
};
// Add zero values for each model/team if needed
categories.forEach((category) => {
if (!emptyEntry[category]) {
emptyEntry[category] = 0;
}
});
filledData.push(emptyEntry);
}
// Move to next day
currentDate.setDate(currentDate.getDate() + 1);
}
return filledData;
};
// Update the fetchOverallSpend function
const fetchOverallSpend = async () => {
if (!accessToken) {
return;
}
try {
const data = await adminSpendLogsCall(accessToken);
// Get the first and last day of the current month
const now = new Date();
const firstDay = new Date(now.getFullYear(), now.getMonth(), 1);
const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0);
// Fill in missing dates
const filledData = fillMissingDates(data, firstDay, lastDay, []);
// Calculate total spend for the month and round to 2 decimal places
const monthlyTotal = Number(filledData.reduce((sum, day) => sum + (day.spend || 0), 0).toFixed(2));
setTotalMonthlySpend(monthlyTotal);
setKeySpendData(filledData);
} catch (error) {
console.error("Error fetching overall spend:", error);
}
};
const fetchProviderSpend = () =>
fetchAndSetData(
() => (accessToken ? adminspendByProvider(accessToken, startTime, endTime) : Promise.reject("No access token")),
setSpendByProvider,
"Error fetching provider spend",
);
const fetchTopKeys = async () => {
if (!accessToken) return;
await fetchAndSetData(
async () => {
const top_keys = await adminTopKeysCall(accessToken);
return top_keys.map((k: any) => ({
key: k["api_key"].substring(0, 10),
api_key: k["api_key"],
key_alias: k["key_alias"],
spend: Number(k["total_spend"].toFixed(2)),
}));
},
setTopKeys,
"Error fetching top keys",
);
};
const fetchTopModels = async () => {
if (!accessToken) return;
await fetchAndSetData(
async () => {
const top_models = await adminTopModelsCall(accessToken);
return top_models.map((k: any) => ({
key: k["model"],
spend: formatNumberWithCommas(k["total_spend"], 2),
}));
},
setTopModels,
"Error fetching top models",
);
};
// Update the fetchTeamSpend function
const fetchTeamSpend = async () => {
if (!accessToken) return;
await fetchAndSetData(
async () => {
const teamSpend = await teamSpendLogsCall(accessToken);
// Get the first and last day of the current month
const now = new Date();
const firstDay = new Date(now.getFullYear(), now.getMonth(), 1);
const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0);
// Fill in missing dates with zero values for all teams
const filledData = fillMissingDates(teamSpend.daily_spend, firstDay, lastDay, teamSpend.teams);
setTeamSpendData(filledData);
setUniqueTeamIds(teamSpend.teams);
return teamSpend.total_spend_per_team.map((tspt: any) => ({
name: tspt["team_id"] || "",
value: Number(tspt["total_spend"] || 0),
}));
},
setTotalSpendPerTeam,
"Error fetching team spend",
);
};
const fetchTagNames = () => {
if (!accessToken) return;
fetchAndSetData(
async () => {
const all_tag_names = await allTagNamesCall(accessToken);
return all_tag_names.tag_names;
},
setAllTagNames,
"Error fetching tag names",
);
};
const fetchTopTags = () => {
if (!accessToken) return;
fetchAndSetData(
() => tagsSpendLogsCall(accessToken, dateValue.from?.toISOString(), dateValue.to?.toISOString(), undefined),
(data) => setTopTagsData(data.spend_per_tag),
"Error fetching top tags",
);
};
const fetchTopEndUsers = () => {
if (!accessToken) return;
fetchAndSetData(
() => adminTopEndUsersCall(accessToken, null, undefined, undefined),
setTopUsers,
"Error fetching top end users",
);
};
// Update the fetchGlobalActivity function
const fetchGlobalActivity = async () => {
if (!accessToken) return;
try {
const data = await adminGlobalActivity(accessToken, startTime, endTime);
// Get the date range from the current month
const now = new Date();
const firstDay = new Date(now.getFullYear(), now.getMonth(), 1);
const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0);
// Fill in missing dates for daily_data
const filledDailyData = fillMissingDates(data.daily_data || [], firstDay, lastDay, [
"api_requests",
"total_tokens",
]);
setGlobalActivity({
...data,
daily_data: filledDailyData,
});
} catch (error) {
console.error("Error fetching global activity:", error);
}
};
// Update the fetchGlobalActivityPerModel function
const fetchGlobalActivityPerModel = async () => {
if (!accessToken) return;
try {
const data = await adminGlobalActivityPerModel(accessToken, startTime, endTime);
// Get the date range from the current month
const now = new Date();
const firstDay = new Date(now.getFullYear(), now.getMonth(), 1);
const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0);
// Fill in missing dates for each model's daily data
const filledModelData = data.map((modelData: any) => ({
...modelData,
daily_data: fillMissingDates(modelData.daily_data || [], firstDay, lastDay, ["api_requests", "total_tokens"]),
}));
setGlobalActivityPerModel(filledModelData);
} catch (error) {
console.error("Error fetching global activity per model:", error);
}
};
useEffect(() => {
const initlizeUsageData = async () => {
if (!canViewGlobalSpend) {
return;
}
if (accessToken && token && userRole && userID) {
const proxy_settings: ProxySettings | undefined = await fetchProxySettings();
if (proxy_settings) {
setProxySettings(proxy_settings); // saved in state so it can be used when rendering UI
if (proxy_settings?.DISABLE_EXPENSIVE_DB_QUERIES) {
return; // Don't run expensive UI queries - return out of initlizeUsageData at this point
}
}
fetchOverallSpend();
fetchProviderSpend();
fetchTopKeys();
fetchTopModels();
fetchGlobalActivity();
fetchGlobalActivityPerModel();
if (isAdminOrAdminViewer(userRole)) {
fetchTeamSpend();
fetchTagNames();
fetchTopTags();
fetchTopEndUsers();
}
}
};
initlizeUsageData();
}, [canViewGlobalSpend, accessToken, token, userRole, userID, startTime, endTime]);
if (!canViewGlobalSpend) {
return (
<div className="w-full p-8">
<Card>
<CardHeader>
<CardTitle>Usage</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">
Proxy-wide usage is only available to admin users. Your own usage is on the Usage page.
</p>
</CardContent>
</Card>
</div>
);
}
if (proxySettings?.DISABLE_EXPENSIVE_DB_QUERIES) {
return (
<div className="w-full p-8">
<Card>
<CardHeader>
<CardTitle>Database Query Limit Reached</CardTitle>
</CardHeader>
<CardContent className="flex flex-col items-start gap-4">
<p className="text-sm text-muted-foreground">
SpendLogs in DB has {proxySettings.NUM_SPEND_LOGS_ROWS} rows.
<br></br>
Please follow our guide to view usage when SpendLogs has more than 1M rows.
</p>
<Button
render={
<a href="https://docs.litellm.ai/docs/proxy/cost_tracking" target="_blank" rel="noreferrer">
View Usage Guide
</a>
}
/>
</CardContent>
</Card>
</div>
);
}
return (
<div className="w-full p-8">
<Tabs defaultValue="all-up">
<TabsList variant="line" className="mt-2">
<TabsTrigger value="all-up">All Up</TabsTrigger>
{isAdminOrAdminViewer(userRole) && (
<>
<TabsTrigger value="team-based-usage">Team Based Usage</TabsTrigger>
<TabsTrigger value="customer-usage">Customer Usage</TabsTrigger>
<TabsTrigger value="tag-based-usage">Tag Based Usage</TabsTrigger>
</>
)}
</TabsList>
<TabsContent value="all-up" keepMounted>
<Tabs defaultValue="cost">
<TabsList className="mt-1">
<TabsTrigger value="cost">Cost</TabsTrigger>
<TabsTrigger value="activity">Activity</TabsTrigger>
</TabsList>
<TabsContent value="cost" keepMounted>
<div className="grid h-screen w-full grid-cols-2 gap-2">
<div className="col-span-2">
<p className="mt-2 mb-2 text-lg text-muted-foreground">
Project Spend {new Date().toLocaleString("default", { month: "long" })} 1 -{" "}
{new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0).getDate()}
</p>
<ViewUserSpend userSpend={totalMonthlySpend} selectedTeam={null} userMaxBudget={null} />
</div>
<div className="col-span-2">
<Card>
<CardHeader>
<CardTitle>Monthly Spend</CardTitle>
</CardHeader>
<CardContent>
<BarChart
data={keySpendData}
index="date"
categories={["spend"]}
colors={["cyan"]}
valueFormatter={valueFormatter}
yAxisWidth={100}
tickGap={5}
/>
</CardContent>
</Card>
</div>
<div className="col-span-1">
<Card className="h-full">
<CardHeader>
<CardTitle>Top Virtual Keys</CardTitle>
</CardHeader>
<CardContent>
<TopKeyView topKeys={topKeys} teams={null} topKeysLimit={5} setTopKeysLimit={() => {}} />
</CardContent>
</Card>
</div>
<div className="col-span-1">
<Card className="h-full">
<CardHeader>
<CardTitle>Top Models</CardTitle>
</CardHeader>
<CardContent>
<BarChart
className="mt-4 h-40"
data={topModels}
index="key"
categories={["spend"]}
colors={["cyan"]}
yAxisWidth={200}
layout="vertical"
showXAxis={false}
showLegend={false}
valueFormatter={(value) => `$${formatNumberWithCommas(value, 2)}`}
/>
</CardContent>
</Card>
</div>
<div className="col-span-1"></div>
<div className="col-span-2">
<Card className="mb-2">
<CardHeader>
<CardTitle>Spend by Provider</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2">
<div className="col-span-1">
<DonutChart
className="mt-4 h-40"
variant="pie"
data={spendByProvider}
index="provider"
category="spend"
colors={["cyan"]}
valueFormatter={(value) => `$${formatNumberWithCommas(value, 2)}`}
/>
</div>
<div className="col-span-1">
<Table>
<TableHeader>
<TableRow>
<TableHead>Provider</TableHead>
<TableHead className={NUMERIC_CELL_CLASS}>Spend</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{spendByProvider.map((provider) => (
<TableRow key={provider.provider}>
<TableCell>{provider.provider}</TableCell>
<TableCell className={NUMERIC_CELL_CLASS}>
<MoneyCell value={provider.spend} decimals={2} />
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
</TabsContent>
<TabsContent value="activity" keepMounted>
<div className="grid h-[75vh] w-full grid-cols-1 gap-2">
<Card>
<CardHeader>
<CardTitle>All Up</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2">
<div>
<p className="text-[15px] font-normal text-muted-foreground">
API Requests {valueFormatterNumbers(globalActivity.sum_api_requests)}
</p>
<AreaChart
className="h-40"
data={globalActivity.daily_data}
valueFormatter={valueFormatterNumbers}
index="date"
colors={["cyan"]}
categories={["api_requests"]}
/>
</div>
<div>
<p className="text-[15px] font-normal text-muted-foreground">
Tokens {valueFormatterNumbers(globalActivity.sum_total_tokens)}
</p>
<BarChart
className="h-40"
data={globalActivity.daily_data}
valueFormatter={valueFormatterNumbers}
index="date"
colors={["cyan"]}
categories={["total_tokens"]}
/>
</div>
</div>
</CardContent>
</Card>
{globalActivityPerModel.map((globalActivity, index) => (
<Card key={index}>
<CardHeader>
<CardTitle>{globalActivity.model}</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2">
<div>
<p className="text-[15px] font-normal text-muted-foreground">
API Requests {valueFormatterNumbers(globalActivity.sum_api_requests)}
</p>
<AreaChart
className="h-40"
data={globalActivity.daily_data}
index="date"
colors={["cyan"]}
categories={["api_requests"]}
valueFormatter={valueFormatterNumbers}
/>
</div>
<div>
<p className="text-[15px] font-normal text-muted-foreground">
Tokens {valueFormatterNumbers(globalActivity.sum_total_tokens)}
</p>
<BarChart
className="h-40"
data={globalActivity.daily_data}
index="date"
colors={["cyan"]}
categories={["total_tokens"]}
valueFormatter={valueFormatterNumbers}
/>
</div>
</div>
</CardContent>
</Card>
))}
</div>
</TabsContent>
</Tabs>
</TabsContent>
<TabsContent value="team-based-usage" keepMounted>
<div className="grid h-[75vh] w-full grid-cols-2 gap-2">
<div className="col-span-2">
<Card className="mb-2">
<CardHeader>
<CardTitle>Total Spend Per Team</CardTitle>
</CardHeader>
<CardContent>
<TeamSpendBarList data={totalSpendPerTeam} />
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Daily Spend Per Team</CardTitle>
</CardHeader>
<CardContent>
<BarChart
className="h-72"
data={teamSpendData}
showLegend={true}
index="date"
categories={uniqueTeamIds}
yAxisWidth={80}
stack={true}
/>
</CardContent>
</Card>
</div>
</div>
</TabsContent>
<TabsContent value="customer-usage" keepMounted>
<p className="mb-2 text-[12px] text-muted-foreground italic">
Customers of your LLM API calls. Tracked when a `user` param is passed in your LLM calls{" "}
<a
className="text-primary"
href="https://docs.litellm.ai/docs/proxy/users"
target="_blank"
rel="noreferrer"
>
docs here
</a>
</p>
<div className="grid grid-cols-2">
<div>
<AdvancedDatePicker
align="left"
value={dateValue}
onValueChange={(value) => {
setDateValue(value);
updateEndUserData(value.from, value.to, null);
}}
/>
</div>
<div>
<p className="text-sm text-muted-foreground">Select Key</p>
<Select
value={selectedKeyToken}
onValueChange={(value: string | null) => {
setSelectedKeyToken(value);
updateEndUserData(dateValue.from, dateValue.to, value);
}}
>
<SelectTrigger className="w-full">
<SelectValue placeholder="All Keys">
{(token: string | null) => selectableKeys.find((key) => key.token === token)?.alias ?? "All Keys"}
</SelectValue>
</SelectTrigger>
<SelectContent>
<SelectItem value={null}>All Keys</SelectItem>
{selectableKeys.map((key) => (
<SelectItem key={key.token} value={key.token}>
{key.alias}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
<Card className="mt-4">
<CardContent>
<div className="max-h-[70vh] min-h-[500px] overflow-y-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead>Customer</TableHead>
<TableHead className={NUMERIC_CELL_CLASS}>Spend</TableHead>
<TableHead className={NUMERIC_CELL_CLASS}>Total Events</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{topUsers?.map((user: any, index: number) => (
<TableRow key={index}>
<TableCell>{user.end_user}</TableCell>
<TableCell className={NUMERIC_CELL_CLASS}>
<MoneyCell value={user.total_spend} decimals={2} />
</TableCell>
<TableCell className={NUMERIC_CELL_CLASS}>{user.total_count}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="tag-based-usage" keepMounted>
<div className="grid grid-cols-2">
<div className="col-span-1">
<AdvancedDatePicker
align="left"
className="mb-4"
value={dateValue}
onValueChange={(value) => {
setDateValue(value);
updateTagSpendData(value.from, value.to);
}}
/>
</div>
<div>
<Combobox
multiple
items={tagOptions}
value={tagOptions.filter((option) => selectedTags.includes(option.value))}
onValueChange={(options: TagOption[]) => setSelectedTags(options.map((option) => option.value))}
isItemEqualToValue={(a: TagOption, b: TagOption) => a.value === b.value}
itemToStringLabel={(option: TagOption) => option.label}
>
<ComboboxChips render={<div ref={anchor} />}>
<ComboboxValue>
{(options: TagOption[]) =>
options.map((option) => (
<ComboboxChip key={option.value} aria-label={option.label}>
{option.label}
</ComboboxChip>
))
}
</ComboboxValue>
<ComboboxChipsInput placeholder="Select tags" />
</ComboboxChips>
<ComboboxContent anchor={anchor}>
<ComboboxEmpty>No tags found</ComboboxEmpty>
<ComboboxList>
{(option: TagOption) => (
<ComboboxItem key={option.value} value={option} disabled={option.disabled}>
{option.label}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
</div>
</div>
<div className="mb-4 grid h-[75vh] w-full grid-cols-2 gap-2">
<div className="col-span-2">
<Card>
<CardHeader>
<CardTitle>Spend Per Tag</CardTitle>
</CardHeader>
<CardContent className="flex flex-col gap-2">
<p className="text-sm text-muted-foreground">
Get Started by Tracking cost per tag{" "}
<a
className="text-primary"
href="https://docs.litellm.ai/docs/proxy/cost_tracking"
target="_blank"
rel="noreferrer"
>
here
</a>
</p>
<BarChart className="h-72" data={topTagsData} index="name" categories={["spend"]} colors={["cyan"]} />
</CardContent>
</Card>
</div>
</div>
</TabsContent>
</Tabs>
</div>
);
};
export default UsagePage;

View file

@ -1,22 +0,0 @@
"use client";
import Usage from "./_components/usage";
import { DeprecationBanner } from "@/components/DeprecationBanner";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
export default function OldUsagePage() {
const { accessToken, token, userRole, userId: userID, premiumUser } = useAuthorized();
return (
<>
<DeprecationBanner featureName="The old Usage page" />
<Usage
accessToken={accessToken}
token={token}
userRole={userRole}
userID={userID}
keys={null}
premiumUser={premiumUser}
/>
</>
);
}

View file

@ -102,10 +102,6 @@ vi.mock("@/components/activity_metrics", () => ({
processActivityData: vi.fn(),
}));
vi.mock("@/components/view_user_spend", () => ({
default: () => <div>View User Spend</div>,
}));
vi.mock("@/components/UsagePage/components/EntityUsage/TopKeyView", () => ({
default: () => <div>Top Keys</div>,
}));

View file

@ -444,30 +444,6 @@ describe("Sidebar (leftnav)", () => {
});
expect(screen.queryByText("Prompts")).not.toBeInTheDocument();
});
it("should hide Old Usage from internal users while keeping other Experimental children", async () => {
mockUseAuthorized.mockReturnValue(internalAuth);
renderWithProviders(<Sidebar {...defaultProps} />);
act(() => {
fireEvent.click(screen.getByText("Experimental"));
});
await waitFor(() => {
expect(screen.getByText("API Playground")).toBeInTheDocument();
});
expect(screen.queryByText("Old Usage")).not.toBeInTheDocument();
});
it("should show Old Usage to admins", async () => {
renderWithProviders(<Sidebar {...defaultProps} />);
act(() => {
fireEvent.click(screen.getByText("Experimental"));
});
await waitFor(() => {
expect(screen.getByText("Old Usage")).toBeInTheDocument();
});
});
});
// Workflow Runs, Memory and Guardrails Monitor render a shell and then 401
@ -704,7 +680,6 @@ describe("Sidebar (leftnav)", () => {
expectHref("Models + Endpoints", "/ui/models-and-endpoints");
expectHref("Usage", "/ui/usage");
expectHref("API Reference", "/ui/api-reference");
expectHref("Old Usage", "/ui/old-usage");
});
it("never links a leaf to the legacy ?page= switch", () => {
@ -760,7 +735,6 @@ describe("getBreadcrumb", () => {
it("resolves routes whose segment differs from the sidebar page id", () => {
expect(getBreadcrumb("/ui/models-and-endpoints")).toEqual({ section: "AI Gateway", title: "Models + Endpoints" });
expect(getBreadcrumb("/ui/usage")).toEqual({ section: "Observability", title: "Usage" });
expect(getBreadcrumb("/ui/old-usage")).toEqual({ section: "Developer Tools", title: "Old Usage" });
});
it("titles the dashboard root as Home", () => {

View file

@ -414,14 +414,6 @@ const menuGroups: MenuGroup[] = [
icon: <Tags {...ICON} />,
roles: all_admin_roles,
},
{
key: "4",
page: "usage",
route: "old-usage",
label: "Old Usage",
icon: <BarChart3 {...ICON} />,
roles: rolesWithCapability("viewGlobalSpend"),
},
],
},
],

View file

@ -41,7 +41,6 @@ export const pageDescriptions: Record<string, string> = {
"tag-management": "Organize resources with tags",
prompts: "Manage and version prompt templates",
skills: "Browse and manage Claude Code skills",
usage: "View legacy usage dashboard",
"router-settings": "Configure routing and load balancing settings",
telemetry: "Choose which anonymous usage telemetry this proxy collects and see the last report",
"logging-and-alerts": "Set up logging and alert configurations",

View file

@ -1,144 +0,0 @@
"use client";
import React, { useEffect, useState } from "react";
import { modelAvailableCall } from "./networking";
import { formatNumberWithCommas } from "@/utils/dataUtils";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
// Define the props type
interface ViewUserSpendProps {
userSpend: number | null;
userMaxBudget: number | null;
selectedTeam: any | null;
}
const ViewUserSpend: React.FC<ViewUserSpendProps> = ({ userSpend, userMaxBudget, selectedTeam }) => {
const { accessToken, userRole, userId: userID } = useAuthorized();
let [spend, setSpend] = useState(userSpend !== null ? userSpend : 0.0);
const [maxBudget, setMaxBudget] = useState(
selectedTeam ? Number(formatNumberWithCommas(selectedTeam.max_budget, 4)) : null,
);
useEffect(() => {
if (selectedTeam) {
if (selectedTeam.team_alias === "Default Team") {
setMaxBudget(userMaxBudget);
} else {
let setMaxBudgetFlag = false;
if (selectedTeam.team_memberships) {
/**
* What 'team_memberships' looks like:
* "team_memberships": [
* {
* "user_id": "2c315de3-e7ce-4269-b73e-b039a06187b1",
* "team_id": "test-team_515e6f42-ded2-4f0d-8919-0a1f43c5a45f",
* "budget_id": "0880769f-716a-4149-ab19-7f7651ad4db5",
* "litellm_budget_table": {
"soft_budget": null,
"max_budget": 20.0,
"max_parallel_requests": null,
"tpm_limit": null,
"rpm_limit": null,
"model_max_budget": null,
"budget_duration": null
}
*/
for (const member of selectedTeam.team_memberships) {
if (
member.user_id === userID &&
"max_budget" in member.litellm_budget_table &&
member.litellm_budget_table.max_budget !== null
) {
setMaxBudget(member.litellm_budget_table.max_budget);
setMaxBudgetFlag = true;
}
}
}
if (!setMaxBudgetFlag) {
setMaxBudget(selectedTeam.max_budget);
}
}
} else {
setMaxBudget(userMaxBudget);
}
}, [selectedTeam, userMaxBudget]);
const [userModels, setUserModels] = useState([]);
useEffect(() => {
const fetchData = async () => {
if (!accessToken || !userID || !userRole) {
return;
}
};
const fetchUserModels = async () => {
try {
if (userID === null || userRole === null) {
return;
}
if (accessToken !== null) {
const model_available = await modelAvailableCall(accessToken, userID, userRole);
let available_model_names = model_available["data"].map((element: { id: string }) => element.id);
setUserModels(available_model_names);
}
} catch (error) {
console.error("Error fetching user models:", error);
}
};
fetchUserModels();
fetchData();
}, [userRole, accessToken, userID]);
useEffect(() => {
if (userSpend !== null) {
setSpend(userSpend);
}
}, [userSpend]);
// logic to decide what models to display
let modelsToDisplay = [];
if (selectedTeam && selectedTeam.models) {
modelsToDisplay = selectedTeam.models;
}
// check if "all-proxy-models" is in modelsToDisplay
if (modelsToDisplay && modelsToDisplay.includes("all-proxy-models")) {
modelsToDisplay = userModels;
} else if (modelsToDisplay && modelsToDisplay.includes("all-team-models")) {
modelsToDisplay = selectedTeam.models;
} else if (modelsToDisplay && modelsToDisplay.length === 0) {
modelsToDisplay = userModels;
}
const displayMaxBudget = maxBudget !== null ? `$${formatNumberWithCommas(Number(maxBudget), 4)} limit` : "No limit";
const roundedSpend = spend !== undefined ? formatNumberWithCommas(spend, 4) : null;
return (
<div className="flex items-center">
<div className="flex justify-between gap-x-6">
<div>
<p className="text-sm text-muted-foreground">Total Spend</p>
<p className="text-2xl font-semibold text-foreground">${roundedSpend}</p>
</div>
<div>
<p className="text-sm text-muted-foreground">Max Budget</p>
<p className="text-2xl font-semibold text-foreground">{displayMaxBudget}</p>
</div>
</div>
{/* <div className="ml-auto">
<Accordion>
<AccordionHeader><Text>Team Models</Text></AccordionHeader>
<AccordionBody className="absolute right-0 z-floating bg-card p-2 shadow-lg max-w-xs">
<List>
{modelsToDisplay.map((model: string) => (
<ListItem key={model}>
<Text>{model}</Text>
</ListItem>
))}
</List>
</AccordionBody>
</Accordion>
</div> */}
</div>
);
};
export default ViewUserSpend;

View file

@ -127,7 +127,6 @@ vi.mock("@/components/new_usage", () => ({ default: stub("new-usage") }));
vi.mock("@/components/api_ref", () => ({ default: stub("api-ref") }));
vi.mock("@/components/chat_ui/ChatUI", () => ({ default: stub("chat-ui") }));
vi.mock("@/components/leftnav", () => ({ default: stub("sidebar") }));
vi.mock("@/app/(dashboard)/old-usage/_components/usage", () => ({ default: stub("usage") }));
vi.mock("@/components/cache_dashboard", () => ({ default: stub("cache-dashboard") }));
vi.mock("@/app/(dashboard)/guardrails/_components", () => ({ default: stub("guardrails") }));
vi.mock("@/components/prompts", () => ({ default: stub("prompts") }));