),
@@ -133,6 +149,10 @@ vi.mock("@/app/(dashboard)/hooks/customers/useCustomers", () => ({
useCustomers: vi.fn(),
}));
+vi.mock("@/app/(dashboard)/hooks/uiSettings/useUISettings", () => ({
+ useUISettings: vi.fn(),
+}));
+
vi.mock("@/app/(dashboard)/hooks/agents/useAgents", () => ({
useAgents: vi.fn(),
}));
@@ -166,6 +186,16 @@ describe("UsagePage", () => {
const mockUseAuthorized = vi.mocked(useAuthorized);
const mockUseCurrentUser = vi.mocked(useCurrentUser);
const mockUseInfiniteUsers = vi.mocked(useInfiniteUsers);
+ const mockUseUISettings = vi.mocked(useUISettings);
+ const mockUpdateUiSettings = vi.mocked(networking.updateUiSettings);
+
+ type UISettingsResult = ReturnType;
+ const uiSettingsResolvedWith = (values: Record): UISettingsResult =>
+ ({
+ data: { values, field_schema: { properties: {} } },
+ isLoading: false,
+ isError: false,
+ }) as unknown as UISettingsResult;
const mockSpendData = {
results: [
@@ -373,6 +403,8 @@ describe("UsagePage", () => {
isLoading: false,
error: null,
} as any);
+ mockUseUISettings.mockReturnValue(uiSettingsResolvedWith({}));
+ mockUpdateUiSettings.mockClear();
mockUserDailyActivityAggregatedCall.mockClear();
mockUserDailyActivityCall.mockClear();
mockTagListCall.mockClear();
@@ -1025,6 +1057,120 @@ describe("UsagePage", () => {
});
});
+ describe("admin-configured default date range", () => {
+ const firstFetchedRange = () => {
+ const [, from, to] = mockUserDailyActivityAggregatedCall.mock.calls[0] as [string, Date, Date, ...unknown[]];
+ return { from, to };
+ };
+ const startOfThisMonth = () => {
+ const now = new Date();
+ return new Date(now.getFullYear(), now.getMonth(), 1);
+ };
+ const endOfToday = () => {
+ const now = new Date();
+ return new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999);
+ };
+
+ it("opens on the trailing 7 days when the admin has not set a default", async () => {
+ renderWithProviders();
+
+ await waitFor(() => {
+ expect(mockUserDailyActivityAggregatedCall).toHaveBeenCalledTimes(1);
+ });
+ const { from, to } = firstFetchedRange();
+ expect(to.getTime() - from.getTime()).toBe(7 * 24 * 60 * 60 * 1000);
+ expect(Date.now() - to.getTime()).toBeLessThan(60 * 1000);
+ });
+
+ it("opens a read-only user's first view on the configured month-to-date range", async () => {
+ mockUseAuthorized.mockReturnValue(nonAdminSession);
+ mockUseUISettings.mockReturnValue(uiSettingsResolvedWith({ default_usage_date_range: "month_to_date" }));
+
+ renderWithProviders();
+
+ await waitFor(() => {
+ expect(mockUserDailyActivityAggregatedCall).toHaveBeenCalledTimes(1);
+ });
+ expect(firstFetchedRange()).toEqual({ from: startOfThisMonth(), to: endOfToday() });
+ });
+
+ it("applies the configured range to every request an admin's first view makes", async () => {
+ mockUseUISettings.mockReturnValue(uiSettingsResolvedWith({ default_usage_date_range: "month_to_date" }));
+
+ renderWithProviders();
+
+ await waitFor(() => {
+ expect(mockGatewayDailyActivityCall).toHaveBeenCalledWith("test-token", startOfThisMonth(), endOfToday());
+ });
+ expect(firstFetchedRange()).toEqual({ from: startOfThisMonth(), to: endOfToday() });
+ });
+
+ it("carries the configured range into the entity usage views the user navigates to", async () => {
+ mockUseUISettings.mockReturnValue(uiSettingsResolvedWith({ default_usage_date_range: "month_to_date" }));
+
+ renderWithProviders();
+ await waitFor(() => {
+ expect(mockUserDailyActivityAggregatedCall).toHaveBeenCalled();
+ });
+
+ act(() => {
+ fireEvent.change(screen.getByTestId("usage-view-select"), { target: { value: "team" } });
+ });
+
+ const entityUsage = await screen.findByTestId("entity-usage");
+ expect(entityUsage).toHaveAttribute("data-entity-type", "team");
+ expect(entityUsage).toHaveAttribute("data-date-from", startOfThisMonth().toISOString());
+ expect(entityUsage).toHaveAttribute("data-date-to", endOfToday().toISOString());
+ });
+
+ it("does not fetch with the fallback range while the setting is still loading", async () => {
+ mockUseUISettings.mockReturnValue({
+ data: undefined,
+ isLoading: true,
+ isError: false,
+ } as unknown as UISettingsResult);
+
+ const { rerender } = renderWithProviders();
+ await act(async () => {
+ await new Promise((resolve) => setTimeout(resolve, 50));
+ });
+ expect(mockUserDailyActivityAggregatedCall).not.toHaveBeenCalled();
+
+ mockUseUISettings.mockReturnValue(uiSettingsResolvedWith({ default_usage_date_range: "month_to_date" }));
+ rerender();
+
+ await waitFor(() => {
+ expect(mockUserDailyActivityAggregatedCall).toHaveBeenCalledTimes(1);
+ });
+ expect(firstFetchedRange()).toEqual({ from: startOfThisMonth(), to: endOfToday() });
+ });
+
+ it("lets the user pick another range without writing the admin default back", async () => {
+ mockUseAuthorized.mockReturnValue(nonAdminSession);
+ mockUseUISettings.mockReturnValue(uiSettingsResolvedWith({ default_usage_date_range: "month_to_date" }));
+
+ renderWithProviders();
+ await waitFor(() => {
+ expect(mockUserDailyActivityAggregatedCall).toHaveBeenCalledTimes(1);
+ });
+
+ await act(async () => {
+ fireEvent.click(screen.getByTestId("pick-a-different-range"));
+ });
+
+ await waitFor(() => {
+ expect(mockUserDailyActivityAggregatedCall).toHaveBeenCalledTimes(2);
+ });
+ expect(mockUserDailyActivityAggregatedCall).toHaveBeenLastCalledWith(
+ "test-token",
+ new Date("2024-01-01T00:00:00Z"),
+ new Date("2024-01-08T00:00:00Z"),
+ "user-123",
+ );
+ expect(mockUpdateUiSettings).not.toHaveBeenCalled();
+ });
+ });
+
describe("aggregated endpoint fallback", () => {
it("should fall back to paginated calls when aggregated endpoint fails", async () => {
mockUserDailyActivityAggregatedCall.mockRejectedValue(new Error("Aggregated endpoint not available"));
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/UsagePageView.tsx b/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/UsagePageView.tsx
index cbdfc8f39e6..b83a8178398 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/UsagePageView.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/UsagePageView.tsx
@@ -20,6 +20,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip
import { useAgents } from "@/app/(dashboard)/hooks/agents/useAgents";
import { useCustomers } from "@/app/(dashboard)/hooks/customers/useCustomers";
+import { useUISettings } from "@/app/(dashboard)/hooks/uiSettings/useUISettings";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import useIsOrgAdmin from "@/app/(dashboard)/hooks/useIsOrgAdmin";
import { useCurrentUser } from "@/app/(dashboard)/hooks/users/useCurrentUser";
@@ -63,6 +64,7 @@ import { TOP_MODEL_LIMITS } from "./EntityUsage/TopModelView";
import TopKeyView from "@/components/UsagePage/components/EntityUsage/TopKeyView";
import UsageAIChatPanel from "./UsageAIChatPanel";
import { UsageOption, UsageViewSelect } from "./UsageViewSelect/UsageViewSelect";
+import { DEFAULT_USAGE_DATE_RANGE_SETTING_KEY, resolveDefaultUsageDateRange } from "./defaultUsageDateRange";
interface UsagePageProps {
teams: Team[];
@@ -86,15 +88,14 @@ const UsagePage: React.FC = ({ teams, organizations }) => {
// Separate loading states for better UX
const [isDateChanging, setIsDateChanging] = useState(false);
- // Create initial dates outside of state to prevent recreation
- const initialFromDate = useMemo(() => new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), []);
- const initialToDate = useMemo(() => new Date(), []);
-
- // Single date state that directly triggers data fetching
- const [dateValue, setDateValue] = useState({
- from: initialFromDate,
- to: initialToDate,
- });
+ const { data: uiSettings, isLoading: isUISettingsLoading } = useUISettings();
+ const configuredDefaultRange: unknown = uiSettings?.values?.[DEFAULT_USAGE_DATE_RANGE_SETTING_KEY];
+ const defaultDateValue = useMemo(
+ () => (isUISettingsLoading ? null : resolveDefaultUsageDateRange(configuredDefaultRange)),
+ [isUISettingsLoading, configuredDefaultRange],
+ );
+ const [pickedDateValue, setPickedDateValue] = useState(null);
+ const dateValue: DateRangePickerValue = pickedDateValue ?? defaultDateValue ?? {};
const [fetchedTags, setFetchedTags] = useState | null>(null);
// No [] default: an unresolved query must stay undefined so the customer
@@ -260,7 +261,7 @@ const UsagePage: React.FC = ({ teams, organizations }) => {
setIsDateChanging(true);
// Update date immediately for UI responsiveness
- setDateValue(newValue);
+ setPickedDateValue(newValue);
}, []);
// Derived states from userSpendData
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/defaultUsageDateRange.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/defaultUsageDateRange.test.ts
new file mode 100644
index 00000000000..1d9013f537f
--- /dev/null
+++ b/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/defaultUsageDateRange.test.ts
@@ -0,0 +1,65 @@
+import { afterEach, describe, expect, it, vi } from "vitest";
+import { resolveDefaultUsageDateRange } from "./defaultUsageDateRange";
+
+const local = (y: number, m: number, d: number, time = "00:00:00.000") =>
+ new Date(`${y}-${String(m).padStart(2, "0")}-${String(d).padStart(2, "0")}T${time}`);
+const END_OF_DAY = "23:59:59.999";
+
+describe("resolveDefaultUsageDateRange", () => {
+ describe("fallback when the admin has not configured a default", () => {
+ it.each([undefined, null, "", "last_90_days", 7, { id: "month_to_date" }])(
+ "opens on the trailing 7 days for %j",
+ (setting) => {
+ const now = local(2026, 3, 15, "10:30:00.000");
+
+ const range = resolveDefaultUsageDateRange(setting, now);
+
+ expect(range.from).toEqual(local(2026, 3, 8, "10:30:00.000"));
+ expect(range.to).toEqual(now);
+ },
+ );
+ });
+
+ describe("month_to_date", () => {
+ afterEach(() => {
+ vi.unstubAllEnvs();
+ });
+
+ it("starts at local midnight on the 1st of the current month and ends at the end of today", () => {
+ const range = resolveDefaultUsageDateRange("month_to_date", local(2026, 3, 15, "10:30:00.000"));
+
+ expect(range.from).toEqual(local(2026, 3, 1));
+ expect(range.to).toEqual(local(2026, 3, 15, END_OF_DAY));
+ });
+
+ it("rolls over to the new month as soon as the 1st begins", () => {
+ const lastOfMarch = resolveDefaultUsageDateRange("month_to_date", local(2026, 3, 31, END_OF_DAY));
+ const firstOfApril = resolveDefaultUsageDateRange("month_to_date", local(2026, 4, 1, "00:00:00.001"));
+
+ expect(lastOfMarch.from).toEqual(local(2026, 3, 1));
+ expect(firstOfApril.from).toEqual(local(2026, 4, 1));
+ expect(firstOfApril.to).toEqual(local(2026, 4, 1, END_OF_DAY));
+ });
+
+ it("keeps the month boundary in the viewer's local timezone rather than UTC", () => {
+ vi.stubEnv("TZ", "Pacific/Kiritimati");
+
+ const range = resolveDefaultUsageDateRange("month_to_date", new Date("2026-01-31T10:30:00.000Z"));
+
+ expect(range.from.toISOString()).toBe("2026-01-31T10:00:00.000Z");
+ expect(range.to.toISOString()).toBe("2026-02-01T09:59:59.999Z");
+ });
+ });
+
+ it.each([
+ ["today", local(2026, 3, 15), local(2026, 3, 15, END_OF_DAY)],
+ ["last_7_days", local(2026, 3, 8), local(2026, 3, 15, END_OF_DAY)],
+ ["last_30_days", local(2026, 2, 13), local(2026, 3, 15, END_OF_DAY)],
+ ["year_to_date", local(2026, 1, 1), local(2026, 3, 15, END_OF_DAY)],
+ ])("resolves %s to the same window the date picker preset uses", (preset, from, to) => {
+ const range = resolveDefaultUsageDateRange(preset, local(2026, 3, 15, "10:30:00.000"));
+
+ expect(range.from).toEqual(from);
+ expect(range.to).toEqual(to);
+ });
+});
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/defaultUsageDateRange.ts b/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/defaultUsageDateRange.ts
new file mode 100644
index 00000000000..f2451f15dad
--- /dev/null
+++ b/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/defaultUsageDateRange.ts
@@ -0,0 +1,10 @@
+import { isDateRangePresetId, resolveDateRangePreset } from "@/components/shared/date_range_presets";
+
+export const DEFAULT_USAGE_DATE_RANGE_SETTING_KEY = "default_usage_date_range";
+
+const FALLBACK_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
+
+export const resolveDefaultUsageDateRange = (setting: unknown, now: Date = new Date()): { from: Date; to: Date } =>
+ isDateRangePresetId(setting)
+ ? resolveDateRangePreset(setting, now)
+ : { from: new Date(now.getTime() - FALLBACK_WINDOW_MS), to: now };
diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.test.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.test.tsx
index c2834e65498..01567cc86e1 100644
--- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.test.tsx
+++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.test.tsx
@@ -1,5 +1,7 @@
import { render, screen, fireEvent, act } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
import { beforeEach, describe, expect, it, vi } from "vitest";
+import { chooseSelectOption } from "@/../tests/test-utils";
import UISettings from "./UISettings";
import { toast } from "@/lib/toast";
@@ -155,4 +157,70 @@ describe("UISettings", () => {
);
expect(toast.success).toHaveBeenCalledWith("UI settings updated successfully");
});
+
+ describe("default Usage date range", () => {
+ const selectWithValue = (value: unknown) => {
+ const response = buildSettingsResponse();
+ mockUseUISettings.mockReturnValue({
+ ...response,
+ data: { ...response.data, values: { ...response.data.values, default_usage_date_range: value } },
+ });
+ };
+
+ it("shows the 7-day fallback when the admin has not set a default", () => {
+ selectWithValue(null);
+
+ render();
+
+ expect(screen.getByRole("combobox", { name: "Default Usage date range" })).toHaveTextContent(
+ "Last 7 days (default)",
+ );
+ });
+
+ it("shows the persisted preset", () => {
+ selectWithValue("month_to_date");
+
+ render();
+
+ expect(screen.getByRole("combobox", { name: "Default Usage date range" })).toHaveTextContent("Month to date");
+ });
+
+ it("persists the chosen preset under default_usage_date_range", async () => {
+ const mutateMock = vi.fn((_settings, options) => {
+ options?.onSuccess?.();
+ });
+ mockUseUpdateUISettings.mockReturnValue({ mutate: mutateMock, isPending: false, error: null });
+ selectWithValue(null);
+ const user = userEvent.setup();
+
+ render();
+ await chooseSelectOption(
+ user,
+ screen.getByRole("combobox", { name: "Default Usage date range" }),
+ "Month to date",
+ );
+
+ expect(mutateMock).toHaveBeenCalledWith(
+ { default_usage_date_range: "month_to_date" },
+ expect.objectContaining({ onSuccess: expect.any(Function), onError: expect.any(Function) }),
+ );
+ expect(toast.success).toHaveBeenCalledWith("UI settings updated successfully");
+ });
+
+ it("clears the setting back to the fallback with null", async () => {
+ const mutateMock = vi.fn();
+ mockUseUpdateUISettings.mockReturnValue({ mutate: mutateMock, isPending: false, error: null });
+ selectWithValue("month_to_date");
+ const user = userEvent.setup();
+
+ render();
+ await chooseSelectOption(
+ user,
+ screen.getByRole("combobox", { name: "Default Usage date range" }),
+ "Last 7 days (default)",
+ );
+
+ expect(mutateMock).toHaveBeenCalledWith({ default_usage_date_range: null }, expect.anything());
+ });
+ });
});
diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.tsx
index 612ca05d083..22bd98160cb 100644
--- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.tsx
+++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.tsx
@@ -5,7 +5,13 @@ import { useUpdateUISettings } from "@/app/(dashboard)/hooks/uiSettings/useUpdat
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import { toast } from "@/lib/toast";
import { Alert, AlertDescription, AlertTitle } from "@/components/shared/Alert";
+import {
+ DATE_RANGE_PRESETS,
+ isDateRangePresetId,
+ type DateRangePresetId,
+} from "@/components/shared/date_range_presets";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Separator } from "@/components/ui/separator";
import { Skeleton } from "@/components/ui/skeleton";
import { Switch } from "@/components/ui/switch";
@@ -45,6 +51,13 @@ function SettingRow({
);
}
+const FALLBACK_USAGE_DATE_RANGE_LABEL = "Last 7 days (default)";
+
+const usageDateRangeItems = [
+ { value: null, label: FALLBACK_USAGE_DATE_RANGE_LABEL },
+ ...DATE_RANGE_PRESETS.map((preset) => ({ value: preset.id, label: preset.label })),
+];
+
export default function UISettings() {
const { accessToken } = useAuthorized();
const { data, isLoading, isError, error } = useUISettings();
@@ -65,7 +78,11 @@ export default function UISettings() {
const allowVectorStoresTeamAdminsProperty = schema?.properties?.allow_vector_stores_for_team_admins;
const scopeUserSearchProperty = schema?.properties?.scope_user_search_to_org;
const disableCustomApiKeysProperty = schema?.properties?.disable_custom_api_keys;
+ const defaultUsageDateRangeProperty = schema?.properties?.default_usage_date_range;
const values = data?.values ?? {};
+ const defaultUsageDateRange: DateRangePresetId | null = isDateRangePresetId(values.default_usage_date_range)
+ ? values.default_usage_date_range
+ : null;
const isDisabledForInternalUsers = Boolean(values.disable_model_add_for_internal_users);
const isDisabledTeamAdminDeleteTeamUser = Boolean(values.disable_team_admin_delete_team_user);
const isAgentsDisabled = Boolean(values.disable_agents_for_internal_users);
@@ -252,6 +269,20 @@ export default function UISettings() {
);
};
+ const handleChangeDefaultUsageDateRange = (value: DateRangePresetId | null) => {
+ updateSettings(
+ { default_usage_date_range: value },
+ {
+ onSuccess: () => {
+ toast.success("UI settings updated successfully");
+ },
+ onError: (error) => {
+ toast.fromError(error);
+ },
+ },
+ );
+ };
+
const handleToggleDisableCustomApiKeys = (checked: boolean) => {
updateSettings(
{ disable_custom_api_keys: checked },
@@ -432,6 +463,32 @@ export default function UISettings() {
}
/>
+
+
+
Default Usage date range
+
+ {defaultUsageDateRangeProperty?.description ??
+ "Date range the Usage page opens with for every user, in the viewer's browser timezone. Unset falls back to the last 7 days."}
+