mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-08 22:21:35 +00:00
[UI] - Move passthrough endpoints under Models + Endpoints (#11871)
* use cost_per_request * fix cost_per_request * fixes cost_per_request * fixes for cost per request for pass through * ui fix param name * fixes for _set_cost_per_request * test cost per request pass through endpoints * Update tests/test_litellm/proxy/pass_through_endpoints/test_pass_through_endpoints.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * tests pass through endpoints * rename left nav * move pass through to models + endpoints * move pass through under models * add pt info view * add get/update pt * fixes for PT ui * allow updating pt endpoints * fix pass through quick actions --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
4782d435ed
commit
6630b39c79
7 changed files with 566 additions and 18 deletions
|
|
@ -1139,11 +1139,88 @@ async def get_pass_through_endpoints(
|
|||
"/config/pass_through_endpoint/{endpoint_id}",
|
||||
dependencies=[Depends(user_api_key_auth)],
|
||||
)
|
||||
async def update_pass_through_endpoints(request: Request, endpoint_id: str):
|
||||
async def update_pass_through_endpoints(
|
||||
endpoint_id: str,
|
||||
data: PassThroughGenericEndpoint,
|
||||
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
|
||||
):
|
||||
"""
|
||||
Update a pass-through endpoint
|
||||
"""
|
||||
pass
|
||||
from litellm.proxy.proxy_server import (
|
||||
get_config_general_settings,
|
||||
update_config_general_settings,
|
||||
)
|
||||
|
||||
## Get existing pass-through endpoint field value
|
||||
try:
|
||||
response: ConfigFieldInfo = await get_config_general_settings(
|
||||
field_name="pass_through_endpoints", user_api_key_dict=user_api_key_dict
|
||||
)
|
||||
except Exception:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail={"error": "No pass-through endpoints found"},
|
||||
)
|
||||
|
||||
pass_through_endpoint_data: Optional[List] = response.field_value
|
||||
if pass_through_endpoint_data is None:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail={"error": "No pass-through endpoints found"},
|
||||
)
|
||||
|
||||
# Find and update the endpoint
|
||||
updated_endpoint: Optional[PassThroughGenericEndpoint] = None
|
||||
endpoint_found = False
|
||||
|
||||
for idx, endpoint in enumerate(pass_through_endpoint_data):
|
||||
_endpoint: Optional[PassThroughGenericEndpoint] = None
|
||||
if isinstance(endpoint, dict):
|
||||
_endpoint = PassThroughGenericEndpoint(**endpoint)
|
||||
elif isinstance(endpoint, PassThroughGenericEndpoint):
|
||||
_endpoint = endpoint
|
||||
|
||||
if _endpoint is not None and _endpoint.path == endpoint_id:
|
||||
endpoint_found = True
|
||||
# Get the update data as dict, excluding None values for partial updates
|
||||
update_data = data.model_dump(exclude_none=True)
|
||||
|
||||
# Start with existing endpoint data
|
||||
endpoint_dict = _endpoint.model_dump()
|
||||
|
||||
# Update with new data (only non-None values)
|
||||
endpoint_dict.update(update_data)
|
||||
|
||||
# Ensure the path stays the same (can't change the endpoint_id)
|
||||
endpoint_dict["path"] = endpoint_id
|
||||
|
||||
# Create updated endpoint object
|
||||
updated_endpoint = PassThroughGenericEndpoint(**endpoint_dict)
|
||||
|
||||
# Update the list
|
||||
pass_through_endpoint_data[idx] = endpoint_dict
|
||||
break
|
||||
|
||||
if not endpoint_found:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail={
|
||||
"error": f"Endpoint with path '{endpoint_id}' not found"
|
||||
},
|
||||
)
|
||||
|
||||
## Update db
|
||||
updated_data = ConfigFieldUpdate(
|
||||
field_name="pass_through_endpoints",
|
||||
field_value=pass_through_endpoint_data,
|
||||
config_type="general_settings",
|
||||
)
|
||||
await update_config_general_settings(
|
||||
data=updated_data, user_api_key_dict=user_api_key_dict
|
||||
)
|
||||
|
||||
return PassThroughEndpointResponse(endpoints=[updated_endpoint] if updated_endpoint else [])
|
||||
|
||||
|
||||
@router.post(
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ const AddPassThroughEndpoint: React.FC<AddFallbacksProps> = ({
|
|||
return (
|
||||
<div>
|
||||
<Button
|
||||
className="mx-auto mb-4"
|
||||
className="mx-auto mb-4 mt-4"
|
||||
onClick={() => setIsModalVisible(true)}
|
||||
>
|
||||
+ Add Pass-Through Endpoint
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ const Sidebar: React.FC<SidebarProps> = ({
|
|||
const menuItems: MenuItem[] = [
|
||||
{ key: "1", page: "api-keys", label: "Virtual Keys", icon: <KeyOutlined /> },
|
||||
{ key: "3", page: "llm-playground", label: "Test Key", icon: <PlayCircleOutlined />, roles: rolesWithWriteAccess },
|
||||
{ key: "2", page: "models", label: "Models", icon: <BlockOutlined />, roles: rolesWithWriteAccess },
|
||||
{ key: "2", page: "models", label: "Models + Endpoints", icon: <BlockOutlined />, roles: rolesWithWriteAccess },
|
||||
{ key: "12", page: "new_usage", label: "Usage", icon: <BarChartOutlined />, roles: [...all_admin_roles, ...internalUserRoles] },
|
||||
{ key: "6", page: "teams", label: "Teams", icon: <TeamOutlined /> },
|
||||
{ key: "17", page: "organizations", label: "Organizations", icon: <BankOutlined />, roles: all_admin_roles },
|
||||
|
|
@ -90,7 +90,6 @@ const Sidebar: React.FC<SidebarProps> = ({
|
|||
roles: all_admin_roles,
|
||||
children: [
|
||||
{ key: "11", page: "general-settings", label: "Router Settings", icon: <SettingOutlined />, roles: all_admin_roles },
|
||||
{ key: "12", page: "pass-through-settings", label: "Pass-Through", icon: <ApiOutlined />, roles: all_admin_roles },
|
||||
{ key: "8", page: "settings", label: "Logging & Alerts", icon: <SettingOutlined />, roles: all_admin_roles },
|
||||
{ key: "13", page: "admin-panel", label: "Admin Settings", icon: <SettingOutlined />, roles: all_admin_roles },
|
||||
]
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ import AddModelTab from "./add_model/add_model_tab";
|
|||
import { ModelDataTable } from "./model_dashboard/table";
|
||||
import { columns } from "./model_dashboard/columns";
|
||||
import HealthCheckComponent from "./model_dashboard/HealthCheckComponent";
|
||||
import PassThroughSettings from "./pass_through_settings";
|
||||
import { all_admin_roles } from "@/utils/roles";
|
||||
import { Table as TableInstance } from '@tanstack/react-table';
|
||||
|
||||
|
|
@ -1094,6 +1095,7 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
|||
{all_admin_roles.includes(userRole) ? <Tab>All Models</Tab> : <Tab>Your Models</Tab>}
|
||||
<Tab>Add Model</Tab>
|
||||
{all_admin_roles.includes(userRole) && <Tab>LLM Credentials</Tab>}
|
||||
{all_admin_roles.includes(userRole) && <Tab>Pass-Through Endpoints</Tab>}
|
||||
{all_admin_roles.includes(userRole) && <Tab>
|
||||
Health Status
|
||||
</Tab>}
|
||||
|
|
@ -1263,6 +1265,14 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
|||
<TabPanel>
|
||||
<CredentialsPanel accessToken={accessToken} uploadProps={uploadProps} credentialList={credentialsList} fetchCredentials={fetchCredentials} />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<PassThroughSettings
|
||||
accessToken={accessToken}
|
||||
userRole={userRole}
|
||||
userID={userID}
|
||||
modelData={modelData}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<HealthCheckComponent
|
||||
accessToken={accessToken}
|
||||
|
|
|
|||
|
|
@ -5631,4 +5631,75 @@ export const getRemainingUsers = async (accessToken: string): Promise<{
|
|||
console.error("Failed to fetch remaining users:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const updatePassThroughEndpoint = async (
|
||||
accessToken: string,
|
||||
endpointPath: string,
|
||||
formValues: Record<string, any>
|
||||
) => {
|
||||
try {
|
||||
let url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/config/pass_through_endpoint/${encodeURIComponent(endpointPath)}`
|
||||
: `/config/pass_through_endpoint/${encodeURIComponent(endpointPath)}`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(formValues),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.text();
|
||||
handleError(errorData);
|
||||
throw new Error("Network response was not ok");
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
message.success("Pass through endpoint updated successfully");
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Failed to update pass through endpoint:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const getPassThroughEndpointInfo = async (
|
||||
accessToken: string,
|
||||
endpointPath: string
|
||||
) => {
|
||||
try {
|
||||
let url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/config/pass_through_endpoint?endpoint_id=${encodeURIComponent(endpointPath)}`
|
||||
: `/config/pass_through_endpoint?endpoint_id=${encodeURIComponent(endpointPath)}`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.text();
|
||||
handleError(errorData);
|
||||
throw new Error("Network response was not ok");
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const endpoints = data["endpoints"];
|
||||
|
||||
if (!endpoints || endpoints.length === 0) {
|
||||
throw new Error("Pass through endpoint not found");
|
||||
}
|
||||
|
||||
return endpoints[0]; // Return the first (and should be only) endpoint
|
||||
} catch (error) {
|
||||
console.error("Failed to get pass through endpoint info:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
353
ui/litellm-dashboard/src/components/pass_through_info.tsx
Normal file
353
ui/litellm-dashboard/src/components/pass_through_info.tsx
Normal file
|
|
@ -0,0 +1,353 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
Card,
|
||||
Title,
|
||||
Text,
|
||||
Grid,
|
||||
Badge,
|
||||
Button as TremorButton,
|
||||
Tab,
|
||||
TabGroup,
|
||||
TabList,
|
||||
TabPanel,
|
||||
TabPanels,
|
||||
TextInput,
|
||||
} from "@tremor/react";
|
||||
import { Button, Form, Input, Switch, message, InputNumber } from "antd";
|
||||
import {
|
||||
getPassThroughEndpointInfo,
|
||||
updatePassThroughEndpoint,
|
||||
deletePassThroughEndpointsCall
|
||||
} from "./networking";
|
||||
import { Eye, EyeOff } from "lucide-react";
|
||||
|
||||
export interface PassThroughInfoProps {
|
||||
endpointPath: string;
|
||||
onClose: () => void;
|
||||
accessToken: string | null;
|
||||
isAdmin: boolean;
|
||||
onEndpointUpdated?: () => void;
|
||||
}
|
||||
|
||||
interface PassThroughEndpoint {
|
||||
path: string;
|
||||
target: string;
|
||||
headers: Record<string, any>;
|
||||
include_subpath?: boolean;
|
||||
cost_per_request?: number;
|
||||
}
|
||||
|
||||
// Password field component for headers
|
||||
const PasswordField: React.FC<{ value: Record<string, any> }> = ({ value }) => {
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const headerString = JSON.stringify(value, null, 2);
|
||||
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<pre className="font-mono text-xs bg-gray-50 p-2 rounded max-w-md overflow-auto">
|
||||
{showPassword ? headerString : "••••••••"}
|
||||
</pre>
|
||||
<button
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="p-1 hover:bg-gray-100 rounded"
|
||||
type="button"
|
||||
>
|
||||
{showPassword ? (
|
||||
<EyeOff className="w-4 h-4 text-gray-500" />
|
||||
) : (
|
||||
<Eye className="w-4 h-4 text-gray-500" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const PassThroughInfoView: React.FC<PassThroughInfoProps> = ({
|
||||
endpointPath,
|
||||
onClose,
|
||||
accessToken,
|
||||
isAdmin,
|
||||
onEndpointUpdated
|
||||
}) => {
|
||||
const [endpointData, setEndpointData] = useState<PassThroughEndpoint | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const fetchEndpointInfo = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
if (!accessToken) return;
|
||||
const response = await getPassThroughEndpointInfo(accessToken, endpointPath);
|
||||
setEndpointData(response);
|
||||
} catch (error) {
|
||||
message.error("Failed to load pass through endpoint information");
|
||||
console.error("Error fetching endpoint info:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchEndpointInfo();
|
||||
}, [endpointPath, accessToken]);
|
||||
|
||||
const handleEndpointUpdate = async (values: any) => {
|
||||
try {
|
||||
if (!accessToken) return;
|
||||
|
||||
// Parse headers if provided as string
|
||||
let headers = {};
|
||||
if (values.headers) {
|
||||
try {
|
||||
headers = typeof values.headers === 'string'
|
||||
? JSON.parse(values.headers)
|
||||
: values.headers;
|
||||
} catch (e) {
|
||||
message.error("Invalid JSON format for headers");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const updateData = {
|
||||
target: values.target,
|
||||
headers: headers,
|
||||
include_subpath: values.include_subpath,
|
||||
cost_per_request: values.cost_per_request,
|
||||
};
|
||||
|
||||
await updatePassThroughEndpoint(accessToken, endpointPath, updateData);
|
||||
message.success("Pass through endpoint updated successfully");
|
||||
fetchEndpointInfo();
|
||||
setIsEditing(false);
|
||||
if (onEndpointUpdated) {
|
||||
onEndpointUpdated();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating endpoint:", error);
|
||||
message.error("Failed to update pass through endpoint");
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteEndpoint = async () => {
|
||||
try {
|
||||
if (!accessToken) return;
|
||||
|
||||
await deletePassThroughEndpointsCall(accessToken, endpointPath);
|
||||
message.success("Pass through endpoint deleted successfully");
|
||||
onClose();
|
||||
if (onEndpointUpdated) {
|
||||
onEndpointUpdated();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error deleting endpoint:", error);
|
||||
message.error("Failed to delete pass through endpoint");
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <div className="p-4">Loading...</div>;
|
||||
}
|
||||
|
||||
if (!endpointData) {
|
||||
return <div className="p-4">Pass through endpoint not found</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<div>
|
||||
<Button onClick={onClose} className="mb-4">← Back</Button>
|
||||
<Title>Pass Through Endpoint</Title>
|
||||
<Text className="text-gray-500 font-mono">{endpointData.path}</Text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TabGroup>
|
||||
<TabList className="mb-4">
|
||||
<Tab key="overview">Overview</Tab>
|
||||
{isAdmin ? <Tab key="settings">Settings</Tab> : <></>}
|
||||
</TabList>
|
||||
|
||||
<TabPanels>
|
||||
{/* Overview Panel */}
|
||||
<TabPanel>
|
||||
<Grid numItems={1} numItemsSm={2} numItemsLg={3} className="gap-6">
|
||||
<Card>
|
||||
<Text>Path</Text>
|
||||
<div className="mt-2">
|
||||
<Title className="font-mono">{endpointData.path}</Title>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Text>Target</Text>
|
||||
<div className="mt-2">
|
||||
<Title>{endpointData.target}</Title>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Text>Configuration</Text>
|
||||
<div className="mt-2 space-y-2">
|
||||
<div>
|
||||
<Badge color={endpointData.include_subpath ? "green" : "gray"}>
|
||||
{endpointData.include_subpath ? "Include Subpath" : "Exact Path"}
|
||||
</Badge>
|
||||
</div>
|
||||
{endpointData.cost_per_request !== undefined && (
|
||||
<div>
|
||||
<Text>Cost per request: ${endpointData.cost_per_request}</Text>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
{endpointData.headers && Object.keys(endpointData.headers).length > 0 && (
|
||||
<Card className="mt-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<Text className="font-medium">Headers</Text>
|
||||
<Badge color="blue">
|
||||
{Object.keys(endpointData.headers).length} headers configured
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<PasswordField value={endpointData.headers} />
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
</TabPanel>
|
||||
|
||||
{/* Settings Panel (only for admins) */}
|
||||
{isAdmin && (
|
||||
<TabPanel>
|
||||
<Card>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<Title>Pass Through Endpoint Settings</Title>
|
||||
<div className="space-x-2">
|
||||
{!isEditing && (
|
||||
<>
|
||||
<TremorButton
|
||||
onClick={() => setIsEditing(true)}
|
||||
>
|
||||
Edit Settings
|
||||
</TremorButton>
|
||||
<TremorButton
|
||||
onClick={handleDeleteEndpoint}
|
||||
variant="secondary"
|
||||
color="red"
|
||||
>
|
||||
Delete Endpoint
|
||||
</TremorButton>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isEditing ? (
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={handleEndpointUpdate}
|
||||
initialValues={{
|
||||
target: endpointData.target,
|
||||
headers: endpointData.headers
|
||||
? JSON.stringify(endpointData.headers, null, 2)
|
||||
: "",
|
||||
include_subpath: endpointData.include_subpath || false,
|
||||
cost_per_request: endpointData.cost_per_request,
|
||||
}}
|
||||
layout="vertical"
|
||||
>
|
||||
<Form.Item
|
||||
label="Target URL"
|
||||
name="target"
|
||||
rules={[{ required: true, message: "Please input a target URL" }]}
|
||||
>
|
||||
<TextInput placeholder="https://api.example.com" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Headers (JSON)"
|
||||
name="headers"
|
||||
>
|
||||
<Input.TextArea
|
||||
rows={5}
|
||||
placeholder='{"Authorization": "Bearer your-token", "Content-Type": "application/json"}'
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Include Subpath"
|
||||
name="include_subpath"
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Cost per Request"
|
||||
name="cost_per_request"
|
||||
>
|
||||
<InputNumber
|
||||
min={0}
|
||||
step={0.01}
|
||||
precision={2}
|
||||
placeholder="0.00"
|
||||
addonBefore="$"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<div className="flex justify-end gap-2 mt-6">
|
||||
<Button onClick={() => setIsEditing(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<TremorButton>
|
||||
Save Changes
|
||||
</TremorButton>
|
||||
</div>
|
||||
</Form>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Text className="font-medium">Path</Text>
|
||||
<div className="font-mono">{endpointData.path}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Text className="font-medium">Target URL</Text>
|
||||
<div>{endpointData.target}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Text className="font-medium">Include Subpath</Text>
|
||||
<Badge color={endpointData.include_subpath ? "green" : "gray"}>
|
||||
{endpointData.include_subpath ? "Yes" : "No"}
|
||||
</Badge>
|
||||
</div>
|
||||
{endpointData.cost_per_request !== undefined && (
|
||||
<div>
|
||||
<Text className="font-medium">Cost per Request</Text>
|
||||
<div>${endpointData.cost_per_request}</div>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<Text className="font-medium">Headers</Text>
|
||||
{endpointData.headers && Object.keys(endpointData.headers).length > 0 ? (
|
||||
<div className="mt-2">
|
||||
<PasswordField value={endpointData.headers} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-gray-500">No headers configured</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</TabPanel>
|
||||
)}
|
||||
</TabPanels>
|
||||
</TabGroup>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PassThroughInfoView;
|
||||
|
|
@ -48,6 +48,7 @@ import {
|
|||
} from "@heroicons/react/outline";
|
||||
import AddFallbacks from "./add_fallbacks";
|
||||
import AddPassThroughEndpoint from "./add_pass_through";
|
||||
import PassThroughInfoView from "./pass_through_info";
|
||||
import openai from "openai";
|
||||
import Paragraph from "antd/es/skeleton/Paragraph";
|
||||
import { DataTable } from "./view_logs/table";
|
||||
|
|
@ -116,6 +117,7 @@ const PassThroughSettings: React.FC<GeneralSettingsPageProps> = ({
|
|||
const [generalSettings, setGeneralSettings] = useState<passThroughItem[]>(
|
||||
[]
|
||||
);
|
||||
const [selectedEndpointPath, setSelectedEndpointPath] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!accessToken || !userRole || !userID) {
|
||||
|
|
@ -127,6 +129,16 @@ const PassThroughSettings: React.FC<GeneralSettingsPageProps> = ({
|
|||
});
|
||||
}, [accessToken, userRole, userID]);
|
||||
|
||||
const handleEndpointUpdated = () => {
|
||||
// Refresh the endpoints list when an endpoint is updated
|
||||
if (accessToken) {
|
||||
getPassThroughEndpointsCall(accessToken).then((data) => {
|
||||
let general_settings = data["endpoints"];
|
||||
setGeneralSettings(general_settings);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleResetField = (fieldName: string, idx: number) => {
|
||||
if (!accessToken) {
|
||||
return;
|
||||
|
|
@ -150,7 +162,16 @@ const PassThroughSettings: React.FC<GeneralSettingsPageProps> = ({
|
|||
header: "Path",
|
||||
accessorKey: "path",
|
||||
cell: (info: any) => (
|
||||
<Text className="font-mono">{info.getValue()}</Text>
|
||||
<div className="overflow-hidden">
|
||||
<Button
|
||||
size="xs"
|
||||
variant="light"
|
||||
className="font-mono text-blue-500 bg-blue-50 hover:bg-blue-100 text-xs font-normal px-2 py-0.5 text-left overflow-hidden truncate max-w-[200px]"
|
||||
onClick={() => setSelectedEndpointPath(info.getValue())}
|
||||
>
|
||||
{info.getValue()}
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
|
|
@ -168,17 +189,23 @@ const PassThroughSettings: React.FC<GeneralSettingsPageProps> = ({
|
|||
),
|
||||
},
|
||||
{
|
||||
header: "Action",
|
||||
header: "Actions",
|
||||
id: "actions",
|
||||
cell: ({ row }) => (
|
||||
<Icon
|
||||
icon={TrashIcon}
|
||||
color="red"
|
||||
className="cursor-pointer"
|
||||
onClick={() => handleResetField(row.original.path, row.index)}
|
||||
>
|
||||
Delete
|
||||
</Icon>
|
||||
<div className="flex space-x-1">
|
||||
<Icon
|
||||
icon={PencilAltIcon}
|
||||
size="sm"
|
||||
onClick={() => setSelectedEndpointPath(row.original.path)}
|
||||
title="Edit"
|
||||
/>
|
||||
<Icon
|
||||
icon={TrashIcon}
|
||||
size="sm"
|
||||
onClick={() => handleResetField(row.original.path, row.index)}
|
||||
title="Delete"
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
|
@ -187,16 +214,27 @@ const PassThroughSettings: React.FC<GeneralSettingsPageProps> = ({
|
|||
return null;
|
||||
}
|
||||
|
||||
// If a specific endpoint is selected, show the info view
|
||||
if (selectedEndpointPath) {
|
||||
return (
|
||||
<PassThroughInfoView
|
||||
endpointPath={selectedEndpointPath}
|
||||
onClose={() => setSelectedEndpointPath(null)}
|
||||
accessToken={accessToken}
|
||||
isAdmin={userRole === "Admin" || userRole === "admin"}
|
||||
onEndpointUpdated={handleEndpointUpdated}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full h-[75vh] p-6">
|
||||
<div className="mb-2 mt-4">
|
||||
<div>
|
||||
<div>
|
||||
<Title>Pass Through Endpoints</Title>
|
||||
<Text className="text-tremor-content">
|
||||
Configure and manage your pass-through endpoints
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AddPassThroughEndpoint
|
||||
accessToken={accessToken}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue