mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): i18n for regenerate key modal
This commit is contained in:
parent
f293c01f85
commit
f7ab412a24
3 changed files with 85 additions and 29 deletions
|
|
@ -3,6 +3,7 @@ import { CheckOutlined, CopyOutlined, SyncOutlined } from "@ant-design/icons";
|
|||
import { Alert, Button, Col, Flex, Form, Input, InputNumber, Modal, Row, Space, Typography } from "antd";
|
||||
import { useEffect, useState } from "react";
|
||||
import { CopyToClipboard } from "react-copy-to-clipboard";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { KeyResponse } from "../key_team_helpers/key_list";
|
||||
import NotificationManager from "../molecules/notifications_manager";
|
||||
import { regenerateKeyCall } from "../networking";
|
||||
|
|
@ -10,11 +11,6 @@ import { calculateExpiryPreviewFromDuration, formatExpiresUtc, isKeyExpired } fr
|
|||
|
||||
const { Text } = Typography;
|
||||
|
||||
const DURATION_RULE = {
|
||||
pattern: /^(\d+(s|m|h|d|w|mo))?$/,
|
||||
message: "Must be a duration like 30s, 30m, 24h, 2d, 1w, or 1mo",
|
||||
};
|
||||
|
||||
interface RegenerateKeyModalProps {
|
||||
selectedToken: KeyResponse | null;
|
||||
visible: boolean;
|
||||
|
|
@ -23,6 +19,7 @@ interface RegenerateKeyModalProps {
|
|||
}
|
||||
|
||||
export function RegenerateKeyModal({ selectedToken, visible, onClose, onKeyUpdate }: RegenerateKeyModalProps) {
|
||||
const { t } = useTranslation();
|
||||
const { accessToken } = useAuthorized();
|
||||
const [form] = Form.useForm();
|
||||
const [regeneratedKey, setRegeneratedKey] = useState<string | null>(null);
|
||||
|
|
@ -32,10 +29,15 @@ export function RegenerateKeyModal({ selectedToken, visible, onClose, onKeyUpdat
|
|||
const keyIsExpired = isKeyExpired(selectedToken?.expires);
|
||||
const durationValue = Form.useWatch("duration", form);
|
||||
|
||||
const DURATION_RULE = {
|
||||
pattern: /^(\d+(s|m|h|d|w|mo))?$/,
|
||||
message: t("organisms.regenerateKeyModal.durationRuleMessage"),
|
||||
};
|
||||
|
||||
// Expired keys must get a new duration, otherwise regeneration produces a key
|
||||
// that inherits the old (past) expiry and is immediately unusable.
|
||||
const durationRules = keyIsExpired
|
||||
? [{ required: true, message: "Expiration is required for expired keys" }, DURATION_RULE]
|
||||
? [{ required: true, message: t("organisms.regenerateKeyModal.expirationRequired") }, DURATION_RULE]
|
||||
: [DURATION_RULE];
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -62,7 +64,7 @@ export function RegenerateKeyModal({ selectedToken, visible, onClose, onKeyUpdat
|
|||
|
||||
const response = await regenerateKeyCall(accessToken, selectedToken.token || selectedToken.token_id, formValues);
|
||||
setRegeneratedKey(response.key);
|
||||
NotificationManager.success("Virtual Key regenerated successfully");
|
||||
NotificationManager.success(t("organisms.regenerateKeyModal.regenerateSuccess"));
|
||||
|
||||
// Build the update payload. Spread the API response first so any new
|
||||
// fields it returns (new token, timestamps, etc.) are captured, then
|
||||
|
|
@ -112,7 +114,7 @@ export function RegenerateKeyModal({ selectedToken, visible, onClose, onKeyUpdat
|
|||
|
||||
return (
|
||||
<Modal
|
||||
title="Regenerate Virtual Key"
|
||||
title={t("organisms.regenerateKeyModal.title")}
|
||||
open={visible}
|
||||
onCancel={handleClose}
|
||||
width={520}
|
||||
|
|
@ -121,19 +123,19 @@ export function RegenerateKeyModal({ selectedToken, visible, onClose, onKeyUpdat
|
|||
regeneratedKey
|
||||
? [
|
||||
<Space key="footer-actions">
|
||||
<Button onClick={handleClose}>Close</Button>
|
||||
<Button onClick={handleClose}>{t("common.close")}</Button>
|
||||
<CopyToClipboard text={regeneratedKey} onCopy={handleCopyKey}>
|
||||
<Button type="primary" icon={copied ? <CheckOutlined /> : <CopyOutlined />}>
|
||||
{copied ? "Copied" : "Copy Key"}
|
||||
{copied ? t("common.copied") : t("organisms.regenerateKeyModal.copyKey")}
|
||||
</Button>
|
||||
</CopyToClipboard>
|
||||
</Space>,
|
||||
]
|
||||
: [
|
||||
<Space key="footer-actions">
|
||||
<Button onClick={handleClose}>Cancel</Button>
|
||||
<Button onClick={handleClose}>{t("common.cancel")}</Button>
|
||||
<Button type="primary" icon={<SyncOutlined />} onClick={handleRegenerateKey} loading={isRegenerating}>
|
||||
Regenerate
|
||||
{t("organisms.regenerateKeyModal.regenerate")}
|
||||
</Button>
|
||||
</Space>,
|
||||
]
|
||||
|
|
@ -141,18 +143,18 @@ export function RegenerateKeyModal({ selectedToken, visible, onClose, onKeyUpdat
|
|||
>
|
||||
{regeneratedKey ? (
|
||||
<Flex vertical gap="middle">
|
||||
<Alert type="warning" showIcon message="Save it now, you will not see it again" />
|
||||
<Alert type="warning" showIcon message={t("organisms.regenerateKeyModal.saveWarning")} />
|
||||
|
||||
<Flex vertical gap={2}>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||
Key Alias
|
||||
{t("organisms.regenerateKeyModal.keyAlias")}
|
||||
</Text>
|
||||
<Text>{selectedToken?.key_alias || "No alias set"}</Text>
|
||||
<Text>{selectedToken?.key_alias || t("organisms.regenerateKeyModal.noAliasSet")}</Text>
|
||||
</Flex>
|
||||
|
||||
<Flex vertical gap={6}>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||
Virtual Key
|
||||
{t("organisms.regenerateKeyModal.virtualKey")}
|
||||
</Text>
|
||||
<div
|
||||
style={{
|
||||
|
|
@ -172,23 +174,23 @@ export function RegenerateKeyModal({ selectedToken, visible, onClose, onKeyUpdat
|
|||
</Flex>
|
||||
) : (
|
||||
<Form form={form} layout="vertical" style={{ marginTop: 4 }}>
|
||||
<Form.Item name="key_alias" label="Key Alias">
|
||||
<Form.Item name="key_alias" label={t("organisms.regenerateKeyModal.keyAlias")}>
|
||||
<Input disabled />
|
||||
</Form.Item>
|
||||
|
||||
<Row gutter={12}>
|
||||
<Col span={8}>
|
||||
<Form.Item name="max_budget" label="Max Budget (USD)">
|
||||
<Form.Item name="max_budget" label={t("organisms.regenerateKeyModal.maxBudget")}>
|
||||
<InputNumber step={0.01} precision={2} style={{ width: "100%" }} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item name="tpm_limit" label="TPM Limit">
|
||||
<Form.Item name="tpm_limit" label={t("organisms.regenerateKeyModal.tpmLimit")}>
|
||||
<InputNumber style={{ width: "100%" }} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item name="rpm_limit" label="RPM Limit">
|
||||
<Form.Item name="rpm_limit" label={t("organisms.regenerateKeyModal.rpmLimit")}>
|
||||
<InputNumber style={{ width: "100%" }} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
|
|
@ -198,38 +200,40 @@ export function RegenerateKeyModal({ selectedToken, visible, onClose, onKeyUpdat
|
|||
<Col span={12}>
|
||||
<Form.Item
|
||||
name="duration"
|
||||
label="Expire Key"
|
||||
label={t("organisms.regenerateKeyModal.expireKey")}
|
||||
rules={durationRules}
|
||||
extra={
|
||||
<Flex vertical gap={2}>
|
||||
<Text type={keyIsExpired ? "danger" : "secondary"} style={{ fontSize: 12 }}>
|
||||
Current expiry: {selectedToken?.expires ? formatExpiresUtc(selectedToken.expires) : "Never"}
|
||||
{keyIsExpired && " (expired)"}
|
||||
{t("organisms.regenerateKeyModal.currentExpiry", {
|
||||
value: selectedToken?.expires ? formatExpiresUtc(selectedToken.expires) : t("common.never"),
|
||||
})}
|
||||
{keyIsExpired && t("organisms.regenerateKeyModal.expiredSuffix")}
|
||||
</Text>
|
||||
{newExpiryTime && (
|
||||
<Text type="success" style={{ fontSize: 12 }}>
|
||||
New expiry: {newExpiryTime}
|
||||
{t("organisms.regenerateKeyModal.newExpiry", { value: newExpiryTime })}
|
||||
</Text>
|
||||
)}
|
||||
</Flex>
|
||||
}
|
||||
>
|
||||
<Input placeholder="e.g. 30s, 30h, 30d" />
|
||||
<Input placeholder={t("organisms.regenerateKeyModal.durationPlaceholder")} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name="grace_period"
|
||||
label="Grace Period"
|
||||
tooltip="Keep the old key valid for this duration after rotation. Both keys work during this period for seamless cutover. Empty = immediate revoke."
|
||||
label={t("organisms.regenerateKeyModal.gracePeriod")}
|
||||
tooltip={t("organisms.regenerateKeyModal.gracePeriodTooltip")}
|
||||
extra={
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||
Recommended: 24h to 72h for production keys
|
||||
{t("organisms.regenerateKeyModal.gracePeriodRecommended")}
|
||||
</Text>
|
||||
}
|
||||
rules={[DURATION_RULE]}
|
||||
>
|
||||
<Input placeholder="e.g. 24h, 2d" />
|
||||
<Input placeholder={t("organisms.regenerateKeyModal.gracePeriodPlaceholder")} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
|
|||
|
|
@ -8139,6 +8139,32 @@
|
|||
"noModelsFound": "No models found"
|
||||
}
|
||||
},
|
||||
"organisms": {
|
||||
"regenerateKeyModal": {
|
||||
"title": "Regenerate Virtual Key",
|
||||
"saveWarning": "Save it now, you will not see it again",
|
||||
"keyAlias": "Key Alias",
|
||||
"noAliasSet": "No alias set",
|
||||
"virtualKey": "Virtual Key",
|
||||
"copyKey": "Copy Key",
|
||||
"regenerate": "Regenerate",
|
||||
"maxBudget": "Max Budget (USD)",
|
||||
"tpmLimit": "TPM Limit",
|
||||
"rpmLimit": "RPM Limit",
|
||||
"expireKey": "Expire Key",
|
||||
"currentExpiry": "Current expiry: {{value}}",
|
||||
"expiredSuffix": " (expired)",
|
||||
"newExpiry": "New expiry: {{value}}",
|
||||
"gracePeriod": "Grace Period",
|
||||
"gracePeriodTooltip": "Keep the old key valid for this duration after rotation. Both keys work during this period for seamless cutover. Empty = immediate revoke.",
|
||||
"gracePeriodRecommended": "Recommended: 24h to 72h for production keys",
|
||||
"durationPlaceholder": "e.g. 30s, 30h, 30d",
|
||||
"gracePeriodPlaceholder": "e.g. 24h, 2d",
|
||||
"durationRuleMessage": "Must be a duration like 30s, 30m, 24h, 2d, 1w, or 1mo",
|
||||
"expirationRequired": "Expiration is required for expired keys",
|
||||
"regenerateSuccess": "Virtual Key regenerated successfully"
|
||||
}
|
||||
},
|
||||
"alerting": {
|
||||
"alertingSettings": {
|
||||
"saveSuccess": "Wait 10s for proxy to update."
|
||||
|
|
|
|||
|
|
@ -8139,6 +8139,32 @@
|
|||
"noModelsFound": "未找到模型"
|
||||
}
|
||||
},
|
||||
"organisms": {
|
||||
"regenerateKeyModal": {
|
||||
"title": "重新生成虚拟密钥",
|
||||
"saveWarning": "请立即保存,此后将无法再次查看",
|
||||
"keyAlias": "密钥别名",
|
||||
"noAliasSet": "未设置别名",
|
||||
"virtualKey": "虚拟密钥",
|
||||
"copyKey": "复制密钥",
|
||||
"regenerate": "重新生成",
|
||||
"maxBudget": "最大预算(美元)",
|
||||
"tpmLimit": "TPM 限制",
|
||||
"rpmLimit": "RPM 限制",
|
||||
"expireKey": "密钥过期",
|
||||
"currentExpiry": "当前过期时间:{{value}}",
|
||||
"expiredSuffix": "(已过期)",
|
||||
"newExpiry": "新过期时间:{{value}}",
|
||||
"gracePeriod": "宽限期",
|
||||
"gracePeriodTooltip": "轮换后旧密钥在此时长内仍然有效。宽限期内新旧密钥均可使用,便于平滑切换。留空则立即吊销旧密钥。",
|
||||
"gracePeriodRecommended": "生产环境密钥建议设置 24h 至 72h",
|
||||
"durationPlaceholder": "例如 30s、30h、30d",
|
||||
"gracePeriodPlaceholder": "例如 24h、2d",
|
||||
"durationRuleMessage": "请输入有效时长,如 30s、30m、24h、2d、1w 或 1mo",
|
||||
"expirationRequired": "已过期的密钥必须设置新的过期时间",
|
||||
"regenerateSuccess": "虚拟密钥重新生成成功"
|
||||
}
|
||||
},
|
||||
"alerting": {
|
||||
"alertingSettings": {
|
||||
"saveSuccess": "等待代理更新,约需 10 秒。"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue