mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): i18n for view_logs constants and filter options
This commit is contained in:
parent
6bb974f1e8
commit
95af75657c
6 changed files with 147 additions and 55 deletions
|
|
@ -1,9 +1,9 @@
|
|||
import moment from "moment";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { SyncOutlined } from "@ant-design/icons";
|
||||
import { Button, Switch } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { QUICK_SELECT_OPTIONS } from "./constants";
|
||||
import { getQuickSelectOptions } from "./constants";
|
||||
import { getTimeRangeDisplay } from "./logs_utils";
|
||||
import type { PaginatedResponse } from "./log_filter_logic";
|
||||
|
||||
|
|
@ -51,6 +51,7 @@ export function LogsTableToolbar({
|
|||
filteredLogs,
|
||||
}: LogsTableToolbarProps) {
|
||||
const { t } = useTranslation();
|
||||
const quickSelectOptions = useMemo(() => getQuickSelectOptions(t), [t]);
|
||||
const [quickSelectOpen, setQuickSelectOpen] = useState(false);
|
||||
const quickSelectRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
|
|
@ -64,7 +65,7 @@ export function LogsTableToolbar({
|
|||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||
}, []);
|
||||
|
||||
const selectedOption = QUICK_SELECT_OPTIONS.find(
|
||||
const selectedOption = quickSelectOptions.find(
|
||||
(option) => option.value === selectedTimeInterval.value && option.unit === selectedTimeInterval.unit,
|
||||
);
|
||||
const displayLabel = isCustomDate ? getTimeRangeDisplay(isCustomDate, startTime, endTime) : selectedOption?.label;
|
||||
|
|
@ -117,7 +118,7 @@ export function LogsTableToolbar({
|
|||
{quickSelectOpen && (
|
||||
<div className="absolute left-0 mt-2 w-64 bg-white rounded-lg shadow-lg border p-2 z-50">
|
||||
<div className="space-y-1">
|
||||
{QUICK_SELECT_OPTIONS.map((option) => (
|
||||
{quickSelectOptions.map((option) => (
|
||||
<button
|
||||
key={option.label}
|
||||
className={`w-full px-3 py-2 text-left text-sm hover:bg-gray-50 rounded-md ${displayLabel === option.label ? "bg-blue-50 text-blue-600" : ""}`}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
export const ERROR_CODE_OPTIONS: { label: string; value: string }[] = [
|
||||
{ label: "400 - Bad Request", value: "400" },
|
||||
{ label: "401 - Invalid Authentication", value: "401" },
|
||||
{ label: "403 - Permission Denied", value: "403" },
|
||||
{ label: "404 - Not Found", value: "404" },
|
||||
{ label: "408 - Request Timeout", value: "408" },
|
||||
{ label: "422 - Unprocessable Entity", value: "422" },
|
||||
{ label: "429 - Rate Limited", value: "429" },
|
||||
{ label: "500 - Internal Server Error", value: "500" },
|
||||
{ label: "502 - Bad Gateway", value: "502" },
|
||||
{ label: "503 - Service Unavailable", value: "503" },
|
||||
{ label: "529 - Overloaded", value: "529" },
|
||||
import type { TFunction } from "i18next";
|
||||
|
||||
export const getErrorCodeOptions = (t: TFunction): { label: string; value: string }[] => [
|
||||
{ label: t("viewLogs.constants.errorCode400"), value: "400" },
|
||||
{ label: t("viewLogs.constants.errorCode401"), value: "401" },
|
||||
{ label: t("viewLogs.constants.errorCode403"), value: "403" },
|
||||
{ label: t("viewLogs.constants.errorCode404"), value: "404" },
|
||||
{ label: t("viewLogs.constants.errorCode408"), value: "408" },
|
||||
{ label: t("viewLogs.constants.errorCode422"), value: "422" },
|
||||
{ label: t("viewLogs.constants.errorCode429"), value: "429" },
|
||||
{ label: t("viewLogs.constants.errorCode500"), value: "500" },
|
||||
{ label: t("viewLogs.constants.errorCode502"), value: "502" },
|
||||
{ label: t("viewLogs.constants.errorCode503"), value: "503" },
|
||||
{ label: t("viewLogs.constants.errorCode529"), value: "529" },
|
||||
];
|
||||
|
||||
/** Call types that represent MCP tool invocations (shared across columns, index, drawer). */
|
||||
|
|
@ -18,11 +20,11 @@ export const MCP_CALL_TYPES = ["call_mcp_tool", "list_mcp_tools"];
|
|||
/** Call types that represent agent/A2A requests (e.g. asend_message). */
|
||||
export const AGENT_CALL_TYPES = ["asend_message"];
|
||||
|
||||
export const QUICK_SELECT_OPTIONS: { label: string; value: number; unit: string }[] = [
|
||||
{ label: "Last Minute", value: 1, unit: "minutes" },
|
||||
{ label: "Last 15 Minutes", value: 15, unit: "minutes" },
|
||||
{ label: "Last Hour", value: 1, unit: "hours" },
|
||||
{ label: "Last 4 Hours", value: 4, unit: "hours" },
|
||||
{ label: "Last 24 Hours", value: 24, unit: "hours" },
|
||||
{ label: "Last 7 Days", value: 7, unit: "days" },
|
||||
export const getQuickSelectOptions = (t: TFunction): { label: string; value: number; unit: string }[] => [
|
||||
{ label: t("viewLogs.constants.quickSelectLastMinute"), value: 1, unit: "minutes" },
|
||||
{ label: t("viewLogs.constants.quickSelectLast15Minutes"), value: 15, unit: "minutes" },
|
||||
{ label: t("viewLogs.constants.quickSelectLastHour"), value: 1, unit: "hours" },
|
||||
{ label: t("viewLogs.constants.quickSelectLast4Hours"), value: 4, unit: "hours" },
|
||||
{ label: t("viewLogs.constants.quickSelectLast24Hours"), value: 24, unit: "hours" },
|
||||
{ label: t("viewLogs.constants.quickSelectLast7Days"), value: 7, unit: "days" },
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,35 +1,36 @@
|
|||
import type { TFunction } from "i18next";
|
||||
import FilterTeamDropdown from "../common_components/FilterTeamDropdown";
|
||||
import { PaginatedKeyAliasSelect } from "../KeyAliasSelect/PaginatedKeyAliasSelect/PaginatedKeyAliasSelect";
|
||||
import { PaginatedModelSelect } from "../ModelSelect/PaginatedModelSelect/PaginatedModelSelect";
|
||||
import { FilterOption } from "../molecules/filter";
|
||||
import { allEndUsersCall } from "../networking";
|
||||
import { ERROR_CODE_OPTIONS } from "./constants";
|
||||
import { getErrorCodeOptions } from "./constants";
|
||||
import { FILTER_KEYS } from "./log_filter_logic";
|
||||
|
||||
export function getLogFilterOptions(accessToken: string): FilterOption[] {
|
||||
export function getLogFilterOptions(accessToken: string, t: TFunction): FilterOption[] {
|
||||
return [
|
||||
{
|
||||
name: "Team ID",
|
||||
label: "Team ID",
|
||||
name: FILTER_KEYS.TEAM_ID,
|
||||
label: t("viewLogs.filterOptions.teamIdLabel"),
|
||||
customComponent: FilterTeamDropdown,
|
||||
},
|
||||
{
|
||||
name: "Status",
|
||||
label: "Status",
|
||||
name: FILTER_KEYS.STATUS,
|
||||
label: t("viewLogs.filterOptions.statusLabel"),
|
||||
isSearchable: false,
|
||||
options: [
|
||||
{ label: "Success", value: "success" },
|
||||
{ label: "Failure", value: "failure" },
|
||||
{ label: t("viewLogs.filterOptions.statusSuccess"), value: "success" },
|
||||
{ label: t("viewLogs.filterOptions.statusFailure"), value: "failure" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Key Alias",
|
||||
label: "Key Alias",
|
||||
name: FILTER_KEYS.KEY_ALIAS,
|
||||
label: t("viewLogs.filterOptions.keyAliasLabel"),
|
||||
customComponent: PaginatedKeyAliasSelect,
|
||||
},
|
||||
{
|
||||
name: "End User",
|
||||
label: "End User",
|
||||
name: FILTER_KEYS.END_USER,
|
||||
label: t("viewLogs.filterOptions.endUserLabel"),
|
||||
isSearchable: true,
|
||||
searchFn: async (searchText: string) => {
|
||||
const data = await allEndUsersCall(accessToken);
|
||||
|
|
@ -39,38 +40,42 @@ export function getLogFilterOptions(accessToken: string): FilterOption[] {
|
|||
},
|
||||
},
|
||||
{
|
||||
name: "Error Code",
|
||||
label: "Error Code",
|
||||
name: FILTER_KEYS.ERROR_CODE,
|
||||
label: t("viewLogs.filterOptions.errorCodeLabel"),
|
||||
isSearchable: true,
|
||||
searchFn: async (searchText: string) => {
|
||||
if (!searchText) return ERROR_CODE_OPTIONS;
|
||||
const errorCodeOptions = getErrorCodeOptions(t);
|
||||
if (!searchText) return errorCodeOptions;
|
||||
const lower = searchText.toLowerCase();
|
||||
const filtered = ERROR_CODE_OPTIONS.filter((opt) => opt.label.toLowerCase().includes(lower));
|
||||
const isExactValue = ERROR_CODE_OPTIONS.some((opt) => opt.value === searchText.trim());
|
||||
const filtered = errorCodeOptions.filter((opt) => opt.label.toLowerCase().includes(lower));
|
||||
const isExactValue = errorCodeOptions.some((opt) => opt.value === searchText.trim());
|
||||
if (!isExactValue && searchText.trim()) {
|
||||
filtered.push({ label: `Use custom code: ${searchText.trim()}`, value: searchText.trim() });
|
||||
filtered.push({
|
||||
label: t("viewLogs.filterOptions.customCodeLabel", { code: searchText.trim() }),
|
||||
value: searchText.trim(),
|
||||
});
|
||||
}
|
||||
return filtered;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Error Message",
|
||||
label: "Error Message",
|
||||
name: FILTER_KEYS.ERROR_MESSAGE,
|
||||
label: t("viewLogs.filterOptions.errorMessageLabel"),
|
||||
isSearchable: false,
|
||||
},
|
||||
{
|
||||
name: "Key Hash",
|
||||
label: "Key Hash",
|
||||
name: FILTER_KEYS.KEY_HASH,
|
||||
label: t("viewLogs.filterOptions.keyHashLabel"),
|
||||
isSearchable: false,
|
||||
},
|
||||
{
|
||||
name: "Model",
|
||||
label: "Model",
|
||||
name: FILTER_KEYS.MODEL,
|
||||
label: t("viewLogs.filterOptions.modelLabel"),
|
||||
customComponent: PaginatedModelSelect,
|
||||
},
|
||||
{
|
||||
name: FILTER_KEYS.PUBLIC_MODEL_OR_SEARCH_TOOL,
|
||||
label: "Public model / search tool",
|
||||
label: t("viewLogs.filterOptions.publicModelLabel"),
|
||||
isSearchable: false,
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import moment from "moment";
|
||||
import { useCallback, useDeferredValue, useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@tremor/react";
|
||||
import { internalUserRoles } from "../../utils/roles";
|
||||
import DeletedKeysPage from "../DeletedKeysPage/DeletedKeysPage";
|
||||
|
|
@ -27,6 +28,7 @@ interface SpendLogsTableProps {
|
|||
}
|
||||
|
||||
export default function SpendLogsTable({ accessToken, token, userRole, userID, premiumUser }: SpendLogsTableProps) {
|
||||
const { t } = useTranslation();
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize] = useState(50);
|
||||
|
|
@ -237,15 +239,15 @@ export default function SpendLogsTable({ accessToken, token, userRole, userID, p
|
|||
<div className="w-full max-w-screen p-6 overflow-x-hidden box-border">
|
||||
<TabGroup defaultIndex={0} onIndexChange={(index) => setActiveTab(index === 0 ? "request logs" : "audit logs")}>
|
||||
<TabList>
|
||||
<Tab>Request Logs</Tab>
|
||||
<Tab>Audit Logs</Tab>
|
||||
<Tab>Deleted Keys</Tab>
|
||||
<Tab>Deleted Teams</Tab>
|
||||
<Tab>{t("viewLogs.index.tabRequestLogs")}</Tab>
|
||||
<Tab>{t("viewLogs.index.tabAuditLogs")}</Tab>
|
||||
<Tab>{t("viewLogs.index.tabDeletedKeys")}</Tab>
|
||||
<Tab>{t("viewLogs.index.tabDeletedTeams")}</Tab>
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
<TabPanel>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h1 className="text-xl font-semibold">Request Logs</h1>
|
||||
<h1 className="text-xl font-semibold">{t("viewLogs.index.requestLogsTitle")}</h1>
|
||||
</div>
|
||||
{selectedKeyInfo && selectedKeyIdInfoView && selectedKeyInfo.api_key === selectedKeyIdInfoView ? (
|
||||
<KeyInfoView
|
||||
|
|
@ -253,12 +255,12 @@ export default function SpendLogsTable({ accessToken, token, userRole, userID, p
|
|||
keyData={selectedKeyInfo}
|
||||
teams={allTeams ?? []}
|
||||
onClose={() => setSelectedKeyIdInfoView(null)}
|
||||
backButtonText="Back to Logs"
|
||||
backButtonText={t("viewLogs.index.backToLogs")}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<FilterComponent
|
||||
options={getLogFilterOptions(accessToken)}
|
||||
options={getLogFilterOptions(accessToken, t)}
|
||||
onApplyFilters={handleFilterChange}
|
||||
onResetFilters={handleFilterReset}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -2265,6 +2265,47 @@
|
|||
"tokenFlow": {
|
||||
"breakdown": "{{total}} ({{prompt}} prompt tokens + {{completion}} completion tokens)"
|
||||
},
|
||||
"constants": {
|
||||
"errorCode400": "400 - Bad Request",
|
||||
"errorCode401": "401 - Invalid Authentication",
|
||||
"errorCode403": "403 - Permission Denied",
|
||||
"errorCode404": "404 - Not Found",
|
||||
"errorCode408": "408 - Request Timeout",
|
||||
"errorCode422": "422 - Unprocessable Entity",
|
||||
"errorCode429": "429 - Rate Limited",
|
||||
"errorCode500": "500 - Internal Server Error",
|
||||
"errorCode502": "502 - Bad Gateway",
|
||||
"errorCode503": "503 - Service Unavailable",
|
||||
"errorCode529": "529 - Overloaded",
|
||||
"quickSelectLastMinute": "Last Minute",
|
||||
"quickSelectLast15Minutes": "Last 15 Minutes",
|
||||
"quickSelectLastHour": "Last Hour",
|
||||
"quickSelectLast4Hours": "Last 4 Hours",
|
||||
"quickSelectLast24Hours": "Last 24 Hours",
|
||||
"quickSelectLast7Days": "Last 7 Days"
|
||||
},
|
||||
"filterOptions": {
|
||||
"teamIdLabel": "Team ID",
|
||||
"statusLabel": "Status",
|
||||
"statusSuccess": "Success",
|
||||
"statusFailure": "Failure",
|
||||
"keyAliasLabel": "Key Alias",
|
||||
"endUserLabel": "End User",
|
||||
"errorCodeLabel": "Error Code",
|
||||
"customCodeLabel": "Use custom code: {{code}}",
|
||||
"errorMessageLabel": "Error Message",
|
||||
"keyHashLabel": "Key Hash",
|
||||
"modelLabel": "Model",
|
||||
"publicModelLabel": "Public model / search tool"
|
||||
},
|
||||
"index": {
|
||||
"tabRequestLogs": "Request Logs",
|
||||
"tabAuditLogs": "Audit Logs",
|
||||
"tabDeletedKeys": "Deleted Keys",
|
||||
"tabDeletedTeams": "Deleted Teams",
|
||||
"requestLogsTitle": "Request Logs",
|
||||
"backToLogs": "Back to Logs"
|
||||
},
|
||||
"logsTableToolbar": {
|
||||
"searchPlaceholder": "Search by Request ID",
|
||||
"customRange": "Custom Range",
|
||||
|
|
|
|||
|
|
@ -2265,6 +2265,47 @@
|
|||
"tokenFlow": {
|
||||
"breakdown": "{{total}}({{prompt}} 提示词 Token + {{completion}} 补全 Token)"
|
||||
},
|
||||
"constants": {
|
||||
"errorCode400": "400 - 错误请求",
|
||||
"errorCode401": "401 - 身份验证无效",
|
||||
"errorCode403": "403 - 权限被拒绝",
|
||||
"errorCode404": "404 - 未找到",
|
||||
"errorCode408": "408 - 请求超时",
|
||||
"errorCode422": "422 - 无法处理的实体",
|
||||
"errorCode429": "429 - 速率受限",
|
||||
"errorCode500": "500 - 内部服务器错误",
|
||||
"errorCode502": "502 - 错误网关",
|
||||
"errorCode503": "503 - 服务不可用",
|
||||
"errorCode529": "529 - 过载",
|
||||
"quickSelectLastMinute": "最近 1 分钟",
|
||||
"quickSelectLast15Minutes": "最近 15 分钟",
|
||||
"quickSelectLastHour": "最近 1 小时",
|
||||
"quickSelectLast4Hours": "最近 4 小时",
|
||||
"quickSelectLast24Hours": "最近 24 小时",
|
||||
"quickSelectLast7Days": "最近 7 天"
|
||||
},
|
||||
"filterOptions": {
|
||||
"teamIdLabel": "团队 ID",
|
||||
"statusLabel": "状态",
|
||||
"statusSuccess": "成功",
|
||||
"statusFailure": "失败",
|
||||
"keyAliasLabel": "密钥别名",
|
||||
"endUserLabel": "终端用户",
|
||||
"errorCodeLabel": "错误码",
|
||||
"customCodeLabel": "使用自定义错误码:{{code}}",
|
||||
"errorMessageLabel": "错误消息",
|
||||
"keyHashLabel": "密钥哈希",
|
||||
"modelLabel": "模型",
|
||||
"publicModelLabel": "公共模型 / 搜索工具"
|
||||
},
|
||||
"index": {
|
||||
"tabRequestLogs": "请求日志",
|
||||
"tabAuditLogs": "审计日志",
|
||||
"tabDeletedKeys": "已删除密钥",
|
||||
"tabDeletedTeams": "已删除团队",
|
||||
"requestLogsTitle": "请求日志",
|
||||
"backToLogs": "返回日志"
|
||||
},
|
||||
"logsTableToolbar": {
|
||||
"searchPlaceholder": "按请求 ID 搜索",
|
||||
"customRange": "自定义范围",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue