mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): i18n for key info header and edit view
This commit is contained in:
parent
04778d2dad
commit
4eaf3508c1
4 changed files with 295 additions and 91 deletions
|
|
@ -13,6 +13,7 @@ import {
|
|||
TransactionOutlined,
|
||||
FieldTimeOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import LabeledField from "../common_components/LabeledField";
|
||||
import DefaultProxyAdminTag from "../common_components/DefaultProxyAdminTag";
|
||||
|
||||
|
|
@ -45,13 +46,14 @@ interface KeyInfoHeaderProps {
|
|||
}
|
||||
|
||||
function UserField({ userAlias, userEmail, userId }: { userAlias?: string | null; userEmail: string; userId: string }) {
|
||||
const { t } = useTranslation();
|
||||
const labelEl = (
|
||||
<Space size={4}>
|
||||
<Text type="secondary">
|
||||
<UserOutlined />
|
||||
</Text>
|
||||
<Text type="secondary" style={{ fontSize: 12, textTransform: "uppercase", letterSpacing: "0.05em" }}>
|
||||
User
|
||||
{t("templates.keyInfoHeader.user")}
|
||||
</Text>
|
||||
</Space>
|
||||
);
|
||||
|
|
@ -74,9 +76,9 @@ function UserField({ userAlias, userEmail, userId }: { userAlias?: string | null
|
|||
const popoverContent = (
|
||||
<div className="flex flex-col gap-2 text-xs min-w-[200px] max-w-[300px]">
|
||||
{[
|
||||
{ label: "User Alias", value: userAlias ?? null },
|
||||
{ label: "User Email", value: userEmail || null },
|
||||
{ label: "User ID", value: userId || null },
|
||||
{ label: t("templates.keyInfoHeader.userAlias"), value: userAlias ?? null },
|
||||
{ label: t("templates.keyInfoHeader.userEmail"), value: userEmail || null },
|
||||
{ label: t("templates.keyInfoHeader.userId"), value: userId || null },
|
||||
].map(({ label, value }) => (
|
||||
<div key={label} className="flex flex-col min-w-0">
|
||||
<span className="text-gray-400">{label}</span>
|
||||
|
|
@ -134,33 +136,42 @@ export function KeyInfoHeader({
|
|||
onDelete,
|
||||
onResetSpend,
|
||||
canModifyKey = true,
|
||||
backButtonText = "Back to Keys",
|
||||
backButtonText,
|
||||
regenerateDisabled = false,
|
||||
regenerateTooltip,
|
||||
}: KeyInfoHeaderProps) {
|
||||
const { t } = useTranslation();
|
||||
const resolvedBackButtonText = backButtonText ?? t("templates.keyInfoHeader.backToKeys");
|
||||
return (
|
||||
<div>
|
||||
{onCreateNew && (
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={onCreateNew}>
|
||||
Create New Key
|
||||
{t("templates.keyInfoHeader.createNewKey")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<Button type="text" icon={<ArrowLeftOutlined />} onClick={onBack}>
|
||||
{backButtonText}
|
||||
{resolvedBackButtonText}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Flex justify="space-between" align="start" style={{ marginBottom: 20 }}>
|
||||
<div>
|
||||
<Title level={3} copyable={{ tooltips: ["Copy Key Alias", "Copied!"] }} style={{ margin: 0 }}>
|
||||
<Title
|
||||
level={3}
|
||||
copyable={{ tooltips: [t("templates.keyInfoHeader.copyKeyAlias"), t("common.copied")] }}
|
||||
style={{ margin: 0 }}
|
||||
>
|
||||
{data.keyName}
|
||||
</Title>
|
||||
<Text type="secondary" copyable={{ text: data.keyId, tooltips: ["Copy Key ID", "Copied!"] }}>
|
||||
Key ID: {data.keyId}
|
||||
<Text
|
||||
type="secondary"
|
||||
copyable={{ text: data.keyId, tooltips: [t("templates.keyInfoHeader.copyKeyId"), t("common.copied")] }}
|
||||
>
|
||||
{t("templates.keyInfoHeader.keyId", { keyId: data.keyId })}
|
||||
</Text>
|
||||
</div>
|
||||
{canModifyKey && (
|
||||
|
|
@ -168,17 +179,17 @@ export function KeyInfoHeader({
|
|||
<Tooltip title={regenerateTooltip || ""}>
|
||||
<span>
|
||||
<Button icon={<SyncOutlined />} onClick={onRegenerate} disabled={regenerateDisabled}>
|
||||
Regenerate Key
|
||||
{t("templates.keyInfoHeader.regenerateKey")}
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
{onResetSpend && (
|
||||
<Button danger icon={<TransactionOutlined />} onClick={onResetSpend}>
|
||||
Reset Spend
|
||||
{t("templates.keyInfoHeader.resetSpend")}
|
||||
</Button>
|
||||
)}
|
||||
<Button danger icon={<DeleteOutlined />} onClick={onDelete}>
|
||||
Delete Key
|
||||
{t("templates.keyInfoHeader.deleteKey")}
|
||||
</Button>
|
||||
</Space>
|
||||
)}
|
||||
|
|
@ -187,15 +198,19 @@ export function KeyInfoHeader({
|
|||
<Flex align="stretch" gap={40} style={{ marginBottom: 40 }}>
|
||||
<Space direction="vertical" size={16}>
|
||||
<UserField userAlias={data.userAlias} userEmail={data.userEmail} userId={data.userId} />
|
||||
<LabeledField label="Expires" value={data.expires} icon={<FieldTimeOutlined />} />
|
||||
<LabeledField
|
||||
label={t("templates.keyInfoHeader.expires")}
|
||||
value={data.expires}
|
||||
icon={<FieldTimeOutlined />}
|
||||
/>
|
||||
</Space>
|
||||
|
||||
<Divider type="vertical" style={{ height: "auto" }} />
|
||||
|
||||
<Space direction="vertical" size={16}>
|
||||
<LabeledField label="Created At" value={data.createdAt} icon={<CalendarOutlined />} />
|
||||
<LabeledField label={t("common.createdAt")} value={data.createdAt} icon={<CalendarOutlined />} />
|
||||
<LabeledField
|
||||
label="Created By"
|
||||
label={t("templates.keyInfoHeader.createdBy")}
|
||||
value={data.createdBy}
|
||||
icon={<SafetyCertificateOutlined />}
|
||||
truncate
|
||||
|
|
@ -207,8 +222,16 @@ export function KeyInfoHeader({
|
|||
<Divider type="vertical" style={{ height: "auto" }} />
|
||||
|
||||
<Space direction="vertical" size={16}>
|
||||
<LabeledField label="Last Updated" value={data.lastUpdated} icon={<ClockCircleOutlined />} />
|
||||
<LabeledField label="Last Active" value={data.lastActive} icon={<ThunderboltOutlined />} />
|
||||
<LabeledField
|
||||
label={t("templates.keyInfoHeader.lastUpdated")}
|
||||
value={data.lastUpdated}
|
||||
icon={<ClockCircleOutlined />}
|
||||
/>
|
||||
<LabeledField
|
||||
label={t("templates.keyInfoHeader.lastActive")}
|
||||
value={data.lastActive}
|
||||
icon={<ThunderboltOutlined />}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { InfoCircleOutlined } from "@ant-design/icons";
|
|||
import { TextInput, Button as TremorButton } from "@tremor/react";
|
||||
import { Form, Input, Select, Switch, Tooltip } from "antd";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { rolesWithWriteAccess } from "../../utils/roles";
|
||||
import AgentSelector from "../agent_management/AgentSelector";
|
||||
import AccessGroupSelector from "../common_components/AccessGroupSelector";
|
||||
|
|
@ -88,6 +89,7 @@ export function KeyEditView({
|
|||
userRole,
|
||||
premiumUser = false,
|
||||
}: KeyEditViewProps) {
|
||||
const { t } = useTranslation();
|
||||
const canEditGuardrails = premiumUser || (userRole != null && rolesWithWriteAccess.includes(userRole));
|
||||
const [form] = Form.useForm();
|
||||
const [promptsList, setPromptsList] = useState<string[]>([]);
|
||||
|
|
@ -250,7 +252,7 @@ export function KeyEditView({
|
|||
const response = await tagListCall(accessToken);
|
||||
setTagsList(response);
|
||||
} catch (error) {
|
||||
NotificationsManager.fromBackend("Error fetching tags: " + error);
|
||||
NotificationsManager.fromBackend(t("templates.keyEditView.errorFetchingTags", { error }));
|
||||
}
|
||||
};
|
||||
fetchTags();
|
||||
|
|
@ -303,11 +305,11 @@ export function KeyEditView({
|
|||
|
||||
return (
|
||||
<Form form={form} onFinish={handleSubmit} initialValues={initialValues} layout="vertical">
|
||||
<Form.Item label="Key Alias" name="key_alias">
|
||||
<Form.Item label={t("templates.keyEditView.keyAlias")} name="key_alias">
|
||||
<TextInput />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Models" name="models">
|
||||
<Form.Item label={t("templates.keyEditView.models")} name="models">
|
||||
<Form.Item
|
||||
noStyle
|
||||
shouldUpdate={(prevValues, currentValues) =>
|
||||
|
|
@ -331,14 +333,16 @@ export function KeyEditView({
|
|||
<>
|
||||
<Select
|
||||
mode="multiple"
|
||||
placeholder="Select models"
|
||||
placeholder={t("templates.keyEditView.selectModels")}
|
||||
style={{ width: "100%" }}
|
||||
disabled={isDisabled}
|
||||
value={isDisabled ? [] : models}
|
||||
onChange={(value) => setFieldValue("models", value)}
|
||||
>
|
||||
{/* Only show All Team Models if team has models */}
|
||||
{availableModels.length > 0 && <Select.Option value="all-team-models">All Team Models</Select.Option>}
|
||||
{availableModels.length > 0 && (
|
||||
<Select.Option value="all-team-models">{t("templates.keyEditView.allTeamModels")}</Select.Option>
|
||||
)}
|
||||
{availableModels.map((model) => (
|
||||
<Select.Option key={model} value={model}>
|
||||
{model}
|
||||
|
|
@ -347,7 +351,7 @@ export function KeyEditView({
|
|||
</Select>
|
||||
{isDisabled && (
|
||||
<div style={{ fontSize: "11px", color: "#6b7280", marginTop: "2px" }}>
|
||||
Models field is disabled for this key type
|
||||
{t("templates.keyEditView.modelsDisabled")}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
|
@ -356,7 +360,7 @@ export function KeyEditView({
|
|||
</Form.Item>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Key Type">
|
||||
<Form.Item label={t("templates.keyEditView.keyType")}>
|
||||
<Form.Item
|
||||
noStyle
|
||||
shouldUpdate={(prevValues, currentValues) => prevValues.allowed_routes !== currentValues.allowed_routes}
|
||||
|
|
@ -375,7 +379,7 @@ export function KeyEditView({
|
|||
|
||||
return (
|
||||
<Select
|
||||
placeholder="Select key type"
|
||||
placeholder={t("templates.keyEditView.selectKeyType")}
|
||||
style={{ width: "100%" }}
|
||||
optionLabelProp="label"
|
||||
value={keyTypeValue}
|
||||
|
|
@ -394,27 +398,27 @@ export function KeyEditView({
|
|||
}
|
||||
}}
|
||||
>
|
||||
<Select.Option value="default" label="Full Access">
|
||||
<Select.Option value="default" label={t("templates.keyEditView.fullAccess")}>
|
||||
<div style={{ padding: "4px 0" }}>
|
||||
<div style={{ fontWeight: 500 }}>Full Access</div>
|
||||
<div style={{ fontWeight: 500 }}>{t("templates.keyEditView.fullAccess")}</div>
|
||||
<div style={{ fontSize: "11px", color: "#6b7280", marginTop: "2px" }}>
|
||||
Can call all routes (AI APIs, Management, and read-only)
|
||||
{t("templates.keyEditView.fullAccessDesc")}
|
||||
</div>
|
||||
</div>
|
||||
</Select.Option>
|
||||
<Select.Option value="llm_api" label="AI APIs">
|
||||
<Select.Option value="llm_api" label={t("templates.keyEditView.aiApis")}>
|
||||
<div style={{ padding: "4px 0" }}>
|
||||
<div style={{ fontWeight: 500 }}>AI APIs</div>
|
||||
<div style={{ fontWeight: 500 }}>{t("templates.keyEditView.aiApis")}</div>
|
||||
<div style={{ fontSize: "11px", color: "#6b7280", marginTop: "2px" }}>
|
||||
Can call only AI API routes (chat/completions, embeddings, etc.)
|
||||
{t("templates.keyEditView.aiApisDesc")}
|
||||
</div>
|
||||
</div>
|
||||
</Select.Option>
|
||||
<Select.Option value="management" label="Management">
|
||||
<Select.Option value="management" label={t("templates.keyEditView.management")}>
|
||||
<div style={{ padding: "4px 0" }}>
|
||||
<div style={{ fontWeight: 500 }}>Management</div>
|
||||
<div style={{ fontWeight: 500 }}>{t("templates.keyEditView.management")}</div>
|
||||
<div style={{ fontSize: "11px", color: "#6b7280", marginTop: "2px" }}>
|
||||
Can call only management routes (user/team/key management)
|
||||
{t("templates.keyEditView.managementDesc")}
|
||||
</div>
|
||||
</div>
|
||||
</Select.Option>
|
||||
|
|
@ -427,34 +431,38 @@ export function KeyEditView({
|
|||
<Form.Item
|
||||
label={
|
||||
<span>
|
||||
Allowed Routes{" "}
|
||||
<Tooltip title="List of allowed routes for the key (comma-separated). Can be specific routes (e.g., '/chat/completions') or route patterns (e.g., 'llm_api_routes', 'management_routes', '/keys/*'). Leave empty to allow all routes.">
|
||||
{t("templates.keyEditView.allowedRoutes")}{" "}
|
||||
<Tooltip title={t("templates.keyEditView.allowedRoutesTooltip")}>
|
||||
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
|
||||
</Tooltip>
|
||||
</span>
|
||||
}
|
||||
name="allowed_routes"
|
||||
>
|
||||
<Input placeholder="Enter allowed routes (comma-separated). Special values: llm_api_routes, management_routes. Examples: llm_api_routes, /chat/completions, /keys/*. Leave empty to allow all routes" />
|
||||
<Input placeholder={t("templates.keyEditView.allowedRoutesPlaceholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Max Budget (USD)" name="max_budget">
|
||||
<NumericalInput step={0.01} style={{ width: "100%" }} placeholder="Enter a numerical value" />
|
||||
<Form.Item label={t("templates.keyEditView.maxBudget")} name="max_budget">
|
||||
<NumericalInput
|
||||
step={0.01}
|
||||
style={{ width: "100%" }}
|
||||
placeholder={t("templates.keyEditView.enterNumericalValue")}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Reset Budget" name="budget_duration">
|
||||
<Form.Item label={t("templates.keyEditView.resetBudget")} name="budget_duration">
|
||||
<Select placeholder="n/a">
|
||||
<Select.Option value="daily">Daily</Select.Option>
|
||||
<Select.Option value="weekly">Weekly</Select.Option>
|
||||
<Select.Option value="monthly">Monthly</Select.Option>
|
||||
<Select.Option value="daily">{t("templates.keyEditView.daily")}</Select.Option>
|
||||
<Select.Option value="weekly">{t("templates.keyEditView.weekly")}</Select.Option>
|
||||
<Select.Option value="monthly">{t("templates.keyEditView.monthly")}</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={
|
||||
<span>
|
||||
Budget Windows{" "}
|
||||
<Tooltip title="Set multiple independent budget windows (e.g., hourly $10 AND monthly $200). Each window tracks spend separately and resets on its own schedule.">
|
||||
{t("templates.keyEditView.budgetWindows")}{" "}
|
||||
<Tooltip title={t("templates.keyEditView.budgetWindowsTooltip")}>
|
||||
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
|
||||
</Tooltip>
|
||||
</span>
|
||||
|
|
@ -463,31 +471,31 @@ export function KeyEditView({
|
|||
<BudgetWindowsEditor value={budgetLimits} onChange={setBudgetLimits} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="TPM Limit" name="tpm_limit">
|
||||
<Form.Item label={t("templates.keyEditView.tpmLimit")} name="tpm_limit">
|
||||
<NumericalInput min={0} />
|
||||
</Form.Item>
|
||||
|
||||
<RateLimitTypeFormItem type="tpm" name="tpm_limit_type" showDetailedDescriptions={false} />
|
||||
|
||||
<Form.Item label="RPM Limit" name="rpm_limit">
|
||||
<Form.Item label={t("templates.keyEditView.rpmLimit")} name="rpm_limit">
|
||||
<NumericalInput min={0} />
|
||||
</Form.Item>
|
||||
|
||||
<RateLimitTypeFormItem type="rpm" name="rpm_limit_type" showDetailedDescriptions={false} />
|
||||
|
||||
<Form.Item label="Max Parallel Requests" name="max_parallel_requests">
|
||||
<Form.Item label={t("templates.keyEditView.maxParallelRequests")} name="max_parallel_requests">
|
||||
<NumericalInput min={0} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Model TPM Limit" name="model_tpm_limit">
|
||||
<Form.Item label={t("templates.keyEditView.modelTpmLimit")} name="model_tpm_limit">
|
||||
<Input.TextArea rows={4} placeholder='{"gpt-4": 100, "claude-v1": 200}' />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Model RPM Limit" name="model_rpm_limit">
|
||||
<Form.Item label={t("templates.keyEditView.modelRpmLimit")} name="model_rpm_limit">
|
||||
<Input.TextArea rows={4} placeholder='{"gpt-4": 100, "claude-v1": 200}' />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Guardrails" name="guardrails">
|
||||
<Form.Item label={t("templates.keyEditView.guardrails")} name="guardrails">
|
||||
{accessToken && (
|
||||
<GuardrailSelector
|
||||
onChange={(v) => {
|
||||
|
|
@ -502,8 +510,8 @@ export function KeyEditView({
|
|||
<Form.Item
|
||||
label={
|
||||
<span>
|
||||
Disable Global Guardrails{" "}
|
||||
<Tooltip title="When enabled, this key will bypass any guardrails configured to run on every request (global guardrails)">
|
||||
{t("templates.keyEditView.disableGlobalGuardrails")}{" "}
|
||||
<Tooltip title={t("templates.keyEditView.disableGlobalGuardrailsTooltip")}>
|
||||
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
|
||||
</Tooltip>
|
||||
</span>
|
||||
|
|
@ -511,14 +519,14 @@ export function KeyEditView({
|
|||
name="disable_global_guardrails"
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={!canEditGuardrails} checkedChildren="Yes" unCheckedChildren="No" />
|
||||
<Switch disabled={!canEditGuardrails} checkedChildren={t("common.yes")} unCheckedChildren={t("common.no")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={
|
||||
<span>
|
||||
Policies{" "}
|
||||
<Tooltip title="Apply policies to this key to control guardrails and other settings">
|
||||
{t("templates.keyEditView.policies")}{" "}
|
||||
<Tooltip title={t("templates.keyEditView.policiesTooltip")}>
|
||||
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
|
||||
</Tooltip>
|
||||
</span>
|
||||
|
|
@ -536,11 +544,11 @@ export function KeyEditView({
|
|||
)}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Tags" name="tags">
|
||||
<Form.Item label={t("templates.keyEditView.tags")} name="tags">
|
||||
<Select
|
||||
mode="tags"
|
||||
style={{ width: "100%" }}
|
||||
placeholder="Select or enter tags"
|
||||
placeholder={t("templates.keyEditView.selectOrEnterTags")}
|
||||
options={Object.values(tagsList).map((tag) => ({
|
||||
value: tag.name,
|
||||
label: tag.name,
|
||||
|
|
@ -549,18 +557,18 @@ export function KeyEditView({
|
|||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Prompts" name="prompts">
|
||||
<Tooltip title={!premiumUser ? "Setting prompts by key is a premium feature" : ""} placement="top">
|
||||
<Form.Item label={t("templates.keyEditView.prompts")} name="prompts">
|
||||
<Tooltip title={!premiumUser ? t("templates.keyEditView.promptsPremiumTooltip") : ""} placement="top">
|
||||
<Select
|
||||
mode="tags"
|
||||
style={{ width: "100%" }}
|
||||
disabled={!premiumUser}
|
||||
placeholder={
|
||||
!premiumUser
|
||||
? "Premium feature - Upgrade to set prompts by key"
|
||||
? t("templates.keyEditView.promptsPremiumPlaceholder")
|
||||
: Array.isArray(keyData.metadata?.prompts) && keyData.metadata.prompts.length > 0
|
||||
? `Current: ${keyData.metadata.prompts.join(", ")}`
|
||||
: "Select or enter prompts"
|
||||
? t("templates.keyEditView.currentPrompts", { prompts: keyData.metadata.prompts.join(", ") })
|
||||
: t("templates.keyEditView.selectOrEnterPrompts")
|
||||
}
|
||||
options={promptsList.map((name) => ({ value: name, label: name }))}
|
||||
/>
|
||||
|
|
@ -570,54 +578,53 @@ export function KeyEditView({
|
|||
<Form.Item
|
||||
label={
|
||||
<span>
|
||||
Access Groups{" "}
|
||||
<Tooltip title="Assign access groups to this key. Access groups control which models, MCP servers, and agents this key can use">
|
||||
{t("templates.keyEditView.accessGroups")}{" "}
|
||||
<Tooltip title={t("templates.keyEditView.accessGroupsTooltip")}>
|
||||
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
|
||||
</Tooltip>
|
||||
</span>
|
||||
}
|
||||
name="access_group_ids"
|
||||
>
|
||||
<AccessGroupSelector placeholder="Select access groups (optional)" />
|
||||
<AccessGroupSelector placeholder={t("templates.keyEditView.selectAccessGroups")} />
|
||||
</Form.Item>
|
||||
|
||||
<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"
|
||||
>
|
||||
<Form.Item label={t("templates.keyEditView.allowedPassThroughRoutes")} name="allowed_passthrough_routes">
|
||||
<Tooltip title={!premiumUser ? t("templates.keyEditView.passThroughPremiumTooltip") : ""} placement="top">
|
||||
<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"
|
||||
? t("templates.keyEditView.passThroughPremiumPlaceholder")
|
||||
: Array.isArray(keyData.metadata?.allowed_passthrough_routes) &&
|
||||
keyData.metadata.allowed_passthrough_routes.length > 0
|
||||
? `Current: ${keyData.metadata.allowed_passthrough_routes.join(", ")}`
|
||||
: "Select or enter allowed pass through routes"
|
||||
? t("templates.keyEditView.currentPassThroughRoutes", {
|
||||
routes: keyData.metadata.allowed_passthrough_routes.join(", "),
|
||||
})
|
||||
: t("templates.keyEditView.selectOrEnterPassThroughRoutes")
|
||||
}
|
||||
disabled={!premiumUser}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Vector Stores" name="vector_stores">
|
||||
<Form.Item label={t("templates.keyEditView.vectorStores")} name="vector_stores">
|
||||
<VectorStoreSelector
|
||||
onChange={(values: string[]) => form.setFieldValue("vector_stores", values)}
|
||||
value={form.getFieldValue("vector_stores")}
|
||||
accessToken={accessToken || ""}
|
||||
placeholder="Select vector stores"
|
||||
placeholder={t("templates.keyEditView.selectVectorStores")}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="MCP Servers / Access Groups" name="mcp_servers_and_groups">
|
||||
<Form.Item label={t("templates.keyEditView.mcpServers")} name="mcp_servers_and_groups">
|
||||
<MCPServerSelector
|
||||
onChange={(val) => form.setFieldValue("mcp_servers_and_groups", val)}
|
||||
value={form.getFieldValue("mcp_servers_and_groups")}
|
||||
accessToken={accessToken || ""}
|
||||
placeholder="Select MCP servers or access groups (optional)"
|
||||
placeholder={t("templates.keyEditView.selectMcpServers")}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
|
|
@ -645,20 +652,20 @@ export function KeyEditView({
|
|||
)}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Agents / Access Groups" name="agents_and_groups">
|
||||
<Form.Item label={t("templates.keyEditView.agents")} name="agents_and_groups">
|
||||
<AgentSelector
|
||||
onChange={(val) => form.setFieldValue("agents_and_groups", val)}
|
||||
value={form.getFieldValue("agents_and_groups")}
|
||||
accessToken={accessToken || ""}
|
||||
placeholder="Select agents or access groups (optional)"
|
||||
placeholder={t("templates.keyEditView.selectAgents")}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={
|
||||
<span>
|
||||
Organization{" "}
|
||||
<Tooltip title="The organization this key belongs to. Selecting an organization filters the available teams.">
|
||||
{t("templates.keyEditView.organization")}{" "}
|
||||
<Tooltip title={t("templates.keyEditView.organizationTooltip")}>
|
||||
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
|
||||
</Tooltip>
|
||||
</span>
|
||||
|
|
@ -677,12 +684,12 @@ export function KeyEditView({
|
|||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Team ID"
|
||||
label={t("templates.keyEditView.teamId")}
|
||||
name="team_id"
|
||||
help={enableProjectsUI && hasProject ? "Team is locked because this key belongs to a project" : undefined}
|
||||
help={enableProjectsUI && hasProject ? t("templates.keyEditView.teamLockedByProject") : undefined}
|
||||
>
|
||||
<Select
|
||||
placeholder="Select team"
|
||||
placeholder={t("templates.keyEditView.selectTeam")}
|
||||
showSearch
|
||||
disabled={enableProjectsUI && hasProject}
|
||||
style={{ width: "100%" }}
|
||||
|
|
@ -715,11 +722,11 @@ export function KeyEditView({
|
|||
</Select>
|
||||
</Form.Item>
|
||||
{enableProjectsUI && hasProject && (
|
||||
<Form.Item label="Project">
|
||||
<Form.Item label={t("templates.keyEditView.project")}>
|
||||
<Input value={projectDisplay ?? ""} disabled />
|
||||
</Form.Item>
|
||||
)}
|
||||
<Form.Item label="Logging Settings" name="logging_settings">
|
||||
<Form.Item label={t("templates.keyEditView.loggingSettings")} name="logging_settings">
|
||||
<EditLoggingSettings
|
||||
value={form.getFieldValue("logging_settings")}
|
||||
onChange={(values) => form.setFieldValue("logging_settings", values)}
|
||||
|
|
@ -734,7 +741,7 @@ export function KeyEditView({
|
|||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Metadata" name="metadata">
|
||||
<Form.Item label={t("templates.keyEditView.metadata")} name="metadata">
|
||||
<Input.TextArea rows={10} />
|
||||
</Form.Item>
|
||||
|
||||
|
|
@ -775,10 +782,10 @@ export function KeyEditView({
|
|||
<div className="sticky z-10 bg-white p-4 border-t border-gray-200 bottom-[-1.5rem] inset-x-[-1.5rem]">
|
||||
<div className="flex justify-end items-center gap-2">
|
||||
<TremorButton variant="secondary" onClick={onCancel} disabled={isKeySaving}>
|
||||
Cancel
|
||||
{t("common.cancel")}
|
||||
</TremorButton>
|
||||
<TremorButton type="submit" loading={isKeySaving}>
|
||||
Save Changes
|
||||
{t("templates.keyEditView.saveChanges")}
|
||||
</TremorButton>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7784,5 +7784,92 @@
|
|||
"saveFailed": "Failed to save routing group",
|
||||
"deleteFailed": "Failed to delete routing group"
|
||||
}
|
||||
},
|
||||
"templates": {
|
||||
"keyInfoHeader": {
|
||||
"user": "User",
|
||||
"userAlias": "User Alias",
|
||||
"userEmail": "User Email",
|
||||
"userId": "User ID",
|
||||
"createNewKey": "Create New Key",
|
||||
"backToKeys": "Back to Keys",
|
||||
"copyKeyAlias": "Copy Key Alias",
|
||||
"copyKeyId": "Copy Key ID",
|
||||
"keyId": "Key ID: {{keyId}}",
|
||||
"regenerateKey": "Regenerate Key",
|
||||
"resetSpend": "Reset Spend",
|
||||
"deleteKey": "Delete Key",
|
||||
"expires": "Expires",
|
||||
"createdBy": "Created By",
|
||||
"lastUpdated": "Last Updated",
|
||||
"lastActive": "Last Active"
|
||||
},
|
||||
"keyEditView": {
|
||||
"keyAlias": "Key Alias",
|
||||
"models": "Models",
|
||||
"selectModels": "Select models",
|
||||
"allTeamModels": "All Team Models",
|
||||
"modelsDisabled": "Models field is disabled for this key type",
|
||||
"keyType": "Key Type",
|
||||
"selectKeyType": "Select key type",
|
||||
"fullAccess": "Full Access",
|
||||
"fullAccessDesc": "Can call all routes (AI APIs, Management, and read-only)",
|
||||
"aiApis": "AI APIs",
|
||||
"aiApisDesc": "Can call only AI API routes (chat/completions, embeddings, etc.)",
|
||||
"management": "Management",
|
||||
"managementDesc": "Can call only management routes (user/team/key management)",
|
||||
"allowedRoutes": "Allowed Routes",
|
||||
"allowedRoutesTooltip": "List of allowed routes for the key (comma-separated). Can be specific routes (e.g., '/chat/completions') or route patterns (e.g., 'llm_api_routes', 'management_routes', '/keys/*'). Leave empty to allow all routes.",
|
||||
"allowedRoutesPlaceholder": "Enter allowed routes (comma-separated). Special values: llm_api_routes, management_routes. Examples: llm_api_routes, /chat/completions, /keys/*. Leave empty to allow all routes",
|
||||
"maxBudget": "Max Budget (USD)",
|
||||
"enterNumericalValue": "Enter a numerical value",
|
||||
"resetBudget": "Reset Budget",
|
||||
"daily": "Daily",
|
||||
"weekly": "Weekly",
|
||||
"monthly": "Monthly",
|
||||
"budgetWindows": "Budget Windows",
|
||||
"budgetWindowsTooltip": "Set multiple independent budget windows (e.g., hourly $10 AND monthly $200). Each window tracks spend separately and resets on its own schedule.",
|
||||
"tpmLimit": "TPM Limit",
|
||||
"rpmLimit": "RPM Limit",
|
||||
"maxParallelRequests": "Max Parallel Requests",
|
||||
"modelTpmLimit": "Model TPM Limit",
|
||||
"modelRpmLimit": "Model RPM Limit",
|
||||
"guardrails": "Guardrails",
|
||||
"disableGlobalGuardrails": "Disable Global Guardrails",
|
||||
"disableGlobalGuardrailsTooltip": "When enabled, this key will bypass any guardrails configured to run on every request (global guardrails)",
|
||||
"policies": "Policies",
|
||||
"policiesTooltip": "Apply policies to this key to control guardrails and other settings",
|
||||
"tags": "Tags",
|
||||
"selectOrEnterTags": "Select or enter tags",
|
||||
"prompts": "Prompts",
|
||||
"promptsPremiumTooltip": "Setting prompts by key is a premium feature",
|
||||
"promptsPremiumPlaceholder": "Premium feature - Upgrade to set prompts by key",
|
||||
"currentPrompts": "Current: {{prompts}}",
|
||||
"selectOrEnterPrompts": "Select or enter prompts",
|
||||
"accessGroups": "Access Groups",
|
||||
"accessGroupsTooltip": "Assign access groups to this key. Access groups control which models, MCP servers, and agents this key can use",
|
||||
"selectAccessGroups": "Select access groups (optional)",
|
||||
"allowedPassThroughRoutes": "Allowed Pass Through Routes",
|
||||
"passThroughPremiumTooltip": "Setting allowed pass through routes by key is a premium feature",
|
||||
"passThroughPremiumPlaceholder": "Premium feature - Upgrade to set allowed pass through routes by key",
|
||||
"currentPassThroughRoutes": "Current: {{routes}}",
|
||||
"selectOrEnterPassThroughRoutes": "Select or enter allowed pass through routes",
|
||||
"vectorStores": "Vector Stores",
|
||||
"selectVectorStores": "Select vector stores",
|
||||
"mcpServers": "MCP Servers / Access Groups",
|
||||
"selectMcpServers": "Select MCP servers or access groups (optional)",
|
||||
"agents": "Agents / Access Groups",
|
||||
"selectAgents": "Select agents or access groups (optional)",
|
||||
"organization": "Organization",
|
||||
"organizationTooltip": "The organization this key belongs to. Selecting an organization filters the available teams.",
|
||||
"teamId": "Team ID",
|
||||
"teamLockedByProject": "Team is locked because this key belongs to a project",
|
||||
"selectTeam": "Select team",
|
||||
"project": "Project",
|
||||
"loggingSettings": "Logging Settings",
|
||||
"metadata": "Metadata",
|
||||
"saveChanges": "Save Changes",
|
||||
"errorFetchingTags": "Error fetching tags: {{error}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7784,5 +7784,92 @@
|
|||
"saveFailed": "保存路由组失败",
|
||||
"deleteFailed": "删除路由组失败"
|
||||
}
|
||||
},
|
||||
"templates": {
|
||||
"keyInfoHeader": {
|
||||
"user": "用户",
|
||||
"userAlias": "用户别名",
|
||||
"userEmail": "用户邮箱",
|
||||
"userId": "用户 ID",
|
||||
"createNewKey": "创建新密钥",
|
||||
"backToKeys": "返回密钥列表",
|
||||
"copyKeyAlias": "复制密钥别名",
|
||||
"copyKeyId": "复制密钥 ID",
|
||||
"keyId": "密钥 ID:{{keyId}}",
|
||||
"regenerateKey": "重新生成密钥",
|
||||
"resetSpend": "重置花费",
|
||||
"deleteKey": "删除密钥",
|
||||
"expires": "过期时间",
|
||||
"createdBy": "创建人",
|
||||
"lastUpdated": "最近更新",
|
||||
"lastActive": "最近活跃"
|
||||
},
|
||||
"keyEditView": {
|
||||
"keyAlias": "密钥别名",
|
||||
"models": "模型",
|
||||
"selectModels": "选择模型",
|
||||
"allTeamModels": "所有团队模型",
|
||||
"modelsDisabled": "该密钥类型已禁用模型字段",
|
||||
"keyType": "密钥类型",
|
||||
"selectKeyType": "选择密钥类型",
|
||||
"fullAccess": "完全访问",
|
||||
"fullAccessDesc": "可调用所有路由(AI API、管理及只读)",
|
||||
"aiApis": "AI API",
|
||||
"aiApisDesc": "仅可调用 AI API 路由(chat/completions、embeddings 等)",
|
||||
"management": "管理",
|
||||
"managementDesc": "仅可调用管理路由(用户/团队/密钥管理)",
|
||||
"allowedRoutes": "允许的路由",
|
||||
"allowedRoutesTooltip": "密钥允许的路由列表(逗号分隔)。可以是具体路由(如 '/chat/completions')或路由模式(如 'llm_api_routes'、'management_routes'、'/keys/*')。留空表示允许所有路由。",
|
||||
"allowedRoutesPlaceholder": "输入允许的路由(逗号分隔)。特殊值:llm_api_routes、management_routes。示例:llm_api_routes, /chat/completions, /keys/*。留空表示允许所有路由",
|
||||
"maxBudget": "最大预算(USD)",
|
||||
"enterNumericalValue": "请输入数值",
|
||||
"resetBudget": "重置预算",
|
||||
"daily": "每日",
|
||||
"weekly": "每周",
|
||||
"monthly": "每月",
|
||||
"budgetWindows": "预算窗口",
|
||||
"budgetWindowsTooltip": "设置多个独立的预算窗口(例如每小时 $10 且每月 $200)。每个窗口独立追踪花费并按各自周期重置。",
|
||||
"tpmLimit": "TPM 限制",
|
||||
"rpmLimit": "RPM 限制",
|
||||
"maxParallelRequests": "最大并发请求数",
|
||||
"modelTpmLimit": "模型 TPM 限制",
|
||||
"modelRpmLimit": "模型 RPM 限制",
|
||||
"guardrails": "护栏",
|
||||
"disableGlobalGuardrails": "禁用全局护栏",
|
||||
"disableGlobalGuardrailsTooltip": "启用后,该密钥将跳过配置为对每个请求运行的护栏(全局护栏)",
|
||||
"policies": "策略",
|
||||
"policiesTooltip": "为该密钥应用策略以控制护栏和其他设置",
|
||||
"tags": "标签",
|
||||
"selectOrEnterTags": "选择或输入标签",
|
||||
"prompts": "提示词",
|
||||
"promptsPremiumTooltip": "按密钥设置提示词是高级功能",
|
||||
"promptsPremiumPlaceholder": "高级功能 - 升级后可按密钥设置提示词",
|
||||
"currentPrompts": "当前:{{prompts}}",
|
||||
"selectOrEnterPrompts": "选择或输入提示词",
|
||||
"accessGroups": "访问组",
|
||||
"accessGroupsTooltip": "为该密钥分配访问组。访问组控制该密钥可使用的模型、MCP 服务器和 Agent",
|
||||
"selectAccessGroups": "选择访问组(可选)",
|
||||
"allowedPassThroughRoutes": "允许的透传路由",
|
||||
"passThroughPremiumTooltip": "按密钥设置允许的透传路由是高级功能",
|
||||
"passThroughPremiumPlaceholder": "高级功能 - 升级后可按密钥设置允许的透传路由",
|
||||
"currentPassThroughRoutes": "当前:{{routes}}",
|
||||
"selectOrEnterPassThroughRoutes": "选择或输入允许的透传路由",
|
||||
"vectorStores": "向量存储",
|
||||
"selectVectorStores": "选择向量存储",
|
||||
"mcpServers": "MCP 服务器 / 访问组",
|
||||
"selectMcpServers": "选择 MCP 服务器或访问组(可选)",
|
||||
"agents": "Agent / 访问组",
|
||||
"selectAgents": "选择 Agent 或访问组(可选)",
|
||||
"organization": "组织",
|
||||
"organizationTooltip": "该密钥所属的组织。选择组织可筛选可用团队。",
|
||||
"teamId": "团队 ID",
|
||||
"teamLockedByProject": "团队已锁定,因为该密钥属于某个项目",
|
||||
"selectTeam": "选择团队",
|
||||
"project": "项目",
|
||||
"loggingSettings": "日志设置",
|
||||
"metadata": "元数据",
|
||||
"saveChanges": "保存更改",
|
||||
"errorFetchingTags": "获取标签时出错:{{error}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue