From 5e5a94ac8d436fe191497dd3723d043dac25f5b2 Mon Sep 17 00:00:00 2001 From: Ryan Crabbe Date: Wed, 22 Apr 2026 17:28:05 -0700 Subject: [PATCH] Surface budget_reset_at on team info and members tab Adds a formatBudgetReset helper (dayjs-based, with validity guard) that renders the next reset as "today" / "in N days" / "on MMM D, YYYY". The team budget card now shows the team's reset timestamp and the member- default reset (when a shared team_member_budget is configured), and the Members tab gains a Budget Reset column per member. --- .../src/components/team/TeamInfo.tsx | 20 +++++++++++++++---- .../src/components/team/TeamMemberTab.tsx | 19 ++++++++++++++++++ ui/litellm-dashboard/src/utils/budgetUtils.ts | 13 ++++++++++++ 3 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 ui/litellm-dashboard/src/utils/budgetUtils.ts diff --git a/ui/litellm-dashboard/src/components/team/TeamInfo.tsx b/ui/litellm-dashboard/src/components/team/TeamInfo.tsx index 4bc7ff3ea8e..04b9b53140d 100644 --- a/ui/litellm-dashboard/src/components/team/TeamInfo.tsx +++ b/ui/litellm-dashboard/src/components/team/TeamInfo.tsx @@ -15,6 +15,7 @@ import { teamUpdateCall, } from "@/components/networking"; import { useGuardrails } from "@/app/(dashboard)/hooks/guardrails/useGuardrails"; +import { formatBudgetReset } from "@/utils/budgetUtils"; import { formatNumberWithCommas } from "@/utils/dataUtils"; import { mapEmptyStringToNull } from "@/utils/keyUpdateUtils"; import { isProxyAdminRole } from "@/utils/roles"; @@ -70,6 +71,7 @@ export interface TeamMembership { rpm_limit: number | null; model_max_budget: Record | null; budget_duration: string | null; + budget_reset_at: string | null; allowed_models?: string[] | null; }; } @@ -120,6 +122,7 @@ export interface TeamData { team_member_budget_table: { max_budget: number; budget_duration: string; + budget_reset_at: string | null; tpm_limit: number | null; rpm_limit: number | null; } | null; @@ -732,12 +735,21 @@ const TeamInfoView: React.FC = ({ of {info.max_budget === null ? "Unlimited" : `$${formatNumberWithCommas(info.max_budget, 4)}`} - {info.budget_duration && Reset: {info.budget_duration}} + {formatBudgetReset(info.budget_reset_at) && ( + Resets {formatBudgetReset(info.budget_reset_at)} + )}
{info.team_member_budget_table && ( - - Team Member Budget: ${formatNumberWithCommas(info.team_member_budget_table.max_budget, 4)} - + <> + + Team Member Budget: ${formatNumberWithCommas(info.team_member_budget_table.max_budget, 4)} + + {formatBudgetReset(info.team_member_budget_table.budget_reset_at) && ( + + Member budgets reset {formatBudgetReset(info.team_member_budget_table.budget_reset_at)} + + )} + )} diff --git a/ui/litellm-dashboard/src/components/team/TeamMemberTab.tsx b/ui/litellm-dashboard/src/components/team/TeamMemberTab.tsx index 7c9e47d9e1b..e997875968d 100644 --- a/ui/litellm-dashboard/src/components/team/TeamMemberTab.tsx +++ b/ui/litellm-dashboard/src/components/team/TeamMemberTab.tsx @@ -1,6 +1,7 @@ import { useUISettings } from "@/app/(dashboard)/hooks/uiSettings/useUISettings"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import { Member } from "@/components/networking"; +import { formatBudgetReset } from "@/utils/budgetUtils"; import { formatNumberWithCommas } from "@/utils/dataUtils"; import { isProxyAdminRole, isUserTeamAdminForSingleTeam } from "@/utils/roles"; import { InfoCircleOutlined } from "@ant-design/icons"; @@ -88,6 +89,12 @@ export default function TeamMemberTab({ return models && models.length > 0 ? models : null; }; + const getUserBudgetReset = (userId: string | null): string | null => { + if (!userId) return null; + const membership = teamData.team_memberships.find((tm) => tm.user_id === userId); + return formatBudgetReset(membership?.litellm_budget_table?.budget_reset_at); + }; + const extraColumns: ColumnsType = [ { title: ( @@ -146,6 +153,18 @@ export default function TeamMemberTab({ ); }, }, + { + title: "Budget Reset", + key: "budget_reset", + render: (_: unknown, record: Member) => { + const reset = getUserBudgetReset(record.user_id); + return reset ? ( + {reset} + ) : ( + — + ); + }, + }, { title: ( diff --git a/ui/litellm-dashboard/src/utils/budgetUtils.ts b/ui/litellm-dashboard/src/utils/budgetUtils.ts new file mode 100644 index 00000000000..ba13528bee1 --- /dev/null +++ b/ui/litellm-dashboard/src/utils/budgetUtils.ts @@ -0,0 +1,13 @@ +import dayjs from "dayjs"; + +export function formatBudgetReset(iso: string | null | undefined): string | null { + if (!iso) return null; + const resetDate = dayjs(iso); + if (!resetDate.isValid()) return null; + + const days = resetDate.diff(dayjs(), "day"); + if (days < 0) return `on ${resetDate.format("MMM D, YYYY")}`; + if (days === 0) return "today"; + if (days < 7) return `in ${days} day${days === 1 ? "" : "s"}`; + return `on ${resetDate.format("MMM D, YYYY")}`; +}