mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-03 02:22:24 +00:00
feat(ui): i18n for virtual keys table
This commit is contained in:
parent
c8b27b6d52
commit
ebe6cb92f5
3 changed files with 153 additions and 60 deletions
|
|
@ -3,6 +3,7 @@ import { useKeys } from "@/app/(dashboard)/hooks/keys/useKeys";
|
|||
import { useOrganizations } from "@/app/(dashboard)/hooks/organizations/useOrganizations";
|
||||
import { formatNumberWithCommas } from "@/utils/dataUtils";
|
||||
import { ChevronDownIcon, ChevronRightIcon, ChevronUpIcon, SwitchVerticalIcon } from "@heroicons/react/outline";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
ColumnDef,
|
||||
flexRender,
|
||||
|
|
@ -53,6 +54,7 @@ interface VirtualKeysTableProps {
|
|||
*/
|
||||
|
||||
export function VirtualKeysTable({ teams, organizations, onSortChange, currentSort }: VirtualKeysTableProps) {
|
||||
const { t } = useTranslation();
|
||||
const { data: fetchedOrganizations } = useOrganizations();
|
||||
const resolvedOrganizations = fetchedOrganizations ?? organizations ?? [];
|
||||
const [selectedKey, setSelectedKey] = useState<KeyResponse | null>(null);
|
||||
|
|
@ -154,7 +156,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
|
|||
{
|
||||
id: "token",
|
||||
accessorKey: "token",
|
||||
header: "Key ID",
|
||||
header: t("virtualKeys.virtualKeysTable.keyId"),
|
||||
size: 100,
|
||||
enableSorting: true,
|
||||
cell: (info) => {
|
||||
|
|
@ -178,7 +180,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
|
|||
{
|
||||
id: "key_alias",
|
||||
accessorKey: "key_alias",
|
||||
header: "Key Alias",
|
||||
header: t("virtualKeys.virtualKeysTable.keyAlias"),
|
||||
size: 150,
|
||||
enableSorting: true,
|
||||
cell: (info) => {
|
||||
|
|
@ -193,7 +195,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
|
|||
},
|
||||
{
|
||||
id: "status",
|
||||
header: "Status",
|
||||
header: t("common.status"),
|
||||
size: 100,
|
||||
enableSorting: false,
|
||||
cell: ({ row }) => {
|
||||
|
|
@ -201,18 +203,18 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
|
|||
if (key.blocked !== true) {
|
||||
return (
|
||||
<Tag color="green" data-testid={`key-status-${key.token_id}`}>
|
||||
Active
|
||||
{t("common.active")}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
const isScimBlocked = (key.metadata as Record<string, unknown> | null | undefined)?.scim_blocked === true;
|
||||
const reason = isScimBlocked
|
||||
? "Blocked by SCIM (external identity provider deactivated or deleted the owning user)."
|
||||
: "Blocked. Requests using this key will be rejected with 401.";
|
||||
? t("virtualKeys.virtualKeysTable.blockedByScimReason")
|
||||
: t("virtualKeys.virtualKeysTable.blockedReason");
|
||||
return (
|
||||
<Tooltip title={reason}>
|
||||
<Tag color="red" data-testid={`key-status-${key.token_id}`}>
|
||||
Blocked
|
||||
{t("virtualKeys.virtualKeysTable.blocked")}
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
);
|
||||
|
|
@ -221,7 +223,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
|
|||
{
|
||||
id: "key_name",
|
||||
accessorKey: "key_name",
|
||||
header: "Secret Key",
|
||||
header: t("virtualKeys.virtualKeysTable.secretKey"),
|
||||
size: 120,
|
||||
enableSorting: false,
|
||||
cell: (info) => <span className="font-mono text-xs">{info.getValue() as string}</span>,
|
||||
|
|
@ -229,7 +231,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
|
|||
{
|
||||
id: "team_alias",
|
||||
accessorKey: "team_id",
|
||||
header: "Team",
|
||||
header: t("virtualKeys.virtualKeysTable.team"),
|
||||
size: 120,
|
||||
enableSorting: false,
|
||||
cell: (info) => {
|
||||
|
|
@ -248,7 +250,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
|
|||
{
|
||||
id: "organization_alias",
|
||||
accessorKey: "org_id",
|
||||
header: "Organization",
|
||||
header: t("virtualKeys.virtualKeysTable.organization"),
|
||||
size: 140,
|
||||
enableSorting: false,
|
||||
cell: (info) => {
|
||||
|
|
@ -269,8 +271,8 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
|
|||
accessorKey: "user",
|
||||
header: () => (
|
||||
<span className="flex items-center gap-1">
|
||||
User
|
||||
<Popover content="Displays the first available value: User Alias, User Email, or User ID." trigger="hover">
|
||||
{t("virtualKeys.virtualKeysTable.user")}
|
||||
<Popover content={t("virtualKeys.virtualKeysTable.userPopoverHint")} trigger="hover">
|
||||
<InfoCircleOutlined className="text-gray-400 text-xs cursor-help" />
|
||||
</Popover>
|
||||
</span>
|
||||
|
|
@ -289,9 +291,9 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
|
|||
const popoverContent = (
|
||||
<div className="flex flex-col gap-2 text-xs min-w-[200px] max-w-[300px]">
|
||||
{[
|
||||
{ label: "User Alias", value: userAlias },
|
||||
{ label: "User Email", value: userEmail },
|
||||
{ label: "User ID", value: userId },
|
||||
{ label: t("virtualKeys.virtualKeysTable.userAlias"), value: userAlias },
|
||||
{ label: t("virtualKeys.virtualKeysTable.userEmail"), value: userEmail },
|
||||
{ label: t("virtualKeys.virtualKeysTable.userId"), value: userId },
|
||||
].map(({ label, value }) => (
|
||||
<div key={label} className="flex flex-col min-w-0">
|
||||
<span className="text-gray-400">{label}</span>
|
||||
|
|
@ -332,7 +334,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
|
|||
{
|
||||
id: "created_at",
|
||||
accessorKey: "created_at",
|
||||
header: "Created At",
|
||||
header: t("common.createdAt"),
|
||||
size: 120,
|
||||
enableSorting: true,
|
||||
cell: (info) => {
|
||||
|
|
@ -343,7 +345,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
|
|||
{
|
||||
id: "created_by",
|
||||
accessorKey: "created_by",
|
||||
header: "Created By",
|
||||
header: t("virtualKeys.virtualKeysTable.createdBy"),
|
||||
size: 160,
|
||||
enableSorting: false,
|
||||
cell: (info) => {
|
||||
|
|
@ -360,9 +362,9 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
|
|||
const popoverContent = (
|
||||
<div className="flex flex-col gap-2 text-xs min-w-[200px] max-w-[300px]">
|
||||
{[
|
||||
{ label: "User Alias", value: userAlias },
|
||||
{ label: "User Email", value: userEmail },
|
||||
{ label: "User ID", value: userId },
|
||||
{ label: t("virtualKeys.virtualKeysTable.userAlias"), value: userAlias },
|
||||
{ label: t("virtualKeys.virtualKeysTable.userEmail"), value: userEmail },
|
||||
{ label: t("virtualKeys.virtualKeysTable.userId"), value: userId },
|
||||
].map(({ label, value }) => (
|
||||
<div key={label} className="flex flex-col min-w-0">
|
||||
<span className="text-gray-400">{label}</span>
|
||||
|
|
@ -403,12 +405,12 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
|
|||
{
|
||||
id: "updated_at",
|
||||
accessorKey: "updated_at",
|
||||
header: "Updated At",
|
||||
header: t("common.updatedAt"),
|
||||
size: 120,
|
||||
enableSorting: true,
|
||||
cell: (info) => {
|
||||
const value = info.getValue();
|
||||
return value ? new Date(value as string).toLocaleDateString() : "Never";
|
||||
return value ? new Date(value as string).toLocaleDateString() : t("common.never");
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -416,11 +418,8 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
|
|||
accessorKey: "last_active",
|
||||
header: () => (
|
||||
<span className="flex items-center gap-1">
|
||||
Last Active
|
||||
<Popover
|
||||
content="This is a new field and is not backfilled. Only new key usage will update this value."
|
||||
trigger="hover"
|
||||
>
|
||||
{t("virtualKeys.virtualKeysTable.lastActive")}
|
||||
<Popover content={t("virtualKeys.virtualKeysTable.lastActiveHint")} trigger="hover">
|
||||
<InfoCircleOutlined className="text-gray-400 text-xs cursor-help" />
|
||||
</Popover>
|
||||
</span>
|
||||
|
|
@ -429,7 +428,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
|
|||
enableSorting: false,
|
||||
cell: (info) => {
|
||||
const value = info.getValue();
|
||||
if (!value) return "Unknown";
|
||||
if (!value) return t("common.unknown");
|
||||
const date = new Date(value as string);
|
||||
return (
|
||||
<Tooltip title={date.toLocaleString(undefined, { dateStyle: "medium", timeStyle: "long" })}>
|
||||
|
|
@ -441,18 +440,18 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
|
|||
{
|
||||
id: "expires",
|
||||
accessorKey: "expires",
|
||||
header: "Expires",
|
||||
header: t("virtualKeys.virtualKeysTable.expires"),
|
||||
size: 120,
|
||||
enableSorting: false,
|
||||
cell: (info) => {
|
||||
const value = info.getValue();
|
||||
return value ? new Date(value as string).toLocaleDateString() : "Never";
|
||||
return value ? new Date(value as string).toLocaleDateString() : t("common.never");
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "spend",
|
||||
accessorKey: "spend",
|
||||
header: "Spend (USD)",
|
||||
header: t("virtualKeys.virtualKeysTable.spendUsd"),
|
||||
size: 100,
|
||||
enableSorting: true,
|
||||
cell: (info) => formatNumberWithCommas(info.getValue() as number, 4),
|
||||
|
|
@ -460,13 +459,13 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
|
|||
{
|
||||
id: "max_budget",
|
||||
accessorKey: "max_budget",
|
||||
header: "Budget (USD)",
|
||||
header: t("virtualKeys.virtualKeysTable.budgetUsd"),
|
||||
size: 110,
|
||||
enableSorting: true,
|
||||
cell: (info) => {
|
||||
const maxBudget = info.getValue() as number | null;
|
||||
if (maxBudget === null) {
|
||||
return "Unlimited";
|
||||
return t("virtualKeys.virtualKeysTable.unlimited");
|
||||
}
|
||||
return `$${formatNumberWithCommas(maxBudget)}`;
|
||||
},
|
||||
|
|
@ -474,18 +473,18 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
|
|||
{
|
||||
id: "budget_reset_at",
|
||||
accessorKey: "budget_reset_at",
|
||||
header: "Budget Reset",
|
||||
header: t("virtualKeys.virtualKeysTable.budgetReset"),
|
||||
size: 130,
|
||||
enableSorting: false,
|
||||
cell: (info) => {
|
||||
const value = info.getValue();
|
||||
return value ? new Date(value as string).toLocaleString() : "Never";
|
||||
return value ? new Date(value as string).toLocaleString() : t("common.never");
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "models",
|
||||
accessorKey: "models",
|
||||
header: "Models",
|
||||
header: t("virtualKeys.virtualKeysTable.models"),
|
||||
size: 200,
|
||||
enableSorting: false,
|
||||
cell: (info) => {
|
||||
|
|
@ -496,7 +495,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
|
|||
<div className="flex flex-col">
|
||||
{models.length === 0 ? (
|
||||
<Badge size={"xs"} className="mb-1" color="red">
|
||||
<Text>All Proxy Models</Text>
|
||||
<Text>{t("virtualKeys.virtualKeysTable.allProxyModels")}</Text>
|
||||
</Badge>
|
||||
) : (
|
||||
<>
|
||||
|
|
@ -520,7 +519,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
|
|||
{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("virtualKeys.virtualKeysTable.allProxyModels")}</Text>
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge key={index} size={"xs"} color="blue">
|
||||
|
|
@ -534,9 +533,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
|
|||
)}
|
||||
{models.length > 3 && !expandedAccordions[info.row.id] && (
|
||||
<Badge size={"xs"} color="gray" className="cursor-pointer">
|
||||
<Text>
|
||||
+{models.length - 3} {models.length - 3 === 1 ? "more model" : "more models"}
|
||||
</Text>
|
||||
<Text>{t("virtualKeys.virtualKeysTable.moreModel", { count: models.length - 3 })}</Text>
|
||||
</Badge>
|
||||
)}
|
||||
{expandedAccordions[info.row.id] && (
|
||||
|
|
@ -544,7 +541,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
|
|||
{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("virtualKeys.virtualKeysTable.allProxyModels")}</Text>
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge key={index + 3} size={"xs"} color="blue">
|
||||
|
|
@ -570,27 +567,35 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
|
|||
},
|
||||
{
|
||||
id: "rate_limits",
|
||||
header: "Rate Limits",
|
||||
header: t("virtualKeys.virtualKeysTable.rateLimits"),
|
||||
size: 140,
|
||||
enableSorting: false,
|
||||
cell: ({ row }) => {
|
||||
const key = row.original;
|
||||
return (
|
||||
<div>
|
||||
<div>TPM: {key.tpm_limit !== null ? key.tpm_limit : "Unlimited"}</div>
|
||||
<div>RPM: {key.rpm_limit !== null ? key.rpm_limit : "Unlimited"}</div>
|
||||
<div>
|
||||
{t("virtualKeys.virtualKeysTable.tpmLimit", {
|
||||
value: key.tpm_limit !== null ? key.tpm_limit : t("virtualKeys.virtualKeysTable.unlimited"),
|
||||
})}
|
||||
</div>
|
||||
<div>
|
||||
{t("virtualKeys.virtualKeysTable.rpmLimit", {
|
||||
value: key.rpm_limit !== null ? key.rpm_limit : t("virtualKeys.virtualKeysTable.unlimited"),
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
[teams, resolvedOrganizations],
|
||||
[teams, resolvedOrganizations, t],
|
||||
);
|
||||
|
||||
const filterOptions: FilterOption[] = [
|
||||
{
|
||||
name: "Team ID",
|
||||
label: "Team ID",
|
||||
label: t("virtualKeys.virtualKeysTable.filterTeamId"),
|
||||
isSearchable: true,
|
||||
searchFn: async (searchText: string) => {
|
||||
if (!allTeams || allTeams.length === 0) return [];
|
||||
|
|
@ -609,7 +614,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
|
|||
},
|
||||
{
|
||||
name: "Organization ID",
|
||||
label: "Organization ID",
|
||||
label: t("virtualKeys.virtualKeysTable.filterOrgId"),
|
||||
isSearchable: true,
|
||||
searchFn: async (searchText: string) => {
|
||||
if (!allOrganizations || allOrganizations.length === 0) return [];
|
||||
|
|
@ -621,24 +626,24 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
|
|||
return filteredOrgs
|
||||
.filter((org) => org.organization_id !== null && org.organization_id !== undefined)
|
||||
.map((org) => ({
|
||||
label: `${org.organization_id || "Unknown"} (${org.organization_id})`,
|
||||
label: `${org.organization_id || t("common.unknown")} (${org.organization_id})`,
|
||||
value: org.organization_id as string,
|
||||
}));
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Key Alias",
|
||||
label: "Key Alias",
|
||||
label: t("virtualKeys.virtualKeysTable.filterKeyAlias"),
|
||||
customComponent: PaginatedKeyAliasSelect,
|
||||
},
|
||||
{
|
||||
name: "User ID",
|
||||
label: "User ID",
|
||||
label: t("virtualKeys.virtualKeysTable.filterUserId"),
|
||||
isSearchable: false,
|
||||
},
|
||||
{
|
||||
name: "Key Hash",
|
||||
label: "Key Hash",
|
||||
label: t("virtualKeys.virtualKeysTable.filterKeyHash"),
|
||||
isSearchable: false,
|
||||
},
|
||||
];
|
||||
|
|
@ -725,7 +730,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
|
|||
<Skeleton.Node active style={{ width: 200, height: 20 }} />
|
||||
) : (
|
||||
<span className="inline-flex text-sm text-gray-700">
|
||||
Showing {rangeLabel} of {totalCount} results
|
||||
{t("virtualKeys.virtualKeysTable.showingResults", { range: rangeLabel, total: totalCount })}
|
||||
</span>
|
||||
)}
|
||||
|
||||
|
|
@ -734,9 +739,9 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
|
|||
icon={<SyncOutlined spin={isButtonLoading} />}
|
||||
onClick={handleRefresh}
|
||||
disabled={isButtonLoading}
|
||||
title="Fetch data"
|
||||
title={t("virtualKeys.virtualKeysTable.fetchData")}
|
||||
>
|
||||
{isButtonLoading ? "Fetching" : "Fetch"}
|
||||
{isButtonLoading ? t("virtualKeys.virtualKeysTable.fetching") : t("virtualKeys.virtualKeysTable.fetch")}
|
||||
</AntButton>
|
||||
</div>
|
||||
|
||||
|
|
@ -745,7 +750,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
|
|||
<Skeleton.Node active style={{ width: 74, height: 20 }} />
|
||||
) : (
|
||||
<span className="text-sm text-gray-700">
|
||||
Page {pageIndex + 1} of {table.getPageCount()}
|
||||
{t("virtualKeys.virtualKeysTable.pageOf", { page: pageIndex + 1, total: table.getPageCount() })}
|
||||
</span>
|
||||
)}
|
||||
|
||||
|
|
@ -757,7 +762,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
|
|||
disabled={isLoading || !table.getCanPreviousPage()}
|
||||
className="px-3 py-1 text-sm border rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
Previous
|
||||
{t("common.previous")}
|
||||
</button>
|
||||
)}
|
||||
|
||||
|
|
@ -769,7 +774,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
|
|||
disabled={isLoading || !table.getCanNextPage()}
|
||||
className="px-3 py-1 text-sm border rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
Next
|
||||
{t("common.next")}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -856,7 +861,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
|
|||
<TableRow>
|
||||
<TableCell colSpan={columns.length} className="h-8 text-center">
|
||||
<div className="text-center text-gray-500">
|
||||
<p>🚅 Loading keys...</p>
|
||||
<p>{t("virtualKeys.virtualKeysTable.loadingKeys")}</p>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
|
@ -883,7 +888,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
|
|||
<TableRow>
|
||||
<TableCell colSpan={columns.length} className="h-8 text-center">
|
||||
<div className="text-center text-gray-500">
|
||||
<p>No keys found</p>
|
||||
<p>{t("virtualKeys.virtualKeysTable.noKeysFound")}</p>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
|
|
|||
|
|
@ -8316,5 +8316,49 @@
|
|||
"notSet": "Not Set",
|
||||
"updateSettings": "Update Settings"
|
||||
}
|
||||
},
|
||||
"virtualKeys": {
|
||||
"virtualKeysTable": {
|
||||
"keyId": "Key ID",
|
||||
"keyAlias": "Key Alias",
|
||||
"secretKey": "Secret Key",
|
||||
"team": "Team",
|
||||
"organization": "Organization",
|
||||
"user": "User",
|
||||
"createdBy": "Created By",
|
||||
"lastActive": "Last Active",
|
||||
"expires": "Expires",
|
||||
"spendUsd": "Spend (USD)",
|
||||
"budgetUsd": "Budget (USD)",
|
||||
"budgetReset": "Budget Reset",
|
||||
"models": "Models",
|
||||
"rateLimits": "Rate Limits",
|
||||
"blocked": "Blocked",
|
||||
"blockedByScimReason": "Blocked by SCIM (external identity provider deactivated or deleted the owning user).",
|
||||
"blockedReason": "Blocked. Requests using this key will be rejected with 401.",
|
||||
"userPopoverHint": "Displays the first available value: User Alias, User Email, or User ID.",
|
||||
"userAlias": "User Alias",
|
||||
"userEmail": "User Email",
|
||||
"userId": "User ID",
|
||||
"allProxyModels": "All Proxy Models",
|
||||
"moreModel_one": "+{{count}} more model",
|
||||
"moreModel_other": "+{{count}} more models",
|
||||
"tpmLimit": "TPM: {{value}}",
|
||||
"rpmLimit": "RPM: {{value}}",
|
||||
"unlimited": "Unlimited",
|
||||
"lastActiveHint": "This is a new field and is not backfilled. Only new key usage will update this value.",
|
||||
"loadingKeys": "🚅 Loading keys...",
|
||||
"noKeysFound": "No keys found",
|
||||
"showingResults": "Showing {{range}} of {{total}} results",
|
||||
"fetchData": "Fetch data",
|
||||
"fetching": "Fetching",
|
||||
"fetch": "Fetch",
|
||||
"pageOf": "Page {{page}} of {{total}}",
|
||||
"filterTeamId": "Team ID",
|
||||
"filterOrgId": "Organization ID",
|
||||
"filterKeyAlias": "Key Alias",
|
||||
"filterUserId": "User ID",
|
||||
"filterKeyHash": "Key Hash"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8316,5 +8316,49 @@
|
|||
"notSet": "未设置",
|
||||
"updateSettings": "更新设置"
|
||||
}
|
||||
},
|
||||
"virtualKeys": {
|
||||
"virtualKeysTable": {
|
||||
"keyId": "密钥 ID",
|
||||
"keyAlias": "密钥别名",
|
||||
"secretKey": "密钥",
|
||||
"team": "团队",
|
||||
"organization": "组织",
|
||||
"user": "用户",
|
||||
"createdBy": "创建者",
|
||||
"lastActive": "最后活跃",
|
||||
"expires": "过期时间",
|
||||
"spendUsd": "花费(USD)",
|
||||
"budgetUsd": "预算(USD)",
|
||||
"budgetReset": "预算重置",
|
||||
"models": "模型",
|
||||
"rateLimits": "速率限制",
|
||||
"blocked": "已封禁",
|
||||
"blockedByScimReason": "被 SCIM 封禁(外部身份提供商已停用或删除了该密钥所属用户)。",
|
||||
"blockedReason": "已封禁。使用此密钥的请求将以 401 被拒绝。",
|
||||
"userPopoverHint": "按优先顺序显示:用户别名、用户邮箱或用户 ID。",
|
||||
"userAlias": "用户别名",
|
||||
"userEmail": "用户邮箱",
|
||||
"userId": "用户 ID",
|
||||
"allProxyModels": "所有代理模型",
|
||||
"moreModel_one": "+{{count}} 个模型",
|
||||
"moreModel_other": "+{{count}} 个模型",
|
||||
"tpmLimit": "TPM: {{value}}",
|
||||
"rpmLimit": "RPM: {{value}}",
|
||||
"unlimited": "无限制",
|
||||
"lastActiveHint": "这是一个新字段,未回填历史数据。仅新的密钥使用记录会更新此值。",
|
||||
"loadingKeys": "🚅 正在加载密钥...",
|
||||
"noKeysFound": "未找到密钥",
|
||||
"showingResults": "显示第 {{range}} 条,共 {{total}} 条",
|
||||
"fetchData": "拉取数据",
|
||||
"fetching": "拉取中",
|
||||
"fetch": "拉取",
|
||||
"pageOf": "第 {{page}} 页,共 {{total}} 页",
|
||||
"filterTeamId": "团队 ID",
|
||||
"filterOrgId": "组织 ID",
|
||||
"filterKeyAlias": "密钥别名",
|
||||
"filterUserId": "用户 ID",
|
||||
"filterKeyHash": "密钥哈希"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue