mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): pass_through_settings to shadcn
- @tremor Text/Button/Icon/Title + antd Badge/Tooltip + @heroicons PencilAlt/Trash/InformationCircle \u2192 shadcn AlertDialog/Badge/ Button/Tooltip + lucide Eye/EyeOff/Info/Pencil/Trash2. - Delete confirm modal rewritten as AlertDialog (was a hand-rolled bg-gray-500 overlay). - Categorical: blue ID pill, blue/indigo method badges, emerald auth badge. File added to .eslintrc.json overrides. Gates: TS \u2713 | Build \u2713. Co-authored-by: yuneng-jiang <yuneng-berri@users.noreply.github.com>
This commit is contained in:
parent
8d84cacf77
commit
cb273143d2
2 changed files with 175 additions and 110 deletions
|
|
@ -54,6 +54,7 @@
|
|||
"src/components/agents/agent_table.tsx",
|
||||
"src/components/view_users/columns.tsx",
|
||||
"src/components/Projects/ProjectsPage.tsx",
|
||||
"src/components/pass_through_settings.tsx",
|
||||
"src/components/permissions/VectorStorePermissions.tsx",
|
||||
"src/components/permissions/AgentPermissions.tsx",
|
||||
"src/components/permissions/MCPServerPermissions.tsx",
|
||||
|
|
|
|||
|
|
@ -1,36 +1,42 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { Text, Button, Icon, Title } from "@tremor/react";
|
||||
import { deletePassThroughEndpointsCall, getPassThroughEndpointsCall } from "./networking";
|
||||
import { Badge, Tooltip } from "antd";
|
||||
import { PencilAltIcon, TrashIcon, InformationCircleIcon } from "@heroicons/react/outline";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import {
|
||||
deletePassThroughEndpointsCall,
|
||||
getPassThroughEndpointsCall,
|
||||
} from "./networking";
|
||||
import { Eye, EyeOff, Info, Pencil, Trash2 } from "lucide-react";
|
||||
import AddPassThroughEndpoint from "./add_pass_through";
|
||||
import PassThroughInfoView from "./pass_through_info";
|
||||
import { DataTable } from "./view_logs/table";
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
import { Eye, EyeOff } from "lucide-react";
|
||||
import NotificationsManager from "./molecules/notifications_manager";
|
||||
|
||||
interface GeneralSettingsPageProps {
|
||||
accessToken: string | null;
|
||||
userRole: string | null;
|
||||
userID: string | null;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
modelData: any;
|
||||
premiumUser?: boolean;
|
||||
}
|
||||
|
||||
interface routingStrategyArgs {
|
||||
ttl?: number;
|
||||
lowest_latency_buffer?: number;
|
||||
}
|
||||
|
||||
interface nestedFieldItem {
|
||||
field_name: string;
|
||||
field_type: string;
|
||||
field_value: any;
|
||||
field_description: string;
|
||||
stored_in_db: boolean | null;
|
||||
}
|
||||
|
||||
export interface passThroughItem {
|
||||
id?: string;
|
||||
path: string;
|
||||
|
|
@ -40,7 +46,10 @@ export interface passThroughItem {
|
|||
cost_per_request?: number;
|
||||
auth?: boolean;
|
||||
methods?: string[];
|
||||
guardrails?: Record<string, { request_fields?: string[]; response_fields?: string[] } | null>;
|
||||
guardrails?: Record<
|
||||
string,
|
||||
{ request_fields?: string[]; response_fields?: string[] } | null
|
||||
>;
|
||||
default_query_params?: Record<string, string>;
|
||||
}
|
||||
|
||||
|
|
@ -51,17 +60,36 @@ const PasswordField: React.FC<{ value: object }> = ({ value }) => {
|
|||
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="font-mono text-xs">{showPassword ? headerString : "••••••••"}</span>
|
||||
<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" />}
|
||||
<span className="font-mono text-xs">
|
||||
{showPassword ? headerString : "••••••••"}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="p-1 hover:bg-muted rounded"
|
||||
type="button"
|
||||
>
|
||||
{showPassword ? (
|
||||
<EyeOff className="w-4 h-4 text-muted-foreground" />
|
||||
) : (
|
||||
<Eye className="w-4 h-4 text-muted-foreground" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const PassThroughSettings: React.FC<GeneralSettingsPageProps> = ({ accessToken, userRole, userID, modelData, premiumUser }) => {
|
||||
const PassThroughSettings: React.FC<GeneralSettingsPageProps> = ({
|
||||
accessToken,
|
||||
userRole,
|
||||
userID,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
modelData,
|
||||
premiumUser,
|
||||
}) => {
|
||||
const [generalSettings, setGeneralSettings] = useState<passThroughItem[]>([]);
|
||||
const [selectedEndpointId, setSelectedEndpointId] = useState<string | null>(null);
|
||||
const [selectedEndpointId, setSelectedEndpointId] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
const [endpointToDelete, setEndpointToDelete] = useState<string | null>(null);
|
||||
|
||||
|
|
@ -70,23 +98,21 @@ const PassThroughSettings: React.FC<GeneralSettingsPageProps> = ({ accessToken,
|
|||
return;
|
||||
}
|
||||
getPassThroughEndpointsCall(accessToken).then((data) => {
|
||||
let general_settings = data["endpoints"];
|
||||
const general_settings = data["endpoints"];
|
||||
setGeneralSettings(general_settings);
|
||||
});
|
||||
}, [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"];
|
||||
const general_settings = data["endpoints"];
|
||||
setGeneralSettings(general_settings);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (endpointId: string) => {
|
||||
// Set the endpoint to delete and open the confirmation modal
|
||||
setEndpointToDelete(endpointId);
|
||||
setIsDeleteModalOpen(true);
|
||||
};
|
||||
|
|
@ -99,45 +125,53 @@ const PassThroughSettings: React.FC<GeneralSettingsPageProps> = ({ accessToken,
|
|||
try {
|
||||
await deletePassThroughEndpointsCall(accessToken, endpointToDelete);
|
||||
|
||||
const updatedSettings = generalSettings.filter((setting) => setting.id !== endpointToDelete);
|
||||
const updatedSettings = generalSettings.filter(
|
||||
(setting) => setting.id !== endpointToDelete,
|
||||
);
|
||||
setGeneralSettings(updatedSettings);
|
||||
|
||||
NotificationsManager.success("Endpoint deleted successfully.");
|
||||
} catch (error) {
|
||||
console.error("Error deleting the endpoint:", error);
|
||||
NotificationsManager.fromBackend("Error deleting the endpoint: " + error);
|
||||
NotificationsManager.fromBackend(
|
||||
"Error deleting the endpoint: " + error,
|
||||
);
|
||||
}
|
||||
|
||||
// Close the confirmation modal and reset the endpointToDelete
|
||||
setIsDeleteModalOpen(false);
|
||||
setEndpointToDelete(null);
|
||||
};
|
||||
|
||||
const cancelDelete = () => {
|
||||
// Close the confirmation modal and reset the endpointToDelete
|
||||
setIsDeleteModalOpen(false);
|
||||
setEndpointToDelete(null);
|
||||
};
|
||||
|
||||
const handleResetField = (endpointId: string, idx: number) => {
|
||||
// Use handleDelete instead of direct deletion
|
||||
const handleResetField = (endpointId: string) => {
|
||||
handleDelete(endpointId);
|
||||
};
|
||||
|
||||
// Define columns for the DataTable
|
||||
const columns: ColumnDef<passThroughItem>[] = [
|
||||
{
|
||||
header: "ID",
|
||||
accessorKey: "id",
|
||||
cell: (info: any) => (
|
||||
<Tooltip title={info.row.original.id}>
|
||||
<div
|
||||
className="font-mono text-blue-500 bg-blue-50 hover:bg-blue-100 text-xs font-normal px-2 py-0.5 text-left w-full truncate whitespace-nowrap cursor-pointer max-w-[15ch]"
|
||||
onClick={() => info.row.original.id && setSelectedEndpointId(info.row.original.id)}
|
||||
>
|
||||
{info.row.original.id}
|
||||
</div>
|
||||
</Tooltip>
|
||||
cell: (info) => (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div
|
||||
className="font-mono text-blue-500 bg-blue-50 hover:bg-blue-100 dark:bg-blue-950/30 dark:hover:bg-blue-950/60 text-xs font-normal px-2 py-0.5 text-left w-full truncate whitespace-nowrap cursor-pointer max-w-[15ch]"
|
||||
onClick={() =>
|
||||
info.row.original.id &&
|
||||
setSelectedEndpointId(info.row.original.id)
|
||||
}
|
||||
>
|
||||
{info.row.original.id}
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{info.row.original.id}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
),
|
||||
},
|
||||
{
|
||||
|
|
@ -147,27 +181,43 @@ const PassThroughSettings: React.FC<GeneralSettingsPageProps> = ({ accessToken,
|
|||
{
|
||||
header: "Target",
|
||||
accessorKey: "target",
|
||||
cell: (info: any) => <Text>{info.getValue()}</Text>,
|
||||
cell: (info) => (
|
||||
<span className="text-sm">{info.getValue() as string}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: () => (
|
||||
<div className="flex items-center gap-1">
|
||||
<span>Methods</span>
|
||||
<Tooltip title="HTTP methods supported by this endpoint">
|
||||
<InformationCircleIcon className="w-4 h-4 text-gray-400 cursor-help" />
|
||||
</Tooltip>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="w-3.5 h-3.5 text-muted-foreground cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
HTTP methods supported by this endpoint
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
),
|
||||
accessorKey: "methods",
|
||||
cell: (info: any) => {
|
||||
const methods = info.getValue();
|
||||
cell: (info) => {
|
||||
const methods = info.getValue() as string[] | undefined;
|
||||
if (!methods || methods.length === 0) {
|
||||
return <Badge color="blue">ALL</Badge>;
|
||||
return (
|
||||
<Badge className="bg-blue-100 text-blue-700 dark:bg-blue-950 dark:text-blue-300">
|
||||
ALL
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{methods.map((method: string) => (
|
||||
<Badge key={method} color="indigo" className="text-xs">
|
||||
<Badge
|
||||
key={method}
|
||||
className="text-xs bg-indigo-100 text-indigo-700 dark:bg-indigo-950 dark:text-indigo-300"
|
||||
>
|
||||
{method}
|
||||
</Badge>
|
||||
))}
|
||||
|
|
@ -179,36 +229,63 @@ const PassThroughSettings: React.FC<GeneralSettingsPageProps> = ({ accessToken,
|
|||
header: () => (
|
||||
<div className="flex items-center gap-1">
|
||||
<span>Authentication</span>
|
||||
<Tooltip title="LiteLLM Virtual Key required to call endpoint">
|
||||
<InformationCircleIcon className="w-4 h-4 text-gray-400 cursor-help" />
|
||||
</Tooltip>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="w-3.5 h-3.5 text-muted-foreground cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
LiteLLM Virtual Key required to call endpoint
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
),
|
||||
accessorKey: "auth",
|
||||
cell: (info: any) => <Badge color={info.getValue() ? "green" : "gray"}>{info.getValue() ? "Yes" : "No"}</Badge>,
|
||||
cell: (info) => (
|
||||
<Badge
|
||||
className={
|
||||
info.getValue()
|
||||
? "bg-emerald-100 text-emerald-700 dark:bg-emerald-950 dark:text-emerald-300"
|
||||
: "bg-muted text-muted-foreground"
|
||||
}
|
||||
>
|
||||
{info.getValue() ? "Yes" : "No"}
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: "Headers",
|
||||
accessorKey: "headers",
|
||||
cell: (info: any) => <PasswordField value={info.getValue() || {}} />,
|
||||
cell: (info) => <PasswordField value={(info.getValue() as object) || {}} />,
|
||||
},
|
||||
{
|
||||
header: "Actions",
|
||||
id: "actions",
|
||||
cell: ({ row }) => (
|
||||
<div className="flex space-x-1">
|
||||
<Icon
|
||||
icon={PencilAltIcon}
|
||||
size="sm"
|
||||
onClick={() => row.original.id && setSelectedEndpointId(row.original.id)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7"
|
||||
onClick={() =>
|
||||
row.original.id && setSelectedEndpointId(row.original.id)
|
||||
}
|
||||
title="Edit"
|
||||
/>
|
||||
<Icon
|
||||
icon={TrashIcon}
|
||||
size="sm"
|
||||
onClick={() => handleResetField(row.original.id!, row.index)}
|
||||
aria-label="Edit"
|
||||
>
|
||||
<Pencil className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7 text-destructive hover:text-destructive"
|
||||
onClick={() => handleResetField(row.original.id!)}
|
||||
title="Delete"
|
||||
/>
|
||||
aria-label="Delete"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
|
|
@ -218,12 +295,10 @@ const PassThroughSettings: React.FC<GeneralSettingsPageProps> = ({ accessToken,
|
|||
return null;
|
||||
}
|
||||
|
||||
// If a specific endpoint is selected, show the info view
|
||||
if (selectedEndpointId) {
|
||||
// Find the endpoint by ID to get the endpoint data for the info view
|
||||
console.log("selectedEndpointId", selectedEndpointId);
|
||||
console.log("generalSettings", generalSettings);
|
||||
const selectedEndpoint = generalSettings.find((endpoint) => endpoint.id === selectedEndpointId);
|
||||
const selectedEndpoint = generalSettings.find(
|
||||
(endpoint) => endpoint.id === selectedEndpointId,
|
||||
);
|
||||
|
||||
if (!selectedEndpoint) {
|
||||
return <div>Endpoint not found</div>;
|
||||
|
|
@ -244,8 +319,10 @@ const PassThroughSettings: React.FC<GeneralSettingsPageProps> = ({ accessToken,
|
|||
return (
|
||||
<div>
|
||||
<div>
|
||||
<Title>Pass Through Endpoints</Title>
|
||||
<Text className="text-tremor-content">Configure and manage your pass-through endpoints</Text>
|
||||
<h2 className="text-xl font-semibold">Pass Through Endpoints</h2>
|
||||
<p className="text-muted-foreground">
|
||||
Configure and manage your pass-through endpoints
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<AddPassThroughEndpoint
|
||||
|
|
@ -264,42 +341,29 @@ const PassThroughSettings: React.FC<GeneralSettingsPageProps> = ({ accessToken,
|
|||
noDataMessage="No pass-through endpoints configured"
|
||||
/>
|
||||
|
||||
{isDeleteModalOpen && (
|
||||
<div className="fixed z-10 inset-0 overflow-y-auto">
|
||||
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
||||
<div className="fixed inset-0 transition-opacity" aria-hidden="true">
|
||||
<div className="absolute inset-0 bg-gray-500 opacity-75"></div>
|
||||
</div>
|
||||
|
||||
{/* Modal Panel */}
|
||||
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
|
||||
​
|
||||
</span>
|
||||
|
||||
{/* Confirmation Modal Content */}
|
||||
<div className="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full">
|
||||
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||
<div className="sm:flex sm:items-start">
|
||||
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900">Delete Pass-Through Endpoint</h3>
|
||||
<div className="mt-2">
|
||||
<p className="text-sm text-gray-500">
|
||||
Are you sure you want to delete this pass-through endpoint? This action cannot be undone.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
|
||||
<Button onClick={confirmDelete} color="red" className="ml-2">
|
||||
Delete
|
||||
</Button>
|
||||
<Button onClick={cancelDelete}>Cancel</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<AlertDialog
|
||||
open={isDeleteModalOpen}
|
||||
onOpenChange={(o) => (!o ? cancelDelete() : undefined)}
|
||||
>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Delete Pass-Through Endpoint</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Are you sure you want to delete this pass-through endpoint? This
|
||||
action cannot be undone.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel onClick={cancelDelete}>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={confirmDelete}
|
||||
className="bg-destructive text-white hover:bg-destructive/90"
|
||||
>
|
||||
Delete
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue