mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): i18n for organizations, pass-through info, and onboarding link
This commit is contained in:
parent
7c0faa8bc6
commit
38f2137eca
6 changed files with 385 additions and 121 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import React from "react";
|
||||
import { Text } from "@tremor/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import VectorStorePermissions from "./permissions/VectorStorePermissions";
|
||||
import MCPServerPermissions from "./permissions/MCPServerPermissions";
|
||||
import AgentPermissions from "./permissions/AgentPermissions";
|
||||
|
|
@ -29,6 +30,7 @@ export function ObjectPermissionsView({
|
|||
className = "",
|
||||
accessToken,
|
||||
}: ObjectPermissionsViewProps) {
|
||||
const { t } = useTranslation();
|
||||
const vectorStores = objectPermission?.vector_stores || [];
|
||||
const mcpServers = objectPermission?.mcp_servers || [];
|
||||
const mcpAccessGroups = objectPermission?.mcp_access_groups || [];
|
||||
|
|
@ -50,10 +52,10 @@ export function ObjectPermissionsView({
|
|||
/>
|
||||
<AgentPermissions agents={agents} agentAccessGroups={agentAccessGroups} accessToken={accessToken} />
|
||||
<div className="rounded-md border border-gray-100 p-4">
|
||||
<Text className="text-sm font-medium text-gray-800">Search tools</Text>
|
||||
<Text className="text-sm font-medium text-gray-800">{t("objectPermissionsView.searchToolsTitle")}</Text>
|
||||
{searchTools.length === 0 ? (
|
||||
<Text className="mt-1 block text-xs text-gray-500">
|
||||
No restriction — all configured search tools are allowed for this team.
|
||||
{t("objectPermissionsView.searchToolsNoRestriction")}
|
||||
</Text>
|
||||
) : (
|
||||
<Text className="mt-1 block text-xs text-gray-700">{searchTools.join(", ")}</Text>
|
||||
|
|
@ -67,8 +69,8 @@ export function ObjectPermissionsView({
|
|||
<div className={`bg-white border border-gray-200 rounded-lg p-6 ${className}`}>
|
||||
<div className="flex items-center gap-2 mb-6">
|
||||
<div>
|
||||
<Text className="font-semibold text-gray-900">Object Permissions</Text>
|
||||
<Text className="text-xs text-gray-500">Access control for Vector Stores and MCP Servers</Text>
|
||||
<Text className="font-semibold text-gray-900">{t("objectPermissionsView.title")}</Text>
|
||||
<Text className="text-xs text-gray-500">{t("objectPermissionsView.subtitle")}</Text>
|
||||
</div>
|
||||
</div>
|
||||
{content}
|
||||
|
|
@ -78,7 +80,7 @@ export function ObjectPermissionsView({
|
|||
|
||||
return (
|
||||
<div className={`${className}`}>
|
||||
<Text className="font-medium text-gray-900 mb-3">Object Permissions</Text>
|
||||
<Text className="font-medium text-gray-900 mb-3">{t("objectPermissionsView.title")}</Text>
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import React from "react";
|
|||
import { Button, Modal, Typography } from "antd";
|
||||
import { CopyToClipboard } from "react-copy-to-clipboard";
|
||||
import { Text } from "@tremor/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import NotificationsManager from "./molecules/notifications_manager";
|
||||
|
||||
export interface InvitationLink {
|
||||
|
|
@ -32,6 +33,7 @@ export default function OnboardingModal({
|
|||
invitationLinkData,
|
||||
modalType = "invitation",
|
||||
}: OnboardingProps) {
|
||||
const { t } = useTranslation();
|
||||
const { Title, Paragraph } = Typography;
|
||||
const handleInvitationOk = () => {
|
||||
setIsInvitationLinkModalVisible(false);
|
||||
|
|
@ -62,7 +64,11 @@ export default function OnboardingModal({
|
|||
|
||||
return (
|
||||
<Modal
|
||||
title={modalType === "invitation" ? "Invitation Link" : "Reset Password Link"}
|
||||
title={
|
||||
modalType === "invitation"
|
||||
? t("onboardingLink.invitationLinkTitle")
|
||||
: t("onboardingLink.resetPasswordLinkTitle")
|
||||
}
|
||||
open={isInvitationLinkModalVisible}
|
||||
width={800}
|
||||
footer={null}
|
||||
|
|
@ -71,23 +77,29 @@ export default function OnboardingModal({
|
|||
>
|
||||
<Paragraph>
|
||||
{modalType === "invitation"
|
||||
? "Copy and send the generated link to onboard this user to the proxy."
|
||||
: "Copy and send the generated link to the user to reset their password."}
|
||||
? t("onboardingLink.invitationLinkDesc")
|
||||
: t("onboardingLink.resetPasswordLinkDesc")}
|
||||
</Paragraph>
|
||||
<div className="flex justify-between pt-5 pb-2">
|
||||
<Text className="text-base">User ID</Text>
|
||||
<Text className="text-base">{t("onboardingLink.userId")}</Text>
|
||||
<Text>{invitationLinkData?.user_id}</Text>
|
||||
</div>
|
||||
<div className="flex justify-between pt-5 pb-2">
|
||||
<Text>{modalType === "invitation" ? "Invitation Link" : "Reset Password Link"}</Text>
|
||||
<Text>
|
||||
{modalType === "invitation"
|
||||
? t("onboardingLink.invitationLinkTitle")
|
||||
: t("onboardingLink.resetPasswordLinkTitle")}
|
||||
</Text>
|
||||
<Text>
|
||||
<Text>{getInvitationUrl()}</Text>
|
||||
</Text>
|
||||
</div>
|
||||
<div className="flex justify-end mt-5">
|
||||
<CopyToClipboard text={getInvitationUrl()} onCopy={() => NotificationsManager.success("Copied!")}>
|
||||
<CopyToClipboard text={getInvitationUrl()} onCopy={() => NotificationsManager.success(t("common.copied"))}>
|
||||
<Button type="primary">
|
||||
{modalType === "invitation" ? "Copy invitation link" : "Copy password reset link"}
|
||||
{modalType === "invitation"
|
||||
? t("onboardingLink.copyInvitationLink")
|
||||
: t("onboardingLink.copyPasswordResetLink")}
|
||||
</Button>
|
||||
</CopyToClipboard>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import OrganizationFilters, { FilterState } from "@/app/(dashboard)/organizations/OrganizationFilters";
|
||||
import { InfoCircleOutlined } from "@ant-design/icons";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { ChevronDownIcon, ChevronRightIcon, RefreshIcon } from "@heroicons/react/outline";
|
||||
import {
|
||||
Badge,
|
||||
|
|
@ -71,6 +72,7 @@ const OrganizationsTable: React.FC<OrganizationsTableProps> = ({
|
|||
setOrganizations,
|
||||
premiumUser,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [selectedOrgId, setSelectedOrgId] = useState<string | null>(null);
|
||||
const [editOrg, setEditOrg] = useState(false);
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
|
|
@ -138,7 +140,7 @@ const OrganizationsTable: React.FC<OrganizationsTableProps> = ({
|
|||
try {
|
||||
setIsDeleting(true);
|
||||
await organizationDeleteCall(accessToken, orgToDelete);
|
||||
NotificationsManager.success("Organization deleted successfully");
|
||||
NotificationsManager.success(t("organizations.deleteSuccess"));
|
||||
|
||||
setIsDeleteModalOpen(false);
|
||||
setOrgToDelete(null);
|
||||
|
|
@ -186,7 +188,7 @@ const OrganizationsTable: React.FC<OrganizationsTableProps> = ({
|
|||
}
|
||||
|
||||
await organizationCreateCall(accessToken, values);
|
||||
NotificationsManager.success("Organization created successfully");
|
||||
NotificationsManager.success(t("organizations.createSuccess"));
|
||||
setIsOrgModalVisible(false);
|
||||
form.resetFields();
|
||||
// Refresh organizations list
|
||||
|
|
@ -205,11 +207,12 @@ const OrganizationsTable: React.FC<OrganizationsTableProps> = ({
|
|||
return (
|
||||
<div>
|
||||
<Text>
|
||||
This is a LiteLLM Enterprise feature, and requires a valid key to use. Get a trial key{" "}
|
||||
<a href="https://www.litellm.ai/#pricing" target="_blank" rel="noopener noreferrer">
|
||||
here
|
||||
</a>
|
||||
.
|
||||
<Trans
|
||||
i18nKey="organizations.enterpriseFeature"
|
||||
components={{
|
||||
pricingLink: <a href="https://www.litellm.ai/#pricing" target="_blank" rel="noopener noreferrer" />,
|
||||
}}
|
||||
/>
|
||||
</Text>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -221,7 +224,7 @@ const OrganizationsTable: React.FC<OrganizationsTableProps> = ({
|
|||
<Col numColSpan={1} className="flex flex-col gap-2">
|
||||
{(userRole === "Admin" || userRole === "Org Admin") && (
|
||||
<Button className="w-fit" onClick={() => setIsOrgModalVisible(true)}>
|
||||
+ Create New Organization
|
||||
{t("organizations.createNewOrg")}
|
||||
</Button>
|
||||
)}
|
||||
{selectedOrgId ? (
|
||||
|
|
@ -241,10 +244,10 @@ const OrganizationsTable: React.FC<OrganizationsTableProps> = ({
|
|||
<TabGroup className="gap-2 h-[75vh] w-full">
|
||||
<TabList className="flex justify-between mt-2 w-full items-center">
|
||||
<div className="flex">
|
||||
<Tab>Your Organizations</Tab>
|
||||
<Tab>{t("organizations.tabYourOrgs")}</Tab>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
{lastRefreshed && <Text>Last Refreshed: {lastRefreshed}</Text>}
|
||||
{lastRefreshed && <Text>{t("organizations.lastRefreshed", { time: lastRefreshed })}</Text>}
|
||||
<Icon
|
||||
icon={RefreshIcon}
|
||||
variant="shadow"
|
||||
|
|
@ -256,7 +259,7 @@ const OrganizationsTable: React.FC<OrganizationsTableProps> = ({
|
|||
</TabList>
|
||||
<TabPanels>
|
||||
<TabPanel>
|
||||
<Text>Click on “Organization ID” to view organization details.</Text>
|
||||
<Text>{t("organizations.clickOrgIdHint")}</Text>
|
||||
<Grid numItems={1} className="gap-2 pt-2 pb-2 h-[75vh] w-full mt-2">
|
||||
<Col numColSpan={1}>
|
||||
<Card className="w-full mx-auto flex-auto overflow-hidden overflow-y-auto max-h-[50vh]">
|
||||
|
|
@ -274,15 +277,15 @@ const OrganizationsTable: React.FC<OrganizationsTableProps> = ({
|
|||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableHeaderCell>Organization ID</TableHeaderCell>
|
||||
<TableHeaderCell>Organization Name</TableHeaderCell>
|
||||
<TableHeaderCell>Created</TableHeaderCell>
|
||||
<TableHeaderCell>Spend (USD)</TableHeaderCell>
|
||||
<TableHeaderCell>Budget (USD)</TableHeaderCell>
|
||||
<TableHeaderCell>Models</TableHeaderCell>
|
||||
<TableHeaderCell>TPM / RPM Limits</TableHeaderCell>
|
||||
<TableHeaderCell>Info</TableHeaderCell>
|
||||
<TableHeaderCell>Actions</TableHeaderCell>
|
||||
<TableHeaderCell>{t("organizations.colOrgId")}</TableHeaderCell>
|
||||
<TableHeaderCell>{t("organizations.colOrgName")}</TableHeaderCell>
|
||||
<TableHeaderCell>{t("common.createdAt")}</TableHeaderCell>
|
||||
<TableHeaderCell>{t("organizations.colSpend")}</TableHeaderCell>
|
||||
<TableHeaderCell>{t("organizations.colBudget")}</TableHeaderCell>
|
||||
<TableHeaderCell>{t("organizations.colModels")}</TableHeaderCell>
|
||||
<TableHeaderCell>{t("organizations.colTpmRpmLimits")}</TableHeaderCell>
|
||||
<TableHeaderCell>{t("organizations.colInfo")}</TableHeaderCell>
|
||||
<TableHeaderCell>{t("common.actions")}</TableHeaderCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
|
||||
|
|
@ -309,14 +312,16 @@ const OrganizationsTable: React.FC<OrganizationsTableProps> = ({
|
|||
</TableCell>
|
||||
<TableCell>{org.organization_alias}</TableCell>
|
||||
<TableCell>
|
||||
{org.created_at ? new Date(org.created_at).toLocaleDateString() : "N/A"}
|
||||
{org.created_at
|
||||
? new Date(org.created_at).toLocaleDateString()
|
||||
: t("organizations.notAvailable")}
|
||||
</TableCell>
|
||||
<TableCell>{formatNumberWithCommas(org.spend, 4)}</TableCell>
|
||||
<TableCell>
|
||||
{org.litellm_budget_table?.max_budget !== null &&
|
||||
org.litellm_budget_table?.max_budget !== undefined
|
||||
? org.litellm_budget_table?.max_budget
|
||||
: "No limit"}
|
||||
: t("organizations.noLimit")}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
style={{
|
||||
|
|
@ -331,7 +336,7 @@ const OrganizationsTable: React.FC<OrganizationsTableProps> = ({
|
|||
<div className="flex flex-col">
|
||||
{org.models.length === 0 ? (
|
||||
<Badge size={"xs"} className="mb-1" color="red">
|
||||
<Text>All Proxy Models</Text>
|
||||
<Text>{t("organizations.allProxyModels")}</Text>
|
||||
</Badge>
|
||||
) : (
|
||||
<>
|
||||
|
|
@ -360,7 +365,7 @@ const OrganizationsTable: React.FC<OrganizationsTableProps> = ({
|
|||
{org.models.slice(0, 3).map((model, index) =>
|
||||
model === "all-proxy-models" ? (
|
||||
<Badge key={index} size={"xs"} color="red">
|
||||
<Text>All Proxy Models</Text>
|
||||
<Text>{t("organizations.allProxyModels")}</Text>
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge key={index} size={"xs"} color="blue">
|
||||
|
|
@ -376,10 +381,9 @@ const OrganizationsTable: React.FC<OrganizationsTableProps> = ({
|
|||
!expandedAccordions[org.organization_id || ""] && (
|
||||
<Badge size={"xs"} color="gray" className="cursor-pointer">
|
||||
<Text>
|
||||
+{org.models.length - 3}{" "}
|
||||
{org.models.length - 3 === 1
|
||||
? "more model"
|
||||
: "more models"}
|
||||
{t("organizations.moreModels", {
|
||||
count: org.models.length - 3,
|
||||
})}
|
||||
</Text>
|
||||
</Badge>
|
||||
)}
|
||||
|
|
@ -388,7 +392,7 @@ const OrganizationsTable: React.FC<OrganizationsTableProps> = ({
|
|||
{org.models.slice(3).map((model, index) =>
|
||||
model === "all-proxy-models" ? (
|
||||
<Badge key={index + 3} size={"xs"} color="red">
|
||||
<Text>All Proxy Models</Text>
|
||||
<Text>{t("organizations.allProxyModels")}</Text>
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge key={index + 3} size={"xs"} color="blue">
|
||||
|
|
@ -412,26 +416,28 @@ const OrganizationsTable: React.FC<OrganizationsTableProps> = ({
|
|||
</TableCell>
|
||||
<TableCell>
|
||||
<Text>
|
||||
TPM:{" "}
|
||||
{t("organizations.tpmLabel")}:{" "}
|
||||
{org.litellm_budget_table?.tpm_limit
|
||||
? org.litellm_budget_table?.tpm_limit
|
||||
: "Unlimited"}
|
||||
: t("organizations.unlimited")}
|
||||
<br />
|
||||
RPM:{" "}
|
||||
{t("organizations.rpmLabel")}:{" "}
|
||||
{org.litellm_budget_table?.rpm_limit
|
||||
? org.litellm_budget_table?.rpm_limit
|
||||
: "Unlimited"}
|
||||
: t("organizations.unlimited")}
|
||||
</Text>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Text>{org.members?.length || 0} Members</Text>
|
||||
<Text>
|
||||
{t("organizations.memberCount", { count: org.members?.length || 0 })}
|
||||
</Text>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{userRole === "Admin" && (
|
||||
<>
|
||||
<TableIconActionButton
|
||||
variant="Edit"
|
||||
tooltipText="Edit organization"
|
||||
tooltipText={t("organizations.editOrgTooltip")}
|
||||
onClick={() => {
|
||||
setSelectedOrgId(org.organization_id);
|
||||
setEditOrg(true);
|
||||
|
|
@ -439,7 +445,7 @@ const OrganizationsTable: React.FC<OrganizationsTableProps> = ({
|
|||
/>
|
||||
<TableIconActionButton
|
||||
variant="Delete"
|
||||
tooltipText="Delete organization"
|
||||
tooltipText={t("organizations.deleteOrgTooltip")}
|
||||
onClick={() => handleDelete(org.organization_id)}
|
||||
/>
|
||||
</>
|
||||
|
|
@ -459,21 +465,27 @@ const OrganizationsTable: React.FC<OrganizationsTableProps> = ({
|
|||
)}
|
||||
</Col>
|
||||
</Grid>
|
||||
<Modal title="Create Organization" visible={isOrgModalVisible} width={800} footer={null} onCancel={handleCancel}>
|
||||
<Modal
|
||||
title={t("organizations.createModalTitle")}
|
||||
visible={isOrgModalVisible}
|
||||
width={800}
|
||||
footer={null}
|
||||
onCancel={handleCancel}
|
||||
>
|
||||
<Form form={form} onFinish={handleCreate} labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} labelAlign="left">
|
||||
<Form.Item
|
||||
label="Organization Name"
|
||||
label={t("organizations.formOrgName")}
|
||||
name="organization_alias"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: "Please input an organization name",
|
||||
message: t("organizations.formOrgNameRequired"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<TextInput placeholder="" />
|
||||
</Form.Item>
|
||||
<Form.Item label="Models" name="models">
|
||||
<Form.Item label={t("organizations.formModels")} name="models">
|
||||
<ModelSelect
|
||||
options={{ showAllProxyModelsOverride: true, includeSpecialOptions: true }}
|
||||
value={form.getFieldValue("models")}
|
||||
|
|
@ -482,81 +494,81 @@ const OrganizationsTable: React.FC<OrganizationsTableProps> = ({
|
|||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Max Budget (USD)" name="max_budget">
|
||||
<Form.Item label={t("organizations.formMaxBudget")} name="max_budget">
|
||||
<NumericalInput step={0.01} precision={2} width={200} />
|
||||
</Form.Item>
|
||||
<Form.Item label="Reset Budget" name="budget_duration">
|
||||
<Form.Item label={t("organizations.formResetBudget")} name="budget_duration">
|
||||
<Select2 defaultValue={null} placeholder="n/a">
|
||||
<Select2.Option value="24h">daily</Select2.Option>
|
||||
<Select2.Option value="7d">weekly</Select2.Option>
|
||||
<Select2.Option value="30d">monthly</Select2.Option>
|
||||
<Select2.Option value="24h">{t("commonComponents.durationSelect.daily")}</Select2.Option>
|
||||
<Select2.Option value="7d">{t("commonComponents.durationSelect.weekly")}</Select2.Option>
|
||||
<Select2.Option value="30d">{t("commonComponents.durationSelect.monthly")}</Select2.Option>
|
||||
</Select2>
|
||||
</Form.Item>
|
||||
<Form.Item label="Tokens per minute Limit (TPM)" name="tpm_limit">
|
||||
<Form.Item label={t("organizations.formTpmLimit")} name="tpm_limit">
|
||||
<NumericalInput step={1} width={400} />
|
||||
</Form.Item>
|
||||
<Form.Item label="Requests per minute Limit (RPM)" name="rpm_limit">
|
||||
<Form.Item label={t("organizations.formRpmLimit")} name="rpm_limit">
|
||||
<NumericalInput step={1} width={400} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={
|
||||
<span>
|
||||
Allowed Vector Stores{" "}
|
||||
<Tooltip title="Select which vector stores this organization can access by default. Leave empty for access to all vector stores">
|
||||
{t("organizations.formAllowedVectorStores")}{" "}
|
||||
<Tooltip title={t("organizations.formAllowedVectorStoresTooltip")}>
|
||||
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
|
||||
</Tooltip>
|
||||
</span>
|
||||
}
|
||||
name="allowed_vector_store_ids"
|
||||
className="mt-4"
|
||||
help="Select vector stores this organization can access. Leave empty for access to all vector stores"
|
||||
help={t("organizations.formAllowedVectorStoresHelp")}
|
||||
>
|
||||
<VectorStoreSelector
|
||||
onChange={(values) => form.setFieldValue("allowed_vector_store_ids", values)}
|
||||
value={form.getFieldValue("allowed_vector_store_ids")}
|
||||
accessToken={accessToken || ""}
|
||||
placeholder="Select vector stores (optional)"
|
||||
placeholder={t("organizations.formAllowedVectorStoresPlaceholder")}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={
|
||||
<span>
|
||||
Allowed MCP Servers{" "}
|
||||
<Tooltip title="Select which MCP servers and access groups this organization can access by default.">
|
||||
{t("organizations.formAllowedMcpServers")}{" "}
|
||||
<Tooltip title={t("organizations.formAllowedMcpServersTooltip")}>
|
||||
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
|
||||
</Tooltip>
|
||||
</span>
|
||||
}
|
||||
name="allowed_mcp_servers_and_groups"
|
||||
className="mt-4"
|
||||
help="Select MCP servers and access groups this organization can access."
|
||||
help={t("organizations.formAllowedMcpServersHelp")}
|
||||
>
|
||||
<MCPServerSelector
|
||||
onChange={(values) => form.setFieldValue("allowed_mcp_servers_and_groups", values)}
|
||||
value={form.getFieldValue("allowed_mcp_servers_and_groups")}
|
||||
accessToken={accessToken || ""}
|
||||
placeholder="Select MCP servers and access groups (optional)"
|
||||
placeholder={t("organizations.formAllowedMcpServersPlaceholder")}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Metadata" name="metadata">
|
||||
<Form.Item label={t("organizations.formMetadata")} name="metadata">
|
||||
<Input.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
|
||||
<div style={{ textAlign: "right", marginTop: "10px" }}>
|
||||
<Button type="submit">Create Organization</Button>
|
||||
<Button type="submit">{t("organizations.createModalTitle")}</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
<DeleteResourceModal
|
||||
isOpen={isDeleteModalOpen}
|
||||
title="Delete Organization?"
|
||||
message="Are you sure you want to delete this organization? This action cannot be undone."
|
||||
resourceInformationTitle="Organization Information"
|
||||
resourceInformation={[{ label: "Organization ID", value: orgToDelete, code: true }]}
|
||||
title={t("organizations.deleteModalTitle")}
|
||||
message={t("organizations.deleteModalMessage")}
|
||||
resourceInformationTitle={t("organizations.deleteModalResourceTitle")}
|
||||
resourceInformation={[{ label: t("organizations.colOrgId"), value: orgToDelete, code: true }]}
|
||||
onCancel={cancelDelete}
|
||||
onOk={confirmDelete}
|
||||
confirmLoading={isDeleting}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import {
|
|||
TextInput,
|
||||
} from "@tremor/react";
|
||||
import { Button, Form, Input, Switch, InputNumber, Select } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { updatePassThroughEndpoint, deletePassThroughEndpointsCall } from "./networking";
|
||||
import { Eye, EyeOff } from "lucide-react";
|
||||
import RoutePreview from "./route_preview";
|
||||
|
|
@ -70,6 +71,7 @@ const PassThroughInfoView: React.FC<PassThroughInfoProps> = ({
|
|||
premiumUser = false,
|
||||
onEndpointUpdated,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [endpointData, setEndpointData] = useState<PassThroughEndpoint | null>(initialEndpointData);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
|
|
@ -90,7 +92,7 @@ const PassThroughInfoView: React.FC<PassThroughInfoProps> = ({
|
|||
try {
|
||||
headers = typeof values.headers === "string" ? JSON.parse(values.headers) : values.headers;
|
||||
} catch (e) {
|
||||
NotificationsManager.fromBackend("Invalid JSON format for headers");
|
||||
NotificationsManager.fromBackend(t("passThroughInfo.invalidJsonHeaders"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -120,7 +122,7 @@ const PassThroughInfoView: React.FC<PassThroughInfoProps> = ({
|
|||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating endpoint:", error);
|
||||
NotificationsManager.fromBackend("Failed to update pass through endpoint");
|
||||
NotificationsManager.fromBackend(t("passThroughInfo.updateFailed"));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -129,23 +131,23 @@ const PassThroughInfoView: React.FC<PassThroughInfoProps> = ({
|
|||
if (!accessToken || !endpointData?.id) return;
|
||||
|
||||
await deletePassThroughEndpointsCall(accessToken, endpointData.id);
|
||||
NotificationsManager.success("Pass through endpoint deleted successfully");
|
||||
NotificationsManager.success(t("passThroughInfo.deleteSuccess"));
|
||||
onClose();
|
||||
if (onEndpointUpdated) {
|
||||
onEndpointUpdated();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error deleting endpoint:", error);
|
||||
NotificationsManager.fromBackend("Failed to delete pass through endpoint");
|
||||
NotificationsManager.fromBackend(t("passThroughInfo.deleteFailed"));
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <div className="p-4">Loading...</div>;
|
||||
return <div className="p-4">{t("common.loading")}</div>;
|
||||
}
|
||||
|
||||
if (!endpointData) {
|
||||
return <div className="p-4">Pass through endpoint not found</div>;
|
||||
return <div className="p-4">{t("passThroughInfo.notFound")}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -153,17 +155,17 @@ const PassThroughInfoView: React.FC<PassThroughInfoProps> = ({
|
|||
<div className="flex justify-between items-center mb-6">
|
||||
<div>
|
||||
<Button onClick={onClose} className="mb-4">
|
||||
← Back
|
||||
{t("passThroughInfo.backButton")}
|
||||
</Button>
|
||||
<Title>Pass Through Endpoint: {endpointData.path}</Title>
|
||||
<Title>{t("passThroughInfo.pageTitle", { path: endpointData.path })}</Title>
|
||||
<Text className="text-gray-500 font-mono">{endpointData.id}</Text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TabGroup>
|
||||
<TabList className="mb-4">
|
||||
<Tab key="overview">Overview</Tab>
|
||||
{isAdmin ? <Tab key="settings">Settings</Tab> : <></>}
|
||||
<Tab key="overview">{t("passThroughInfo.tabOverview")}</Tab>
|
||||
{isAdmin ? <Tab key="settings">{t("common.settings")}</Tab> : <></>}
|
||||
</TabList>
|
||||
|
||||
<TabPanels>
|
||||
|
|
@ -171,35 +173,37 @@ const PassThroughInfoView: React.FC<PassThroughInfoProps> = ({
|
|||
<TabPanel>
|
||||
<Grid numItems={1} numItemsSm={2} numItemsLg={3} className="gap-6">
|
||||
<Card>
|
||||
<Text>Path</Text>
|
||||
<Text>{t("passThroughInfo.labelPath")}</Text>
|
||||
<div className="mt-2">
|
||||
<Title className="font-mono">{endpointData.path}</Title>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Text>Target</Text>
|
||||
<Text>{t("passThroughInfo.labelTarget")}</Text>
|
||||
<div className="mt-2">
|
||||
<Title>{endpointData.target}</Title>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Text>Configuration</Text>
|
||||
<Text>{t("passThroughInfo.labelConfiguration")}</Text>
|
||||
<div className="mt-2 space-y-2">
|
||||
<div>
|
||||
<Badge color={endpointData.include_subpath ? "green" : "gray"}>
|
||||
{endpointData.include_subpath ? "Include Subpath" : "Exact Path"}
|
||||
{endpointData.include_subpath
|
||||
? t("passThroughInfo.includeSubpath")
|
||||
: t("passThroughInfo.exactPath")}
|
||||
</Badge>
|
||||
</div>
|
||||
<div>
|
||||
<Badge color={endpointData.auth ? "blue" : "gray"}>
|
||||
{endpointData.auth ? "Auth Required" : "No Auth"}
|
||||
{endpointData.auth ? t("passThroughInfo.authRequired") : t("passThroughInfo.noAuth")}
|
||||
</Badge>
|
||||
</div>
|
||||
{endpointData.methods && endpointData.methods.length > 0 && (
|
||||
<div>
|
||||
<Text className="text-xs text-gray-500">HTTP Methods:</Text>
|
||||
<Text className="text-xs text-gray-500">{t("passThroughInfo.httpMethods")}</Text>
|
||||
<div className="flex flex-wrap gap-1 mt-1">
|
||||
{endpointData.methods.map((method) => (
|
||||
<Badge key={method} color="indigo" size="sm">
|
||||
|
|
@ -211,12 +215,12 @@ const PassThroughInfoView: React.FC<PassThroughInfoProps> = ({
|
|||
)}
|
||||
{(!endpointData.methods || endpointData.methods.length === 0) && (
|
||||
<div>
|
||||
<Text className="text-xs text-gray-500">All HTTP methods supported</Text>
|
||||
<Text className="text-xs text-gray-500">{t("passThroughInfo.allHttpMethodsSupported")}</Text>
|
||||
</div>
|
||||
)}
|
||||
{endpointData.cost_per_request !== undefined && (
|
||||
<div>
|
||||
<Text>Cost per request: ${endpointData.cost_per_request}</Text>
|
||||
<Text>{t("passThroughInfo.costPerRequest", { cost: endpointData.cost_per_request })}</Text>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -235,8 +239,10 @@ const PassThroughInfoView: React.FC<PassThroughInfoProps> = ({
|
|||
{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>
|
||||
<Text className="font-medium">{t("passThroughInfo.labelHeaders")}</Text>
|
||||
<Badge color="blue">
|
||||
{t("passThroughInfo.headersConfigured", { count: Object.keys(endpointData.headers).length })}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<PasswordField value={endpointData.headers} />
|
||||
|
|
@ -247,8 +253,10 @@ const PassThroughInfoView: React.FC<PassThroughInfoProps> = ({
|
|||
{endpointData.guardrails && Object.keys(endpointData.guardrails).length > 0 && (
|
||||
<Card className="mt-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<Text className="font-medium">Guardrails</Text>
|
||||
<Badge color="purple">{Object.keys(endpointData.guardrails).length} guardrails configured</Badge>
|
||||
<Text className="font-medium">{t("passThroughInfo.labelGuardrails")}</Text>
|
||||
<Badge color="purple">
|
||||
{t("passThroughInfo.guardrailsConfigured", { count: Object.keys(endpointData.guardrails).length })}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="mt-4 space-y-2">
|
||||
{Object.entries(endpointData.guardrails).map(([name, settings]) => (
|
||||
|
|
@ -256,13 +264,21 @@ const PassThroughInfoView: React.FC<PassThroughInfoProps> = ({
|
|||
<div className="font-medium text-sm">{name}</div>
|
||||
{settings && (settings.request_fields || settings.response_fields) && (
|
||||
<div className="mt-2 text-xs text-gray-600 space-y-1">
|
||||
{settings.request_fields && <div>Request fields: {settings.request_fields.join(", ")}</div>}
|
||||
{settings.request_fields && (
|
||||
<div>
|
||||
{t("passThroughInfo.requestFields", { fields: settings.request_fields.join(", ") })}
|
||||
</div>
|
||||
)}
|
||||
{settings.response_fields && (
|
||||
<div>Response fields: {settings.response_fields.join(", ")}</div>
|
||||
<div>
|
||||
{t("passThroughInfo.responseFields", { fields: settings.response_fields.join(", ") })}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{!settings && <div className="text-xs text-gray-600 mt-1">Uses entire payload</div>}
|
||||
{!settings && (
|
||||
<div className="text-xs text-gray-600 mt-1">{t("passThroughInfo.usesEntirePayload")}</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -275,13 +291,15 @@ const PassThroughInfoView: React.FC<PassThroughInfoProps> = ({
|
|||
<TabPanel>
|
||||
<Card>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<Title>Pass Through Endpoint Settings</Title>
|
||||
<Title>{t("passThroughInfo.settingsTitle")}</Title>
|
||||
<div className="space-x-2">
|
||||
{!isEditing && (
|
||||
<>
|
||||
<TremorButton onClick={() => setIsEditing(true)}>Edit Settings</TremorButton>
|
||||
<TremorButton onClick={() => setIsEditing(true)}>
|
||||
{t("passThroughInfo.editSettings")}
|
||||
</TremorButton>
|
||||
<TremorButton onClick={handleDeleteEndpoint} variant="secondary" color="red">
|
||||
Delete Endpoint
|
||||
{t("passThroughInfo.deleteEndpoint")}
|
||||
</TremorButton>
|
||||
</>
|
||||
)}
|
||||
|
|
@ -303,14 +321,14 @@ const PassThroughInfoView: React.FC<PassThroughInfoProps> = ({
|
|||
layout="vertical"
|
||||
>
|
||||
<Form.Item
|
||||
label="Target URL"
|
||||
label={t("passThroughInfo.formTargetUrl")}
|
||||
name="target"
|
||||
rules={[{ required: true, message: "Please input a target URL" }]}
|
||||
rules={[{ required: true, message: t("passThroughInfo.formTargetUrlRequired") }]}
|
||||
>
|
||||
<TextInput placeholder="https://api.example.com" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Headers (JSON)" name="headers">
|
||||
<Form.Item label={t("passThroughInfo.formHeadersJson")} name="headers">
|
||||
<Input.TextArea
|
||||
rows={5}
|
||||
placeholder='{"Authorization": "Bearer your-token", "Content-Type": "application/json"}'
|
||||
|
|
@ -318,17 +336,17 @@ const PassThroughInfoView: React.FC<PassThroughInfoProps> = ({
|
|||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="HTTP Methods (Optional)"
|
||||
label={t("passThroughInfo.formHttpMethods")}
|
||||
name="methods"
|
||||
extra={
|
||||
selectedMethods.length === 0
|
||||
? "All HTTP methods supported (default)"
|
||||
: `Only ${selectedMethods.join(", ")} requests will be routed to this endpoint`
|
||||
? t("passThroughInfo.allHttpMethodsDefault")
|
||||
: t("passThroughInfo.selectedMethodsDesc", { methods: selectedMethods.join(", ") })
|
||||
}
|
||||
>
|
||||
<Select
|
||||
mode="multiple"
|
||||
placeholder="Select methods (leave empty for all)"
|
||||
placeholder={t("passThroughInfo.selectMethodsPlaceholder")}
|
||||
value={selectedMethods}
|
||||
onChange={setSelectedMethods}
|
||||
allowClear
|
||||
|
|
@ -342,11 +360,15 @@ const PassThroughInfoView: React.FC<PassThroughInfoProps> = ({
|
|||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Include Subpath" name="include_subpath" valuePropName="checked">
|
||||
<Form.Item
|
||||
label={t("passThroughInfo.formIncludeSubpath")}
|
||||
name="include_subpath"
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Cost per Request" name="cost_per_request">
|
||||
<Form.Item label={t("passThroughInfo.formCostPerRequest")} name="cost_per_request">
|
||||
<InputNumber min={0} step={0.01} precision={2} placeholder="0.00" addonBefore="$" />
|
||||
</Form.Item>
|
||||
|
||||
|
|
@ -368,44 +390,46 @@ const PassThroughInfoView: React.FC<PassThroughInfoProps> = ({
|
|||
</div>
|
||||
|
||||
<div className="flex justify-end gap-2 mt-6">
|
||||
<Button onClick={() => setIsEditing(false)}>Cancel</Button>
|
||||
<TremorButton>Save Changes</TremorButton>
|
||||
<Button onClick={() => setIsEditing(false)}>{t("common.cancel")}</Button>
|
||||
<TremorButton>{t("passThroughInfo.saveChanges")}</TremorButton>
|
||||
</div>
|
||||
</Form>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Text className="font-medium">Path</Text>
|
||||
<Text className="font-medium">{t("passThroughInfo.labelPath")}</Text>
|
||||
<div className="font-mono">{endpointData.path}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Text className="font-medium">Target URL</Text>
|
||||
<Text className="font-medium">{t("passThroughInfo.formTargetUrl")}</Text>
|
||||
<div>{endpointData.target}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Text className="font-medium">Include Subpath</Text>
|
||||
<Text className="font-medium">{t("passThroughInfo.formIncludeSubpath")}</Text>
|
||||
<Badge color={endpointData.include_subpath ? "green" : "gray"}>
|
||||
{endpointData.include_subpath ? "Yes" : "No"}
|
||||
{endpointData.include_subpath ? t("common.yes") : t("common.no")}
|
||||
</Badge>
|
||||
</div>
|
||||
{endpointData.cost_per_request !== undefined && (
|
||||
<div>
|
||||
<Text className="font-medium">Cost per Request</Text>
|
||||
<Text className="font-medium">{t("passThroughInfo.formCostPerRequest")}</Text>
|
||||
<div>${endpointData.cost_per_request}</div>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<Text className="font-medium">Authentication Required</Text>
|
||||
<Badge color={endpointData.auth ? "green" : "gray"}>{endpointData.auth ? "Yes" : "No"}</Badge>
|
||||
<Text className="font-medium">{t("passThroughInfo.authenticationRequired")}</Text>
|
||||
<Badge color={endpointData.auth ? "green" : "gray"}>
|
||||
{endpointData.auth ? t("common.yes") : t("common.no")}
|
||||
</Badge>
|
||||
</div>
|
||||
<div>
|
||||
<Text className="font-medium">Headers</Text>
|
||||
<Text className="font-medium">{t("passThroughInfo.labelHeaders")}</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 className="text-gray-500">{t("passThroughInfo.noHeadersConfigured")}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1466,5 +1466,112 @@
|
|||
"createdAt": "Created At",
|
||||
"updatedAt": "Updated At",
|
||||
"comingSoon": "Coming soon"
|
||||
},
|
||||
"objectPermissionsView": {
|
||||
"title": "Object Permissions",
|
||||
"subtitle": "Access control for Vector Stores and MCP Servers",
|
||||
"searchToolsTitle": "Search tools",
|
||||
"searchToolsNoRestriction": "No restriction — all configured search tools are allowed for this team."
|
||||
},
|
||||
"onboardingLink": {
|
||||
"invitationLinkTitle": "Invitation Link",
|
||||
"resetPasswordLinkTitle": "Reset Password Link",
|
||||
"invitationLinkDesc": "Copy and send the generated link to onboard this user to the proxy.",
|
||||
"resetPasswordLinkDesc": "Copy and send the generated link to the user to reset their password.",
|
||||
"userId": "User ID",
|
||||
"copyInvitationLink": "Copy invitation link",
|
||||
"copyPasswordResetLink": "Copy password reset link"
|
||||
},
|
||||
"organizations": {
|
||||
"enterpriseFeature": "This is a LiteLLM Enterprise feature, and requires a valid key to use. Get a trial key <pricingLink>here</pricingLink>.",
|
||||
"createNewOrg": "+ Create New Organization",
|
||||
"tabYourOrgs": "Your Organizations",
|
||||
"lastRefreshed": "Last Refreshed: {{time}}",
|
||||
"clickOrgIdHint": "Click on \"Organization ID\" to view organization details.",
|
||||
"colOrgId": "Organization ID",
|
||||
"colOrgName": "Organization Name",
|
||||
"colSpend": "Spend (USD)",
|
||||
"colBudget": "Budget (USD)",
|
||||
"colModels": "Models",
|
||||
"colTpmRpmLimits": "TPM / RPM Limits",
|
||||
"colInfo": "Info",
|
||||
"notAvailable": "N/A",
|
||||
"noLimit": "No limit",
|
||||
"allProxyModels": "All Proxy Models",
|
||||
"moreModels_one": "+{{count}} more model",
|
||||
"moreModels_other": "+{{count}} more models",
|
||||
"tpmLabel": "TPM",
|
||||
"rpmLabel": "RPM",
|
||||
"unlimited": "Unlimited",
|
||||
"memberCount_one": "{{count}} Member",
|
||||
"memberCount_other": "{{count}} Members",
|
||||
"editOrgTooltip": "Edit organization",
|
||||
"deleteOrgTooltip": "Delete organization",
|
||||
"createModalTitle": "Create Organization",
|
||||
"formOrgName": "Organization Name",
|
||||
"formOrgNameRequired": "Please input an organization name",
|
||||
"formModels": "Models",
|
||||
"formMaxBudget": "Max Budget (USD)",
|
||||
"formResetBudget": "Reset Budget",
|
||||
"formTpmLimit": "Tokens per minute Limit (TPM)",
|
||||
"formRpmLimit": "Requests per minute Limit (RPM)",
|
||||
"formAllowedVectorStores": "Allowed Vector Stores",
|
||||
"formAllowedVectorStoresTooltip": "Select which vector stores this organization can access by default. Leave empty for access to all vector stores",
|
||||
"formAllowedVectorStoresHelp": "Select vector stores this organization can access. Leave empty for access to all vector stores",
|
||||
"formAllowedVectorStoresPlaceholder": "Select vector stores (optional)",
|
||||
"formAllowedMcpServers": "Allowed MCP Servers",
|
||||
"formAllowedMcpServersTooltip": "Select which MCP servers and access groups this organization can access by default.",
|
||||
"formAllowedMcpServersHelp": "Select MCP servers and access groups this organization can access.",
|
||||
"formAllowedMcpServersPlaceholder": "Select MCP servers and access groups (optional)",
|
||||
"formMetadata": "Metadata",
|
||||
"deleteModalTitle": "Delete Organization?",
|
||||
"deleteModalMessage": "Are you sure you want to delete this organization? This action cannot be undone.",
|
||||
"deleteModalResourceTitle": "Organization Information",
|
||||
"deleteSuccess": "Organization deleted successfully",
|
||||
"createSuccess": "Organization created successfully"
|
||||
},
|
||||
"passThroughInfo": {
|
||||
"notFound": "Pass through endpoint not found",
|
||||
"backButton": "← Back",
|
||||
"pageTitle": "Pass Through Endpoint: {{path}}",
|
||||
"tabOverview": "Overview",
|
||||
"labelPath": "Path",
|
||||
"labelTarget": "Target",
|
||||
"labelConfiguration": "Configuration",
|
||||
"includeSubpath": "Include Subpath",
|
||||
"exactPath": "Exact Path",
|
||||
"authRequired": "Auth Required",
|
||||
"noAuth": "No Auth",
|
||||
"httpMethods": "HTTP Methods:",
|
||||
"allHttpMethodsSupported": "All HTTP methods supported",
|
||||
"costPerRequest": "Cost per request: ${{cost}}",
|
||||
"labelHeaders": "Headers",
|
||||
"headersConfigured_one": "{{count}} header configured",
|
||||
"headersConfigured_other": "{{count}} headers configured",
|
||||
"labelGuardrails": "Guardrails",
|
||||
"guardrailsConfigured_one": "{{count}} guardrail configured",
|
||||
"guardrailsConfigured_other": "{{count}} guardrails configured",
|
||||
"requestFields": "Request fields: {{fields}}",
|
||||
"responseFields": "Response fields: {{fields}}",
|
||||
"usesEntirePayload": "Uses entire payload",
|
||||
"settingsTitle": "Pass Through Endpoint Settings",
|
||||
"editSettings": "Edit Settings",
|
||||
"deleteEndpoint": "Delete Endpoint",
|
||||
"formTargetUrl": "Target URL",
|
||||
"formTargetUrlRequired": "Please input a target URL",
|
||||
"formHeadersJson": "Headers (JSON)",
|
||||
"formHttpMethods": "HTTP Methods (Optional)",
|
||||
"allHttpMethodsDefault": "All HTTP methods supported (default)",
|
||||
"selectedMethodsDesc": "Only {{methods}} requests will be routed to this endpoint",
|
||||
"selectMethodsPlaceholder": "Select methods (leave empty for all)",
|
||||
"formIncludeSubpath": "Include Subpath",
|
||||
"formCostPerRequest": "Cost per Request",
|
||||
"saveChanges": "Save Changes",
|
||||
"authenticationRequired": "Authentication Required",
|
||||
"noHeadersConfigured": "No headers configured",
|
||||
"invalidJsonHeaders": "Invalid JSON format for headers",
|
||||
"updateFailed": "Failed to update pass through endpoint",
|
||||
"deleteSuccess": "Pass through endpoint deleted successfully",
|
||||
"deleteFailed": "Failed to delete pass through endpoint"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1466,5 +1466,112 @@
|
|||
"createdAt": "创建时间",
|
||||
"updatedAt": "更新时间",
|
||||
"comingSoon": "即将推出"
|
||||
},
|
||||
"objectPermissionsView": {
|
||||
"title": "对象权限",
|
||||
"subtitle": "向量存储与 MCP 服务器的访问控制",
|
||||
"searchToolsTitle": "搜索工具",
|
||||
"searchToolsNoRestriction": "无限制 — 该团队可使用所有已配置的搜索工具。"
|
||||
},
|
||||
"onboardingLink": {
|
||||
"invitationLinkTitle": "邀请链接",
|
||||
"resetPasswordLinkTitle": "重置密码链接",
|
||||
"invitationLinkDesc": "复制并将生成的链接发送给该用户,以便将其接入代理。",
|
||||
"resetPasswordLinkDesc": "复制并将生成的链接发送给该用户,以便其重置密码。",
|
||||
"userId": "用户 ID",
|
||||
"copyInvitationLink": "复制邀请链接",
|
||||
"copyPasswordResetLink": "复制密码重置链接"
|
||||
},
|
||||
"organizations": {
|
||||
"enterpriseFeature": "这是 LiteLLM 企业版功能,需要有效的密钥才能使用。获取试用密钥请访问<pricingLink>此处</pricingLink>。",
|
||||
"createNewOrg": "+ 创建新组织",
|
||||
"tabYourOrgs": "我的组织",
|
||||
"lastRefreshed": "最后刷新:{{time}}",
|
||||
"clickOrgIdHint": "点击「组织 ID」可查看组织详情。",
|
||||
"colOrgId": "组织 ID",
|
||||
"colOrgName": "组织名称",
|
||||
"colSpend": "花费(USD)",
|
||||
"colBudget": "预算(USD)",
|
||||
"colModels": "模型",
|
||||
"colTpmRpmLimits": "TPM / RPM 限制",
|
||||
"colInfo": "信息",
|
||||
"notAvailable": "N/A",
|
||||
"noLimit": "无限制",
|
||||
"allProxyModels": "所有代理模型",
|
||||
"moreModels_one": "+{{count}} 个模型",
|
||||
"moreModels_other": "+{{count}} 个模型",
|
||||
"tpmLabel": "TPM",
|
||||
"rpmLabel": "RPM",
|
||||
"unlimited": "无限制",
|
||||
"memberCount_one": "{{count}} 位成员",
|
||||
"memberCount_other": "{{count}} 位成员",
|
||||
"editOrgTooltip": "编辑组织",
|
||||
"deleteOrgTooltip": "删除组织",
|
||||
"createModalTitle": "创建组织",
|
||||
"formOrgName": "组织名称",
|
||||
"formOrgNameRequired": "请输入组织名称",
|
||||
"formModels": "模型",
|
||||
"formMaxBudget": "最大预算(USD)",
|
||||
"formResetBudget": "重置预算",
|
||||
"formTpmLimit": "每分钟 Token 限制(TPM)",
|
||||
"formRpmLimit": "每分钟请求限制(RPM)",
|
||||
"formAllowedVectorStores": "允许的向量存储",
|
||||
"formAllowedVectorStoresTooltip": "选择此组织默认可访问的向量存储。留空则允许访问所有向量存储",
|
||||
"formAllowedVectorStoresHelp": "选择此组织可访问的向量存储。留空则允许访问所有向量存储",
|
||||
"formAllowedVectorStoresPlaceholder": "选择向量存储(可选)",
|
||||
"formAllowedMcpServers": "允许的 MCP 服务器",
|
||||
"formAllowedMcpServersTooltip": "选择此组织默认可访问的 MCP 服务器和访问组。",
|
||||
"formAllowedMcpServersHelp": "选择此组织可访问的 MCP 服务器和访问组。",
|
||||
"formAllowedMcpServersPlaceholder": "选择 MCP 服务器和访问组(可选)",
|
||||
"formMetadata": "元数据",
|
||||
"deleteModalTitle": "删除组织?",
|
||||
"deleteModalMessage": "确定要删除此组织吗?此操作无法撤销。",
|
||||
"deleteModalResourceTitle": "组织信息",
|
||||
"deleteSuccess": "组织已成功删除",
|
||||
"createSuccess": "组织已成功创建"
|
||||
},
|
||||
"passThroughInfo": {
|
||||
"notFound": "未找到透传端点",
|
||||
"backButton": "← 返回",
|
||||
"pageTitle": "透传端点:{{path}}",
|
||||
"tabOverview": "概览",
|
||||
"labelPath": "路径",
|
||||
"labelTarget": "目标",
|
||||
"labelConfiguration": "配置",
|
||||
"includeSubpath": "包含子路径",
|
||||
"exactPath": "精确路径",
|
||||
"authRequired": "需要认证",
|
||||
"noAuth": "无需认证",
|
||||
"httpMethods": "HTTP 方法:",
|
||||
"allHttpMethodsSupported": "支持所有 HTTP 方法",
|
||||
"costPerRequest": "每次请求费用:${{cost}}",
|
||||
"labelHeaders": "请求头",
|
||||
"headersConfigured_one": "已配置 {{count}} 个请求头",
|
||||
"headersConfigured_other": "已配置 {{count}} 个请求头",
|
||||
"labelGuardrails": "护栏",
|
||||
"guardrailsConfigured_one": "已配置 {{count}} 个护栏",
|
||||
"guardrailsConfigured_other": "已配置 {{count}} 个护栏",
|
||||
"requestFields": "请求字段:{{fields}}",
|
||||
"responseFields": "响应字段:{{fields}}",
|
||||
"usesEntirePayload": "检查整个载荷",
|
||||
"settingsTitle": "透传端点设置",
|
||||
"editSettings": "编辑设置",
|
||||
"deleteEndpoint": "删除端点",
|
||||
"formTargetUrl": "目标 URL",
|
||||
"formTargetUrlRequired": "请输入目标 URL",
|
||||
"formHeadersJson": "请求头(JSON)",
|
||||
"formHttpMethods": "HTTP 方法(可选)",
|
||||
"allHttpMethodsDefault": "支持所有 HTTP 方法(默认)",
|
||||
"selectedMethodsDesc": "仅 {{methods}} 请求将路由到此端点",
|
||||
"selectMethodsPlaceholder": "选择方法(留空表示全部)",
|
||||
"formIncludeSubpath": "包含子路径",
|
||||
"formCostPerRequest": "每次请求费用",
|
||||
"saveChanges": "保存更改",
|
||||
"authenticationRequired": "需要认证",
|
||||
"noHeadersConfigured": "未配置请求头",
|
||||
"invalidJsonHeaders": "请求头的 JSON 格式无效",
|
||||
"updateFailed": "更新透传端点失败",
|
||||
"deleteSuccess": "透传端点已成功删除",
|
||||
"deleteFailed": "删除透传端点失败"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue