feat(teams/): support allowed_passthrough_routes on team create + update

allows admin to specify what passthrough routes the team has access to
This commit is contained in:
Krrish Dholakia 2025-10-09 17:11:57 -07:00
parent f608aefc2d
commit 82d7a7248e
5 changed files with 113 additions and 38 deletions

View file

@ -0,0 +1,67 @@
import React, { useEffect, useState } from "react";
import { Select } from "antd";
import { getPassThroughEndpointsCall } from "../networking";
interface PassThroughRoutesSelectorProps {
onChange: (selectedRoutes: string[]) => void;
value?: string[];
className?: string;
accessToken: string;
placeholder?: string;
disabled?: boolean;
}
const PassThroughRoutesSelector: React.FC<PassThroughRoutesSelectorProps> = ({
onChange,
value,
className,
accessToken,
placeholder = "Select pass through routes",
disabled = false,
}) => {
const [passThroughRoutes, setPassThroughRoutes] = useState<string[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
const fetchPassThroughRoutes = async () => {
if (!accessToken) return;
setLoading(true);
try {
const response = await getPassThroughEndpointsCall(accessToken);
if (response.endpoints) {
const routes = response.endpoints.map((route: { path: string }) => route.path);
setPassThroughRoutes(routes);
}
} catch (error) {
console.error("Error fetching pass through routes:", error);
} finally {
setLoading(false);
}
};
fetchPassThroughRoutes();
}, [accessToken]);
return (
<Select
mode="tags"
placeholder={placeholder}
onChange={onChange}
value={value}
loading={loading}
className={className}
options={passThroughRoutes.map((route) => ({
label: route,
value: route,
}))}
optionFilterProp="label"
showSearch
style={{ width: "100%" }}
disabled={disabled}
/>
);
};
export default PassThroughRoutesSelector;

View file

@ -18,9 +18,9 @@ import {
keyCreateServiceAccountCall,
fetchMCPAccessGroups,
getPromptsList,
getPassThroughEndpointsCall,
} from "../networking";
import VectorStoreSelector from "../vector_store_management/VectorStoreSelector";
import PassThroughRoutesSelector from "../common_components/PassThroughRoutesSelector";
import { Team } from "../key_team_helpers/key_list";
import TeamDropdown from "../common_components/team_dropdown";
import { InfoCircleOutlined } from "@ant-design/icons";
@ -160,7 +160,6 @@ const CreateKey: React.FC<CreateKeyProps> = ({
const [predefinedTags, setPredefinedTags] = useState(getPredefinedTags(data));
const [guardrailsList, setGuardrailsList] = useState<string[]>([]);
const [promptsList, setPromptsList] = useState<string[]>([]);
const [passThroughRoutesList, setPassThroughRoutesList] = useState<string[]>([]);
const [loggingSettings, setLoggingSettings] = useState<any[]>([]);
const [selectedCreateKeyTeam, setSelectedCreateKeyTeam] = useState<Team | null>(team);
const [isCreateUserModalVisible, setIsCreateUserModalVisible] = useState(false);
@ -242,19 +241,8 @@ const CreateKey: React.FC<CreateKeyProps> = ({
}
};
const fetchPassThroughEndpoints = async () => {
try {
const response = await getPassThroughEndpointsCall(accessToken);
const passThroughPaths = response.endpoints.map((endpoint: { path: string }) => endpoint.path);
setPassThroughRoutesList(passThroughPaths);
} catch (error) {
console.error("Failed to fetch pass through endpoints:", error);
}
};
fetchGuardrails();
fetchPrompts();
fetchPassThroughEndpoints();
}, [accessToken]);
// Fetch possible user roles when component mounts
@ -931,9 +919,9 @@ const CreateKey: React.FC<CreateKeyProps> = ({
label={
<span>
Allowed Pass Through Routes{" "}
<Tooltip title="Allow this key to use specific prompt templates">
<Tooltip title="Allow this key to use specific pass through routes">
<a
href="https://docs.litellm.ai/docs/proxy/prompt_management"
href="https://docs.litellm.ai/docs/proxy/pass_through"
target="_blank"
rel="noopener noreferrer"
onClick={(e) => e.stopPropagation()} // Prevent accordion from collapsing when clicking link
@ -951,14 +939,14 @@ const CreateKey: React.FC<CreateKeyProps> = ({
: "Premium feature - Upgrade to set pass through routes by key"
}
>
<Select
mode="tags"
style={{ width: "100%" }}
disabled={!premiumUser}
<PassThroughRoutesSelector
onChange={(values: string[]) => form.setFieldValue("allowed_passthrough_routes", values)}
value={form.getFieldValue("allowed_passthrough_routes")}
accessToken={accessToken}
placeholder={
!premiumUser ? "Premium feature - Upgrade to set pass through routes by key" : "Select or enter pass through routes"
}
options={passThroughRoutesList.map((name) => ({ value: name, label: name }))}
disabled={!premiumUser}
/>
</Form.Item>
<Form.Item

View file

@ -51,6 +51,7 @@ import { fetchMCPAccessGroups } from "../networking";
import { CheckIcon, CopyIcon } from "lucide-react";
import { copyToClipboard as utilCopyToClipboard } from "../../utils/dataUtils";
import NotificationsManager from "../molecules/notifications_manager";
import PassThroughRoutesSelector from "../common_components/PassThroughRoutesSelector";
export interface TeamMembership {
user_id: string;
@ -672,6 +673,15 @@ const TeamInfoView: React.FC<TeamInfoProps> = ({
/>
</Form.Item>
<Form.Item label="Allowed Pass Through Routes" name="allowed_passthrough_routes">
<PassThroughRoutesSelector
onChange={(values: string[]) => form.setFieldValue("allowed_passthrough_routes", values)}
value={form.getFieldValue("allowed_passthrough_routes")}
accessToken={accessToken || ""}
placeholder="Select pass through routes"
/>
</Form.Item>
<Form.Item label="MCP Servers / Access Groups" name="mcp_servers_and_groups">
<MCPServerSelector
onChange={(val) => form.setFieldValue("mcp_servers_and_groups", val)}

View file

@ -63,6 +63,7 @@ import AvailableTeamsPanel from "@/components/team/available_teams";
import VectorStoreSelector from "./vector_store_management/VectorStoreSelector";
import PremiumLoggingSettings from "./common_components/PremiumLoggingSettings";
import type { KeyResponse, Team } from "./key_team_helpers/key_list";
import PassThroughRoutesSelector from "./common_components/PassThroughRoutesSelector";
import { formatNumberWithCommas } from "../utils/dataUtils";
import { AlertTriangleIcon, XIcon } from "lucide-react";
import MCPServerSelector from "./mcp_server_management/MCPServerSelector";
@ -1251,6 +1252,26 @@ const Teams: React.FC<TeamProps> = ({
placeholder="Select vector stores (optional)"
/>
</Form.Item>
<Form.Item
label={
<span>
Allowed Pass Through Routes{" "}
<Tooltip title="Select which pass through routes this team can access by default. Leave empty for access to all pass through routes">
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
</Tooltip>
</span>
}
name="allowed_passthrough_routes"
className="mt-8"
help="Select pass through routes this team can access. Leave empty for access to all pass through routes"
>
<PassThroughRoutesSelector
onChange={(values: string[]) => form.setFieldValue("allowed_passthrough_routes", values)}
value={form.getFieldValue("allowed_passthrough_routes")}
accessToken={accessToken || ""}
placeholder="Select pass through routes (optional)"
/>
</Form.Item>
</AccordionBody>
</Accordion>

View file

@ -3,7 +3,7 @@ import { Form, Input, Select, Button as AntdButton, Tooltip } from "antd";
import { Button as TremorButton, TextInput } from "@tremor/react";
import { KeyResponse } from "../key_team_helpers/key_list";
import { fetchTeamModels } from "../organisms/create_key_button";
import { modelAvailableCall, getPromptsList, getPassThroughEndpointsCall } from "../networking";
import { modelAvailableCall, getPromptsList } from "../networking";
import NumericalInput from "../shared/numerical_input";
import VectorStoreSelector from "../vector_store_management/VectorStoreSelector";
import MCPServerSelector from "../mcp_server_management/MCPServerSelector";
@ -15,6 +15,7 @@ import { mapInternalToDisplayNames, mapDisplayToInternalNames } from "../callbac
import GuardrailSelector from "@/components/guardrails/GuardrailSelector";
import KeyLifecycleSettings from "../common_components/KeyLifecycleSettings";
import RateLimitTypeFormItem from "../common_components/RateLimitTypeFormItem";
import PassThroughRoutesSelector from "../common_components/PassThroughRoutesSelector";
interface KeyEditViewProps {
keyData: KeyResponse;
@ -59,7 +60,6 @@ export function KeyEditView({
const [form] = Form.useForm();
const [userModels, setUserModels] = useState<string[]>([]);
const [promptsList, setPromptsList] = useState<string[]>([]);
const [passThroughRoutesList, setPassThroughRoutesList] = useState<string[]>([]);
const team = teams?.find((team) => team.team_id === keyData.team_id);
const [availableModels, setAvailableModels] = useState<string[]>([]);
const [mcpAccessGroups, setMcpAccessGroups] = useState<string[]>([]);
@ -114,19 +114,8 @@ export function KeyEditView({
}
};
const fetchPassThroughRoutes = async () => {
if (!accessToken) return;
try {
const response = await getPassThroughEndpointsCall(accessToken);
setPassThroughRoutesList(response.endpoints.map((route: { path: string }) => route.path));
} catch (error) {
console.error("Failed to fetch pass through routes:", error);
}
};
fetchPrompts();
fetchModels();
fetchPassThroughRoutes();
}, [userID, userRole, accessToken, team, keyData.team_id]);
// Sync disabled callbacks with form when component mounts
@ -290,10 +279,10 @@ export function KeyEditView({
<Form.Item label="Allowed Pass Through Routes" name="allowed_passthrough_routes">
<Tooltip title={!premiumUser ? "Setting allowed pass through routes by key is a premium feature" : ""} placement="top">
<Select
mode="tags"
style={{ width: "100%" }}
disabled={!premiumUser}
<PassThroughRoutesSelector
onChange={(values: string[]) => form.setFieldValue("allowed_passthrough_routes", values)}
value={form.getFieldValue("allowed_passthrough_routes")}
accessToken={accessToken || ""}
placeholder={
!premiumUser
? "Premium feature - Upgrade to set allowed pass through routes by key"
@ -301,7 +290,7 @@ export function KeyEditView({
? `Current: ${keyData.metadata.allowed_passthrough_routes.join(", ")}`
: "Select or enter allowed pass through routes"
}
options={passThroughRoutesList.map((name) => ({ value: name, label: name }))}
disabled={!premiumUser}
/>
</Tooltip>
</Form.Item>