From fb73995c4039687a24a37bcc47deb70fedb4bd78 Mon Sep 17 00:00:00 2001 From: ryan-crabbe-berri Date: Wed, 20 May 2026 12:35:49 -0700 Subject: [PATCH] feat(ui): team passthrough routes create parity + edit load fix (#28098) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(ui): team allowed_passthrough_routes create parity + edit load fix Add the Allowed Pass Through Routes selector to the create-team modal (previously only on the edit form), and fix the edit form silently dropping the field: it lives under team metadata, so initialValues must read info.metadata.allowed_passthrough_routes — otherwise the selector renders empty and saving wipes admin-set routes. Both selectors are gated to premium proxy admins, mirroring the server-side gate. Resolves LIT-3019 * fix(ui): persist team allowed_passthrough_routes edits on save The edit form loaded the selector but the save path never wrote it back: allowed_passthrough_routes stayed in the raw metadata JSON textarea and parsedMetadata (from that textarea) always won, so selector edits were silently discarded. Strip it from the textarea initialValues and overlay values.allowed_passthrough_routes into updateData.metadata, mirroring how guardrails is handled. Resolves LIT-3019 * fix(ui): preserve team passthrough routes for non-proxy-admins on save Only proxy admins may set allowed_passthrough_routes (server-side gate). For non-proxy-admins, write the team's stored value back into metadata instead of the form value, so saving an unrelated setting can't silently wipe routes; omit the key entirely when the team never had any. Resolves LIT-3019 --- .../src/components/OldTeams.tsx | 25 +++++++++++++ .../src/components/team/TeamInfo.tsx | 36 +++++++++++++++---- 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/ui/litellm-dashboard/src/components/OldTeams.tsx b/ui/litellm-dashboard/src/components/OldTeams.tsx index b9305e4723a..da00ad911b0 100644 --- a/ui/litellm-dashboard/src/components/OldTeams.tsx +++ b/ui/litellm-dashboard/src/components/OldTeams.tsx @@ -45,6 +45,7 @@ import OrganizationDropdown from "./common_components/OrganizationDropdown"; import TableIconActionButton from "./common_components/IconActionButton/TableIconActionButtons/TableIconActionButton"; import { teamListCall as v2TeamListCall, type TeamsResponse } from "@/app/(dashboard)/hooks/teams/useTeams"; import AccessGroupSelector from "./common_components/AccessGroupSelector"; +import PassThroughRoutesSelector from "./common_components/PassThroughRoutesSelector"; import AgentSelector from "./agent_management/AgentSelector"; import ModelAliasManager from "./common_components/ModelAliasManager"; import PremiumLoggingSettings from "./common_components/PremiumLoggingSettings"; @@ -1446,6 +1447,30 @@ const Teams: React.FC = ({ placeholder="Select vector stores (optional)" /> + + + form.setFieldValue("allowed_passthrough_routes", values)} + value={form.getFieldValue("allowed_passthrough_routes")} + accessToken={accessToken || ""} + placeholder="Select pass through routes (optional)" + disabled={!premiumUser || !isProxyAdminRole(userRole || "")} + /> + + diff --git a/ui/litellm-dashboard/src/components/team/TeamInfo.tsx b/ui/litellm-dashboard/src/components/team/TeamInfo.tsx index 8f3553a8395..ce9d15e1e19 100644 --- a/ui/litellm-dashboard/src/components/team/TeamInfo.tsx +++ b/ui/litellm-dashboard/src/components/team/TeamInfo.tsx @@ -502,6 +502,14 @@ const TeamInfoView: React.FC = ({ (n) => !(values.guardrails || []).includes(n), ); + // Non-proxy-admins can't set allowed_passthrough_routes; preserve the + // stored value so an unrelated save can't wipe it. + const passthroughRoutesMetadata = is_proxy_admin + ? { allowed_passthrough_routes: values.allowed_passthrough_routes || [] } + : info.metadata?.allowed_passthrough_routes + ? { allowed_passthrough_routes: info.metadata.allowed_passthrough_routes } + : {}; + const updateData: any = { team_id: teamId, team_alias: values.team_alias, @@ -515,6 +523,7 @@ const TeamInfoView: React.FC = ({ budget_duration: values.budget_duration, metadata: { ...parsedMetadata, + ...passthroughRoutesMetadata, guardrails: (values.guardrails || []).filter((n: string) => !globalGuardrailNames.has(n)), opted_out_global_guardrails: optedOutGlobalGuardrails, ...(values.logging_settings?.length > 0 ? { logging: values.logging_settings } : {}), @@ -961,7 +970,7 @@ const TeamInfoView: React.FC = ({ : "", metadata: info.metadata ? JSON.stringify( - (({ logging, secret_manager_settings, soft_budget_alerting_emails, model_tpm_limit, model_rpm_limit, ...rest }) => rest)(info.metadata), + (({ logging, secret_manager_settings, soft_budget_alerting_emails, model_tpm_limit, model_rpm_limit, allowed_passthrough_routes, ...rest }) => rest)(info.metadata), null, 2, ) @@ -986,6 +995,7 @@ const TeamInfoView: React.FC = ({ }, access_group_ids: info.access_group_ids || [], default_team_member_models: info.default_team_member_models || [], + allowed_passthrough_routes: info.metadata?.allowed_passthrough_routes || [], }} layout="vertical" > @@ -1338,12 +1348,24 @@ const TeamInfoView: React.FC = ({ - form.setFieldValue("allowed_passthrough_routes", values)} - value={form.getFieldValue("allowed_passthrough_routes")} - accessToken={accessToken || ""} - placeholder="Select pass through routes" - /> + + form.setFieldValue("allowed_passthrough_routes", values)} + value={form.getFieldValue("allowed_passthrough_routes")} + accessToken={accessToken || ""} + placeholder="Select pass through routes" + disabled={!premiumUser || !is_proxy_admin} + /> +