mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): i18n for policies impact views and main page
This commit is contained in:
parent
6f3463d207
commit
1505d4477d
5 changed files with 262 additions and 100 deletions
|
|
@ -2,6 +2,7 @@ import React, { useState } from "react";
|
|||
import { Icon } from "@tremor/react";
|
||||
import { EyeIcon } from "@heroicons/react/outline";
|
||||
import { Tooltip, Tag, Popover, Spin } from "antd";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import { PolicyAttachment } from "./types";
|
||||
import { estimateAttachmentImpactCall } from "../networking";
|
||||
|
||||
|
|
@ -9,6 +10,7 @@ const ImpactPopover: React.FC<{ attachment: PolicyAttachment; accessToken: strin
|
|||
attachment,
|
||||
accessToken,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [impact, setImpact] = useState<any>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
|
|
@ -36,21 +38,29 @@ const ImpactPopover: React.FC<{ attachment: PolicyAttachment; accessToken: strin
|
|||
|
||||
const content = loading ? (
|
||||
<div className="p-2 text-center">
|
||||
<Spin size="small" /> Loading...
|
||||
<Spin size="small" /> {t("common.loading")}
|
||||
</div>
|
||||
) : impact ? (
|
||||
<div className="text-xs" style={{ maxWidth: 280 }}>
|
||||
{impact.affected_keys_count === -1 ? (
|
||||
<p className="font-medium text-amber-600">Global scope — affects all keys and teams</p>
|
||||
<p className="font-medium text-amber-600">{t("policies.impactPopover.globalScope")}</p>
|
||||
) : (
|
||||
<>
|
||||
<p className="mb-1">
|
||||
<strong>{impact.affected_keys_count}</strong> key{impact.affected_keys_count !== 1 ? "s" : ""},{" "}
|
||||
<strong>{impact.affected_teams_count}</strong> team{impact.affected_teams_count !== 1 ? "s" : ""} affected
|
||||
<Trans
|
||||
i18nKey="policies.impactPopover.keysTeamsAffected"
|
||||
values={{
|
||||
keysCount: impact.affected_keys_count,
|
||||
keysWord: t("policies.impactPopover.keyWord", { count: impact.affected_keys_count }),
|
||||
teamsCount: impact.affected_teams_count,
|
||||
teamsWord: t("policies.impactPopover.teamWord", { count: impact.affected_teams_count }),
|
||||
}}
|
||||
components={[<strong key="keys" />, <strong key="teams" />]}
|
||||
/>
|
||||
</p>
|
||||
{impact.sample_keys.length > 0 && (
|
||||
<div className="mb-1">
|
||||
<span className="text-gray-500">Keys: </span>
|
||||
<span className="text-gray-500">{t("policies.impactPopover.keysLabel")} </span>
|
||||
{impact.sample_keys.map((k: string) => (
|
||||
<Tag key={k} style={{ fontSize: 10, margin: 1 }}>
|
||||
{k}
|
||||
|
|
@ -60,34 +70,34 @@ const ImpactPopover: React.FC<{ attachment: PolicyAttachment; accessToken: strin
|
|||
)}
|
||||
{impact.sample_teams.length > 0 && (
|
||||
<div>
|
||||
<span className="text-gray-500">Teams: </span>
|
||||
{impact.sample_teams.map((t: string) => (
|
||||
<Tag key={t} style={{ fontSize: 10, margin: 1 }}>
|
||||
{t}
|
||||
<span className="text-gray-500">{t("policies.impactPopover.teamsLabel")} </span>
|
||||
{impact.sample_teams.map((teamName: string) => (
|
||||
<Tag key={teamName} style={{ fontSize: 10, margin: 1 }}>
|
||||
{teamName}
|
||||
</Tag>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{impact.affected_keys_count === 0 && impact.affected_teams_count === 0 && (
|
||||
<p className="text-gray-400">No keys or teams currently affected</p>
|
||||
<p className="text-gray-400">{t("policies.impactPopover.noKeysOrTeams")}</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-xs text-gray-400">Click to load</p>
|
||||
<p className="text-xs text-gray-400">{t("policies.impactPopover.clickToLoad")}</p>
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover
|
||||
content={content}
|
||||
title="Blast Radius"
|
||||
title={t("policies.impactPopover.blastRadius")}
|
||||
trigger="click"
|
||||
onOpenChange={(open) => {
|
||||
if (open) loadImpact();
|
||||
}}
|
||||
>
|
||||
<Tooltip title="View blast radius">
|
||||
<Tooltip title={t("policies.impactPopover.viewBlastRadius")}>
|
||||
<Icon icon={EyeIcon} size="sm" className="cursor-pointer hover:text-blue-500" />
|
||||
</Tooltip>
|
||||
</Popover>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React from "react";
|
||||
import { Alert, Tag, Typography } from "antd";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
|
|
@ -15,34 +16,37 @@ interface ImpactPreviewAlertProps {
|
|||
}
|
||||
|
||||
const ImpactPreviewAlert: React.FC<ImpactPreviewAlertProps> = ({ impactResult }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Alert
|
||||
type={impactResult.affected_keys_count === -1 ? "warning" : "info"}
|
||||
showIcon
|
||||
className="mb-4"
|
||||
message="Impact Preview"
|
||||
message={t("policies.impactPreviewAlert.message")}
|
||||
description={
|
||||
impactResult.affected_keys_count === -1 ? (
|
||||
<Text>
|
||||
Global scope — this will affect <strong>all keys and teams</strong>.
|
||||
<Trans i18nKey="policies.impactPreviewAlert.globalScopeDesc" components={{ strong: <strong /> }} />
|
||||
</Text>
|
||||
) : (
|
||||
<div>
|
||||
<Text>
|
||||
This attachment would affect{" "}
|
||||
<strong>
|
||||
{impactResult.affected_keys_count} key{impactResult.affected_keys_count !== 1 ? "s" : ""}
|
||||
</strong>{" "}
|
||||
and{" "}
|
||||
<strong>
|
||||
{impactResult.affected_teams_count} team{impactResult.affected_teams_count !== 1 ? "s" : ""}
|
||||
</strong>
|
||||
.
|
||||
<Trans
|
||||
i18nKey="policies.impactPreviewAlert.attachmentAffects"
|
||||
values={{
|
||||
keysCount: impactResult.affected_keys_count,
|
||||
keysWord: t("policies.impactPreviewAlert.keyWord", { count: impactResult.affected_keys_count }),
|
||||
teamsCount: impactResult.affected_teams_count,
|
||||
teamsWord: t("policies.impactPreviewAlert.teamWord", { count: impactResult.affected_teams_count }),
|
||||
}}
|
||||
components={[<strong key="keys" />, <strong key="teams" />]}
|
||||
/>
|
||||
</Text>
|
||||
{impactResult.sample_keys.length > 0 && (
|
||||
<div className="mt-1">
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||
Keys:{" "}
|
||||
{t("policies.impactPreviewAlert.keysLabel")}{" "}
|
||||
</Text>
|
||||
{impactResult.sample_keys.slice(0, 5).map((k: string) => (
|
||||
<Tag key={k} style={{ fontSize: 11 }}>
|
||||
|
|
@ -51,7 +55,7 @@ const ImpactPreviewAlert: React.FC<ImpactPreviewAlertProps> = ({ impactResult })
|
|||
))}
|
||||
{impactResult.affected_keys_count > 5 && (
|
||||
<Text type="secondary" style={{ fontSize: 11 }}>
|
||||
and {impactResult.affected_keys_count - 5} more...
|
||||
{t("policies.impactPreviewAlert.andMore", { count: impactResult.affected_keys_count - 5 })}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -59,16 +63,16 @@ const ImpactPreviewAlert: React.FC<ImpactPreviewAlertProps> = ({ impactResult })
|
|||
{impactResult.sample_teams.length > 0 && (
|
||||
<div className="mt-1">
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||
Teams:{" "}
|
||||
{t("policies.impactPreviewAlert.teamsLabel")}{" "}
|
||||
</Text>
|
||||
{impactResult.sample_teams.slice(0, 5).map((t: string) => (
|
||||
<Tag key={t} style={{ fontSize: 11 }}>
|
||||
{t}
|
||||
{impactResult.sample_teams.slice(0, 5).map((teamName: string) => (
|
||||
<Tag key={teamName} style={{ fontSize: 11 }}>
|
||||
{teamName}
|
||||
</Tag>
|
||||
))}
|
||||
{impactResult.affected_teams_count > 5 && (
|
||||
<Text type="secondary" style={{ fontSize: 11 }}>
|
||||
and {impactResult.affected_teams_count - 5} more...
|
||||
{t("policies.impactPreviewAlert.andMore", { count: impactResult.affected_teams_count - 5 })}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import { Button, TabGroup, TabList, Tab, TabPanels, TabPanel } from "@tremor/react";
|
||||
import { Alert } from "antd";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
|
||||
import MessageManager from "@/components/molecules/message_manager";
|
||||
import { InfoCircleOutlined } from "@ant-design/icons";
|
||||
|
|
@ -39,6 +40,7 @@ interface PoliciesPanelProps {
|
|||
}
|
||||
|
||||
const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole }) => {
|
||||
const { t } = useTranslation();
|
||||
const [policiesList, setPoliciesList] = useState<Policy[]>([]);
|
||||
const [attachmentsList, setAttachmentsList] = useState<PolicyAttachment[]>([]);
|
||||
const [guardrailsList, setGuardrailsList] = useState<Guardrail[]>([]);
|
||||
|
|
@ -78,11 +80,11 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole })
|
|||
setPoliciesList(response.policies || []);
|
||||
} catch (error) {
|
||||
console.error("Error fetching policies:", error);
|
||||
MessageManager.error("Failed to fetch policies");
|
||||
MessageManager.error(t("policies.policiesView.fetchPoliciesFailed"));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [accessToken]);
|
||||
}, [accessToken, t]);
|
||||
|
||||
const fetchAttachments = useCallback(async () => {
|
||||
if (!accessToken) return;
|
||||
|
|
@ -93,11 +95,11 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole })
|
|||
setAttachmentsList(response.attachments || []);
|
||||
} catch (error) {
|
||||
console.error("Error fetching attachments:", error);
|
||||
MessageManager.error("Failed to fetch attachments");
|
||||
MessageManager.error(t("policies.policiesView.fetchAttachmentsFailed"));
|
||||
} finally {
|
||||
setIsAttachmentsLoading(false);
|
||||
}
|
||||
}, [accessToken]);
|
||||
}, [accessToken, t]);
|
||||
|
||||
const fetchGuardrails = useCallback(async () => {
|
||||
if (!accessToken) return;
|
||||
|
|
@ -146,11 +148,11 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole })
|
|||
setIsDeleting(true);
|
||||
try {
|
||||
await deletePolicyCall(accessToken, policyToDelete.policy_id);
|
||||
MessageManager.success(`Policy "${policyToDelete.policy_name}" deleted successfully`);
|
||||
MessageManager.success(t("policies.policiesView.policyDeletedSuccess", { name: policyToDelete.policy_name }));
|
||||
await fetchPolicies();
|
||||
} catch (error) {
|
||||
console.error("Error deleting policy:", error);
|
||||
MessageManager.error("Failed to delete policy");
|
||||
MessageManager.error(t("policies.policiesView.deletePolicyFailed"));
|
||||
} finally {
|
||||
setIsDeleting(false);
|
||||
setIsDeleteModalOpen(false);
|
||||
|
|
@ -195,7 +197,7 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole })
|
|||
|
||||
const handleUseTemplate = async (template: any) => {
|
||||
if (!accessToken) {
|
||||
MessageManager.error("Authentication required");
|
||||
MessageManager.error(t("policies.policiesView.authenticationRequired"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -223,7 +225,7 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole })
|
|||
setIsGuardrailSelectionModalOpen(true);
|
||||
} catch (error) {
|
||||
console.error("Error fetching guardrails:", error);
|
||||
MessageManager.error("Failed to load guardrails. Please try again.");
|
||||
MessageManager.error(t("policies.policiesView.loadGuardrailsFailed"));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -273,7 +275,7 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole })
|
|||
await proceedWithTemplate(enrichedTemplate);
|
||||
} catch (error) {
|
||||
console.error("Error enriching template:", error);
|
||||
MessageManager.error("Failed to configure template. Please try again.");
|
||||
MessageManager.error(t("policies.policiesView.configureTemplateFailed"));
|
||||
setIsEnrichingTemplate(false);
|
||||
}
|
||||
};
|
||||
|
|
@ -320,16 +322,17 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole })
|
|||
|
||||
// Show success message
|
||||
if (createdGuardrails.length > 0) {
|
||||
MessageManager.success(
|
||||
`Created ${createdGuardrails.length} guardrail${createdGuardrails.length > 1 ? "s" : ""}! Complete the policy form to save.`,
|
||||
);
|
||||
MessageManager.success(t("policies.policiesView.guardrailsCreated", { count: createdGuardrails.length }));
|
||||
} else {
|
||||
MessageManager.success("Template ready! Complete the policy form to save.");
|
||||
MessageManager.success(t("policies.policiesView.templateReady"));
|
||||
}
|
||||
|
||||
if (failedGuardrails.length > 0) {
|
||||
MessageManager.warning(
|
||||
`Failed to create ${failedGuardrails.length} guardrail(s): ${failedGuardrails.join(", ")}. You may need to create them manually.`,
|
||||
t("policies.policiesView.guardrailsCreateFailed", {
|
||||
count: failedGuardrails.length,
|
||||
list: failedGuardrails.join(", "),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -348,7 +351,7 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole })
|
|||
setTemplateQueue([]);
|
||||
setTemplateQueueProgress(null);
|
||||
console.error("Error creating guardrails:", error);
|
||||
MessageManager.error("Failed to create guardrails. Please try again.");
|
||||
MessageManager.error(t("policies.policiesView.createGuardrailsFailed"));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -363,26 +366,24 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole })
|
|||
<div className="w-full mx-auto flex-auto overflow-y-auto m-8 p-2">
|
||||
<TabGroup index={activeTab} onIndexChange={setActiveTab}>
|
||||
<TabList className="mb-4">
|
||||
<Tab>Templates</Tab>
|
||||
<Tab>Policies</Tab>
|
||||
<Tab>Attachments</Tab>
|
||||
<Tab>Policy Simulator</Tab>
|
||||
<Tab>{t("policies.policiesView.tabTemplates")}</Tab>
|
||||
<Tab>{t("policies.policiesView.tabPolicies")}</Tab>
|
||||
<Tab>{t("policies.policiesView.tabAttachments")}</Tab>
|
||||
<Tab>{t("policies.policiesView.tabPolicySimulator")}</Tab>
|
||||
</TabList>
|
||||
|
||||
<TabPanels>
|
||||
<TabPanel>
|
||||
<Alert
|
||||
message="About Policies"
|
||||
message={t("policies.policiesView.aboutPoliciesTitle")}
|
||||
description={
|
||||
<div>
|
||||
<p className="mb-3">
|
||||
Use policies to group guardrails and control which ones run for specific teams, keys, or models.
|
||||
</p>
|
||||
<p className="mb-2 font-semibold">Why use policies?</p>
|
||||
<p className="mb-3">{t("policies.policiesView.aboutPoliciesDesc")}</p>
|
||||
<p className="mb-2 font-semibold">{t("policies.policiesView.whyUsePolicies")}</p>
|
||||
<ul className="list-disc list-inside mb-3 space-y-1 ml-2">
|
||||
<li>Enable/disable specific guardrails for teams, keys, or models</li>
|
||||
<li>Group guardrails into a single policy</li>
|
||||
<li>Inherit from existing policies and override what you need</li>
|
||||
<li>{t("policies.policiesView.whyPoliciesBullet1")}</li>
|
||||
<li>{t("policies.policiesView.whyPoliciesBullet2")}</li>
|
||||
<li>{t("policies.policiesView.whyPoliciesBullet3")}</li>
|
||||
</ul>
|
||||
<a
|
||||
href="https://docs.litellm.ai/docs/proxy/guardrails/guardrail_policies"
|
||||
|
|
@ -390,7 +391,7 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole })
|
|||
rel="noopener noreferrer"
|
||||
className="text-blue-600 hover:text-blue-800 underline inline-block mt-1"
|
||||
>
|
||||
Learn more in the documentation →
|
||||
{t("policies.policiesView.learnMoreDocs")}
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
|
|
@ -410,17 +411,15 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole })
|
|||
|
||||
<TabPanel>
|
||||
<Alert
|
||||
message="About Policies"
|
||||
message={t("policies.policiesView.aboutPoliciesTitle")}
|
||||
description={
|
||||
<div>
|
||||
<p className="mb-3">
|
||||
Use policies to group guardrails and control which ones run for specific teams, keys, or models.
|
||||
</p>
|
||||
<p className="mb-2 font-semibold">Why use policies?</p>
|
||||
<p className="mb-3">{t("policies.policiesView.aboutPoliciesDesc")}</p>
|
||||
<p className="mb-2 font-semibold">{t("policies.policiesView.whyUsePolicies")}</p>
|
||||
<ul className="list-disc list-inside mb-3 space-y-1 ml-2">
|
||||
<li>Enable/disable specific guardrails for teams, keys, or models</li>
|
||||
<li>Group guardrails into a single policy</li>
|
||||
<li>Inherit from existing policies and override what you need</li>
|
||||
<li>{t("policies.policiesView.whyPoliciesBullet1")}</li>
|
||||
<li>{t("policies.policiesView.whyPoliciesBullet2")}</li>
|
||||
<li>{t("policies.policiesView.whyPoliciesBullet3")}</li>
|
||||
</ul>
|
||||
<a
|
||||
href="https://docs.litellm.ai/docs/proxy/guardrails/guardrail_policies"
|
||||
|
|
@ -428,7 +427,7 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole })
|
|||
rel="noopener noreferrer"
|
||||
className="text-blue-600 hover:text-blue-800 underline inline-block mt-1"
|
||||
>
|
||||
Learn more in the documentation →
|
||||
{t("policies.policiesView.learnMoreDocs")}
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
|
|
@ -441,7 +440,7 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole })
|
|||
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<Button onClick={handleAddPolicy} disabled={!accessToken}>
|
||||
+ Add New Policy
|
||||
{t("policies.policiesView.addNewPolicy")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
|
@ -490,14 +489,16 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole })
|
|||
|
||||
<DeleteResourceModal
|
||||
isOpen={isDeleteModalOpen}
|
||||
title="Delete Policy"
|
||||
message={`Are you sure you want to delete policy: ${policyToDelete?.policy_name}? This action cannot be undone.`}
|
||||
resourceInformationTitle="Policy Information"
|
||||
title={t("policies.policiesView.deletePolicyTitle")}
|
||||
message={t("policies.policiesView.deletePolicyMessage", {
|
||||
name: policyToDelete?.policy_name,
|
||||
})}
|
||||
resourceInformationTitle={t("policies.policiesView.policyInformationTitle")}
|
||||
resourceInformation={[
|
||||
{ label: "Name", value: policyToDelete?.policy_name },
|
||||
{ label: "ID", value: policyToDelete?.policy_id, code: true },
|
||||
{ label: "Description", value: policyToDelete?.description || "-" },
|
||||
{ label: "Inherits From", value: policyToDelete?.inherit || "-" },
|
||||
{ label: t("common.name"), value: policyToDelete?.policy_name },
|
||||
{ label: t("policies.policiesView.labelId"), value: policyToDelete?.policy_id, code: true },
|
||||
{ label: t("common.description"), value: policyToDelete?.description || "-" },
|
||||
{ label: t("policies.policiesView.labelInheritsFrom"), value: policyToDelete?.inherit || "-" },
|
||||
]}
|
||||
onCancel={handleDeleteCancel}
|
||||
onOk={handleDeleteConfirm}
|
||||
|
|
@ -526,32 +527,29 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole })
|
|||
|
||||
<TabPanel>
|
||||
<Alert
|
||||
message="About Policy Attachments"
|
||||
message={t("policies.policiesView.aboutAttachmentsTitle")}
|
||||
description={
|
||||
<div>
|
||||
<p className="mb-3">
|
||||
Policy attachments control where your policies apply. Policies don't do anything until you
|
||||
attach them to specific teams, keys, models, tags, or globally.
|
||||
</p>
|
||||
<p className="mb-2 font-semibold">Attachment Scopes:</p>
|
||||
<p className="mb-3">{t("policies.policiesView.aboutAttachmentsDesc")}</p>
|
||||
<p className="mb-2 font-semibold">{t("policies.policiesView.attachmentScopes")}</p>
|
||||
<ul className="list-disc list-inside mb-3 space-y-1 ml-2">
|
||||
<li>
|
||||
<strong>Global (*)</strong> - Applies to all requests
|
||||
<Trans i18nKey="policies.policiesView.scopeGlobal" components={{ strong: <strong /> }} />
|
||||
</li>
|
||||
<li>
|
||||
<strong>Teams</strong> - Applies only to specific teams
|
||||
<Trans i18nKey="policies.policiesView.scopeTeams" components={{ strong: <strong /> }} />
|
||||
</li>
|
||||
<li>
|
||||
<strong>Keys</strong> - Applies only to specific API keys (supports wildcards like dev-*)
|
||||
<Trans i18nKey="policies.policiesView.scopeKeys" components={{ strong: <strong /> }} />
|
||||
</li>
|
||||
<li>
|
||||
<strong>Models</strong> - Applies only when specific models are used
|
||||
<Trans i18nKey="policies.policiesView.scopeModels" components={{ strong: <strong /> }} />
|
||||
</li>
|
||||
<li>
|
||||
<strong>Tags</strong> - Matches tags from key/team <code>metadata.tags</code> or tags passed
|
||||
dynamically in the request body (<code>metadata.tags</code>). Use this to enforce policies across
|
||||
groups, e.g. "all keys tagged <code>healthcare</code> get HIPAA guardrails." Supports
|
||||
wildcards (<code>prod-*</code>).
|
||||
<Trans
|
||||
i18nKey="policies.policiesView.scopeTags"
|
||||
components={{ strong: <strong />, code: <code /> }}
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
<a
|
||||
|
|
@ -560,7 +558,7 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole })
|
|||
rel="noopener noreferrer"
|
||||
className="text-blue-600 hover:text-blue-800 underline inline-block mt-1"
|
||||
>
|
||||
Learn more about attachments →
|
||||
{t("policies.policiesView.learnMoreAttachments")}
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
|
|
@ -572,8 +570,8 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole })
|
|||
/>
|
||||
|
||||
<Alert
|
||||
message="Enterprise Feature Notice"
|
||||
description="Parts of policy attachments will be on LiteLLM Enterprise in subsequent releases."
|
||||
message={t("policies.policiesView.enterpriseFeatureTitle")}
|
||||
description={t("policies.policiesView.enterpriseFeatureDesc")}
|
||||
type="warning"
|
||||
showIcon
|
||||
closable
|
||||
|
|
@ -585,7 +583,7 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole })
|
|||
onClick={() => setIsAddAttachmentModalVisible(true)}
|
||||
disabled={!accessToken || policiesList.length === 0}
|
||||
>
|
||||
+ Add New Attachment
|
||||
{t("policies.policiesView.addNewAttachment")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
|
@ -615,13 +613,13 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole })
|
|||
|
||||
<DeleteResourceModal
|
||||
isOpen={isDeleteAttachmentModalOpen}
|
||||
title="Delete Attachment"
|
||||
message="Are you sure you want to delete this attachment? This action cannot be undone."
|
||||
resourceInformationTitle="Attachment Information"
|
||||
title={t("policies.policiesView.deleteAttachmentTitle")}
|
||||
message={t("policies.policiesView.deleteAttachmentMessage")}
|
||||
resourceInformationTitle={t("policies.policiesView.attachmentInformationTitle")}
|
||||
resourceInformation={[
|
||||
{ label: "Attachment ID", value: attachmentToDelete?.attachment_id, code: true },
|
||||
{ label: "Policy", value: attachmentToDelete?.policy_name ?? "-" },
|
||||
{ label: "Scope", value: attachmentToDelete?.scope ?? "-" },
|
||||
{ label: t("policies.policiesView.labelAttachmentId"), value: attachmentToDelete?.attachment_id, code: true },
|
||||
{ label: t("policies.policiesView.labelPolicy"), value: attachmentToDelete?.policy_name ?? "-" },
|
||||
{ label: t("policies.policiesView.labelScope"), value: attachmentToDelete?.scope ?? "-" },
|
||||
]}
|
||||
onCancel={handleAttachmentDeleteCancel}
|
||||
onOk={handleAttachmentDeleteConfirm}
|
||||
|
|
|
|||
|
|
@ -4823,6 +4823,81 @@
|
|||
"createAndUseTemplate_one": "Create {{count}} Guardrail & Use Template",
|
||||
"createAndUseTemplate_other": "Create {{count}} Guardrails & Use Template",
|
||||
"useTemplate": "Use Template"
|
||||
},
|
||||
"impactPopover": {
|
||||
"blastRadius": "Blast Radius",
|
||||
"viewBlastRadius": "View blast radius",
|
||||
"globalScope": "Global scope — affects all keys and teams",
|
||||
"keysTeamsAffected": "<0>{{keysCount}}</0> {{keysWord}}, <1>{{teamsCount}}</1> {{teamsWord}} affected",
|
||||
"keyWord_one": "key",
|
||||
"keyWord_other": "keys",
|
||||
"teamWord_one": "team",
|
||||
"teamWord_other": "teams",
|
||||
"keysLabel": "Keys:",
|
||||
"teamsLabel": "Teams:",
|
||||
"noKeysOrTeams": "No keys or teams currently affected",
|
||||
"clickToLoad": "Click to load"
|
||||
},
|
||||
"impactPreviewAlert": {
|
||||
"message": "Impact Preview",
|
||||
"globalScopeDesc": "Global scope — this will affect <strong>all keys and teams</strong>.",
|
||||
"attachmentAffects": "This attachment would affect <0>{{keysCount}}</0> {{keysWord}} and <1>{{teamsCount}}</1> {{teamsWord}}.",
|
||||
"keyWord_one": "key",
|
||||
"keyWord_other": "keys",
|
||||
"teamWord_one": "team",
|
||||
"teamWord_other": "teams",
|
||||
"keysLabel": "Keys:",
|
||||
"teamsLabel": "Teams:",
|
||||
"andMore": "and {{count}} more..."
|
||||
},
|
||||
"policiesView": {
|
||||
"tabTemplates": "Templates",
|
||||
"tabPolicies": "Policies",
|
||||
"tabAttachments": "Attachments",
|
||||
"tabPolicySimulator": "Policy Simulator",
|
||||
"aboutPoliciesTitle": "About Policies",
|
||||
"aboutPoliciesDesc": "Use policies to group guardrails and control which ones run for specific teams, keys, or models.",
|
||||
"whyUsePolicies": "Why use policies?",
|
||||
"whyPoliciesBullet1": "Enable/disable specific guardrails for teams, keys, or models",
|
||||
"whyPoliciesBullet2": "Group guardrails into a single policy",
|
||||
"whyPoliciesBullet3": "Inherit from existing policies and override what you need",
|
||||
"learnMoreDocs": "Learn more in the documentation →",
|
||||
"addNewPolicy": "+ Add New Policy",
|
||||
"aboutAttachmentsTitle": "About Policy Attachments",
|
||||
"aboutAttachmentsDesc": "Policy attachments control where your policies apply. Policies don't do anything until you attach them to specific teams, keys, models, tags, or globally.",
|
||||
"attachmentScopes": "Attachment Scopes:",
|
||||
"scopeGlobal": "<strong>Global (*)</strong> - Applies to all requests",
|
||||
"scopeTeams": "<strong>Teams</strong> - Applies only to specific teams",
|
||||
"scopeKeys": "<strong>Keys</strong> - Applies only to specific API keys (supports wildcards like dev-*)",
|
||||
"scopeModels": "<strong>Models</strong> - Applies only when specific models are used",
|
||||
"scopeTags": "<strong>Tags</strong> - Matches tags from key/team <code>metadata.tags</code> or tags passed dynamically in the request body (<code>metadata.tags</code>). Use this to enforce policies across groups, e.g. \"all keys tagged <code>healthcare</code> get HIPAA guardrails.\" Supports wildcards (<code>prod-*</code>).",
|
||||
"learnMoreAttachments": "Learn more about attachments →",
|
||||
"enterpriseFeatureTitle": "Enterprise Feature Notice",
|
||||
"enterpriseFeatureDesc": "Parts of policy attachments will be on LiteLLM Enterprise in subsequent releases.",
|
||||
"addNewAttachment": "+ Add New Attachment",
|
||||
"deletePolicyTitle": "Delete Policy",
|
||||
"deletePolicyMessage": "Are you sure you want to delete policy: {{name}}? This action cannot be undone.",
|
||||
"policyInformationTitle": "Policy Information",
|
||||
"labelId": "ID",
|
||||
"labelInheritsFrom": "Inherits From",
|
||||
"deleteAttachmentTitle": "Delete Attachment",
|
||||
"deleteAttachmentMessage": "Are you sure you want to delete this attachment? This action cannot be undone.",
|
||||
"attachmentInformationTitle": "Attachment Information",
|
||||
"labelAttachmentId": "Attachment ID",
|
||||
"labelPolicy": "Policy",
|
||||
"labelScope": "Scope",
|
||||
"fetchPoliciesFailed": "Failed to fetch policies",
|
||||
"fetchAttachmentsFailed": "Failed to fetch attachments",
|
||||
"authenticationRequired": "Authentication required",
|
||||
"loadGuardrailsFailed": "Failed to load guardrails. Please try again.",
|
||||
"configureTemplateFailed": "Failed to configure template. Please try again.",
|
||||
"policyDeletedSuccess": "Policy \"{{name}}\" deleted successfully",
|
||||
"deletePolicyFailed": "Failed to delete policy",
|
||||
"guardrailsCreated_one": "Created {{count}} guardrail! Complete the policy form to save.",
|
||||
"guardrailsCreated_other": "Created {{count}} guardrails! Complete the policy form to save.",
|
||||
"templateReady": "Template ready! Complete the policy form to save.",
|
||||
"guardrailsCreateFailed": "Failed to create {{count}} guardrail(s): {{list}}. You may need to create them manually.",
|
||||
"createGuardrailsFailed": "Failed to create guardrails. Please try again."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4823,6 +4823,81 @@
|
|||
"createAndUseTemplate_one": "创建 {{count}} 个护栏并使用模板",
|
||||
"createAndUseTemplate_other": "创建 {{count}} 个护栏并使用模板",
|
||||
"useTemplate": "使用模板"
|
||||
},
|
||||
"impactPopover": {
|
||||
"blastRadius": "影响范围",
|
||||
"viewBlastRadius": "查看影响范围",
|
||||
"globalScope": "全局范围 — 影响所有密钥和团队",
|
||||
"keysTeamsAffected": "<0>{{keysCount}}</0> {{keysWord}},<1>{{teamsCount}}</1> {{teamsWord}}受到影响",
|
||||
"keyWord_one": "个密钥",
|
||||
"keyWord_other": "个密钥",
|
||||
"teamWord_one": "个团队",
|
||||
"teamWord_other": "个团队",
|
||||
"keysLabel": "密钥:",
|
||||
"teamsLabel": "团队:",
|
||||
"noKeysOrTeams": "当前没有密钥或团队受到影响",
|
||||
"clickToLoad": "点击加载"
|
||||
},
|
||||
"impactPreviewAlert": {
|
||||
"message": "影响预览",
|
||||
"globalScopeDesc": "全局范围 — 将影响<strong>所有密钥和团队</strong>。",
|
||||
"attachmentAffects": "此附件将影响 <0>{{keysCount}}</0> {{keysWord}}和 <1>{{teamsCount}}</1> {{teamsWord}}。",
|
||||
"keyWord_one": "个密钥",
|
||||
"keyWord_other": "个密钥",
|
||||
"teamWord_one": "个团队",
|
||||
"teamWord_other": "个团队",
|
||||
"keysLabel": "密钥:",
|
||||
"teamsLabel": "团队:",
|
||||
"andMore": "及另外 {{count}} 个..."
|
||||
},
|
||||
"policiesView": {
|
||||
"tabTemplates": "模板",
|
||||
"tabPolicies": "策略",
|
||||
"tabAttachments": "附件",
|
||||
"tabPolicySimulator": "策略模拟器",
|
||||
"aboutPoliciesTitle": "关于策略",
|
||||
"aboutPoliciesDesc": "使用策略对护栏进行分组,并控制哪些护栏针对特定团队、密钥或模型运行。",
|
||||
"whyUsePolicies": "为什么使用策略?",
|
||||
"whyPoliciesBullet1": "为团队、密钥或模型启用/禁用特定护栏",
|
||||
"whyPoliciesBullet2": "将多个护栏组合到一个策略中",
|
||||
"whyPoliciesBullet3": "继承现有策略并按需覆盖",
|
||||
"learnMoreDocs": "在文档中了解更多 →",
|
||||
"addNewPolicy": "+ 添加新策略",
|
||||
"aboutAttachmentsTitle": "关于策略附件",
|
||||
"aboutAttachmentsDesc": "策略附件控制策略的应用范围。策略在附加到特定团队、密钥、模型、标签或全局之前不会生效。",
|
||||
"attachmentScopes": "附件范围:",
|
||||
"scopeGlobal": "<strong>全局 (*)</strong> - 适用于所有请求",
|
||||
"scopeTeams": "<strong>团队</strong> - 仅适用于特定团队",
|
||||
"scopeKeys": "<strong>密钥</strong> - 仅适用于特定 API 密钥(支持通配符,如 dev-*)",
|
||||
"scopeModels": "<strong>模型</strong> - 仅在使用特定模型时适用",
|
||||
"scopeTags": "<strong>标签</strong> - 匹配密钥/团队 <code>metadata.tags</code> 中的标签,或在请求体中动态传入的标签(<code>metadata.tags</code>)。用于跨组强制执行策略,例如:所有标记为 <code>healthcare</code> 的密钥使用 HIPAA 护栏。支持通配符(<code>prod-*</code>)。",
|
||||
"learnMoreAttachments": "了解更多关于附件的信息 →",
|
||||
"enterpriseFeatureTitle": "企业版功能通知",
|
||||
"enterpriseFeatureDesc": "策略附件的部分功能将在后续版本中纳入 LiteLLM 企业版。",
|
||||
"addNewAttachment": "+ 添加新附件",
|
||||
"deletePolicyTitle": "删除策略",
|
||||
"deletePolicyMessage": "确定要删除策略:{{name}}?此操作无法撤销。",
|
||||
"policyInformationTitle": "策略信息",
|
||||
"labelId": "ID",
|
||||
"labelInheritsFrom": "继承自",
|
||||
"deleteAttachmentTitle": "删除附件",
|
||||
"deleteAttachmentMessage": "确定要删除此附件吗?此操作无法撤销。",
|
||||
"attachmentInformationTitle": "附件信息",
|
||||
"labelAttachmentId": "附件 ID",
|
||||
"labelPolicy": "策略",
|
||||
"labelScope": "范围",
|
||||
"fetchPoliciesFailed": "获取策略失败",
|
||||
"fetchAttachmentsFailed": "获取附件失败",
|
||||
"authenticationRequired": "需要身份验证",
|
||||
"loadGuardrailsFailed": "加载护栏失败,请重试。",
|
||||
"configureTemplateFailed": "配置模板失败,请重试。",
|
||||
"policyDeletedSuccess": "策略「{{name}}」已成功删除",
|
||||
"deletePolicyFailed": "删除策略失败",
|
||||
"guardrailsCreated_one": "已创建 {{count}} 个护栏!请完成策略表单以保存。",
|
||||
"guardrailsCreated_other": "已创建 {{count}} 个护栏!请完成策略表单以保存。",
|
||||
"templateReady": "模板已就绪!请完成策略表单以保存。",
|
||||
"guardrailsCreateFailed": "{{count}} 个护栏创建失败:{{list}}。您可能需要手动创建。",
|
||||
"createGuardrailsFailed": "创建护栏失败,请重试。"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue