mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): i18n for top-level components batch 1
This commit is contained in:
parent
bd289dc5e8
commit
867abf24f9
8 changed files with 543 additions and 160 deletions
|
|
@ -16,6 +16,7 @@ import {
|
|||
} from "@tremor/react";
|
||||
import { Alert, Button as Button2, Form, Input, Modal, Space, Tabs, Typography } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import NewBadge from "./common_components/NewBadge";
|
||||
import { useBaseUrl } from "./constants";
|
||||
import NotificationsManager from "./molecules/notifications_manager";
|
||||
|
|
@ -35,6 +36,7 @@ interface AdminPanelProps {
|
|||
}
|
||||
|
||||
const AdminPanel: React.FC<AdminPanelProps> = ({ proxySettings }) => {
|
||||
const { t } = useTranslation();
|
||||
const { premiumUser, accessToken, userId: userID } = useAuthorized();
|
||||
const [form] = Form.useForm();
|
||||
const [isAddSSOModalVisible, setIsAddSSOModalVisible] = useState(false);
|
||||
|
|
@ -48,7 +50,7 @@ const AdminPanel: React.FC<AdminPanelProps> = ({ proxySettings }) => {
|
|||
const [ssoConfigured, setSsoConfigured] = useState<boolean>(false);
|
||||
|
||||
const baseUrl = useBaseUrl();
|
||||
const all_ip_address_allowed = "All IP Addresses Allowed";
|
||||
const all_ip_address_allowed = t("adminPanel.allIPAddressesAllowed");
|
||||
|
||||
let nonSssoUrl = baseUrl;
|
||||
nonSssoUrl += "/fallback/login";
|
||||
|
|
@ -77,9 +79,7 @@ const AdminPanel: React.FC<AdminPanelProps> = ({ proxySettings }) => {
|
|||
const handleShowAllowedIPs = async () => {
|
||||
try {
|
||||
if (premiumUser !== true) {
|
||||
NotificationsManager.fromBackend(
|
||||
"This feature is only available for premium users. Please upgrade your account.",
|
||||
);
|
||||
NotificationsManager.fromBackend(t("adminPanel.notifications.premiumOnlyFeature"));
|
||||
return;
|
||||
}
|
||||
if (accessToken) {
|
||||
|
|
@ -90,7 +90,7 @@ const AdminPanel: React.FC<AdminPanelProps> = ({ proxySettings }) => {
|
|||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching allowed IPs:", error);
|
||||
NotificationsManager.fromBackend(`Failed to fetch allowed IPs ${error}`);
|
||||
NotificationsManager.fromBackend(t("adminPanel.fetchAllowedIpsFailed", { error }));
|
||||
setAllowedIPs([all_ip_address_allowed]);
|
||||
} finally {
|
||||
if (premiumUser === true) {
|
||||
|
|
@ -106,11 +106,11 @@ const AdminPanel: React.FC<AdminPanelProps> = ({ proxySettings }) => {
|
|||
// Fetch the updated list of IPs
|
||||
const updatedIPs = await getAllowedIPs(accessToken);
|
||||
setAllowedIPs(updatedIPs);
|
||||
NotificationsManager.success("IP address added successfully");
|
||||
NotificationsManager.success(t("adminPanel.notifications.ipAddedSuccess"));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error adding IP:", error);
|
||||
NotificationsManager.fromBackend(`Failed to add IP address ${error}`);
|
||||
NotificationsManager.fromBackend(t("adminPanel.addIpFailed", { error }));
|
||||
} finally {
|
||||
setIsAddIPModalVisible(false);
|
||||
}
|
||||
|
|
@ -128,10 +128,10 @@ const AdminPanel: React.FC<AdminPanelProps> = ({ proxySettings }) => {
|
|||
// Fetch the updated list of IPs
|
||||
const updatedIPs = await getAllowedIPs(accessToken);
|
||||
setAllowedIPs(updatedIPs.length > 0 ? updatedIPs : [all_ip_address_allowed]);
|
||||
NotificationsManager.success("IP address deleted successfully");
|
||||
NotificationsManager.success(t("adminPanel.notifications.ipDeletedSuccess"));
|
||||
} catch (error) {
|
||||
console.error("Error deleting IP:", error);
|
||||
NotificationsManager.fromBackend(`Failed to delete IP address ${error}`);
|
||||
NotificationsManager.fromBackend(t("adminPanel.deleteIpFailed", { error }));
|
||||
} finally {
|
||||
setIsDeleteIPModalVisible(false);
|
||||
setIPToDelete(null);
|
||||
|
|
@ -186,19 +186,19 @@ const AdminPanel: React.FC<AdminPanelProps> = ({ proxySettings }) => {
|
|||
const tabItems = [
|
||||
{
|
||||
key: "sso-settings",
|
||||
label: "SSO Settings",
|
||||
label: t("adminPanel.tabs.ssoSettings"),
|
||||
children: <SSOSettings />,
|
||||
},
|
||||
{
|
||||
key: "security-settings",
|
||||
label: "Security Settings",
|
||||
label: t("adminPanel.tabs.securitySettings"),
|
||||
children: (
|
||||
<>
|
||||
<Card>
|
||||
<Title level={4}> ✨ Security Settings</Title>
|
||||
<Title level={4}> ✨ {t("adminPanel.securityCard.title")}</Title>
|
||||
<Alert
|
||||
message="SSO Configuration Deprecated"
|
||||
description="Editing SSO Settings on this page is deprecated and will be removed in a future version. Please use the SSO Settings tab for SSO configuration."
|
||||
message={t("adminPanel.securityCard.deprecatedAlertMessage")}
|
||||
description={t("adminPanel.securityCard.deprecatedAlertDescription")}
|
||||
type="warning"
|
||||
showIcon
|
||||
/>
|
||||
|
|
@ -213,12 +213,12 @@ const AdminPanel: React.FC<AdminPanelProps> = ({ proxySettings }) => {
|
|||
>
|
||||
<div>
|
||||
<Button style={{ width: "150px" }} onClick={() => setIsAddSSOModalVisible(true)}>
|
||||
{ssoConfigured ? "Edit SSO Settings" : "Add SSO"}
|
||||
{ssoConfigured ? t("adminPanel.securityCard.editSSOSettings") : t("adminPanel.securityCard.addSSO")}
|
||||
</Button>
|
||||
</div>
|
||||
<div>
|
||||
<Button style={{ width: "150px" }} onClick={handleShowAllowedIPs}>
|
||||
Allowed IPs
|
||||
{t("adminPanel.securityCard.allowedIPs")}
|
||||
</Button>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -227,10 +227,10 @@ const AdminPanel: React.FC<AdminPanelProps> = ({ proxySettings }) => {
|
|||
onClick={() =>
|
||||
premiumUser === true
|
||||
? setIsUIAccessControlModalVisible(true)
|
||||
: NotificationsManager.fromBackend("Only premium users can configure UI access control")
|
||||
: NotificationsManager.fromBackend(t("adminPanel.notifications.premiumOnlyUIAccessControl"))
|
||||
}
|
||||
>
|
||||
UI Access Control
|
||||
{t("adminPanel.securityCard.uiAccessControl")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -250,24 +250,26 @@ const AdminPanel: React.FC<AdminPanelProps> = ({ proxySettings }) => {
|
|||
ssoConfigured={ssoConfigured}
|
||||
/>
|
||||
<Modal
|
||||
title="Manage Allowed IP Addresses"
|
||||
title={t("adminPanel.manageAllowedIPModal.title")}
|
||||
width={800}
|
||||
open={isAllowedIPModalVisible}
|
||||
onCancel={() => setIsAllowedIPModalVisible(false)}
|
||||
footer={[
|
||||
<Button className="mx-1" key="add" onClick={() => setIsAddIPModalVisible(true)}>
|
||||
Add IP Address
|
||||
{t("adminPanel.manageAllowedIPModal.addIPAddress")}
|
||||
</Button>,
|
||||
<Button key="close" onClick={() => setIsAllowedIPModalVisible(false)}>
|
||||
Close
|
||||
{t("common.close")}
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableHeaderCell>IP Address</TableHeaderCell>
|
||||
<TableHeaderCell className="text-right">Action</TableHeaderCell>
|
||||
<TableHeaderCell>{t("adminPanel.manageAllowedIPModal.ipAddressColumn")}</TableHeaderCell>
|
||||
<TableHeaderCell className="text-right">
|
||||
{t("adminPanel.manageAllowedIPModal.actionColumn")}
|
||||
</TableHeaderCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
|
|
@ -277,7 +279,7 @@ const AdminPanel: React.FC<AdminPanelProps> = ({ proxySettings }) => {
|
|||
<TableCell className="text-right">
|
||||
{ip !== all_ip_address_allowed && (
|
||||
<Button onClick={() => handleDeleteIP(ip)} color="red" size="xs">
|
||||
Delete
|
||||
{t("common.delete")}
|
||||
</Button>
|
||||
)}
|
||||
</TableCell>
|
||||
|
|
@ -288,41 +290,41 @@ const AdminPanel: React.FC<AdminPanelProps> = ({ proxySettings }) => {
|
|||
</Modal>
|
||||
|
||||
<Modal
|
||||
title="Add Allowed IP Address"
|
||||
title={t("adminPanel.addIPModal.title")}
|
||||
open={isAddIPModalVisible}
|
||||
onCancel={() => setIsAddIPModalVisible(false)}
|
||||
footer={null}
|
||||
>
|
||||
<Form onFinish={handleAddIP}>
|
||||
<Form.Item name="ip" rules={[{ required: true, message: "Please enter an IP address" }]}>
|
||||
<Input placeholder="Enter IP address" />
|
||||
<Form.Item name="ip" rules={[{ required: true, message: t("adminPanel.addIPModal.inputRequired") }]}>
|
||||
<Input placeholder={t("adminPanel.addIPModal.inputPlaceholder")} />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Button2 htmlType="submit">Add IP Address</Button2>
|
||||
<Button2 htmlType="submit">{t("adminPanel.addIPModal.submitButton")}</Button2>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title="Confirm Delete"
|
||||
title={t("adminPanel.deleteIPModal.title")}
|
||||
open={isDeleteIPModalVisible}
|
||||
onCancel={() => setIsDeleteIPModalVisible(false)}
|
||||
onOk={confirmDeleteIP}
|
||||
footer={[
|
||||
<Button className="mx-1" key="delete" onClick={() => confirmDeleteIP()}>
|
||||
Yes
|
||||
{t("common.yes")}
|
||||
</Button>,
|
||||
<Button key="close" onClick={() => setIsDeleteIPModalVisible(false)}>
|
||||
Close
|
||||
{t("common.close")}
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<Text>Are you sure you want to delete the IP address: {ipToDelete}?</Text>
|
||||
<Text>{t("adminPanel.deleteIPModal.confirmText", { ip: ipToDelete })}</Text>
|
||||
</Modal>
|
||||
|
||||
{/* UI Access Control Modal */}
|
||||
<Modal
|
||||
title="UI Access Control Settings"
|
||||
title={t("adminPanel.uiAccessControlModal.title")}
|
||||
open={isUIAccessControlModalVisible}
|
||||
width={600}
|
||||
footer={null}
|
||||
|
|
@ -333,13 +335,13 @@ const AdminPanel: React.FC<AdminPanelProps> = ({ proxySettings }) => {
|
|||
accessToken={accessToken}
|
||||
onSuccess={() => {
|
||||
handleUIAccessControlOk();
|
||||
NotificationsManager.success("UI Access Control settings updated successfully");
|
||||
NotificationsManager.success(t("adminPanel.uiAccessControlModal.updateSuccess"));
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
</div>
|
||||
<Callout title="Login without SSO" color="teal">
|
||||
If you need to login without sso, you can access{" "}
|
||||
<Callout title={t("adminPanel.loginWithoutSSO.title")} color="teal">
|
||||
{t("adminPanel.loginWithoutSSO.description")}{" "}
|
||||
<a href={nonSssoUrl} target="_blank" rel="noopener noreferrer">
|
||||
<b>{nonSssoUrl}</b>{" "}
|
||||
</a>
|
||||
|
|
@ -349,7 +351,7 @@ const AdminPanel: React.FC<AdminPanelProps> = ({ proxySettings }) => {
|
|||
},
|
||||
{
|
||||
key: "scim",
|
||||
label: "SCIM",
|
||||
label: t("adminPanel.tabs.scim"),
|
||||
children: <SCIMConfig accessToken={accessToken} userID={userID} proxySettings={proxySettings} />,
|
||||
},
|
||||
{
|
||||
|
|
@ -357,7 +359,7 @@ const AdminPanel: React.FC<AdminPanelProps> = ({ proxySettings }) => {
|
|||
label: (
|
||||
<Space>
|
||||
<Text>
|
||||
UI Settings <NewBadge />
|
||||
{t("adminPanel.tabs.uiSettings")} <NewBadge />
|
||||
</Text>
|
||||
</Space>
|
||||
),
|
||||
|
|
@ -365,20 +367,20 @@ const AdminPanel: React.FC<AdminPanelProps> = ({ proxySettings }) => {
|
|||
},
|
||||
{
|
||||
key: "logging-settings",
|
||||
label: "Logging Settings",
|
||||
label: t("adminPanel.tabs.loggingSettings"),
|
||||
children: <LoggingSettings />,
|
||||
},
|
||||
{
|
||||
key: "hashicorp-vault",
|
||||
label: "Hashicorp Vault",
|
||||
label: t("adminPanel.tabs.hashicorpVault"),
|
||||
children: <HashicorpVault />,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="w-full m-2 mt-2 p-8">
|
||||
<Title level={4}>Admin Access </Title>
|
||||
<Paragraph>Go to 'Internal Users' page to add other admins.</Paragraph>
|
||||
<Title level={4}>{t("adminPanel.title")} </Title>
|
||||
<Paragraph>{t("adminPanel.goToInternalUsers")}</Paragraph>
|
||||
<Tabs items={tabItems} />
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { userBulkUpdateUserCall, teamBulkMemberAddCall, Member } from "./network
|
|||
import { UserEditView } from "./user_edit_view";
|
||||
import NotificationsManager from "./molecules/notifications_manager";
|
||||
import MessageManager from "@/components/molecules/message_manager";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const { Text, Title } = Typography;
|
||||
|
||||
|
|
@ -32,6 +33,7 @@ const BulkEditUserModal: React.FC<BulkEditUserModalProps> = ({
|
|||
userModels,
|
||||
allowAllUsers = false,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [selectedTeams, setSelectedTeams] = useState<string[]>([]);
|
||||
const [teamBudget, setTeamBudget] = useState<number | null>(null);
|
||||
|
|
@ -71,7 +73,7 @@ const BulkEditUserModal: React.FC<BulkEditUserModalProps> = ({
|
|||
const handleSubmit = async (formValues: any) => {
|
||||
console.log("formValues", formValues);
|
||||
if (!accessToken) {
|
||||
NotificationsManager.fromBackend("Access token not found");
|
||||
NotificationsManager.fromBackend(t("bulkEditUsers.notifications.accessTokenNotFound"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -107,7 +109,7 @@ const BulkEditUserModal: React.FC<BulkEditUserModalProps> = ({
|
|||
const hasTeamAdditions = addToTeams && selectedTeams.length > 0;
|
||||
|
||||
if (!hasUserUpdates && !hasTeamAdditions) {
|
||||
NotificationsManager.fromBackend("Please modify at least one field or select teams to add users to");
|
||||
NotificationsManager.fromBackend(t("bulkEditUsers.notifications.noFieldsModified"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -117,10 +119,10 @@ const BulkEditUserModal: React.FC<BulkEditUserModalProps> = ({
|
|||
if (hasUserUpdates) {
|
||||
if (updateAllUsers) {
|
||||
const result = await userBulkUpdateUserCall(accessToken, updatePayload, undefined, true);
|
||||
successMessages.push(`Updated all users (${result.total_requested} total)`);
|
||||
successMessages.push(t("bulkEditUsers.notifications.updatedAllUsers", { total: result.total_requested }));
|
||||
} else {
|
||||
await userBulkUpdateUserCall(accessToken, updatePayload, userIds);
|
||||
successMessages.push(`Updated ${userIds.length} user(s)`);
|
||||
successMessages.push(t("bulkEditUsers.notifications.updatedUsers", { count: userIds.length }));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -174,11 +176,13 @@ const BulkEditUserModal: React.FC<BulkEditUserModalProps> = ({
|
|||
|
||||
if (successfulTeams.length > 0) {
|
||||
const totalAdditions = successfulTeams.reduce((sum, r) => sum + r.successfulAdditions, 0);
|
||||
successMessages.push(`Added users to ${successfulTeams.length} team(s) (${totalAdditions} total additions)`);
|
||||
successMessages.push(
|
||||
t("bulkEditUsers.notifications.addedToTeams", { count: successfulTeams.length, total: totalAdditions }),
|
||||
);
|
||||
}
|
||||
|
||||
if (failedTeams.length > 0) {
|
||||
MessageManager.warning(`Failed to add users to ${failedTeams.length} team(s)`);
|
||||
MessageManager.warning(t("bulkEditUsers.notifications.failedToAddToTeams", { count: failedTeams.length }));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -196,7 +200,7 @@ const BulkEditUserModal: React.FC<BulkEditUserModalProps> = ({
|
|||
onCancel();
|
||||
} catch (error) {
|
||||
console.error("Bulk operation failed:", error);
|
||||
NotificationsManager.fromBackend("Failed to perform bulk operations");
|
||||
NotificationsManager.fromBackend(t("bulkEditUsers.notifications.bulkOpFailed"));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -207,18 +211,22 @@ const BulkEditUserModal: React.FC<BulkEditUserModalProps> = ({
|
|||
open={open}
|
||||
onCancel={handleCancel}
|
||||
footer={null}
|
||||
title={updateAllUsers ? "Bulk Edit All Users" : `Bulk Edit ${selectedUsers.length} User(s)`}
|
||||
title={
|
||||
updateAllUsers
|
||||
? t("bulkEditUsers.titleAllUsers")
|
||||
: t("bulkEditUsers.titleSelectedUsers", { count: selectedUsers.length })
|
||||
}
|
||||
width={800}
|
||||
>
|
||||
{allowAllUsers && (
|
||||
<div className="mb-4">
|
||||
<Checkbox checked={updateAllUsers} onChange={(e) => setUpdateAllUsers(e.target.checked)}>
|
||||
<Text strong>Update ALL users in the system</Text>
|
||||
<Text strong>{t("bulkEditUsers.updateAllUsersLabel")}</Text>
|
||||
</Checkbox>
|
||||
{updateAllUsers && (
|
||||
<div style={{ marginTop: 8 }}>
|
||||
<Text type="warning" style={{ fontSize: "12px" }}>
|
||||
⚠️ This will apply changes to ALL users in the system, not just the selected ones.
|
||||
⚠️ {t("bulkEditUsers.updateAllUsersWarning")}
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -227,7 +235,7 @@ const BulkEditUserModal: React.FC<BulkEditUserModalProps> = ({
|
|||
|
||||
{!updateAllUsers && (
|
||||
<div className="mb-4">
|
||||
<Title level={5}>Selected Users ({selectedUsers.length}):</Title>
|
||||
<Title level={5}>{t("bulkEditUsers.selectedUsersTitle", { count: selectedUsers.length })}</Title>
|
||||
<Table
|
||||
size="small"
|
||||
bordered
|
||||
|
|
@ -237,7 +245,7 @@ const BulkEditUserModal: React.FC<BulkEditUserModalProps> = ({
|
|||
rowKey="user_id"
|
||||
columns={[
|
||||
{
|
||||
title: "User ID",
|
||||
title: t("bulkEditUsers.columns.userId"),
|
||||
dataIndex: "user_id",
|
||||
key: "user_id",
|
||||
width: "30%",
|
||||
|
|
@ -248,18 +256,18 @@ const BulkEditUserModal: React.FC<BulkEditUserModalProps> = ({
|
|||
),
|
||||
},
|
||||
{
|
||||
title: "Email",
|
||||
title: t("bulkEditUsers.columns.email"),
|
||||
dataIndex: "user_email",
|
||||
key: "user_email",
|
||||
width: "25%",
|
||||
render: (text: string) => (
|
||||
<Text type="secondary" style={{ fontSize: "12px" }}>
|
||||
{text || "No email"}
|
||||
{text || t("bulkEditUsers.noEmail")}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Current Role",
|
||||
title: t("bulkEditUsers.columns.currentRole"),
|
||||
dataIndex: "user_role",
|
||||
key: "user_role",
|
||||
width: "25%",
|
||||
|
|
@ -268,12 +276,14 @@ const BulkEditUserModal: React.FC<BulkEditUserModalProps> = ({
|
|||
),
|
||||
},
|
||||
{
|
||||
title: "Budget",
|
||||
title: t("bulkEditUsers.columns.budget"),
|
||||
dataIndex: "max_budget",
|
||||
key: "max_budget",
|
||||
width: "20%",
|
||||
render: (budget: number | null) => (
|
||||
<Text style={{ fontSize: "12px" }}>{budget !== null ? `$${budget}` : "Unlimited"}</Text>
|
||||
<Text style={{ fontSize: "12px" }}>
|
||||
{budget !== null ? `$${budget}` : t("bulkEditUsers.unlimited")}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
]}
|
||||
|
|
@ -285,25 +295,29 @@ const BulkEditUserModal: React.FC<BulkEditUserModalProps> = ({
|
|||
|
||||
<div className="mb-4">
|
||||
<Text>
|
||||
<strong>Instructions:</strong> Fill in the fields below with the values you want to apply to all selected
|
||||
users. You can bulk edit: role, budget, models, and metadata. You can also add users to teams.
|
||||
<strong>{t("bulkEditUsers.instructionsLabel")}</strong> {t("bulkEditUsers.instructions")}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
{/* Team Management Section */}
|
||||
<Card title="Team Management" size="small" className="mb-4" style={{ backgroundColor: "#fafafa" }}>
|
||||
<Card
|
||||
title={t("bulkEditUsers.teamManagement.sectionTitle")}
|
||||
size="small"
|
||||
className="mb-4"
|
||||
style={{ backgroundColor: "#fafafa" }}
|
||||
>
|
||||
<Space direction="vertical" style={{ width: "100%" }}>
|
||||
<Checkbox checked={addToTeams} onChange={(e) => setAddToTeams(e.target.checked)}>
|
||||
Add selected users to teams
|
||||
{t("bulkEditUsers.teamManagement.addToTeamsLabel")}
|
||||
</Checkbox>
|
||||
|
||||
{addToTeams && (
|
||||
<>
|
||||
<div>
|
||||
<Text strong>Select Teams:</Text>
|
||||
<Text strong>{t("bulkEditUsers.teamManagement.selectTeamsLabel")}</Text>
|
||||
<Select
|
||||
mode="multiple"
|
||||
placeholder="Select teams to add users to"
|
||||
placeholder={t("bulkEditUsers.teamManagement.selectTeamsPlaceholder")}
|
||||
value={selectedTeams}
|
||||
onChange={setSelectedTeams}
|
||||
style={{ width: "100%", marginTop: 8 }}
|
||||
|
|
@ -317,9 +331,9 @@ const BulkEditUserModal: React.FC<BulkEditUserModalProps> = ({
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<Text strong>Team Budget (Optional):</Text>
|
||||
<Text strong>{t("bulkEditUsers.teamManagement.teamBudgetLabel")}</Text>
|
||||
<InputNumber
|
||||
placeholder="Max budget per user in team"
|
||||
placeholder={t("bulkEditUsers.teamManagement.teamBudgetPlaceholder")}
|
||||
value={teamBudget}
|
||||
onChange={(value) => setTeamBudget(value)}
|
||||
style={{ width: "100%", marginTop: 8 }}
|
||||
|
|
@ -328,13 +342,12 @@ const BulkEditUserModal: React.FC<BulkEditUserModalProps> = ({
|
|||
precision={2}
|
||||
/>
|
||||
<Text type="secondary" style={{ fontSize: "12px" }}>
|
||||
Leave empty for unlimited budget within team limits
|
||||
{t("bulkEditUsers.teamManagement.teamBudgetHint")}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<Text type="secondary" style={{ fontSize: "12px" }}>
|
||||
Users will be added with "user" role by default. All users will be added to each selected
|
||||
team.
|
||||
{t("bulkEditUsers.teamManagement.defaultRoleNote")}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
|
|
@ -356,7 +369,11 @@ const BulkEditUserModal: React.FC<BulkEditUserModalProps> = ({
|
|||
|
||||
{loading && (
|
||||
<div style={{ textAlign: "center", marginTop: "10px" }}>
|
||||
<Text>Updating {updateAllUsers ? "all users" : selectedUsers.length} user(s)...</Text>
|
||||
<Text>
|
||||
{updateAllUsers
|
||||
? t("bulkEditUsers.updatingAllUsers")
|
||||
: t("bulkEditUsers.updatingUsers", { count: selectedUsers.length })}
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import {
|
|||
Typography,
|
||||
} from "antd";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import BulkCreateUsers from "./bulk_create_users_button";
|
||||
import TeamDropdown from "./common_components/team_dropdown";
|
||||
import { getModelDisplayName } from "./key_team_helpers/fetch_available_models_team_key";
|
||||
|
|
@ -68,6 +69,7 @@ export const CreateUserButton: React.FC<CreateuserProps> = ({
|
|||
onUserCreated,
|
||||
isEmbedded = false,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const queryClient = useQueryClient();
|
||||
const [uiSettings, setUISettings] = useState<UISettings | null>(null);
|
||||
const [form] = Form.useForm();
|
||||
|
|
@ -128,7 +130,7 @@ export const CreateUserButton: React.FC<CreateuserProps> = ({
|
|||
send_invite_email?: boolean;
|
||||
}) => {
|
||||
try {
|
||||
NotificationsManager.info("Making API Call");
|
||||
NotificationsManager.info(t("createUserButton.notifications.makingApiCall"));
|
||||
if (!isEmbedded) {
|
||||
setIsModalVisible(true);
|
||||
}
|
||||
|
|
@ -175,11 +177,12 @@ export const CreateUserButton: React.FC<CreateuserProps> = ({
|
|||
setIsInvitationLinkModalVisible(true);
|
||||
}
|
||||
|
||||
NotificationsManager.success("API user Created");
|
||||
NotificationsManager.success(t("createUserButton.notifications.userCreated"));
|
||||
form.resetFields();
|
||||
localStorage.removeItem("userData" + userID);
|
||||
} catch (error: any) {
|
||||
const errorMessage = error.response?.data?.detail || error?.message || "Error creating the user";
|
||||
const errorMessage =
|
||||
error.response?.data?.detail || error?.message || t("createUserButton.notifications.errorCreatingUser");
|
||||
NotificationsManager.fromBackend(errorMessage);
|
||||
console.error("Error creating the user:", error);
|
||||
}
|
||||
|
|
@ -197,13 +200,12 @@ export const CreateUserButton: React.FC<CreateuserProps> = ({
|
|||
initialValues={{ user_role: "internal_user_viewer", send_invite_email: true }}
|
||||
>
|
||||
<Alert
|
||||
message="Email invitations"
|
||||
message={t("createUserButton.emailInvitations")}
|
||||
description={
|
||||
<>
|
||||
New users receive an email invite only when an email integration (SMTP, Resend, or SendGrid) is
|
||||
configured.{" "}
|
||||
{t("createUserButton.emailInvitationsDesc")}{" "}
|
||||
<Link href="https://docs.litellm.ai/docs/proxy/email" target="_blank">
|
||||
Learn how to set up email notifications
|
||||
{t("createUserButton.learnHowToSetupEmail")}
|
||||
</Link>
|
||||
</>
|
||||
}
|
||||
|
|
@ -211,10 +213,10 @@ export const CreateUserButton: React.FC<CreateuserProps> = ({
|
|||
showIcon
|
||||
className="mb-4"
|
||||
/>
|
||||
<Form.Item label="User Email" name="user_email">
|
||||
<Form.Item label={t("createUserButton.userEmailLabel")} name="user_email">
|
||||
<TextInput placeholder="" />
|
||||
</Form.Item>
|
||||
<Form.Item label="User Role" name="user_role">
|
||||
<Form.Item label={t("createUserButton.userRoleLabel")} name="user_role">
|
||||
<Select2>
|
||||
{possibleUIRoles &&
|
||||
Object.entries(possibleUIRoles).map(([role, { ui_label, description }]) => (
|
||||
|
|
@ -229,20 +231,20 @@ export const CreateUserButton: React.FC<CreateuserProps> = ({
|
|||
))}
|
||||
</Select2>
|
||||
</Form.Item>
|
||||
<Form.Item label="Team" name="team_id">
|
||||
<Form.Item label={t("createUserButton.teamLabel")} name="team_id">
|
||||
<TeamDropdown />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Metadata" name="metadata">
|
||||
<Input.TextArea rows={4} placeholder="Enter metadata as JSON" />
|
||||
<Form.Item label={t("createUserButton.metadataLabel")} name="metadata">
|
||||
<Input.TextArea rows={4} placeholder={t("createUserButton.metadataPlaceholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Send invitation email" name="send_invite_email" valuePropName="checked">
|
||||
<Form.Item label={t("createUserButton.sendInviteEmailLabel")} name="send_invite_email" valuePropName="checked">
|
||||
<Checkbox />
|
||||
</Form.Item>
|
||||
|
||||
<div style={{ textAlign: "right", marginTop: "10px" }}>
|
||||
<Button htmlType="submit">Create User</Button>
|
||||
<Button htmlType="submit">{t("createUserButton.createUserSubmitButton")}</Button>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
|
|
@ -252,11 +254,11 @@ export const CreateUserButton: React.FC<CreateuserProps> = ({
|
|||
return (
|
||||
<div className="flex gap-2">
|
||||
<Button type="primary" className="mb-0" onClick={() => setIsModalVisible(true)}>
|
||||
+ Invite User
|
||||
{t("createUserButton.inviteUserButton")}
|
||||
</Button>
|
||||
<BulkCreateUsers accessToken={accessToken} teams={teams} possibleUIRoles={possibleUIRoles} />
|
||||
<Modal
|
||||
title="Invite User"
|
||||
title={t("createUserButton.inviteUserModalTitle")}
|
||||
open={isModalVisible}
|
||||
width={800}
|
||||
footer={null}
|
||||
|
|
@ -264,15 +266,14 @@ export const CreateUserButton: React.FC<CreateuserProps> = ({
|
|||
onCancel={handleCancel}
|
||||
>
|
||||
<Space direction="vertical" size="middle">
|
||||
<Text className="mb-1">Create a User who can own keys</Text>
|
||||
<Text className="mb-1">{t("createUserButton.createUserOwnsKeys")}</Text>
|
||||
<Alert
|
||||
message="Email invitations"
|
||||
message={t("createUserButton.emailInvitations")}
|
||||
description={
|
||||
<>
|
||||
New users receive an email invite only when an email integration (SMTP, Resend, or SendGrid) is
|
||||
configured.{" "}
|
||||
{t("createUserButton.emailInvitationsDesc")}{" "}
|
||||
<Link href="https://docs.litellm.ai/docs/proxy/email" target="_blank">
|
||||
Learn how to set up email notifications
|
||||
{t("createUserButton.learnHowToSetupEmail")}
|
||||
</Link>
|
||||
</>
|
||||
}
|
||||
|
|
@ -289,14 +290,14 @@ export const CreateUserButton: React.FC<CreateuserProps> = ({
|
|||
labelAlign="left"
|
||||
initialValues={{ user_role: "internal_user_viewer", send_invite_email: true }}
|
||||
>
|
||||
<Form.Item label="User Email" name="user_email">
|
||||
<Form.Item label={t("createUserButton.userEmailLabel")} name="user_email">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={
|
||||
<span>
|
||||
Global Proxy Role{" "}
|
||||
<Tooltip title="This role is independent of any team/org specific roles. Configure Team / Organization Admins in the Settings">
|
||||
{t("createUserButton.globalProxyRoleLabel")}{" "}
|
||||
<Tooltip title={t("createUserButton.globalProxyRoleTooltip")}>
|
||||
<InfoCircleOutlined />
|
||||
</Tooltip>
|
||||
</span>
|
||||
|
|
@ -318,20 +319,24 @@ export const CreateUserButton: React.FC<CreateuserProps> = ({
|
|||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Team"
|
||||
label={t("createUserButton.teamLabel")}
|
||||
className="gap-2"
|
||||
name="team_id"
|
||||
help="If selected, user will be added as a 'user' role to the team."
|
||||
help={t("createUserButton.teamHelp")}
|
||||
>
|
||||
<TeamDropdown />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Organization"
|
||||
label={t("createUserButton.organizationLabel")}
|
||||
name="organization_ids"
|
||||
help="The user will be added to the selected organization(s)."
|
||||
help={t("createUserButton.organizationHelp")}
|
||||
>
|
||||
<Select mode="multiple" placeholder="Select Organization" style={{ width: "100%" }}>
|
||||
<Select
|
||||
mode="multiple"
|
||||
placeholder={t("createUserButton.selectOrganizationPlaceholder")}
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
{organizations.map((org) => (
|
||||
<Option key={org.organization_id} value={org.organization_id}>
|
||||
{org.organization_alias} ({org.organization_id})
|
||||
|
|
@ -340,36 +345,44 @@ export const CreateUserButton: React.FC<CreateuserProps> = ({
|
|||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Metadata" name="metadata">
|
||||
<Input.TextArea rows={4} placeholder="Enter metadata as JSON" />
|
||||
<Form.Item label={t("createUserButton.metadataLabel")} name="metadata">
|
||||
<Input.TextArea rows={4} placeholder={t("createUserButton.metadataPlaceholder")} />
|
||||
</Form.Item>
|
||||
<Form.Item label="Send invitation email" name="send_invite_email" valuePropName="checked">
|
||||
<Form.Item
|
||||
label={t("createUserButton.sendInviteEmailLabel")}
|
||||
name="send_invite_email"
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Checkbox />
|
||||
</Form.Item>
|
||||
<Accordion>
|
||||
<AccordionHeader>
|
||||
<Text strong>Personal Key Creation</Text>
|
||||
<Text strong>{t("createUserButton.personalKeyCreation")}</Text>
|
||||
</AccordionHeader>
|
||||
<AccordionBody>
|
||||
<Form.Item
|
||||
className="gap-2"
|
||||
label={
|
||||
<span>
|
||||
Models{" "}
|
||||
<Tooltip title="Models user has access to, outside of team scope.">
|
||||
{t("createUserButton.modelsLabel")}{" "}
|
||||
<Tooltip title={t("createUserButton.modelsTooltip")}>
|
||||
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
|
||||
</Tooltip>
|
||||
</span>
|
||||
}
|
||||
name="models"
|
||||
help="Models user has access to, outside of team scope."
|
||||
help={t("createUserButton.modelsHelp")}
|
||||
>
|
||||
<Select2 mode="multiple" placeholder="Select models" style={{ width: "100%" }}>
|
||||
<Select2
|
||||
mode="multiple"
|
||||
placeholder={t("createUserButton.modelsPlaceholder")}
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
<Select2.Option key="all-proxy-models" value="all-proxy-models">
|
||||
All Proxy Models
|
||||
{t("createUserButton.allProxyModels")}
|
||||
</Select2.Option>
|
||||
<Select2.Option key="no-default-models" value="no-default-models">
|
||||
No Default Models
|
||||
{t("createUserButton.noDefaultModels")}
|
||||
</Select2.Option>
|
||||
{userModels.map((model) => (
|
||||
<Select2.Option key={model} value={model}>
|
||||
|
|
@ -383,7 +396,7 @@ export const CreateUserButton: React.FC<CreateuserProps> = ({
|
|||
|
||||
<div style={{ textAlign: "right", marginTop: "10px" }}>
|
||||
<Button type="primary" icon={<UserAddOutlined />} htmlType="submit">
|
||||
Invite User
|
||||
{t("createUserButton.inviteUserSubmitButton")}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
|
|
|
|||
|
|
@ -3,12 +3,14 @@
|
|||
import React from "react";
|
||||
import { Alert } from "antd";
|
||||
import { useHealthReadinessDetails } from "@/app/(dashboard)/hooks/healthReadiness/useHealthReadinessDetails";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
|
||||
interface DebugWarningBannerProps {
|
||||
accessToken: string | null;
|
||||
}
|
||||
|
||||
export const DebugWarningBanner: React.FC<DebugWarningBannerProps> = ({ accessToken }) => {
|
||||
const { t } = useTranslation();
|
||||
const { data: healthData } = useHealthReadinessDetails(accessToken);
|
||||
|
||||
// Only show banner if detailed debug mode is explicitly enabled
|
||||
|
|
@ -18,14 +20,8 @@ export const DebugWarningBanner: React.FC<DebugWarningBannerProps> = ({ accessTo
|
|||
|
||||
return (
|
||||
<Alert
|
||||
message="Performance Warning: Detailed Debug Mode Active"
|
||||
description={
|
||||
<>
|
||||
Detailed debug logging (<code>LITELLM_LOG=DEBUG</code>) is currently enabled. This mode logs extensive
|
||||
diagnostic information and will significantly degrade performance. It should only be used for troubleshooting
|
||||
and disabled in production environments.
|
||||
</>
|
||||
}
|
||||
message={t("debugWarningBanner.message")}
|
||||
description={<Trans i18nKey="debugWarningBanner.description" components={{ code: <code /> }} />}
|
||||
type="warning"
|
||||
showIcon
|
||||
banner
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import BudgetDurationDropdown, { getBudgetDurationLabel } from "./common_compone
|
|||
import { getModelDisplayName } from "./key_team_helpers/fetch_available_models_team_key";
|
||||
import { formatNumberWithCommas } from "@/utils/dataUtils";
|
||||
import NotificationManager from "./molecules/notifications_manager";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface DefaultUserSettingsProps {
|
||||
accessToken: string | null;
|
||||
|
|
@ -27,6 +28,7 @@ const DefaultUserSettings: React.FC<DefaultUserSettingsProps> = ({
|
|||
userID,
|
||||
userRole,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [settings, setSettings] = useState<any>(null);
|
||||
const [isEditing, setIsEditing] = useState<boolean>(false);
|
||||
|
|
@ -62,7 +64,7 @@ const DefaultUserSettings: React.FC<DefaultUserSettingsProps> = ({
|
|||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching SSO settings:", error);
|
||||
NotificationManager.fromBackend("Failed to fetch SSO settings");
|
||||
NotificationManager.fromBackend(t("defaultUserSettings.notifications.fetchFailed"));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -90,7 +92,7 @@ const DefaultUserSettings: React.FC<DefaultUserSettingsProps> = ({
|
|||
setIsEditing(false);
|
||||
} catch (error) {
|
||||
console.error("Error updating SSO settings:", error);
|
||||
NotificationManager.fromBackend("Failed to update settings: " + error);
|
||||
NotificationManager.fromBackend(t("defaultUserSettings.notifications.updateFailed", { error: String(error) }));
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
|
|
@ -158,29 +160,29 @@ const DefaultUserSettings: React.FC<DefaultUserSettingsProps> = ({
|
|||
{normalizedTeams.map((team, index) => (
|
||||
<div key={index} className="border rounded-lg p-4 bg-gray-50">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<Text className="font-medium">Team {index + 1}</Text>
|
||||
<Text className="font-medium">{t("defaultUserSettings.teamEntry.teamLabel", { number: index + 1 })}</Text>
|
||||
<Button size="small" danger icon={<DeleteOutlined />} onClick={() => removeTeam(index)}>
|
||||
Remove
|
||||
{t("defaultUserSettings.teamEntry.removeButton")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
||||
<div>
|
||||
<Text className="text-sm font-medium mb-1">Team ID</Text>
|
||||
<Text className="text-sm font-medium mb-1">{t("defaultUserSettings.teamEntry.teamIdLabel")}</Text>
|
||||
<TextInput
|
||||
value={team.team_id}
|
||||
onChange={(e) => updateTeam(index, "team_id", e.target.value)}
|
||||
placeholder="Enter team ID"
|
||||
placeholder={t("defaultUserSettings.teamEntry.teamIdPlaceholder")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text className="text-sm font-medium mb-1">Max Budget in Team</Text>
|
||||
<Text className="text-sm font-medium mb-1">{t("defaultUserSettings.teamEntry.maxBudgetLabel")}</Text>
|
||||
<InputNumber
|
||||
style={{ width: "100%" }}
|
||||
value={team.max_budget_in_team}
|
||||
onChange={(value) => updateTeam(index, "max_budget_in_team", value)}
|
||||
placeholder="Optional"
|
||||
placeholder={t("defaultUserSettings.teamEntry.maxBudgetPlaceholder")}
|
||||
min={0}
|
||||
step={0.01}
|
||||
precision={2}
|
||||
|
|
@ -188,14 +190,14 @@ const DefaultUserSettings: React.FC<DefaultUserSettingsProps> = ({
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<Text className="text-sm font-medium mb-1">User Role</Text>
|
||||
<Text className="text-sm font-medium mb-1">{t("defaultUserSettings.teamEntry.userRoleLabel")}</Text>
|
||||
<Select
|
||||
style={{ width: "100%" }}
|
||||
value={team.user_role}
|
||||
onChange={(value) => updateTeam(index, "user_role", value)}
|
||||
>
|
||||
<Option value="user">User</Option>
|
||||
<Option value="admin">Admin</Option>
|
||||
<Option value="user">{t("defaultUserSettings.displayValues.userRoleOption")}</Option>
|
||||
<Option value="admin">{t("defaultUserSettings.displayValues.adminRoleOption")}</Option>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -203,7 +205,7 @@ const DefaultUserSettings: React.FC<DefaultUserSettingsProps> = ({
|
|||
))}
|
||||
|
||||
<Button icon={<PlusOutlined />} onClick={addTeam} className="w-full">
|
||||
Add Team
|
||||
{t("defaultUserSettings.teamEntry.addTeamButton")}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -314,10 +316,12 @@ const DefaultUserSettings: React.FC<DefaultUserSettingsProps> = ({
|
|||
};
|
||||
|
||||
const renderValue = (key: string, value: any): JSX.Element => {
|
||||
if (value === null || value === undefined) return <span className="text-gray-400">Not set</span>;
|
||||
if (value === null || value === undefined)
|
||||
return <span className="text-gray-400">{t("defaultUserSettings.displayValues.notSet")}</span>;
|
||||
|
||||
if (key === "teams" && Array.isArray(value)) {
|
||||
if (value.length === 0) return <span className="text-gray-400">No teams assigned</span>;
|
||||
if (value.length === 0)
|
||||
return <span className="text-gray-400">{t("defaultUserSettings.displayValues.noTeamsAssigned")}</span>;
|
||||
|
||||
const normalizedTeams = normalizeTeams(value);
|
||||
|
||||
|
|
@ -327,19 +331,21 @@ const DefaultUserSettings: React.FC<DefaultUserSettingsProps> = ({
|
|||
<div key={index} className="border rounded-lg p-3 bg-white">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-2 text-sm">
|
||||
<div>
|
||||
<span className="font-medium text-gray-600">Team ID:</span>
|
||||
<p className="text-gray-900">{team.team_id || "Not specified"}</p>
|
||||
<span className="font-medium text-gray-600">{t("defaultUserSettings.teamEntry.teamIdLabel")}:</span>
|
||||
<p className="text-gray-900">{team.team_id || t("defaultUserSettings.displayValues.notSpecified")}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium text-gray-600">Max Budget:</span>
|
||||
<span className="font-medium text-gray-600">
|
||||
{t("defaultUserSettings.teamEntry.maxBudgetLabel")}:
|
||||
</span>
|
||||
<p className="text-gray-900">
|
||||
{team.max_budget_in_team !== undefined
|
||||
? `$${formatNumberWithCommas(team.max_budget_in_team, 4)}`
|
||||
: "No limit"}
|
||||
: t("defaultUserSettings.displayValues.noLimit")}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium text-gray-600">Role:</span>
|
||||
<span className="font-medium text-gray-600">{t("defaultUserSettings.teamEntry.userRoleLabel")}:</span>
|
||||
<p className="text-gray-900 capitalize">{team.user_role}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -364,11 +370,11 @@ const DefaultUserSettings: React.FC<DefaultUserSettingsProps> = ({
|
|||
}
|
||||
|
||||
if (typeof value === "boolean") {
|
||||
return <span>{value ? "Enabled" : "Disabled"}</span>;
|
||||
return <span>{value ? t("common.enabled") : t("common.disabled")}</span>;
|
||||
}
|
||||
|
||||
if (key === "models" && Array.isArray(value)) {
|
||||
if (value.length === 0) return <span className="text-gray-400">None</span>;
|
||||
if (value.length === 0) return <span className="text-gray-400">{t("common.none")}</span>;
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap gap-2 mt-1">
|
||||
|
|
@ -383,7 +389,7 @@ const DefaultUserSettings: React.FC<DefaultUserSettingsProps> = ({
|
|||
|
||||
if (typeof value === "object") {
|
||||
if (Array.isArray(value)) {
|
||||
if (value.length === 0) return <span className="text-gray-400">None</span>;
|
||||
if (value.length === 0) return <span className="text-gray-400">{t("common.none")}</span>;
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap gap-2 mt-1">
|
||||
|
|
@ -413,7 +419,7 @@ const DefaultUserSettings: React.FC<DefaultUserSettingsProps> = ({
|
|||
if (!settings) {
|
||||
return (
|
||||
<Card>
|
||||
<Text>No settings available or you do not have permission to view them.</Text>
|
||||
<Text>{t("defaultUserSettings.noSettings")}</Text>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
|
@ -423,7 +429,7 @@ const DefaultUserSettings: React.FC<DefaultUserSettingsProps> = ({
|
|||
const { values, field_schema } = settings;
|
||||
|
||||
if (!field_schema || !field_schema.properties) {
|
||||
return <Text>No schema information available</Text>;
|
||||
return <Text>{t("defaultUserSettings.noSchemaInfo")}</Text>;
|
||||
}
|
||||
|
||||
return Object.entries(field_schema.properties).map(([key, property]: [string, any]) => {
|
||||
|
|
@ -434,7 +440,7 @@ const DefaultUserSettings: React.FC<DefaultUserSettingsProps> = ({
|
|||
<div key={key} className="mb-6 pb-6 border-b border-gray-200 last:border-0">
|
||||
<Text className="font-medium text-lg">{displayName}</Text>
|
||||
<Paragraph className="text-sm text-gray-500 mt-1">
|
||||
{property.description || "No description available"}
|
||||
{property.description || t("defaultUserSettings.noDescriptionAvailable")}
|
||||
</Paragraph>
|
||||
|
||||
{isEditing ? (
|
||||
|
|
@ -450,7 +456,7 @@ const DefaultUserSettings: React.FC<DefaultUserSettingsProps> = ({
|
|||
return (
|
||||
<Card>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<Title>Default User Settings</Title>
|
||||
<Title>{t("defaultUserSettings.title")}</Title>
|
||||
{!loading &&
|
||||
settings &&
|
||||
(isEditing ? (
|
||||
|
|
@ -462,15 +468,15 @@ const DefaultUserSettings: React.FC<DefaultUserSettingsProps> = ({
|
|||
}}
|
||||
disabled={saving}
|
||||
>
|
||||
Cancel
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
<Button type="primary" onClick={handleSaveSettings} loading={saving}>
|
||||
Save Changes
|
||||
{t("defaultUserSettings.saveChanges")}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<Button type="primary" onClick={() => setIsEditing(true)}>
|
||||
Edit Settings
|
||||
{t("defaultUserSettings.editSettings")}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React from "react";
|
||||
import { Tag } from "antd";
|
||||
import { GlobalOutlined } from "@ant-design/icons";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface GuardrailSettingsViewProps {
|
||||
globalGuardrailNames: Set<string>;
|
||||
|
|
@ -19,6 +20,7 @@ export function GuardrailSettingsView({
|
|||
variant = "card",
|
||||
className = "",
|
||||
}: GuardrailSettingsViewProps) {
|
||||
const { t } = useTranslation();
|
||||
const optedOutSet = new Set(optedOutGlobalGuardrails);
|
||||
const globalsRunning = Array.from(globalGuardrailNames).filter((n) => !optedOutSet.has(n));
|
||||
const nonGlobalOptIns = teamGuardrails.filter((n) => !globalGuardrailNames.has(n));
|
||||
|
|
@ -26,16 +28,19 @@ export function GuardrailSettingsView({
|
|||
const isEmpty = !killSwitchOn && globalsRunning.length === 0 && nonGlobalOptIns.length === 0;
|
||||
|
||||
const content = isEmpty ? (
|
||||
<span className="block text-gray-500">No guardrails configured</span>
|
||||
<span className="block text-gray-500">{t("guardrailSettingsView.noGuardrailsConfigured")}</span>
|
||||
) : (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div>
|
||||
<span className="block text-sm font-medium text-gray-700 mb-2">
|
||||
<GlobalOutlined style={{ marginInlineEnd: 4 }} aria-label="Global guardrail" />
|
||||
Global
|
||||
<GlobalOutlined
|
||||
style={{ marginInlineEnd: 4 }}
|
||||
aria-label={t("guardrailSettingsView.globalGuardrailAriaLabel")}
|
||||
/>
|
||||
{t("guardrailSettingsView.globalLabel")}
|
||||
</span>
|
||||
{killSwitchOn ? (
|
||||
<Tag color="gold">Bypassed for this team</Tag>
|
||||
<Tag color="gold">{t("guardrailSettingsView.bypassedForTeam")}</Tag>
|
||||
) : globalsRunning.length > 0 ? (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{globalsRunning.map((name) => (
|
||||
|
|
@ -45,11 +50,13 @@ export function GuardrailSettingsView({
|
|||
))}
|
||||
</div>
|
||||
) : (
|
||||
<span className="block text-sm text-gray-500">None configured</span>
|
||||
<span className="block text-sm text-gray-500">{t("guardrailSettingsView.noneConfigured")}</span>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<span className="block text-sm font-medium text-gray-700 mb-2">Team-specific</span>
|
||||
<span className="block text-sm font-medium text-gray-700 mb-2">
|
||||
{t("guardrailSettingsView.teamSpecificLabel")}
|
||||
</span>
|
||||
{nonGlobalOptIns.length > 0 ? (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{nonGlobalOptIns.map((name) => (
|
||||
|
|
@ -59,7 +66,7 @@ export function GuardrailSettingsView({
|
|||
))}
|
||||
</div>
|
||||
) : (
|
||||
<span className="block text-sm text-gray-500">None configured</span>
|
||||
<span className="block text-sm text-gray-500">{t("guardrailSettingsView.noneConfigured")}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -70,10 +77,8 @@ export function GuardrailSettingsView({
|
|||
<div className={`bg-white border border-gray-200 rounded-lg p-6 ${className}`}>
|
||||
<div className="flex items-center gap-2 mb-6">
|
||||
<div>
|
||||
<span className="block font-semibold text-gray-900">Guardrails Settings</span>
|
||||
<span className="block text-xs text-gray-500">
|
||||
Global and team-specific guardrails applied to this team
|
||||
</span>
|
||||
<span className="block font-semibold text-gray-900">{t("guardrailSettingsView.cardTitle")}</span>
|
||||
<span className="block text-xs text-gray-500">{t("guardrailSettingsView.cardSubtitle")}</span>
|
||||
</div>
|
||||
</div>
|
||||
{content}
|
||||
|
|
@ -83,7 +88,7 @@ export function GuardrailSettingsView({
|
|||
|
||||
return (
|
||||
<div className={`${className}`}>
|
||||
<span className="block font-medium text-gray-900 mb-3">Guardrails Settings</span>
|
||||
<span className="block font-medium text-gray-900 mb-3">{t("guardrailSettingsView.inlineTitle")}</span>
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -393,6 +393,178 @@
|
|||
"addMember": "Add Member"
|
||||
}
|
||||
},
|
||||
"adminPanel": {
|
||||
"title": "Admin Access",
|
||||
"goToInternalUsers": "Go to 'Internal Users' page to add other admins.",
|
||||
"allIPAddressesAllowed": "All IP Addresses Allowed",
|
||||
"tabs": {
|
||||
"ssoSettings": "SSO Settings",
|
||||
"securitySettings": "Security Settings",
|
||||
"scim": "SCIM",
|
||||
"uiSettings": "UI Settings",
|
||||
"loggingSettings": "Logging Settings",
|
||||
"hashicorpVault": "Hashicorp Vault"
|
||||
},
|
||||
"securityCard": {
|
||||
"title": "Security Settings",
|
||||
"deprecatedAlertMessage": "SSO Configuration Deprecated",
|
||||
"deprecatedAlertDescription": "Editing SSO Settings on this page is deprecated and will be removed in a future version. Please use the SSO Settings tab for SSO configuration.",
|
||||
"editSSOSettings": "Edit SSO Settings",
|
||||
"addSSO": "Add SSO",
|
||||
"allowedIPs": "Allowed IPs",
|
||||
"uiAccessControl": "UI Access Control"
|
||||
},
|
||||
"loginWithoutSSO": {
|
||||
"title": "Login without SSO",
|
||||
"description": "If you need to login without sso, you can access"
|
||||
},
|
||||
"manageAllowedIPModal": {
|
||||
"title": "Manage Allowed IP Addresses",
|
||||
"addIPAddress": "Add IP Address",
|
||||
"ipAddressColumn": "IP Address",
|
||||
"actionColumn": "Action"
|
||||
},
|
||||
"addIPModal": {
|
||||
"title": "Add Allowed IP Address",
|
||||
"inputPlaceholder": "Enter IP address",
|
||||
"inputRequired": "Please enter an IP address",
|
||||
"submitButton": "Add IP Address"
|
||||
},
|
||||
"deleteIPModal": {
|
||||
"title": "Confirm Delete",
|
||||
"confirmText": "Are you sure you want to delete the IP address: {{ip}}?"
|
||||
},
|
||||
"uiAccessControlModal": {
|
||||
"title": "UI Access Control Settings",
|
||||
"updateSuccess": "UI Access Control settings updated successfully"
|
||||
},
|
||||
"notifications": {
|
||||
"premiumOnlyFeature": "This feature is only available for premium users. Please upgrade your account.",
|
||||
"premiumOnlyUIAccessControl": "Only premium users can configure UI access control",
|
||||
"ipAddedSuccess": "IP address added successfully",
|
||||
"ipDeletedSuccess": "IP address deleted successfully"
|
||||
},
|
||||
"fetchAllowedIpsFailed": "Failed to fetch allowed IPs {{error}}",
|
||||
"addIpFailed": "Failed to add IP address {{error}}",
|
||||
"deleteIpFailed": "Failed to delete IP address {{error}}"
|
||||
},
|
||||
"bulkEditUsers": {
|
||||
"titleAllUsers": "Bulk Edit All Users",
|
||||
"titleSelectedUsers": "Bulk Edit {{count}} User(s)",
|
||||
"updateAllUsersLabel": "Update ALL users in the system",
|
||||
"updateAllUsersWarning": "This will apply changes to ALL users in the system, not just the selected ones.",
|
||||
"selectedUsersTitle": "Selected Users ({{count}}):",
|
||||
"columns": {
|
||||
"userId": "User ID",
|
||||
"email": "Email",
|
||||
"currentRole": "Current Role",
|
||||
"budget": "Budget"
|
||||
},
|
||||
"noEmail": "No email",
|
||||
"unlimited": "Unlimited",
|
||||
"instructions": "Fill in the fields below with the values you want to apply to all selected users. You can bulk edit: role, budget, models, and metadata. You can also add users to teams.",
|
||||
"instructionsLabel": "Instructions:",
|
||||
"teamManagement": {
|
||||
"sectionTitle": "Team Management",
|
||||
"addToTeamsLabel": "Add selected users to teams",
|
||||
"selectTeamsLabel": "Select Teams:",
|
||||
"selectTeamsPlaceholder": "Select teams to add users to",
|
||||
"teamBudgetLabel": "Team Budget (Optional):",
|
||||
"teamBudgetPlaceholder": "Max budget per user in team",
|
||||
"teamBudgetHint": "Leave empty for unlimited budget within team limits",
|
||||
"defaultRoleNote": "Users will be added with \"user\" role by default. All users will be added to each selected team."
|
||||
},
|
||||
"updatingUsers": "Updating {{count}} user(s)...",
|
||||
"updatingAllUsers": "Updating all users...",
|
||||
"notifications": {
|
||||
"noFieldsModified": "Please modify at least one field or select teams to add users to",
|
||||
"accessTokenNotFound": "Access token not found",
|
||||
"bulkOpFailed": "Failed to perform bulk operations",
|
||||
"updatedAllUsers": "Updated all users ({{total}} total)",
|
||||
"updatedUsers": "Updated {{count}} user(s)",
|
||||
"addedToTeams": "Added users to {{count}} team(s) ({{total}} total additions)",
|
||||
"failedToAddToTeams": "Failed to add users to {{count}} team(s)"
|
||||
}
|
||||
},
|
||||
"createUserButton": {
|
||||
"inviteUserButton": "+ Invite User",
|
||||
"createUserOwnsKeys": "Create a User who can own keys",
|
||||
"emailInvitations": "Email invitations",
|
||||
"emailInvitationsDesc": "New users receive an email invite only when an email integration (SMTP, Resend, or SendGrid) is configured.",
|
||||
"learnHowToSetupEmail": "Learn how to set up email notifications",
|
||||
"userEmailLabel": "User Email",
|
||||
"userRoleLabel": "User Role",
|
||||
"globalProxyRoleLabel": "Global Proxy Role",
|
||||
"globalProxyRoleTooltip": "This role is independent of any team/org specific roles. Configure Team / Organization Admins in the Settings",
|
||||
"teamLabel": "Team",
|
||||
"teamHelp": "If selected, user will be added as a 'user' role to the team.",
|
||||
"organizationLabel": "Organization",
|
||||
"organizationHelp": "The user will be added to the selected organization(s).",
|
||||
"selectOrganizationPlaceholder": "Select Organization",
|
||||
"metadataLabel": "Metadata",
|
||||
"metadataPlaceholder": "Enter metadata as JSON",
|
||||
"sendInviteEmailLabel": "Send invitation email",
|
||||
"personalKeyCreation": "Personal Key Creation",
|
||||
"modelsLabel": "Models",
|
||||
"modelsTooltip": "Models user has access to, outside of team scope.",
|
||||
"modelsHelp": "Models user has access to, outside of team scope.",
|
||||
"modelsPlaceholder": "Select models",
|
||||
"allProxyModels": "All Proxy Models",
|
||||
"noDefaultModels": "No Default Models",
|
||||
"inviteUserModalTitle": "Invite User",
|
||||
"inviteUserSubmitButton": "Invite User",
|
||||
"createUserSubmitButton": "Create User",
|
||||
"notifications": {
|
||||
"makingApiCall": "Making API Call",
|
||||
"userCreated": "API user Created",
|
||||
"errorCreatingUser": "Error creating the user"
|
||||
}
|
||||
},
|
||||
"debugWarningBanner": {
|
||||
"message": "Performance Warning: Detailed Debug Mode Active",
|
||||
"description": "Detailed debug logging (<code>LITELLM_LOG=DEBUG</code>) is currently enabled. This mode logs extensive diagnostic information and will significantly degrade performance. It should only be used for troubleshooting and disabled in production environments."
|
||||
},
|
||||
"defaultUserSettings": {
|
||||
"title": "Default User Settings",
|
||||
"editSettings": "Edit Settings",
|
||||
"saveChanges": "Save Changes",
|
||||
"noSettings": "No settings available or you do not have permission to view them.",
|
||||
"noSchemaInfo": "No schema information available",
|
||||
"noDescriptionAvailable": "No description available",
|
||||
"teamEntry": {
|
||||
"teamLabel": "Team {{number}}",
|
||||
"removeButton": "Remove",
|
||||
"teamIdLabel": "Team ID",
|
||||
"teamIdPlaceholder": "Enter team ID",
|
||||
"maxBudgetLabel": "Max Budget in Team",
|
||||
"maxBudgetPlaceholder": "Optional",
|
||||
"userRoleLabel": "User Role",
|
||||
"addTeamButton": "Add Team"
|
||||
},
|
||||
"displayValues": {
|
||||
"notSet": "Not set",
|
||||
"noTeamsAssigned": "No teams assigned",
|
||||
"notSpecified": "Not specified",
|
||||
"noLimit": "No limit",
|
||||
"userRoleOption": "User",
|
||||
"adminRoleOption": "Admin"
|
||||
},
|
||||
"notifications": {
|
||||
"fetchFailed": "Failed to fetch SSO settings",
|
||||
"updateFailed": "Failed to update settings: {{error}}"
|
||||
}
|
||||
},
|
||||
"guardrailSettingsView": {
|
||||
"noGuardrailsConfigured": "No guardrails configured",
|
||||
"globalLabel": "Global",
|
||||
"globalGuardrailAriaLabel": "Global guardrail",
|
||||
"bypassedForTeam": "Bypassed for this team",
|
||||
"noneConfigured": "None configured",
|
||||
"teamSpecificLabel": "Team-specific",
|
||||
"cardTitle": "Guardrails Settings",
|
||||
"cardSubtitle": "Global and team-specific guardrails applied to this team",
|
||||
"inlineTitle": "Guardrails Settings"
|
||||
},
|
||||
"common": {
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
|
|
|
|||
|
|
@ -393,6 +393,178 @@
|
|||
"addMember": "添加成员"
|
||||
}
|
||||
},
|
||||
"adminPanel": {
|
||||
"title": "管理员访问",
|
||||
"goToInternalUsers": "前往「内部用户」页面添加其他管理员。",
|
||||
"allIPAddressesAllowed": "允许所有 IP 地址",
|
||||
"tabs": {
|
||||
"ssoSettings": "SSO 设置",
|
||||
"securitySettings": "安全设置",
|
||||
"scim": "SCIM",
|
||||
"uiSettings": "界面设置",
|
||||
"loggingSettings": "日志设置",
|
||||
"hashicorpVault": "Hashicorp Vault"
|
||||
},
|
||||
"securityCard": {
|
||||
"title": "安全设置",
|
||||
"deprecatedAlertMessage": "SSO 配置已弃用",
|
||||
"deprecatedAlertDescription": "在此页面编辑 SSO 设置已弃用,将在未来版本中移除。请使用 SSO 设置标签页进行 SSO 配置。",
|
||||
"editSSOSettings": "编辑 SSO 设置",
|
||||
"addSSO": "添加 SSO",
|
||||
"allowedIPs": "允许的 IP",
|
||||
"uiAccessControl": "界面访问控制"
|
||||
},
|
||||
"loginWithoutSSO": {
|
||||
"title": "不使用 SSO 登录",
|
||||
"description": "如果您需要在不使用 SSO 的情况下登录,可以访问"
|
||||
},
|
||||
"manageAllowedIPModal": {
|
||||
"title": "管理允许的 IP 地址",
|
||||
"addIPAddress": "添加 IP 地址",
|
||||
"ipAddressColumn": "IP 地址",
|
||||
"actionColumn": "操作"
|
||||
},
|
||||
"addIPModal": {
|
||||
"title": "添加允许的 IP 地址",
|
||||
"inputPlaceholder": "输入 IP 地址",
|
||||
"inputRequired": "请输入 IP 地址",
|
||||
"submitButton": "添加 IP 地址"
|
||||
},
|
||||
"deleteIPModal": {
|
||||
"title": "确认删除",
|
||||
"confirmText": "确定要删除 IP 地址:{{ip}}?"
|
||||
},
|
||||
"uiAccessControlModal": {
|
||||
"title": "界面访问控制设置",
|
||||
"updateSuccess": "界面访问控制设置已成功更新"
|
||||
},
|
||||
"notifications": {
|
||||
"premiumOnlyFeature": "此功能仅对高级用户开放,请升级您的账户。",
|
||||
"premiumOnlyUIAccessControl": "只有高级用户才能配置界面访问控制",
|
||||
"ipAddedSuccess": "IP 地址添加成功",
|
||||
"ipDeletedSuccess": "IP 地址删除成功"
|
||||
},
|
||||
"fetchAllowedIpsFailed": "获取允许的 IP 列表失败 {{error}}",
|
||||
"addIpFailed": "添加 IP 地址失败 {{error}}",
|
||||
"deleteIpFailed": "删除 IP 地址失败 {{error}}"
|
||||
},
|
||||
"bulkEditUsers": {
|
||||
"titleAllUsers": "批量编辑所有用户",
|
||||
"titleSelectedUsers": "批量编辑 {{count}} 个用户",
|
||||
"updateAllUsersLabel": "更新系统中的所有用户",
|
||||
"updateAllUsersWarning": "这将对系统中的所有用户应用更改,而不仅仅是所选用户。",
|
||||
"selectedUsersTitle": "已选用户({{count}} 人):",
|
||||
"columns": {
|
||||
"userId": "用户 ID",
|
||||
"email": "邮箱",
|
||||
"currentRole": "当前角色",
|
||||
"budget": "预算"
|
||||
},
|
||||
"noEmail": "无邮箱",
|
||||
"unlimited": "无限制",
|
||||
"instructions": "在下方填写要应用于所有选定用户的值。可批量编辑:角色、预算、模型和元数据,也可将用户添加到团队。",
|
||||
"instructionsLabel": "使用说明:",
|
||||
"teamManagement": {
|
||||
"sectionTitle": "团队管理",
|
||||
"addToTeamsLabel": "将选定用户添加到团队",
|
||||
"selectTeamsLabel": "选择团队:",
|
||||
"selectTeamsPlaceholder": "选择要添加用户的团队",
|
||||
"teamBudgetLabel": "团队预算(可选):",
|
||||
"teamBudgetPlaceholder": "团队内每位用户的最大预算",
|
||||
"teamBudgetHint": "留空表示在团队限制内不设预算上限",
|
||||
"defaultRoleNote": "用户将以 user 角色默认添加,所有用户将被添加到每个选定的团队。"
|
||||
},
|
||||
"updatingUsers": "正在更新 {{count}} 个用户...",
|
||||
"updatingAllUsers": "正在更新所有用户...",
|
||||
"notifications": {
|
||||
"noFieldsModified": "请至少修改一个字段或选择要添加用户的团队",
|
||||
"accessTokenNotFound": "未找到访问令牌",
|
||||
"bulkOpFailed": "批量操作失败",
|
||||
"updatedAllUsers": "已更新所有用户(共 {{total}} 个)",
|
||||
"updatedUsers": "已更新 {{count}} 个用户",
|
||||
"addedToTeams": "已将用户添加到 {{count}} 个团队(共 {{total}} 次添加)",
|
||||
"failedToAddToTeams": "向 {{count}} 个团队添加用户失败"
|
||||
}
|
||||
},
|
||||
"createUserButton": {
|
||||
"inviteUserButton": "+ 邀请用户",
|
||||
"createUserOwnsKeys": "创建可拥有密钥的用户",
|
||||
"emailInvitations": "邮件邀请",
|
||||
"emailInvitationsDesc": "仅当配置了邮件集成(SMTP、Resend 或 SendGrid)时,新用户才会收到邮件邀请。",
|
||||
"learnHowToSetupEmail": "了解如何设置邮件通知",
|
||||
"userEmailLabel": "用户邮箱",
|
||||
"userRoleLabel": "用户角色",
|
||||
"globalProxyRoleLabel": "全局代理角色",
|
||||
"globalProxyRoleTooltip": "此角色独立于任何团队/组织特定角色。在设置中配置团队/组织管理员。",
|
||||
"teamLabel": "团队",
|
||||
"teamHelp": "若选择,用户将以 user 角色添加到该团队。",
|
||||
"organizationLabel": "组织",
|
||||
"organizationHelp": "用户将被添加到所选组织。",
|
||||
"selectOrganizationPlaceholder": "选择组织",
|
||||
"metadataLabel": "元数据",
|
||||
"metadataPlaceholder": "以 JSON 格式输入元数据",
|
||||
"sendInviteEmailLabel": "发送邀请邮件",
|
||||
"personalKeyCreation": "个人密钥创建",
|
||||
"modelsLabel": "模型",
|
||||
"modelsTooltip": "用户在团队范围外可访问的模型。",
|
||||
"modelsHelp": "用户在团队范围外可访问的模型。",
|
||||
"modelsPlaceholder": "选择模型",
|
||||
"allProxyModels": "所有代理模型",
|
||||
"noDefaultModels": "无默认模型",
|
||||
"inviteUserModalTitle": "邀请用户",
|
||||
"inviteUserSubmitButton": "邀请用户",
|
||||
"createUserSubmitButton": "创建用户",
|
||||
"notifications": {
|
||||
"makingApiCall": "正在调用 API",
|
||||
"userCreated": "API 用户已创建",
|
||||
"errorCreatingUser": "创建用户时出错"
|
||||
}
|
||||
},
|
||||
"debugWarningBanner": {
|
||||
"message": "性能警告:详细调试模式已启用",
|
||||
"description": "详细调试日志(<code>LITELLM_LOG=DEBUG</code>)当前已启用。此模式会记录大量诊断信息并显著降低性能,应仅用于排查问题,生产环境请禁用。"
|
||||
},
|
||||
"defaultUserSettings": {
|
||||
"title": "默认用户设置",
|
||||
"editSettings": "编辑设置",
|
||||
"saveChanges": "保存更改",
|
||||
"noSettings": "暂无可用设置,或您没有查看权限。",
|
||||
"noSchemaInfo": "暂无 schema 信息",
|
||||
"noDescriptionAvailable": "暂无描述",
|
||||
"teamEntry": {
|
||||
"teamLabel": "团队 {{number}}",
|
||||
"removeButton": "移除",
|
||||
"teamIdLabel": "团队 ID",
|
||||
"teamIdPlaceholder": "输入团队 ID",
|
||||
"maxBudgetLabel": "团队内最大预算",
|
||||
"maxBudgetPlaceholder": "可选",
|
||||
"userRoleLabel": "用户角色",
|
||||
"addTeamButton": "添加团队"
|
||||
},
|
||||
"displayValues": {
|
||||
"notSet": "未设置",
|
||||
"noTeamsAssigned": "未分配团队",
|
||||
"notSpecified": "未指定",
|
||||
"noLimit": "无上限",
|
||||
"userRoleOption": "用户",
|
||||
"adminRoleOption": "管理员"
|
||||
},
|
||||
"notifications": {
|
||||
"fetchFailed": "获取 SSO 设置失败",
|
||||
"updateFailed": "更新设置失败:{{error}}"
|
||||
}
|
||||
},
|
||||
"guardrailSettingsView": {
|
||||
"noGuardrailsConfigured": "未配置护栏",
|
||||
"globalLabel": "全局",
|
||||
"globalGuardrailAriaLabel": "全局护栏",
|
||||
"bypassedForTeam": "已为此团队绕过",
|
||||
"noneConfigured": "未配置",
|
||||
"teamSpecificLabel": "团队专属",
|
||||
"cardTitle": "护栏设置",
|
||||
"cardSubtitle": "应用于此团队的全局和团队专属护栏",
|
||||
"inlineTitle": "护栏设置"
|
||||
},
|
||||
"common": {
|
||||
"save": "保存",
|
||||
"cancel": "取消",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue