mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-10 22:41:41 +00:00
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:
parent
f608aefc2d
commit
82d7a7248e
5 changed files with 113 additions and 38 deletions
|
|
@ -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;
|
||||
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue