feat(ui): i18n for legacy teams view

This commit is contained in:
吹雪 2026-06-10 12:35:26 +09:00
parent cb21813d80
commit 7bf4de9997
3 changed files with 417 additions and 143 deletions

View file

@ -3,6 +3,7 @@ import AvailableTeamsPanel from "@/components/team/available_teams";
import TeamInfoView from "@/components/team/TeamInfo";
import TeamSSOSettings from "@/components/TeamSSOSettings";
import { isProxyAdminRole } from "@/utils/roles";
import { useTranslation } from "react-i18next";
import { InfoCircleOutlined, PlusOutlined, TeamOutlined, ReloadOutlined } from "@ant-design/icons";
import { Accordion, AccordionBody, AccordionHeader, TextInput } from "@tremor/react";
import {
@ -176,6 +177,7 @@ const Teams: React.FC<TeamProps> = ({
premiumUser = false,
}) => {
console.log(`organizations: ${JSON.stringify(organizations)}`);
const { t } = useTranslation();
const { data: organizationsData } = useOrganizations();
const [isLoading, setIsLoading] = useState(true);
const [fetchError, setFetchError] = useState<string | null>(null);
@ -224,7 +226,7 @@ const Teams: React.FC<TeamProps> = ({
setTeams(response.teams ?? []);
setTotalTeams(response.total ?? 0);
} catch (err: any) {
setFetchError(err?.message || "Failed to fetch teams");
setFetchError(err?.message || t("oldTeams.failedToFetchTeams"));
} finally {
setIsLoading(false);
}
@ -407,9 +409,9 @@ const Teams: React.FC<TeamProps> = ({
setIsTeamDeleting(true);
await teamDeleteCall(accessToken, teamToDelete.team_id);
await fetchTeamsV2();
NotificationsManager.success("Team deleted successfully");
NotificationsManager.success(t("oldTeams.teamDeletedSuccess"));
} catch (error) {
NotificationsManager.fromBackend("Error deleting the team: " + error);
NotificationsManager.fromBackend(t("oldTeams.deleteTeamFailed", { error }));
} finally {
setIsTeamDeleting(false);
setIsDeleteModalOpen(false);
@ -458,7 +460,7 @@ const Teams: React.FC<TeamProps> = ({
throw new Error(`Team alias ${newTeamAlias} already exists, please pick another alias`);
}
NotificationsManager.info("Creating Team");
NotificationsManager.info(t("oldTeams.creatingTeam"));
// Handle logging settings in metadata
if (loggingSettings.length > 0) {
@ -578,7 +580,7 @@ const Teams: React.FC<TeamProps> = ({
}
await teamCreateCall(accessToken, formValues);
NotificationsManager.success("Team created");
NotificationsManager.success(t("oldTeams.teamCreatedSuccess"));
await fetchTeamsV2({
page: currentPage,
size: pageSize,
@ -592,7 +594,7 @@ const Teams: React.FC<TeamProps> = ({
}
} catch (error) {
console.error("Error creating the team:", error);
NotificationsManager.fromBackend("Error creating the team: " + error);
NotificationsManager.fromBackend(t("oldTeams.createTeamFailed", { error }));
}
};
@ -680,7 +682,7 @@ const Teams: React.FC<TeamProps> = ({
const teamColumns: ColumnsType<Team> = useMemo(
() => [
{
title: "Team ID",
title: t("oldTeams.columns.teamId"),
dataIndex: "team_id",
key: "team_id",
width: 170,
@ -700,7 +702,7 @@ const Teams: React.FC<TeamProps> = ({
),
},
{
title: "Team Alias",
title: t("oldTeams.columns.teamAlias"),
dataIndex: "team_alias",
key: "team_alias",
ellipsis: true,
@ -716,7 +718,7 @@ const Teams: React.FC<TeamProps> = ({
),
},
{
title: "Organization",
title: t("oldTeams.columns.organization"),
key: "organization",
width: 160,
ellipsis: true,
@ -732,7 +734,7 @@ const Teams: React.FC<TeamProps> = ({
},
},
{
title: "Resources",
title: t("oldTeams.columns.resources"),
key: "resources",
width: 240,
render: (_: unknown, record: Team) => {
@ -741,7 +743,7 @@ const Teams: React.FC<TeamProps> = ({
const keyCount = perTeamInfo?.[record.team_id]?.keys_count ?? 0;
return (
<Flex gap={12} align="center">
<Tooltip title={`${memberCount} Members`}>
<Tooltip title={t("oldTeams.resources.members", { count: memberCount })}>
<Tag color="purple" style={{ fontSize: 14, padding: "2px 8px", margin: 0 }}>
<Flex align="center" gap={6}>
<UsersIcon size={14} />
@ -749,7 +751,7 @@ const Teams: React.FC<TeamProps> = ({
</Flex>
</Tag>
</Tooltip>
<Tooltip title={`${modelCount} Models`}>
<Tooltip title={t("oldTeams.resources.models", { count: modelCount })}>
<Tag color="blue" style={{ fontSize: 14, padding: "2px 8px", margin: 0 }}>
<Flex align="center" gap={6}>
<LayersIcon size={14} />
@ -757,7 +759,7 @@ const Teams: React.FC<TeamProps> = ({
</Flex>
</Tag>
</Tooltip>
<Tooltip title={`${keyCount} Keys`}>
<Tooltip title={t("oldTeams.resources.keys", { count: keyCount })}>
<Tag color="cyan" style={{ fontSize: 14, padding: "2px 8px", margin: 0 }}>
<Flex align="center" gap={6}>
<KeyIcon size={14} />
@ -770,7 +772,7 @@ const Teams: React.FC<TeamProps> = ({
},
},
{
title: "Spend / Budget",
title: t("oldTeams.columns.spendBudget"),
key: "spend",
width: 200,
sorter: true,
@ -781,7 +783,7 @@ const Teams: React.FC<TeamProps> = ({
const budgetStr =
budgetVal != null
? `$${budgetVal.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
: "Unlimited";
: t("oldTeams.unlimited");
const percent = budgetVal != null && budgetVal > 0 ? Math.min((spendVal / budgetVal) * 100, 100) : null;
return (
<Flex vertical gap={2}>
@ -806,7 +808,7 @@ const Teams: React.FC<TeamProps> = ({
},
},
{
title: "Created",
title: t("oldTeams.columns.created"),
dataIndex: "created_at",
key: "created_at",
width: 130,
@ -821,7 +823,7 @@ const Teams: React.FC<TeamProps> = ({
),
},
{
title: "Actions",
title: t("common.actions"),
key: "actions",
width: 120,
align: "right" as const,
@ -829,19 +831,19 @@ const Teams: React.FC<TeamProps> = ({
<Space size={4}>
<TableIconActionButton
variant="Copy"
tooltipText="Copy Team ID"
tooltipText={t("oldTeams.actions.copyTeamId")}
onClick={() => {
navigator.clipboard
.writeText(record.team_id)
.then(() => message.success("Team ID copied"))
.catch(() => message.error("Failed to copy"));
.then(() => message.success(t("oldTeams.actions.teamIdCopied")))
.catch(() => message.error(t("oldTeams.actions.failedToCopy")));
}}
/>
{userRole === "Admin" && (
<>
<TableIconActionButton
variant="Edit"
tooltipText="Edit team"
tooltipText={t("oldTeams.actions.editTeam")}
dataTestId="edit-team-button"
onClick={() => {
setSelectedTeamId(record.team_id);
@ -850,7 +852,7 @@ const Teams: React.FC<TeamProps> = ({
/>
<TableIconActionButton
variant="Delete"
tooltipText="Delete team"
tooltipText={t("oldTeams.actions.deleteTeam")}
dataTestId="delete-team-button"
onClick={() => handleDelete(record)}
/>
@ -860,7 +862,7 @@ const Teams: React.FC<TeamProps> = ({
),
},
],
[userRole, perTeamInfo, organizationsData, organizations],
[userRole, perTeamInfo, organizationsData, organizations, t],
);
const displayTeams = useMemo(() => teams ?? [], [teams]);
@ -878,13 +880,13 @@ const Teams: React.FC<TeamProps> = ({
return (
<Flex vertical align="center" gap={16} style={{ padding: "64px 0" }}>
<Text type="danger" style={{ fontSize: 15 }}>
Failed to load teams
{t("oldTeams.failedToLoadTeams")}
</Text>
<Text type="secondary" style={{ fontSize: 13 }}>
{fetchError}
</Text>
<Button icon={<ReloadOutlined />} onClick={handleRetry}>
Retry
{t("common.retry")}
</Button>
</Flex>
);
@ -902,11 +904,11 @@ const Teams: React.FC<TeamProps> = ({
<div style={{ padding: "64px 0", textAlign: "center" }}>
<TeamOutlined style={{ fontSize: 40, color: "#d9d9d9", marginBottom: 12 }} />
<div>
<Text style={{ fontSize: 15, color: "#595959" }}>No teams yet</Text>
<Text style={{ fontSize: 15, color: "#595959" }}>{t("oldTeams.noTeamsYet")}</Text>
</div>
<div style={{ marginTop: 4 }}>
<Text type="secondary" style={{ fontSize: 13 }}>
Create your first team to organize members and manage access to models.
{t("oldTeams.createFirstTeamHint")}
</Text>
</div>
{canCreateOrManageTeams(userRole, userID, organizations) && (
@ -917,7 +919,7 @@ const Teams: React.FC<TeamProps> = ({
style={{ marginTop: 16 }}
data-testid="create-team-button"
>
Create Team
{t("oldTeams.createTeam")}
</Button>
)}
</div>
@ -932,7 +934,7 @@ const Teams: React.FC<TeamProps> = ({
const tabItems = [
{
key: "your-teams",
label: "Your Teams",
label: t("oldTeams.tabs.yourTeams"),
children: (
<>
<Card styles={{ body: { padding: 0 } }}>
@ -941,7 +943,7 @@ const Teams: React.FC<TeamProps> = ({
<Input
prefix={<SearchIcon size={16} />}
suffix={isSearching ? <AntDLoadingSpinner size="small" /> : null}
placeholder="Search teams by name or ID..."
placeholder={t("oldTeams.searchPlaceholder")}
onChange={(e) => handleSearchChange(e.target.value)}
allowClear
style={{ maxWidth: 400 }}
@ -963,7 +965,7 @@ const Teams: React.FC<TeamProps> = ({
fetchTeamsV2({ page, size });
}}
size="small"
showTotal={(total) => `${total} teams`}
showTotal={(total) => t("oldTeams.totalTeams", { count: total })}
showSizeChanger
pageSizeOptions={["10", "20", "50"]}
/>
@ -974,23 +976,23 @@ const Teams: React.FC<TeamProps> = ({
<DeleteResourceModal
isOpen={isDeleteModalOpen}
title="Delete Team?"
title={t("oldTeams.deleteModal.title")}
alertMessage={(() => {
const deleteKeyCount = teamToDelete?.keys_count ?? teamToDelete?.keys?.length ?? 0;
return deleteKeyCount === 0
? undefined
: `Warning: This team has ${deleteKeyCount} keys associated with it. Deleting the team will also delete all associated keys. This action is irreversible.`;
: t("oldTeams.deleteModal.alertMessage", { count: deleteKeyCount });
})()}
message="Are you sure you want to delete this team and all its keys? This action cannot be undone."
resourceInformationTitle="Team Information"
message={t("oldTeams.deleteModal.message")}
resourceInformationTitle={t("oldTeams.deleteModal.resourceInformationTitle")}
resourceInformation={[
{ label: "Team ID", value: teamToDelete?.team_id, code: true },
{ label: "Team Name", value: teamToDelete?.team_alias },
{ label: t("oldTeams.deleteModal.labelTeamId"), value: teamToDelete?.team_id, code: true },
{ label: t("oldTeams.deleteModal.labelTeamName"), value: teamToDelete?.team_alias },
{
label: "Keys",
label: t("oldTeams.deleteModal.labelKeys"),
value: teamToDelete?.keys_count ?? teamToDelete?.keys?.length ?? 0,
},
{ label: "Members", value: teamToDelete?.members_with_roles?.length },
{ label: t("oldTeams.deleteModal.labelMembers"), value: teamToDelete?.members_with_roles?.length },
]}
requiredConfirmation={teamToDelete?.team_alias}
onCancel={cancelDelete}
@ -1002,14 +1004,14 @@ const Teams: React.FC<TeamProps> = ({
},
{
key: "available-teams",
label: "Available Teams",
label: t("oldTeams.tabs.availableTeams"),
children: <AvailableTeamsPanel accessToken={accessToken} userID={userID} />,
},
...(isProxyAdminRole(userRole || "")
? [
{
key: "default-settings",
label: "Default Team Settings",
label: t("oldTeams.tabs.defaultTeamSettings"),
children: <TeamSSOSettings accessToken={accessToken} userID={userID || ""} userRole={userRole || ""} />,
},
]
@ -1052,9 +1054,9 @@ const Teams: React.FC<TeamProps> = ({
<Space direction="vertical" size={0}>
<Title level={2} style={{ margin: 0 }}>
<TeamOutlined style={{ marginRight: 8 }} />
Teams
{t("oldTeams.pageTitle")}
</Title>
<Text type="secondary">Manage teams, members, and their access to models and budgets</Text>
<Text type="secondary">{t("oldTeams.pageSubtitle")}</Text>
</Space>
{canCreateOrManageTeams(userRole, userID, organizations) && (
<Button
@ -1063,7 +1065,7 @@ const Teams: React.FC<TeamProps> = ({
onClick={() => setIsTeamModalVisible(true)}
data-testid="create-team-button"
>
Create Team
{t("oldTeams.createTeam")}
</Button>
)}
</Flex>
@ -1074,7 +1076,7 @@ const Teams: React.FC<TeamProps> = ({
{canCreateOrManageTeams(userRole, userID, organizations) && (
<Modal
title="Create Team"
title={t("oldTeams.createTeam")}
open={isTeamModalVisible}
width={1000}
footer={null}
@ -1084,12 +1086,12 @@ const Teams: React.FC<TeamProps> = ({
<Form form={form} onFinish={handleCreate} labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} labelAlign="left">
<>
<Form.Item
label="Team Name"
label={t("oldTeams.form.teamName")}
name="team_alias"
rules={[
{
required: true,
message: "Please input a team name",
message: t("oldTeams.form.teamNameRequired"),
},
]}
>
@ -1106,11 +1108,11 @@ const Teams: React.FC<TeamProps> = ({
<Form.Item
label={
<span>
Organization{" "}
{t("oldTeams.form.organization")}{" "}
<Tooltip
title={
<span>
Organizations can have multiple teams. Learn more about{" "}
{t("oldTeams.form.organizationTooltipPrefix")}{" "}
<a
href="https://docs.litellm.ai/docs/proxy/user_management_heirarchy"
target="_blank"
@ -1121,7 +1123,7 @@ const Teams: React.FC<TeamProps> = ({
}}
onClick={(e) => e.stopPropagation()}
>
user management hierarchy
{t("oldTeams.form.userManagementHierarchy")}
</a>
</span>
}
@ -1138,24 +1140,22 @@ const Teams: React.FC<TeamProps> = ({
? [
{
required: true,
message: "Please select an organization",
message: t("oldTeams.form.organizationRequired"),
},
]
: []
}
help={
isSingleOrg
? "You can only create teams within this organization"
: isOrgAdmin
? "required"
: ""
}
help={isSingleOrg ? t("oldTeams.form.singleOrgHelp") : isOrgAdmin ? t("common.required") : ""}
>
<Select
showSearch
allowClear={!isOrgAdmin}
disabled={isSingleOrg}
placeholder={hasNoOrgs ? "No organizations available" : "Search or select an Organization"}
placeholder={
hasNoOrgs
? t("oldTeams.form.noOrganizationsAvailable")
: t("oldTeams.form.searchOrSelectOrganization")
}
onChange={(value) => {
form.setFieldValue("organization_id", value);
setCurrentOrgForCreateTeam(adminOrgs?.find((org) => org.organization_id === value) || null);
@ -1179,10 +1179,7 @@ const Teams: React.FC<TeamProps> = ({
{/* Show message when org admin needs to select organization */}
{isOrgAdmin && !isSingleOrg && adminOrgs.length > 1 && (
<div className="mb-8 p-4 bg-blue-50 border border-blue-200 rounded-md">
<Text style={{ color: "#1e40af", fontSize: 14 }}>
Please select an organization to create a team for. You can only create teams within
organizations where you are an admin.
</Text>
<Text style={{ color: "#1e40af", fontSize: 14 }}>{t("oldTeams.form.selectOrgAdminHint")}</Text>
</div>
)}
</>
@ -1191,8 +1188,8 @@ const Teams: React.FC<TeamProps> = ({
<Form.Item
label={
<span>
Models{" "}
<Tooltip title="These are the models that your selected team has access to">
{t("oldTeams.form.models")}{" "}
<Tooltip title={t("oldTeams.form.modelsTooltip")}>
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
</Tooltip>
</span>
@ -1200,7 +1197,7 @@ const Teams: React.FC<TeamProps> = ({
rules={[
{
required: true,
message: "Please select at least one model",
message: t("oldTeams.form.modelsRequired"),
},
]}
name="models"
@ -1218,20 +1215,20 @@ const Teams: React.FC<TeamProps> = ({
/>
</Form.Item>
<Form.Item label="Max Budget (USD)" name="max_budget">
<Form.Item label={t("oldTeams.form.maxBudget")} name="max_budget">
<NumericalInput step={0.01} precision={2} width={200} />
</Form.Item>
<Form.Item className="mt-8" label="Reset Budget" name="budget_duration">
<Form.Item className="mt-8" label={t("oldTeams.form.resetBudget")} name="budget_duration">
<Select defaultValue={null} placeholder="n/a">
<Select.Option value="24h">daily</Select.Option>
<Select.Option value="7d">weekly</Select.Option>
<Select.Option value="30d">monthly</Select.Option>
<Select.Option value="24h">{t("oldTeams.form.budgetDaily")}</Select.Option>
<Select.Option value="7d">{t("oldTeams.form.budgetWeekly")}</Select.Option>
<Select.Option value="30d">{t("oldTeams.form.budgetMonthly")}</Select.Option>
</Select>
</Form.Item>
<Form.Item label="Tokens per minute Limit (TPM)" name="tpm_limit">
<Form.Item label={t("oldTeams.form.tpmLimit")} 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("oldTeams.form.rpmLimit")} name="rpm_limit">
<NumericalInput step={1} width={400} />
</Form.Item>
@ -1245,14 +1242,10 @@ const Teams: React.FC<TeamProps> = ({
}}
>
<AccordionHeader>
<b>Additional Settings</b>
<b>{t("oldTeams.accordions.additionalSettings")}</b>
</AccordionHeader>
<AccordionBody>
<Form.Item
label="Team ID"
name="team_id"
help="ID of the team you want to create. If not provided, it will be generated automatically."
>
<Form.Item label={t("oldTeams.form.teamId")} name="team_id" help={t("oldTeams.form.teamIdHelp")}>
<TextInput
onChange={(e) => {
e.target.value = e.target.value.trim();
@ -1260,48 +1253,44 @@ const Teams: React.FC<TeamProps> = ({
/>
</Form.Item>
<Form.Item
label="Team Member Budget (USD)"
label={t("oldTeams.form.teamMemberBudget")}
name="team_member_budget"
normalize={(value) => (value ? Number(value) : undefined)}
tooltip="This is the individual budget for a user in the team."
tooltip={t("oldTeams.form.teamMemberBudgetTooltip")}
>
<NumericalInput step={0.01} precision={2} width={200} />
</Form.Item>
<Form.Item
label="Team Member Key Duration (eg: 1d, 1mo)"
label={t("oldTeams.form.teamMemberKeyDuration")}
name="team_member_key_duration"
tooltip="Set a limit to the duration of a team member's key. Format: 30s (seconds), 30m (minutes), 30h (hours), 30d (days), 1mo (month)"
tooltip={t("oldTeams.form.teamMemberKeyDurationTooltip")}
>
<TextInput placeholder="e.g., 30d" />
<TextInput placeholder={t("oldTeams.form.teamMemberKeyDurationPlaceholder")} />
</Form.Item>
<Form.Item
label="Team Member RPM Limit"
label={t("oldTeams.form.teamMemberRpmLimit")}
name="team_member_rpm_limit"
tooltip="The RPM (Requests Per Minute) limit for individual team members"
tooltip={t("oldTeams.form.teamMemberRpmLimitTooltip")}
>
<NumericalInput step={1} width={400} />
</Form.Item>
<Form.Item
label="Team Member TPM Limit"
label={t("oldTeams.form.teamMemberTpmLimit")}
name="team_member_tpm_limit"
tooltip="The TPM (Tokens Per Minute) limit for individual team members"
tooltip={t("oldTeams.form.teamMemberTpmLimitTooltip")}
>
<NumericalInput step={1} width={400} />
</Form.Item>
<Form.Item
label="Metadata"
name="metadata"
help="Additional team metadata. Enter metadata as JSON object."
>
<Form.Item label={t("oldTeams.form.metadata")} name="metadata" help={t("oldTeams.form.metadataHelp")}>
<Input.TextArea rows={4} />
</Form.Item>
<Form.Item
label="Secret Manager Settings"
label={t("oldTeams.form.secretManagerSettings")}
name="secret_manager_settings"
help={
premiumUser
? "Enter secret manager configuration as a JSON object."
: "Premium feature - Upgrade to manage secret manager settings."
? t("oldTeams.form.secretManagerSettingsHelp")
: t("oldTeams.form.secretManagerSettingsPremiumHelp")
}
rules={[
{
@ -1313,7 +1302,7 @@ const Teams: React.FC<TeamProps> = ({
JSON.parse(value);
return Promise.resolve();
} catch (error) {
return Promise.reject(new Error("Please enter valid JSON"));
return Promise.reject(new Error(t("oldTeams.form.invalidJson")));
}
},
},
@ -1328,8 +1317,8 @@ const Teams: React.FC<TeamProps> = ({
<Form.Item
label={
<span>
Guardrails{" "}
<Tooltip title="Setup your first guardrail">
{t("oldTeams.form.guardrails")}{" "}
<Tooltip title={t("oldTeams.form.guardrailsTooltip")}>
<a
href="https://docs.litellm.ai/docs/proxy/guardrails/quick_start"
target="_blank"
@ -1343,12 +1332,12 @@ const Teams: React.FC<TeamProps> = ({
}
name="guardrails"
className="mt-8"
help="Select existing guardrails or enter new ones"
help={t("oldTeams.form.guardrailsHelp")}
>
<Select
mode="tags"
style={{ width: "100%" }}
placeholder="Select or enter guardrails"
placeholder={t("oldTeams.form.guardrailsPlaceholder")}
options={guardrailsList.map((name) => ({
value: name,
label: name,
@ -1358,8 +1347,8 @@ const Teams: React.FC<TeamProps> = ({
<Form.Item
label={
<span>
Disable Global Guardrails{" "}
<Tooltip title="When enabled, this team will bypass any guardrails configured to run on every request (global guardrails)">
{t("oldTeams.form.disableGlobalGuardrails")}{" "}
<Tooltip title={t("oldTeams.form.disableGlobalGuardrailsTooltip")}>
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
</Tooltip>
</span>
@ -1367,23 +1356,23 @@ const Teams: React.FC<TeamProps> = ({
name="disable_global_guardrails"
className="mt-4"
valuePropName="checked"
help="Bypass global guardrails for this team"
help={t("oldTeams.form.disableGlobalGuardrailsHelp")}
>
<Switch
disabled={!premiumUser}
checkedChildren={
premiumUser ? "Yes" : "Premium feature - Upgrade to disable global guardrails by team"
premiumUser ? t("common.yes") : t("oldTeams.form.disableGlobalGuardrailsPremium")
}
unCheckedChildren={
premiumUser ? "No" : "Premium feature - Upgrade to disable global guardrails by team"
premiumUser ? t("common.no") : t("oldTeams.form.disableGlobalGuardrailsPremium")
}
/>
</Form.Item>
<Form.Item
label={
<span>
Policies{" "}
<Tooltip title="Apply policies to this team to control guardrails and other settings">
{t("oldTeams.form.policies")}{" "}
<Tooltip title={t("oldTeams.form.policiesTooltip")}>
<a
href="https://docs.litellm.ai/docs/proxy/guardrails/guardrail_policies"
target="_blank"
@ -1397,12 +1386,12 @@ const Teams: React.FC<TeamProps> = ({
}
name="policies"
className="mt-8"
help="Select existing policies or enter new ones"
help={t("oldTeams.form.policiesHelp")}
>
<Select
mode="tags"
style={{ width: "100%" }}
placeholder="Select or enter policies"
placeholder={t("oldTeams.form.policiesPlaceholder")}
options={policiesList.map((name) => ({
value: name,
label: name,
@ -1412,45 +1401,49 @@ const Teams: React.FC<TeamProps> = ({
<Form.Item
label={
<span>
Access Groups{" "}
<Tooltip title="Assign access groups to this team. Access groups control which models, MCP servers, and agents this team can use">
{t("oldTeams.form.accessGroups")}{" "}
<Tooltip title={t("oldTeams.form.accessGroupsTooltip")}>
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
</Tooltip>
</span>
}
name="access_group_ids"
className="mt-8"
help="Select access groups to assign to this team"
help={t("oldTeams.form.accessGroupsHelp")}
>
<AccessGroupSelector placeholder="Select access groups (optional)" />
<AccessGroupSelector placeholder={t("oldTeams.form.accessGroupsPlaceholder")} />
</Form.Item>
<Form.Item
label={
<span>
Allowed Vector Stores{" "}
<Tooltip title="Select which vector stores this team can access by default. Leave empty for access to all vector stores">
{t("oldTeams.form.allowedVectorStores")}{" "}
<Tooltip title={t("oldTeams.form.allowedVectorStoresTooltip")}>
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
</Tooltip>
</span>
}
name="allowed_vector_store_ids"
className="mt-8"
help="Select vector stores this team can access. Leave empty for access to all vector stores"
help={t("oldTeams.form.allowedVectorStoresHelp")}
>
<VectorStoreSelector
onChange={(values: string[]) => form.setFieldValue("allowed_vector_store_ids", values)}
value={form.getFieldValue("allowed_vector_store_ids")}
accessToken={accessToken || ""}
placeholder="Select vector stores (optional)"
placeholder={t("oldTeams.form.allowedVectorStoresPlaceholder")}
/>
</Form.Item>
<Form.Item label="Allowed Pass Through Routes" name="allowed_passthrough_routes" className="mt-8">
<Form.Item
label={t("oldTeams.form.allowedPassThroughRoutes")}
name="allowed_passthrough_routes"
className="mt-8"
>
<Tooltip
title={
!premiumUser
? "Premium feature - Upgrade to set allowed pass through routes"
? t("oldTeams.form.allowedPassThroughRoutesPremiumTooltip")
: !isProxyAdminRole(userRole || "")
? "Only proxy admins can set allowed pass through routes"
? t("oldTeams.form.allowedPassThroughRoutesAdminTooltip")
: ""
}
placement="top"
@ -1459,7 +1452,7 @@ const Teams: React.FC<TeamProps> = ({
onChange={(values: string[]) => form.setFieldValue("allowed_passthrough_routes", values)}
value={form.getFieldValue("allowed_passthrough_routes")}
accessToken={accessToken || ""}
placeholder="Select pass through routes (optional)"
placeholder={t("oldTeams.form.allowedPassThroughRoutesPlaceholder")}
disabled={!premiumUser || !isProxyAdminRole(userRole || "")}
/>
</Tooltip>
@ -1469,27 +1462,27 @@ const Teams: React.FC<TeamProps> = ({
<Accordion className="mt-8 mb-8">
<AccordionHeader>
<b>MCP Settings</b>
<b>{t("oldTeams.accordions.mcpSettings")}</b>
</AccordionHeader>
<AccordionBody>
<Form.Item
label={
<span>
Allowed MCP Servers{" "}
<Tooltip title="Select which MCP servers or access groups this team can access">
{t("oldTeams.form.allowedMcpServers")}{" "}
<Tooltip title={t("oldTeams.form.allowedMcpServersTooltip")}>
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
</Tooltip>
</span>
}
name="allowed_mcp_servers_and_groups"
className="mt-4"
help="Select MCP servers or access groups this team can access"
help={t("oldTeams.form.allowedMcpServersHelp")}
>
<MCPServerSelector
onChange={(val: any) => form.setFieldValue("allowed_mcp_servers_and_groups", val)}
value={form.getFieldValue("allowed_mcp_servers_and_groups")}
accessToken={accessToken || ""}
placeholder="Select MCP servers or access groups (optional)"
placeholder={t("oldTeams.form.allowedMcpServersPlaceholder")}
/>
</Form.Item>
@ -1521,27 +1514,27 @@ const Teams: React.FC<TeamProps> = ({
<Accordion className="mt-8 mb-8">
<AccordionHeader>
<b>Agent Settings</b>
<b>{t("oldTeams.accordions.agentSettings")}</b>
</AccordionHeader>
<AccordionBody>
<Form.Item
label={
<span>
Allowed Agents{" "}
<Tooltip title="Select which agents or access groups this team can access">
{t("oldTeams.form.allowedAgents")}{" "}
<Tooltip title={t("oldTeams.form.allowedAgentsTooltip")}>
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
</Tooltip>
</span>
}
name="allowed_agents_and_groups"
className="mt-4"
help="Select agents or access groups this team can access"
help={t("oldTeams.form.allowedAgentsHelp")}
>
<AgentSelector
onChange={(val: any) => form.setFieldValue("allowed_agents_and_groups", val)}
value={form.getFieldValue("allowed_agents_and_groups")}
accessToken={accessToken || ""}
placeholder="Select agents or access groups (optional)"
placeholder={t("oldTeams.form.allowedAgentsPlaceholder")}
/>
</Form.Item>
</AccordionBody>
@ -1549,27 +1542,27 @@ const Teams: React.FC<TeamProps> = ({
<Accordion className="mt-8 mb-8">
<AccordionHeader>
<b>Search Tool Settings</b>
<b>{t("oldTeams.accordions.searchToolSettings")}</b>
</AccordionHeader>
<AccordionBody>
<Form.Item
label={
<span>
Allowed Search Tools{" "}
<Tooltip title="Select which search tools this team can access. Leave empty to allow all search tools.">
{t("oldTeams.form.allowedSearchTools")}{" "}
<Tooltip title={t("oldTeams.form.allowedSearchToolsTooltip")}>
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
</Tooltip>
</span>
}
name="object_permission_search_tools"
className="mt-4"
help="Restrict which configured search tools keys on this team may call."
help={t("oldTeams.form.allowedSearchToolsHelp")}
>
<SearchToolSelector
onChange={(vals: string[]) => form.setFieldValue("object_permission_search_tools", vals)}
value={form.getFieldValue("object_permission_search_tools")}
accessToken={accessToken || ""}
placeholder="Select search tools (optional, empty = all allowed)"
placeholder={t("oldTeams.form.allowedSearchToolsPlaceholder")}
/>
</Form.Item>
</AccordionBody>
@ -1577,7 +1570,7 @@ const Teams: React.FC<TeamProps> = ({
<Accordion className="mt-8 mb-8">
<AccordionHeader>
<b>Logging Settings</b>
<b>{t("oldTeams.accordions.loggingSettings")}</b>
</AccordionHeader>
<AccordionBody>
<div className="mt-4">
@ -1592,7 +1585,7 @@ const Teams: React.FC<TeamProps> = ({
<Accordion key={`router-settings-accordion-${routerSettingsKey}`} className="mt-8 mb-8">
<AccordionHeader>
<b>Router Settings</b>
<b>{t("oldTeams.accordions.routerSettings")}</b>
</AccordionHeader>
<AccordionBody>
<div className="mt-4 w-full">
@ -1611,13 +1604,12 @@ const Teams: React.FC<TeamProps> = ({
<Accordion className="mt-8 mb-8">
<AccordionHeader>
<b>Model Aliases</b>
<b>{t("oldTeams.accordions.modelAliases")}</b>
</AccordionHeader>
<AccordionBody>
<div className="mt-4">
<Text type="secondary" style={{ fontSize: 14, marginBottom: 16, display: "block" }}>
Create custom aliases for models that can be used by team members in API calls. This allows you to
create shortcuts for specific models.
{t("oldTeams.form.modelAliasesDescription")}
</Text>
<ModelAliasManager
accessToken={accessToken || ""}
@ -1631,7 +1623,7 @@ const Teams: React.FC<TeamProps> = ({
</>
<div style={{ textAlign: "right", marginTop: "10px" }}>
<Button htmlType="submit" data-testid="create-team-submit">
Create Team
{t("oldTeams.createTeam")}
</Button>
</div>
</Form>

View file

@ -571,6 +571,147 @@
"helpIconAriaLabel": "Help information",
"docsMenuLabel": "Docs"
},
"oldTeams": {
"failedToFetchTeams": "Failed to fetch teams",
"failedToLoadTeams": "Failed to load teams",
"teamDeletedSuccess": "Team deleted successfully",
"creatingTeam": "Creating Team",
"teamCreatedSuccess": "Team created",
"noTeamsYet": "No teams yet",
"createFirstTeamHint": "Create your first team to organize members and manage access to models.",
"createTeam": "Create Team",
"searchPlaceholder": "Search teams by name or ID...",
"totalTeams_one": "{{count}} team",
"totalTeams_other": "{{count}} teams",
"unlimited": "Unlimited",
"pageTitle": "Teams",
"pageSubtitle": "Manage teams, members, and their access to models and budgets",
"columns": {
"teamId": "Team ID",
"teamAlias": "Team Alias",
"organization": "Organization",
"resources": "Resources",
"spendBudget": "Spend / Budget",
"created": "Created"
},
"resources": {
"members_one": "{{count}} Member",
"members_other": "{{count}} Members",
"models_one": "{{count}} Model",
"models_other": "{{count}} Models",
"keys_one": "{{count}} Key",
"keys_other": "{{count}} Keys"
},
"actions": {
"copyTeamId": "Copy Team ID",
"teamIdCopied": "Team ID copied",
"failedToCopy": "Failed to copy",
"editTeam": "Edit team",
"deleteTeam": "Delete team"
},
"tabs": {
"yourTeams": "Your Teams",
"availableTeams": "Available Teams",
"defaultTeamSettings": "Default Team Settings"
},
"deleteModal": {
"title": "Delete Team?",
"alertMessage": "Warning: This team has {{count}} keys associated with it. Deleting the team will also delete all associated keys. This action is irreversible.",
"message": "Are you sure you want to delete this team and all its keys? This action cannot be undone.",
"resourceInformationTitle": "Team Information",
"labelTeamId": "Team ID",
"labelTeamName": "Team Name",
"labelKeys": "Keys",
"labelMembers": "Members"
},
"form": {
"teamName": "Team Name",
"teamNameRequired": "Please input a team name",
"organization": "Organization",
"organizationTooltipPrefix": "Organizations can have multiple teams. Learn more about",
"userManagementHierarchy": "user management hierarchy",
"organizationRequired": "Please select an organization",
"singleOrgHelp": "You can only create teams within this organization",
"noOrganizationsAvailable": "No organizations available",
"searchOrSelectOrganization": "Search or select an Organization",
"selectOrgAdminHint": "Please select an organization to create a team for. You can only create teams within organizations where you are an admin.",
"models": "Models",
"modelsTooltip": "These are the models that your selected team has access to",
"modelsRequired": "Please select at least one model",
"maxBudget": "Max Budget (USD)",
"resetBudget": "Reset Budget",
"budgetDaily": "daily",
"budgetWeekly": "weekly",
"budgetMonthly": "monthly",
"tpmLimit": "Tokens per minute Limit (TPM)",
"rpmLimit": "Requests per minute Limit (RPM)",
"teamId": "Team ID",
"teamIdHelp": "ID of the team you want to create. If not provided, it will be generated automatically.",
"teamMemberBudget": "Team Member Budget (USD)",
"teamMemberBudgetTooltip": "This is the individual budget for a user in the team.",
"teamMemberKeyDuration": "Team Member Key Duration (eg: 1d, 1mo)",
"teamMemberKeyDurationTooltip": "Set a limit to the duration of a team member's key. Format: 30s (seconds), 30m (minutes), 30h (hours), 30d (days), 1mo (month)",
"teamMemberKeyDurationPlaceholder": "e.g., 30d",
"teamMemberRpmLimit": "Team Member RPM Limit",
"teamMemberRpmLimitTooltip": "The RPM (Requests Per Minute) limit for individual team members",
"teamMemberTpmLimit": "Team Member TPM Limit",
"teamMemberTpmLimitTooltip": "The TPM (Tokens Per Minute) limit for individual team members",
"metadata": "Metadata",
"metadataHelp": "Additional team metadata. Enter metadata as JSON object.",
"secretManagerSettings": "Secret Manager Settings",
"secretManagerSettingsHelp": "Enter secret manager configuration as a JSON object.",
"secretManagerSettingsPremiumHelp": "Premium feature - Upgrade to manage secret manager settings.",
"invalidJson": "Please enter valid JSON",
"guardrails": "Guardrails",
"guardrailsTooltip": "Setup your first guardrail",
"guardrailsHelp": "Select existing guardrails or enter new ones",
"guardrailsPlaceholder": "Select or enter guardrails",
"disableGlobalGuardrails": "Disable Global Guardrails",
"disableGlobalGuardrailsTooltip": "When enabled, this team will bypass any guardrails configured to run on every request (global guardrails)",
"disableGlobalGuardrailsHelp": "Bypass global guardrails for this team",
"disableGlobalGuardrailsPremium": "Premium feature - Upgrade to disable global guardrails by team",
"policies": "Policies",
"policiesTooltip": "Apply policies to this team to control guardrails and other settings",
"policiesHelp": "Select existing policies or enter new ones",
"policiesPlaceholder": "Select or enter policies",
"accessGroups": "Access Groups",
"accessGroupsTooltip": "Assign access groups to this team. Access groups control which models, MCP servers, and agents this team can use",
"accessGroupsHelp": "Select access groups to assign to this team",
"accessGroupsPlaceholder": "Select access groups (optional)",
"allowedVectorStores": "Allowed Vector Stores",
"allowedVectorStoresTooltip": "Select which vector stores this team can access by default. Leave empty for access to all vector stores",
"allowedVectorStoresHelp": "Select vector stores this team can access. Leave empty for access to all vector stores",
"allowedVectorStoresPlaceholder": "Select vector stores (optional)",
"allowedPassThroughRoutes": "Allowed Pass Through Routes",
"allowedPassThroughRoutesPremiumTooltip": "Premium feature - Upgrade to set allowed pass through routes",
"allowedPassThroughRoutesAdminTooltip": "Only proxy admins can set allowed pass through routes",
"allowedPassThroughRoutesPlaceholder": "Select pass through routes (optional)",
"allowedMcpServers": "Allowed MCP Servers",
"allowedMcpServersTooltip": "Select which MCP servers or access groups this team can access",
"allowedMcpServersHelp": "Select MCP servers or access groups this team can access",
"allowedMcpServersPlaceholder": "Select MCP servers or access groups (optional)",
"allowedAgents": "Allowed Agents",
"allowedAgentsTooltip": "Select which agents or access groups this team can access",
"allowedAgentsHelp": "Select agents or access groups this team can access",
"allowedAgentsPlaceholder": "Select agents or access groups (optional)",
"allowedSearchTools": "Allowed Search Tools",
"allowedSearchToolsTooltip": "Select which search tools this team can access. Leave empty to allow all search tools.",
"allowedSearchToolsHelp": "Restrict which configured search tools keys on this team may call.",
"allowedSearchToolsPlaceholder": "Select search tools (optional, empty = all allowed)",
"modelAliasesDescription": "Create custom aliases for models that can be used by team members in API calls. This allows you to create shortcuts for specific models."
},
"accordions": {
"additionalSettings": "Additional Settings",
"mcpSettings": "MCP Settings",
"agentSettings": "Agent Settings",
"searchToolSettings": "Search Tool Settings",
"loggingSettings": "Logging Settings",
"routerSettings": "Router Settings",
"modelAliases": "Model Aliases"
},
"deleteTeamFailed": "Error deleting the team: {{error}}",
"createTeamFailed": "Error creating the team: {{error}}"
},
"common": {
"save": "Save",
"cancel": "Cancel",

View file

@ -571,6 +571,147 @@
"helpIconAriaLabel": "帮助信息",
"docsMenuLabel": "文档"
},
"oldTeams": {
"failedToFetchTeams": "获取团队失败",
"failedToLoadTeams": "加载团队失败",
"teamDeletedSuccess": "团队已成功删除",
"creatingTeam": "正在创建团队",
"teamCreatedSuccess": "团队已创建",
"noTeamsYet": "暂无团队",
"createFirstTeamHint": "创建您的第一个团队,以便组织成员并管理对模型的访问权限。",
"createTeam": "创建团队",
"searchPlaceholder": "按名称或 ID 搜索团队...",
"totalTeams_one": "{{count}} 个团队",
"totalTeams_other": "{{count}} 个团队",
"unlimited": "无限制",
"pageTitle": "团队",
"pageSubtitle": "管理团队、成员及其对模型和预算的访问权限",
"columns": {
"teamId": "团队 ID",
"teamAlias": "团队别名",
"organization": "组织",
"resources": "资源",
"spendBudget": "花费 / 预算",
"created": "创建时间"
},
"resources": {
"members_one": "{{count}} 位成员",
"members_other": "{{count}} 位成员",
"models_one": "{{count}} 个模型",
"models_other": "{{count}} 个模型",
"keys_one": "{{count}} 个密钥",
"keys_other": "{{count}} 个密钥"
},
"actions": {
"copyTeamId": "复制团队 ID",
"teamIdCopied": "团队 ID 已复制",
"failedToCopy": "复制失败",
"editTeam": "编辑团队",
"deleteTeam": "删除团队"
},
"tabs": {
"yourTeams": "我的团队",
"availableTeams": "可用团队",
"defaultTeamSettings": "默认团队设置"
},
"deleteModal": {
"title": "删除团队?",
"alertMessage": "警告:此团队有 {{count}} 个关联密钥。删除团队将同时删除所有关联密钥,此操作不可逆。",
"message": "确定要删除此团队及其所有密钥吗?此操作无法撤销。",
"resourceInformationTitle": "团队信息",
"labelTeamId": "团队 ID",
"labelTeamName": "团队名称",
"labelKeys": "密钥",
"labelMembers": "成员"
},
"form": {
"teamName": "团队名称",
"teamNameRequired": "请输入团队名称",
"organization": "组织",
"organizationTooltipPrefix": "组织可以有多个团队。了解更多关于",
"userManagementHierarchy": "用户管理层级",
"organizationRequired": "请选择一个组织",
"singleOrgHelp": "您只能在此组织内创建团队",
"noOrganizationsAvailable": "暂无可用组织",
"searchOrSelectOrganization": "搜索或选择组织",
"selectOrgAdminHint": "请选择要在其中创建团队的组织。您只能在您担任管理员的组织中创建团队。",
"models": "模型",
"modelsTooltip": "这些是您所选团队可以访问的模型",
"modelsRequired": "请至少选择一个模型",
"maxBudget": "最大预算(USD)",
"resetBudget": "重置预算",
"budgetDaily": "每天",
"budgetWeekly": "每周",
"budgetMonthly": "每月",
"tpmLimit": "每分钟 Token 限制(TPM)",
"rpmLimit": "每分钟请求限制(RPM)",
"teamId": "团队 ID",
"teamIdHelp": "您要创建的团队 ID。若不填写,将自动生成。",
"teamMemberBudget": "团队成员预算(USD)",
"teamMemberBudgetTooltip": "这是团队中单个用户的独立预算。",
"teamMemberKeyDuration": "团队成员密钥有效期(例如:1d、1mo)",
"teamMemberKeyDurationTooltip": "设置团队成员密钥的有效期限制。格式:30s(秒)、30m(分钟)、30h(小时)、30d(天)、1mo(月)",
"teamMemberKeyDurationPlaceholder": "例如:30d",
"teamMemberRpmLimit": "团队成员 RPM 限制",
"teamMemberRpmLimitTooltip": "单个团队成员的每分钟请求数(RPM)限制",
"teamMemberTpmLimit": "团队成员 TPM 限制",
"teamMemberTpmLimitTooltip": "单个团队成员的每分钟 Token 数(TPM)限制",
"metadata": "元数据",
"metadataHelp": "附加团队元数据。以 JSON 对象格式输入。",
"secretManagerSettings": "密钥管理器设置",
"secretManagerSettingsHelp": "以 JSON 对象格式输入密钥管理器配置。",
"secretManagerSettingsPremiumHelp": "高级功能 - 升级以管理密钥管理器设置。",
"invalidJson": "请输入有效的 JSON",
"guardrails": "护栏",
"guardrailsTooltip": "设置您的第一个护栏",
"guardrailsHelp": "选择现有护栏或输入新的护栏",
"guardrailsPlaceholder": "选择或输入护栏",
"disableGlobalGuardrails": "禁用全局护栏",
"disableGlobalGuardrailsTooltip": "启用后,此团队将绕过配置为在每次请求上运行的护栏(全局护栏)",
"disableGlobalGuardrailsHelp": "为此团队绕过全局护栏",
"disableGlobalGuardrailsPremium": "高级功能 - 升级以按团队禁用全局护栏",
"policies": "策略",
"policiesTooltip": "为此团队应用策略以控制护栏和其他设置",
"policiesHelp": "选择现有策略或输入新的策略",
"policiesPlaceholder": "选择或输入策略",
"accessGroups": "访问组",
"accessGroupsTooltip": "为此团队分配访问组。访问组控制该团队可以使用的模型、MCP 服务器和 Agent",
"accessGroupsHelp": "选择要分配给此团队的访问组",
"accessGroupsPlaceholder": "选择访问组(可选)",
"allowedVectorStores": "允许的向量存储",
"allowedVectorStoresTooltip": "选择此团队默认可以访问的向量存储。留空则允许访问所有向量存储",
"allowedVectorStoresHelp": "选择此团队可以访问的向量存储。留空则允许访问所有向量存储",
"allowedVectorStoresPlaceholder": "选择向量存储(可选)",
"allowedPassThroughRoutes": "允许的透传路由",
"allowedPassThroughRoutesPremiumTooltip": "高级功能 - 升级以设置允许的透传路由",
"allowedPassThroughRoutesAdminTooltip": "只有代理管理员可以设置允许的透传路由",
"allowedPassThroughRoutesPlaceholder": "选择透传路由(可选)",
"allowedMcpServers": "允许的 MCP 服务器",
"allowedMcpServersTooltip": "选择此团队可以访问的 MCP 服务器或访问组",
"allowedMcpServersHelp": "选择此团队可以访问的 MCP 服务器或访问组",
"allowedMcpServersPlaceholder": "选择 MCP 服务器或访问组(可选)",
"allowedAgents": "允许的 Agent",
"allowedAgentsTooltip": "选择此团队可以访问的 Agent 或访问组",
"allowedAgentsHelp": "选择此团队可以访问的 Agent 或访问组",
"allowedAgentsPlaceholder": "选择 Agent 或访问组(可选)",
"allowedSearchTools": "允许的搜索工具",
"allowedSearchToolsTooltip": "选择此团队可以访问的搜索工具。留空则允许使用所有搜索工具。",
"allowedSearchToolsHelp": "限制此团队上的密钥可以调用的已配置搜索工具。",
"allowedSearchToolsPlaceholder": "选择搜索工具(可选,留空 = 允许所有)",
"modelAliasesDescription": "为模型创建自定义别名,供团队成员在 API 调用中使用。这样您可以为特定模型创建快捷方式。"
},
"accordions": {
"additionalSettings": "附加设置",
"mcpSettings": "MCP 设置",
"agentSettings": "Agent 设置",
"searchToolSettings": "搜索工具设置",
"loggingSettings": "日志设置",
"routerSettings": "路由设置",
"modelAliases": "模型别名"
},
"deleteTeamFailed": "删除团队失败:{{error}}",
"createTeamFailed": "创建团队失败:{{error}}"
},
"common": {
"save": "保存",
"cancel": "取消",