A proxy admin chose which settings team admins can change. Ask a proxy admin to change anything else.
- {editableFields.has("tpm_limit") && (
-
- {({ ref, value, ...field }) => }
+ {TEAM_ADMIN_SETTINGS_FIELDS.filter((name) => editableFields.has(name)).map((name) => (
+
+ {({ ref, value, ...field }) => (
+
+ )}
- )}
+ ))}
diff --git a/ui/litellm-dashboard/src/components/team/TeamInfo.test.tsx b/ui/litellm-dashboard/src/components/team/TeamInfo.test.tsx
index e954bc1c581..03553d664ba 100644
--- a/ui/litellm-dashboard/src/components/team/TeamInfo.test.tsx
+++ b/ui/litellm-dashboard/src/components/team/TeamInfo.test.tsx
@@ -1918,6 +1918,26 @@ describe("TeamInfoView", () => {
expect(toast.error).not.toHaveBeenCalled();
});
+ it("prefills the RPM limit and budget a team admin may edit with the team's stored values", async () => {
+ const user = userEvent.setup({ delay: null });
+ vi.mocked(networking.teamInfoCall).mockResolvedValue(
+ createMockTeamData({
+ rpm_limit: 50,
+ max_budget: 20,
+ caller_edit_access: { kind: "team_admin", editable_fields: ["rpm_limit", "max_budget"] },
+ }),
+ );
+
+ renderWithProviders();
+
+ await user.click(await screen.findByRole("tab", { name: "Settings" }));
+ await user.click(await screen.findByRole("button", { name: /edit settings/i }));
+
+ expect(await screen.findByLabelText("Requests per minute Limit (RPM)")).toHaveValue(50);
+ expect(screen.getByLabelText("Max Budget (USD)")).toHaveValue(20);
+ expect(screen.getByRole("button", { name: /save changes/i })).toBeDisabled();
+ });
+
it("opens the form when the proxy reports unrestricted access although the props only mark a team admin", async () => {
const user = userEvent.setup({ delay: null });
vi.mocked(networking.teamInfoCall).mockResolvedValue(
diff --git a/ui/litellm-dashboard/src/components/team/TeamInfo.tsx b/ui/litellm-dashboard/src/components/team/TeamInfo.tsx
index 30b648fc53c..df7b06661c2 100644
--- a/ui/litellm-dashboard/src/components/team/TeamInfo.tsx
+++ b/ui/litellm-dashboard/src/components/team/TeamInfo.tsx
@@ -1156,7 +1156,7 @@ const TeamInfoView: React.FC = ({
const teamAdminSettingsEditor =
teamEditAccess.kind === "team_admin" ? (
setIsEditing(false)}
diff --git a/ui/litellm-dashboard/src/components/team/teamAdminEditAccess.test.ts b/ui/litellm-dashboard/src/components/team/teamAdminEditAccess.test.ts
index ded6d775ce8..da3f9bf8289 100644
--- a/ui/litellm-dashboard/src/components/team/teamAdminEditAccess.test.ts
+++ b/ui/litellm-dashboard/src/components/team/teamAdminEditAccess.test.ts
@@ -9,12 +9,16 @@ import {
} from "./teamAdminEditAccess";
describe("teamAdminFieldLabel", () => {
- it("names tpm_limit the way the team settings form does", () => {
- expect(teamAdminFieldLabel("tpm_limit")).toBe("Tokens per minute Limit (TPM)");
+ it.each([
+ ["tpm_limit", "Tokens per minute Limit (TPM)"],
+ ["rpm_limit", "Requests per minute Limit (RPM)"],
+ ["max_budget", "Max Budget (USD)"],
+ ])("names %s the way the team settings form does", (field, label) => {
+ expect(teamAdminFieldLabel(field)).toBe(label);
});
it("falls back to the raw field name for a field the dashboard has no label for", () => {
- expect(teamAdminFieldLabel("max_budget")).toBe("max_budget");
+ expect(teamAdminFieldLabel("team_alias")).toBe("team_alias");
});
});
@@ -46,6 +50,27 @@ describe("teamAdminSettingsChanges", () => {
it("leaves tpm_limit out when the proxy did not enable it for team admins", () => {
expect(teamAdminSettingsChanges({ tpm_limit: "5000" }, stored, new Set(["max_budget"]))).toStrictEqual({});
});
+
+ const allStored = { tpm_limit: 1000, rpm_limit: 10, max_budget: 20 };
+
+ it("sends every enabled field that changed and skips the ones that did not", () => {
+ const values = { tpm_limit: "1000", rpm_limit: "50", max_budget: "12.5" };
+ const enabled = new Set(["tpm_limit", "rpm_limit", "max_budget"]);
+
+ expect(teamAdminSettingsChanges(values, allStored, enabled)).toStrictEqual({ rpm_limit: 50, max_budget: 12.5 });
+ });
+
+ it("sends a cleared max budget as no budget", () => {
+ expect(teamAdminSettingsChanges({ max_budget: "" }, allStored, new Set(["max_budget"]))).toStrictEqual({
+ max_budget: null,
+ });
+ });
+
+ it("leaves out changed fields the proxy did not enable", () => {
+ const values = { tpm_limit: "5000", rpm_limit: "50", max_budget: "5" };
+
+ expect(teamAdminSettingsChanges(values, allStored, new Set(["rpm_limit"]))).toStrictEqual({ rpm_limit: 50 });
+ });
});
describe("parseTeamAdminEditableFields", () => {
diff --git a/ui/litellm-dashboard/src/components/team/teamAdminEditAccess.ts b/ui/litellm-dashboard/src/components/team/teamAdminEditAccess.ts
index 73129923907..b878af03df6 100644
--- a/ui/litellm-dashboard/src/components/team/teamAdminEditAccess.ts
+++ b/ui/litellm-dashboard/src/components/team/teamAdminEditAccess.ts
@@ -39,17 +39,21 @@ export const parseSupportedTeamAdminEditableFields = (uiSettingsFieldSchema: unk
return items.success ? fieldListSchema.parse(items.data.enum) : [];
};
-const TEAM_ADMIN_FIELD_LABELS: ReadonlyMap = new Map([["tpm_limit", "Tokens per minute Limit (TPM)"]]);
+export const TEAM_ADMIN_SETTINGS_FIELDS = ["tpm_limit", "rpm_limit", "max_budget"] as const;
+
+export type TeamAdminSettingsField = (typeof TEAM_ADMIN_SETTINGS_FIELDS)[number];
+
+const TEAM_ADMIN_FIELD_LABELS: ReadonlyMap = new Map([
+ ["tpm_limit", "Tokens per minute Limit (TPM)"],
+ ["rpm_limit", "Requests per minute Limit (RPM)"],
+ ["max_budget", "Max Budget (USD)"],
+]);
export const teamAdminFieldLabel = (field: string): string => TEAM_ADMIN_FIELD_LABELS.get(field) ?? field;
-export interface TeamAdminSettingsValues {
- readonly tpm_limit?: string | number | null;
-}
+export type TeamAdminSettingsValues = { readonly [F in TeamAdminSettingsField]?: string | number | null };
-export interface TeamAdminSettingsChanges {
- readonly tpm_limit?: number | null;
-}
+export type TeamAdminSettingsChanges = { readonly [F in TeamAdminSettingsField]?: number | null };
const numberOrNull = (value: string | number | null | undefined): number | null => {
if (value === null || value === undefined || String(value).trim() === "") return null;
@@ -61,12 +65,13 @@ export const teamAdminSettingsChanges = (
values: TeamAdminSettingsValues,
initialValues: TeamAdminSettingsValues,
editableFields: ReadonlySet,
-): TeamAdminSettingsChanges => {
- const tpmLimit = numberOrNull(values.tpm_limit);
- return editableFields.has("tpm_limit") && tpmLimit !== numberOrNull(initialValues.tpm_limit)
- ? { tpm_limit: tpmLimit }
- : {};
-};
+): TeamAdminSettingsChanges =>
+ Object.fromEntries(
+ TEAM_ADMIN_SETTINGS_FIELDS.flatMap((field) => {
+ const value = numberOrNull(values[field]);
+ return editableFields.has(field) && value !== numberOrNull(initialValues[field]) ? [[field, value]] : [];
+ }),
+ );
export const parseTeamEditAccess = (callerEditAccess: unknown): TeamEditAccess => {
const parsed = callerEditAccessSchema.safeParse(callerEditAccess);