mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): i18n for view_logs batch 4
This commit is contained in:
parent
888cc88826
commit
6bb974f1e8
10 changed files with 390 additions and 124 deletions
|
|
@ -2,6 +2,7 @@ import moment from "moment";
|
|||
import { useEffect, 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 { getTimeRangeDisplay } from "./logs_utils";
|
||||
import type { PaginatedResponse } from "./log_filter_logic";
|
||||
|
|
@ -49,6 +50,7 @@ export function LogsTableToolbar({
|
|||
onRefetch,
|
||||
filteredLogs,
|
||||
}: LogsTableToolbarProps) {
|
||||
const { t } = useTranslation();
|
||||
const [quickSelectOpen, setQuickSelectOpen] = useState(false);
|
||||
const quickSelectRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
|
|
@ -75,7 +77,7 @@ export function LogsTableToolbar({
|
|||
<div className="relative w-64 min-w-0 flex-shrink-0">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search by Request ID"
|
||||
placeholder={t("viewLogs.logsTableToolbar.searchPlaceholder")}
|
||||
className="w-full px-3 py-2 pl-8 border rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
value={searchTerm}
|
||||
onChange={(e) => onSearchChange(e.target.value)}
|
||||
|
|
@ -140,7 +142,7 @@ export function LogsTableToolbar({
|
|||
className={`w-full px-3 py-2 text-left text-sm hover:bg-gray-50 rounded-md ${isCustomDate ? "bg-blue-50 text-blue-600" : ""}`}
|
||||
onClick={() => onIsCustomDateChange(!isCustomDate)}
|
||||
>
|
||||
Custom Range
|
||||
{t("viewLogs.logsTableToolbar.customRange")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -148,7 +150,7 @@ export function LogsTableToolbar({
|
|||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-gray-900">Live Tail</span>
|
||||
<span className="text-sm font-medium text-gray-900">{t("viewLogs.logsTableToolbar.liveTail")}</span>
|
||||
<Switch checked={isLiveTail} defaultChecked={true} onChange={onIsLiveTailChange} />
|
||||
</div>
|
||||
|
||||
|
|
@ -157,9 +159,9 @@ export function LogsTableToolbar({
|
|||
icon={<SyncOutlined spin={isButtonLoading} />}
|
||||
onClick={onRefetch}
|
||||
disabled={isButtonLoading}
|
||||
title="Fetch data"
|
||||
title={t("viewLogs.logsTableToolbar.fetchData")}
|
||||
>
|
||||
{isButtonLoading ? "Fetching" : "Fetch"}
|
||||
{isButtonLoading ? t("viewLogs.logsTableToolbar.fetching") : t("viewLogs.logsTableToolbar.fetch")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
|
@ -176,7 +178,7 @@ export function LogsTableToolbar({
|
|||
className="px-3 py-2 border rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
<span className="text-gray-500">to</span>
|
||||
<span className="text-gray-500">{t("viewLogs.logsTableToolbar.to")}</span>
|
||||
<div>
|
||||
<input
|
||||
type="datetime-local"
|
||||
|
|
@ -194,28 +196,32 @@ export function LogsTableToolbar({
|
|||
|
||||
<div className="flex items-center space-x-4">
|
||||
<span className="text-sm text-gray-700 whitespace-nowrap">
|
||||
Showing {isLoading ? "..." : filteredLogs ? (currentPage - 1) * pageSize + 1 : 0} -{" "}
|
||||
{isLoading ? "..." : filteredLogs ? Math.min(currentPage * pageSize, filteredLogs.total) : 0} of{" "}
|
||||
{isLoading ? "..." : filteredLogs ? filteredLogs.total : 0} results
|
||||
{t("viewLogs.logsTableToolbar.showing", {
|
||||
from: isLoading ? "..." : filteredLogs ? (currentPage - 1) * pageSize + 1 : 0,
|
||||
to: isLoading ? "..." : filteredLogs ? Math.min(currentPage * pageSize, filteredLogs.total) : 0,
|
||||
total: isLoading ? "..." : filteredLogs ? filteredLogs.total : 0,
|
||||
})}
|
||||
</span>
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-sm text-gray-700 min-w-[90px]">
|
||||
Page {isLoading ? "..." : currentPage} of{" "}
|
||||
{isLoading ? "..." : filteredLogs ? filteredLogs.total_pages : 1}
|
||||
{t("viewLogs.logsTableToolbar.page", {
|
||||
current: isLoading ? "..." : currentPage,
|
||||
total: isLoading ? "..." : filteredLogs ? filteredLogs.total_pages : 1,
|
||||
})}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => onCurrentPageChange((p: number) => Math.max(1, p - 1))}
|
||||
disabled={isLoading || currentPage === 1}
|
||||
className="px-3 py-1 text-sm border rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
Previous
|
||||
{t("common.previous")}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onCurrentPageChange((p: number) => Math.min(filteredLogs.total_pages || 1, p + 1))}
|
||||
disabled={isLoading || currentPage === (filteredLogs.total_pages || 1)}
|
||||
className="px-3 py-1 text-sm border rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
Next
|
||||
{t("common.next")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -224,10 +230,10 @@ export function LogsTableToolbar({
|
|||
{isLiveTail && currentPage === 1 && (
|
||||
<div className="mb-4 px-4 py-2 bg-green-50 border border-green-200 rounded-md flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-green-700">Auto-refreshing every 15 seconds</span>
|
||||
<span className="text-sm text-green-700">{t("viewLogs.logsTableToolbar.autoRefreshing")}</span>
|
||||
</div>
|
||||
<button onClick={() => onIsLiveTailChange(false)} className="text-sm text-green-600 hover:text-green-800">
|
||||
Stop
|
||||
{t("viewLogs.logsTableToolbar.stop")}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { LogEntry } from "./columns";
|
|||
import NotificationsManager from "../molecules/notifications_manager";
|
||||
import { JsonView, defaultStyles } from "react-json-view-lite";
|
||||
import "react-json-view-lite/dist/index.css";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface RequestResponsePanelProps {
|
||||
row: {
|
||||
|
|
@ -24,6 +25,7 @@ export function RequestResponsePanel({
|
|||
getRawRequest,
|
||||
formattedResponse,
|
||||
}: RequestResponsePanelProps) {
|
||||
const { t } = useTranslation();
|
||||
const copyToClipboard = async (text: string) => {
|
||||
try {
|
||||
// Try modern clipboard API first
|
||||
|
|
@ -57,18 +59,18 @@ export function RequestResponsePanel({
|
|||
const handleCopyRequest = async () => {
|
||||
const success = await copyToClipboard(JSON.stringify(getRawRequest(), null, 2));
|
||||
if (success) {
|
||||
NotificationsManager.success("Request copied to clipboard");
|
||||
NotificationsManager.success(t("viewLogs.requestResponsePanel.requestCopied"));
|
||||
} else {
|
||||
NotificationsManager.fromBackend("Failed to copy request");
|
||||
NotificationsManager.fromBackend(t("viewLogs.requestResponsePanel.failedToCopyRequest"));
|
||||
}
|
||||
};
|
||||
|
||||
const handleCopyResponse = async () => {
|
||||
const success = await copyToClipboard(JSON.stringify(formattedResponse(), null, 2));
|
||||
if (success) {
|
||||
NotificationsManager.success("Response copied to clipboard");
|
||||
NotificationsManager.success(t("viewLogs.requestResponsePanel.responseCopied"));
|
||||
} else {
|
||||
NotificationsManager.fromBackend("Failed to copy response");
|
||||
NotificationsManager.fromBackend(t("viewLogs.requestResponsePanel.failedToCopyResponse"));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -77,8 +79,12 @@ export function RequestResponsePanel({
|
|||
{/* Request Side */}
|
||||
<div className="bg-white rounded-lg shadow w-full max-w-full overflow-hidden">
|
||||
<div className="flex justify-between items-center p-4 border-b">
|
||||
<h3 className="text-lg font-medium">Request</h3>
|
||||
<button onClick={handleCopyRequest} className="p-1 hover:bg-gray-200 rounded" title="Copy request">
|
||||
<h3 className="text-lg font-medium">{t("viewLogs.requestResponsePanel.request")}</h3>
|
||||
<button
|
||||
onClick={handleCopyRequest}
|
||||
className="p-1 hover:bg-gray-200 rounded"
|
||||
title={t("viewLogs.requestResponsePanel.copyRequest")}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
|
|
@ -106,13 +112,17 @@ export function RequestResponsePanel({
|
|||
<div className="bg-white rounded-lg shadow w-full max-w-full overflow-hidden">
|
||||
<div className="flex justify-between items-center p-4 border-b">
|
||||
<h3 className="text-lg font-medium">
|
||||
Response
|
||||
{hasError && <span className="ml-2 text-sm text-red-600">• HTTP code {errorInfo?.error_code || 400}</span>}
|
||||
{t("viewLogs.requestResponsePanel.response")}
|
||||
{hasError && (
|
||||
<span className="ml-2 text-sm text-red-600">
|
||||
{t("viewLogs.requestResponsePanel.httpErrorCode", { code: errorInfo?.error_code || 400 })}
|
||||
</span>
|
||||
)}
|
||||
</h3>
|
||||
<button
|
||||
onClick={handleCopyResponse}
|
||||
className="p-1 hover:bg-gray-200 rounded"
|
||||
title="Copy response"
|
||||
title={t("viewLogs.requestResponsePanel.copyResponse")}
|
||||
disabled={!hasResponse && !hasError}
|
||||
>
|
||||
<svg
|
||||
|
|
@ -137,7 +147,9 @@ export function RequestResponsePanel({
|
|||
<JsonView data={formattedResponse()} style={defaultStyles} clickToExpandNode />
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-gray-500 text-sm italic text-center py-4">Response data not available</div>
|
||||
<div className="text-gray-500 text-sm italic text-center py-4">
|
||||
{t("viewLogs.requestResponsePanel.responseNotAvailable")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
*/
|
||||
|
||||
import { Typography, Table } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ParsedTool, ParameterRow } from "./types";
|
||||
|
||||
const { Text } = Typography;
|
||||
|
|
@ -12,6 +13,7 @@ interface FormattedToolViewProps {
|
|||
}
|
||||
|
||||
export function FormattedToolView({ tool }: FormattedToolViewProps) {
|
||||
const { t } = useTranslation();
|
||||
// Parse parameters for table display
|
||||
const parameterRows: ParameterRow[] = Object.entries(tool.parameters?.properties || {}).map(
|
||||
([name, schema]: [string, any]) => ({
|
||||
|
|
@ -25,7 +27,7 @@ export function FormattedToolView({ tool }: FormattedToolViewProps) {
|
|||
|
||||
const columns = [
|
||||
{
|
||||
title: "Parameter",
|
||||
title: t("viewLogs.formattedToolView.colParameter"),
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
render: (name: string, record: ParameterRow) => (
|
||||
|
|
@ -36,7 +38,7 @@ export function FormattedToolView({ tool }: FormattedToolViewProps) {
|
|||
),
|
||||
},
|
||||
{
|
||||
title: "Type",
|
||||
title: t("viewLogs.formattedToolView.colType"),
|
||||
dataIndex: "type",
|
||||
key: "type",
|
||||
render: (type: string) => (
|
||||
|
|
@ -46,7 +48,7 @@ export function FormattedToolView({ tool }: FormattedToolViewProps) {
|
|||
),
|
||||
},
|
||||
{
|
||||
title: "Description",
|
||||
title: t("viewLogs.formattedToolView.colDescription"),
|
||||
dataIndex: "description",
|
||||
key: "description",
|
||||
render: (desc: string) => <Text type="secondary">{desc}</Text>,
|
||||
|
|
@ -80,7 +82,7 @@ export function FormattedToolView({ tool }: FormattedToolViewProps) {
|
|||
marginBottom: 8,
|
||||
}}
|
||||
>
|
||||
Parameters
|
||||
{t("viewLogs.formattedToolView.parametersLabel")}
|
||||
</Text>
|
||||
<Table dataSource={parameterRows} columns={columns} pagination={false} size="small" bordered />
|
||||
</div>
|
||||
|
|
@ -97,7 +99,7 @@ export function FormattedToolView({ tool }: FormattedToolViewProps) {
|
|||
marginBottom: 8,
|
||||
}}
|
||||
>
|
||||
Called With
|
||||
{t("viewLogs.formattedToolView.calledWithLabel")}
|
||||
</Text>
|
||||
<div
|
||||
style={{
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
import { useState } from "react";
|
||||
import { Typography, Radio } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ParsedTool } from "./types";
|
||||
import { FormattedToolView } from "./FormattedToolView";
|
||||
import { JsonToolView } from "./JsonToolView";
|
||||
|
|
@ -17,6 +18,7 @@ interface ToolExpandedContentProps {
|
|||
}
|
||||
|
||||
export function ToolExpandedContent({ tool }: ToolExpandedContentProps) {
|
||||
const { t } = useTranslation();
|
||||
const [viewMode, setViewMode] = useState<ViewMode>("formatted");
|
||||
|
||||
return (
|
||||
|
|
@ -30,11 +32,11 @@ export function ToolExpandedContent({ tool }: ToolExpandedContentProps) {
|
|||
}}
|
||||
>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||
Description
|
||||
{t("viewLogs.toolExpandedContent.descriptionLabel")}
|
||||
</Text>
|
||||
<Radio.Group size="small" value={viewMode} onChange={(e) => setViewMode(e.target.value)}>
|
||||
<Radio.Button value="formatted">Formatted</Radio.Button>
|
||||
<Radio.Button value="json">JSON</Radio.Button>
|
||||
<Radio.Button value="formatted">{t("viewLogs.toolExpandedContent.viewFormatted")}</Radio.Button>
|
||||
<Radio.Button value="json">{t("viewLogs.toolExpandedContent.viewJson")}</Radio.Button>
|
||||
</Radio.Group>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
*/
|
||||
|
||||
import { Collapse, Typography } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { LogEntry } from "../columns";
|
||||
import { parseToolsFromLog } from "./utils";
|
||||
import { ToolItem } from "./ToolItem";
|
||||
|
|
@ -15,6 +16,7 @@ interface ToolsSectionProps {
|
|||
}
|
||||
|
||||
export function ToolsSection({ log }: ToolsSectionProps) {
|
||||
const { t } = useTranslation();
|
||||
const tools = parseToolsFromLog(log);
|
||||
|
||||
// Don't render if no tools
|
||||
|
|
@ -40,9 +42,9 @@ export function ToolsSection({ log }: ToolsSectionProps) {
|
|||
key: "1",
|
||||
label: (
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap" }}>
|
||||
<h3 className="text-lg font-medium text-gray-900">Tools</h3>
|
||||
<h3 className="text-lg font-medium text-gray-900">{t("viewLogs.toolsSection.title")}</h3>
|
||||
<Text type="secondary" style={{ fontSize: 14 }}>
|
||||
{totalTools} provided, {calledTools} called
|
||||
{t("viewLogs.toolsSection.summary", { total: totalTools, called: calledTools })}
|
||||
</Text>
|
||||
<Text type="secondary" style={{ fontSize: 14 }}>
|
||||
• {toolNamePreview}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React, { useState } from "react";
|
||||
import { Collapse } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getProviderLogoAndName } from "../provider_info_helpers";
|
||||
|
||||
interface VectorStoreContent {
|
||||
|
|
@ -31,6 +32,7 @@ interface VectorStoreViewerProps {
|
|||
}
|
||||
|
||||
export function VectorStoreViewer({ data }: VectorStoreViewerProps) {
|
||||
const { t } = useTranslation();
|
||||
const [expandedResults, setExpandedResults] = useState<Record<string, boolean>>({});
|
||||
|
||||
if (!data || data.length === 0) {
|
||||
|
|
@ -63,7 +65,7 @@ export function VectorStoreViewer({ data }: VectorStoreViewerProps) {
|
|||
items={[
|
||||
{
|
||||
key: "1",
|
||||
label: <h3 className="text-lg font-medium text-gray-900">Vector Store Requests</h3>,
|
||||
label: <h3 className="text-lg font-medium text-gray-900">{t("viewLogs.vectorStoreViewer.title")}</h3>,
|
||||
children: (
|
||||
<div className="p-4">
|
||||
{data.map((request, index) => (
|
||||
|
|
@ -72,15 +74,17 @@ export function VectorStoreViewer({ data }: VectorStoreViewerProps) {
|
|||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<div className="flex">
|
||||
<span className="font-medium w-1/3">Query:</span>
|
||||
<span className="font-medium w-1/3">{t("viewLogs.vectorStoreViewer.labelQuery")}</span>
|
||||
<span className="font-mono">{request.query}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="font-medium w-1/3">Vector Store ID:</span>
|
||||
<span className="font-medium w-1/3">
|
||||
{t("viewLogs.vectorStoreViewer.labelVectorStoreId")}
|
||||
</span>
|
||||
<span className="font-mono">{request.vector_store_id}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="font-medium w-1/3">Provider:</span>
|
||||
<span className="font-medium w-1/3">{t("viewLogs.vectorStoreViewer.labelProvider")}</span>
|
||||
<span className="flex items-center">
|
||||
{(() => {
|
||||
const { logo, displayName } = getProviderLogoAndName(request.custom_llm_provider);
|
||||
|
|
@ -96,22 +100,22 @@ export function VectorStoreViewer({ data }: VectorStoreViewerProps) {
|
|||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex">
|
||||
<span className="font-medium w-1/3">Start Time:</span>
|
||||
<span className="font-medium w-1/3">{t("viewLogs.vectorStoreViewer.labelStartTime")}</span>
|
||||
<span>{formatTime(request.start_time)}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="font-medium w-1/3">End Time:</span>
|
||||
<span className="font-medium w-1/3">{t("viewLogs.vectorStoreViewer.labelEndTime")}</span>
|
||||
<span>{formatTime(request.end_time)}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span className="font-medium w-1/3">Duration:</span>
|
||||
<span className="font-medium w-1/3">{t("viewLogs.vectorStoreViewer.labelDuration")}</span>
|
||||
<span>{calculateDuration(request.start_time, request.end_time)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 className="font-medium mb-2">Search Results</h4>
|
||||
<h4 className="font-medium mb-2">{t("viewLogs.vectorStoreViewer.searchResults")}</h4>
|
||||
<div className="space-y-2">
|
||||
{request.vector_store_search_response.data.map((result, resultIndex) => {
|
||||
const isExpanded = expandedResults[`${index}-${resultIndex}`] || false;
|
||||
|
|
@ -131,9 +135,12 @@ export function VectorStoreViewer({ data }: VectorStoreViewerProps) {
|
|||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
<div className="flex items-center">
|
||||
<span className="font-medium mr-2">Result {resultIndex + 1}</span>
|
||||
<span className="font-medium mr-2">
|
||||
{t("viewLogs.vectorStoreViewer.resultItem", { number: resultIndex + 1 })}
|
||||
</span>
|
||||
<span className="text-gray-500 text-sm">
|
||||
Score: <span className="font-mono">{result.score.toFixed(4)}</span>
|
||||
{t("viewLogs.vectorStoreViewer.score")}{" "}
|
||||
<span className="font-mono">{result.score.toFixed(4)}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Table, Tag, Input, Select, Button, Pagination, Spin } from "antd";
|
|||
import { ReloadOutlined, LoadingOutlined } from "@ant-design/icons";
|
||||
import type { ColumnsType } from "antd/es/table";
|
||||
import moment from "moment";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { uiAuditLogsCall } from "../networking";
|
||||
import { AuditLogEntry } from "./columns";
|
||||
import { AuditLogDrawer } from "./AuditLogDrawer/AuditLogDrawer";
|
||||
|
|
@ -11,6 +12,14 @@ import DefaultProxyAdminTag from "../common_components/DefaultProxyAdminTag";
|
|||
|
||||
const { Search } = Input;
|
||||
|
||||
const TABLE_NAME_I18N_KEY: Record<string, string> = {
|
||||
LiteLLM_VerificationToken: "viewLogs.auditLogs.tableKeys",
|
||||
LiteLLM_TeamTable: "viewLogs.auditLogs.tableTeams",
|
||||
LiteLLM_UserTable: "viewLogs.auditLogs.tableUsers",
|
||||
LiteLLM_OrganizationTable: "viewLogs.auditLogs.tableOrganizations",
|
||||
LiteLLM_ProxyModelTable: "viewLogs.auditLogs.tableModels",
|
||||
};
|
||||
|
||||
interface AuditLogsProps {
|
||||
accessToken: string | null;
|
||||
token: string | null;
|
||||
|
|
@ -23,14 +32,6 @@ interface AuditLogsProps {
|
|||
const asset_logos_folder = "../ui/assets/";
|
||||
export const auditLogsPreviewImg = `${asset_logos_folder}audit-logs-preview.png`;
|
||||
|
||||
const TABLE_NAME_DISPLAY: Record<string, string> = {
|
||||
LiteLLM_VerificationToken: "Keys",
|
||||
LiteLLM_TeamTable: "Teams",
|
||||
LiteLLM_UserTable: "Users",
|
||||
LiteLLM_OrganizationTable: "Organizations",
|
||||
LiteLLM_ProxyModelTable: "Models",
|
||||
};
|
||||
|
||||
const ACTION_COLOR: Record<string, string> = {
|
||||
created: "green",
|
||||
updated: "blue",
|
||||
|
|
@ -41,6 +42,7 @@ const ACTION_COLOR: Record<string, string> = {
|
|||
const PAGE_SIZE = 50;
|
||||
|
||||
export default function AuditLogs({ userID, userRole, token, accessToken, isActive, premiumUser }: AuditLogsProps) {
|
||||
const { t } = useTranslation();
|
||||
const [page, setPage] = useState(1);
|
||||
|
||||
// Filter state
|
||||
|
|
@ -90,7 +92,7 @@ export default function AuditLogs({ userID, userRole, token, accessToken, isActi
|
|||
|
||||
const columns: ColumnsType<AuditLogEntry> = [
|
||||
{
|
||||
title: "Timestamp",
|
||||
title: t("viewLogs.auditLogs.colTimestamp"),
|
||||
dataIndex: "updated_at",
|
||||
key: "updated_at",
|
||||
width: 200,
|
||||
|
|
@ -101,7 +103,7 @@ export default function AuditLogs({ userID, userRole, token, accessToken, isActi
|
|||
),
|
||||
},
|
||||
{
|
||||
title: "Action",
|
||||
title: t("viewLogs.auditLogs.colAction"),
|
||||
dataIndex: "action",
|
||||
key: "action",
|
||||
width: 100,
|
||||
|
|
@ -112,27 +114,30 @@ export default function AuditLogs({ userID, userRole, token, accessToken, isActi
|
|||
),
|
||||
},
|
||||
{
|
||||
title: "Table",
|
||||
title: t("viewLogs.auditLogs.colTable"),
|
||||
dataIndex: "table_name",
|
||||
key: "table_name",
|
||||
width: 130,
|
||||
render: (val: string) => TABLE_NAME_DISPLAY[val] ?? val,
|
||||
render: (val: string) => {
|
||||
const key = TABLE_NAME_I18N_KEY[val];
|
||||
return key ? t(key) : val;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Object ID",
|
||||
title: t("viewLogs.auditLogs.colObjectId"),
|
||||
dataIndex: "object_id",
|
||||
key: "object_id",
|
||||
render: (val: string) => <span className="font-mono text-xs">{val}</span>,
|
||||
},
|
||||
{
|
||||
title: "Changed By",
|
||||
title: t("viewLogs.auditLogs.colChangedBy"),
|
||||
dataIndex: "changed_by",
|
||||
key: "changed_by",
|
||||
width: 200,
|
||||
render: (val: string) => <DefaultProxyAdminTag userId={val} />,
|
||||
},
|
||||
{
|
||||
title: "API Key (Hash)",
|
||||
title: t("viewLogs.auditLogs.colApiKeyHash"),
|
||||
dataIndex: "changed_by_api_key",
|
||||
key: "changed_by_api_key",
|
||||
width: 140,
|
||||
|
|
@ -143,16 +148,14 @@ export default function AuditLogs({ userID, userRole, token, accessToken, isActi
|
|||
if (!premiumUser) {
|
||||
return (
|
||||
<div style={{ textAlign: "center", marginTop: "20px" }}>
|
||||
<h1 style={{ display: "block", marginBottom: "10px" }}>✨ Enterprise Feature.</h1>
|
||||
<p style={{ display: "block", marginBottom: "10px" }}>
|
||||
This is a LiteLLM Enterprise feature, and requires a valid key to use.
|
||||
</p>
|
||||
<h1 style={{ display: "block", marginBottom: "10px" }}>✨ {t("viewLogs.auditLogs.enterpriseTitle")}</h1>
|
||||
<p style={{ display: "block", marginBottom: "10px" }}>{t("viewLogs.auditLogs.enterpriseDesc")}</p>
|
||||
<p style={{ display: "block", marginBottom: "20px", fontStyle: "italic" }}>
|
||||
Here's a preview of what Audit Logs offer:
|
||||
{t("viewLogs.auditLogs.previewText")}
|
||||
</p>
|
||||
<img
|
||||
src={auditLogsPreviewImg}
|
||||
alt="Audit Logs Preview"
|
||||
alt={t("viewLogs.auditLogs.previewAlt")}
|
||||
style={{
|
||||
maxWidth: "100%",
|
||||
maxHeight: "700px",
|
||||
|
|
@ -177,13 +180,13 @@ export default function AuditLogs({ userID, userRole, token, accessToken, isActi
|
|||
{/* Header */}
|
||||
<div className="border-b px-6 py-4">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h1 className="text-xl font-semibold">Audit Logs</h1>
|
||||
<h1 className="text-xl font-semibold">{t("viewLogs.auditLogs.pageTitle")}</h1>
|
||||
</div>
|
||||
|
||||
{/* Filters + pagination on same row */}
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<Search
|
||||
placeholder="Object ID"
|
||||
placeholder={t("viewLogs.auditLogs.placeholderObjectId")}
|
||||
allowClear
|
||||
style={{ width: 200 }}
|
||||
onSearch={(val) => {
|
||||
|
|
@ -198,7 +201,7 @@ export default function AuditLogs({ userID, userRole, token, accessToken, isActi
|
|||
}}
|
||||
/>
|
||||
<Search
|
||||
placeholder="Changed By"
|
||||
placeholder={t("viewLogs.auditLogs.placeholderChangedBy")}
|
||||
allowClear
|
||||
style={{ width: 180 }}
|
||||
onSearch={(val) => {
|
||||
|
|
@ -213,7 +216,7 @@ export default function AuditLogs({ userID, userRole, token, accessToken, isActi
|
|||
}}
|
||||
/>
|
||||
<Search
|
||||
placeholder="Team ID"
|
||||
placeholder={t("viewLogs.auditLogs.placeholderTeamId")}
|
||||
allowClear
|
||||
style={{ width: 180 }}
|
||||
onSearch={(val) => {
|
||||
|
|
@ -228,7 +231,7 @@ export default function AuditLogs({ userID, userRole, token, accessToken, isActi
|
|||
}}
|
||||
/>
|
||||
<Search
|
||||
placeholder="Key Hash"
|
||||
placeholder={t("viewLogs.auditLogs.placeholderKeyHash")}
|
||||
allowClear
|
||||
style={{ width: 180 }}
|
||||
onSearch={(val) => {
|
||||
|
|
@ -243,14 +246,14 @@ export default function AuditLogs({ userID, userRole, token, accessToken, isActi
|
|||
}}
|
||||
/>
|
||||
<Select
|
||||
placeholder="All Actions"
|
||||
placeholder={t("viewLogs.auditLogs.allActions")}
|
||||
allowClear
|
||||
style={{ width: 140 }}
|
||||
options={[
|
||||
{ label: "Created", value: "created" },
|
||||
{ label: "Updated", value: "updated" },
|
||||
{ label: "Deleted", value: "deleted" },
|
||||
{ label: "Rotated", value: "rotated" },
|
||||
{ label: t("viewLogs.auditLogs.actionCreated"), value: "created" },
|
||||
{ label: t("viewLogs.auditLogs.actionUpdated"), value: "updated" },
|
||||
{ label: t("viewLogs.auditLogs.actionDeleted"), value: "deleted" },
|
||||
{ label: t("viewLogs.auditLogs.actionRotated"), value: "rotated" },
|
||||
]}
|
||||
onChange={(val) => {
|
||||
setAction(val);
|
||||
|
|
@ -258,15 +261,15 @@ export default function AuditLogs({ userID, userRole, token, accessToken, isActi
|
|||
}}
|
||||
/>
|
||||
<Select
|
||||
placeholder="All Tables"
|
||||
placeholder={t("viewLogs.auditLogs.allTables")}
|
||||
allowClear
|
||||
style={{ width: 150 }}
|
||||
options={[
|
||||
{ label: "Keys", value: "LiteLLM_VerificationToken" },
|
||||
{ label: "Teams", value: "LiteLLM_TeamTable" },
|
||||
{ label: "Users", value: "LiteLLM_UserTable" },
|
||||
{ label: "Organizations", value: "LiteLLM_OrganizationTable" },
|
||||
{ label: "Models", value: "LiteLLM_ProxyModelTable" },
|
||||
{ label: t("viewLogs.auditLogs.tableKeys"), value: "LiteLLM_VerificationToken" },
|
||||
{ label: t("viewLogs.auditLogs.tableTeams"), value: "LiteLLM_TeamTable" },
|
||||
{ label: t("viewLogs.auditLogs.tableUsers"), value: "LiteLLM_UserTable" },
|
||||
{ label: t("viewLogs.auditLogs.tableOrganizations"), value: "LiteLLM_OrganizationTable" },
|
||||
{ label: t("viewLogs.auditLogs.tableModels"), value: "LiteLLM_ProxyModelTable" },
|
||||
]}
|
||||
onChange={(val) => {
|
||||
setTableName(val);
|
||||
|
|
@ -285,7 +288,7 @@ export default function AuditLogs({ userID, userRole, token, accessToken, isActi
|
|||
current={page}
|
||||
pageSize={PAGE_SIZE}
|
||||
total={total}
|
||||
showTotal={(t) => `${t} total`}
|
||||
showTotal={(n) => t("viewLogs.auditLogs.showTotal", { total: n })}
|
||||
showSizeChanger={false}
|
||||
size="small"
|
||||
onChange={(p) => setPage(p)}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import type { ColumnDef } from "@tanstack/react-table";
|
|||
import { Badge, Button } from "@tremor/react";
|
||||
import { Tooltip } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import i18n from "@/lib/i18n";
|
||||
import { getProviderLogoAndName } from "../provider_info_helpers";
|
||||
import { TableHeaderSortDropdown } from "../common_components/TableHeaderSortDropdown/TableHeaderSortDropdown";
|
||||
import { TimeCell } from "./time_cell";
|
||||
|
|
@ -110,19 +111,19 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
header: sortProps
|
||||
? () => (
|
||||
<SortableHeader
|
||||
label="Time"
|
||||
label={i18n.t("viewLogs.columns.colTime")}
|
||||
field="startTime"
|
||||
sortBy={sortProps.sortBy}
|
||||
sortOrder={sortProps.sortOrder}
|
||||
onSortChange={sortProps.onSortChange}
|
||||
/>
|
||||
)
|
||||
: "Time",
|
||||
: i18n.t("viewLogs.columns.colTime"),
|
||||
accessorKey: "startTime",
|
||||
cell: (info: any) => <TimeCell utcTime={info.getValue()} />,
|
||||
},
|
||||
{
|
||||
header: "Type",
|
||||
header: i18n.t("viewLogs.columns.colType"),
|
||||
id: "type",
|
||||
cell: (info: any) => {
|
||||
const row = info.row.original;
|
||||
|
|
@ -166,7 +167,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
},
|
||||
},
|
||||
{
|
||||
header: "Status",
|
||||
header: i18n.t("viewLogs.columns.colStatus"),
|
||||
accessorKey: "metadata.status",
|
||||
cell: (info: any) => {
|
||||
const status = info.getValue() || "Success";
|
||||
|
|
@ -178,13 +179,13 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
isSuccess ? "bg-green-100 text-green-800" : "bg-red-100 text-red-800"
|
||||
}`}
|
||||
>
|
||||
{isSuccess ? "Success" : "Failure"}
|
||||
{isSuccess ? i18n.t("viewLogs.columns.statusSuccess") : i18n.t("viewLogs.columns.statusFailure")}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
header: "Session ID",
|
||||
header: i18n.t("viewLogs.columns.colSessionId"),
|
||||
accessorKey: "session_id",
|
||||
cell: (info: any) => {
|
||||
const value = String(info.getValue() || "");
|
||||
|
|
@ -205,7 +206,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
},
|
||||
|
||||
{
|
||||
header: "Request ID",
|
||||
header: i18n.t("viewLogs.columns.colRequestId"),
|
||||
accessorKey: "request_id",
|
||||
cell: (info: any) => (
|
||||
<Tooltip title={String(info.getValue() || "")}>
|
||||
|
|
@ -217,14 +218,14 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
header: sortProps
|
||||
? () => (
|
||||
<SortableHeader
|
||||
label="Cost"
|
||||
label={i18n.t("viewLogs.columns.colCost")}
|
||||
field="spend"
|
||||
sortBy={sortProps.sortBy}
|
||||
sortOrder={sortProps.sortOrder}
|
||||
onSortChange={sortProps.onSortChange}
|
||||
/>
|
||||
)
|
||||
: "Cost",
|
||||
: i18n.t("viewLogs.columns.colCost"),
|
||||
accessorKey: "spend",
|
||||
cell: (info: any) => {
|
||||
const row = info.row.original;
|
||||
|
|
@ -238,7 +239,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
</Tooltip>
|
||||
{mcpCount > 0 && mcpSpend > 0 && (
|
||||
<span className="text-[10px] text-amber-600">
|
||||
incl. {getSpendString(mcpSpend)} from {mcpCount} MCP
|
||||
{i18n.t("viewLogs.columns.mcpCostNote", { spend: getSpendString(mcpSpend), count: mcpCount })}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -249,14 +250,14 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
header: sortProps
|
||||
? () => (
|
||||
<SortableHeader
|
||||
label="Duration (s)"
|
||||
label={i18n.t("viewLogs.columns.colDuration")}
|
||||
field="request_duration_ms"
|
||||
sortBy={sortProps.sortBy}
|
||||
sortOrder={sortProps.sortOrder}
|
||||
onSortChange={sortProps.onSortChange}
|
||||
/>
|
||||
)
|
||||
: "Duration (s)",
|
||||
: i18n.t("viewLogs.columns.colDuration"),
|
||||
accessorKey: "request_duration_ms",
|
||||
cell: (info: any) => {
|
||||
const ms = info.getValue();
|
||||
|
|
@ -273,14 +274,14 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
header: sortProps
|
||||
? () => (
|
||||
<SortableHeader
|
||||
label="TTFT (s)"
|
||||
label={i18n.t("viewLogs.columns.colTtft")}
|
||||
field="ttft_ms"
|
||||
sortBy={sortProps.sortBy}
|
||||
sortOrder={sortProps.sortOrder}
|
||||
onSortChange={sortProps.onSortChange}
|
||||
/>
|
||||
)
|
||||
: "TTFT (s)",
|
||||
: i18n.t("viewLogs.columns.colTtft"),
|
||||
accessorKey: "completionStartTime",
|
||||
cell: (info: any) => {
|
||||
const row = info.row.original;
|
||||
|
|
@ -299,7 +300,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
},
|
||||
},
|
||||
{
|
||||
header: "Team Name",
|
||||
header: i18n.t("viewLogs.columns.colTeamName"),
|
||||
accessorKey: "metadata.user_api_key_team_alias",
|
||||
cell: (info: any) => (
|
||||
<Tooltip title={String(info.getValue() || "-")}>
|
||||
|
|
@ -308,7 +309,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
),
|
||||
},
|
||||
{
|
||||
header: "Key Hash",
|
||||
header: i18n.t("viewLogs.columns.colKeyHash"),
|
||||
accessorKey: "metadata.user_api_key",
|
||||
cell: (info: any) => {
|
||||
const value = String(info.getValue() || "-");
|
||||
|
|
@ -327,7 +328,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
},
|
||||
},
|
||||
{
|
||||
header: "Key Name",
|
||||
header: i18n.t("viewLogs.columns.colKeyName"),
|
||||
accessorKey: "metadata.user_api_key_alias",
|
||||
cell: (info: any) => (
|
||||
<Tooltip title={String(info.getValue() || "-")}>
|
||||
|
|
@ -339,14 +340,14 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
header: sortProps
|
||||
? () => (
|
||||
<SortableHeader
|
||||
label="Model"
|
||||
label={i18n.t("viewLogs.columns.colModel")}
|
||||
field="model"
|
||||
sortBy={sortProps.sortBy}
|
||||
sortOrder={sortProps.sortOrder}
|
||||
onSortChange={sortProps.onSortChange}
|
||||
/>
|
||||
)
|
||||
: "Model",
|
||||
: i18n.t("viewLogs.columns.colModel"),
|
||||
accessorKey: "model",
|
||||
cell: (info: any) => {
|
||||
const row = info.row.original;
|
||||
|
|
@ -376,14 +377,14 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
header: sortProps
|
||||
? () => (
|
||||
<SortableHeader
|
||||
label="Tokens"
|
||||
label={i18n.t("viewLogs.columns.colTokens")}
|
||||
field="total_tokens"
|
||||
sortBy={sortProps.sortBy}
|
||||
sortOrder={sortProps.sortOrder}
|
||||
onSortChange={sortProps.onSortChange}
|
||||
/>
|
||||
)
|
||||
: "Tokens",
|
||||
: i18n.t("viewLogs.columns.colTokens"),
|
||||
accessorKey: "total_tokens",
|
||||
cell: (info: any) => {
|
||||
const row = info.row.original;
|
||||
|
|
@ -398,7 +399,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
},
|
||||
},
|
||||
{
|
||||
header: "Internal User",
|
||||
header: i18n.t("viewLogs.columns.colInternalUser"),
|
||||
accessorKey: "user",
|
||||
cell: (info: any) => (
|
||||
<Tooltip title={String(info.getValue() || "-")}>
|
||||
|
|
@ -407,7 +408,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
),
|
||||
},
|
||||
{
|
||||
header: "End User",
|
||||
header: i18n.t("viewLogs.columns.colEndUser"),
|
||||
accessorKey: "end_user",
|
||||
cell: (info: any) => (
|
||||
<Tooltip title={String(info.getValue() || "-")}>
|
||||
|
|
@ -417,7 +418,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
},
|
||||
|
||||
{
|
||||
header: "Tags",
|
||||
header: i18n.t("viewLogs.columns.colTags"),
|
||||
accessorKey: "request_tags",
|
||||
cell: (info: any) => {
|
||||
const tags = info.getValue();
|
||||
|
|
@ -483,11 +484,11 @@ export const RequestResponsePanel = ({ request, response }: { request: any; resp
|
|||
<div className="grid grid-cols-2 gap-4 mt-4">
|
||||
<div className="rounded-lg border border-gray-200 bg-gray-50">
|
||||
<div className="flex justify-between items-center p-3 border-b border-gray-200">
|
||||
<h3 className="text-sm font-medium">Request</h3>
|
||||
<h3 className="text-sm font-medium">{i18n.t("viewLogs.requestResponsePanel.request")}</h3>
|
||||
<button
|
||||
onClick={() => copyToClipboard(requestStr)}
|
||||
className="p-1 hover:bg-gray-200 rounded"
|
||||
title="Copy request"
|
||||
title={i18n.t("viewLogs.requestResponsePanel.copyRequest")}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
|
@ -510,11 +511,11 @@ export const RequestResponsePanel = ({ request, response }: { request: any; resp
|
|||
|
||||
<div className="rounded-lg border border-gray-200 bg-gray-50">
|
||||
<div className="flex justify-between items-center p-3 border-b border-gray-200">
|
||||
<h3 className="text-sm font-medium">Response</h3>
|
||||
<h3 className="text-sm font-medium">{i18n.t("viewLogs.requestResponsePanel.response")}</h3>
|
||||
<button
|
||||
onClick={() => copyToClipboard(responseStr)}
|
||||
className="p-1 hover:bg-gray-200 rounded"
|
||||
title="Copy response"
|
||||
title={i18n.t("viewLogs.requestResponsePanel.copyResponse")}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
|
@ -550,7 +551,8 @@ const CollapsibleJsonCell = ({ jsonData }: { jsonData: any }) => {
|
|||
return (
|
||||
<div>
|
||||
<button onClick={() => setIsExpanded(!isExpanded)} className="text-blue-500 hover:text-blue-700 text-xs">
|
||||
{isExpanded ? "Hide JSON" : "Show JSON"} ({Object.keys(jsonData).length} fields)
|
||||
{isExpanded ? i18n.t("viewLogs.columns.hideJson") : i18n.t("viewLogs.columns.showJson")} (
|
||||
{Object.keys(jsonData).length} fields)
|
||||
</button>
|
||||
{isExpanded && (
|
||||
<pre className="mt-2 p-2 bg-gray-50 border rounded text-xs overflow-auto max-h-60">{jsonString}</pre>
|
||||
|
|
@ -596,7 +598,7 @@ export const auditLogColumns: ColumnDef<AuditLogEntry>[] = [
|
|||
<button
|
||||
onClick={toggleHandler}
|
||||
style={{ cursor: "pointer" }}
|
||||
aria-label={localExpanded ? "Collapse row" : "Expand row"}
|
||||
aria-label={localExpanded ? i18n.t("viewLogs.columns.collapseRow") : i18n.t("viewLogs.columns.expandRow")}
|
||||
className="w-6 h-6 flex items-center justify-center focus:outline-none"
|
||||
>
|
||||
<svg
|
||||
|
|
@ -617,31 +619,31 @@ export const auditLogColumns: ColumnDef<AuditLogEntry>[] = [
|
|||
},
|
||||
},
|
||||
{
|
||||
header: "Timestamp",
|
||||
header: i18n.t("viewLogs.columns.colTime"),
|
||||
accessorKey: "updated_at",
|
||||
cell: (info: any) => <TimeCell utcTime={info.getValue()} />,
|
||||
},
|
||||
{
|
||||
header: "Table Name",
|
||||
header: i18n.t("viewLogs.columns.colTableName"),
|
||||
accessorKey: "table_name",
|
||||
cell: (info: any) => {
|
||||
const tableName = info.getValue();
|
||||
let displayValue = tableName;
|
||||
switch (tableName) {
|
||||
case "LiteLLM_VerificationToken":
|
||||
displayValue = "Keys";
|
||||
displayValue = i18n.t("viewLogs.columns.auditTableKeys");
|
||||
break;
|
||||
case "LiteLLM_TeamTable":
|
||||
displayValue = "Teams";
|
||||
displayValue = i18n.t("viewLogs.columns.auditTableTeams");
|
||||
break;
|
||||
case "LiteLLM_OrganizationTable":
|
||||
displayValue = "Organizations";
|
||||
displayValue = i18n.t("viewLogs.columns.auditTableOrganizations");
|
||||
break;
|
||||
case "LiteLLM_UserTable":
|
||||
displayValue = "Users";
|
||||
displayValue = i18n.t("viewLogs.columns.auditTableUsers");
|
||||
break;
|
||||
case "LiteLLM_ProxyModelTable":
|
||||
displayValue = "Models";
|
||||
displayValue = i18n.t("viewLogs.columns.auditTableModels");
|
||||
break;
|
||||
default:
|
||||
displayValue = tableName;
|
||||
|
|
@ -650,12 +652,12 @@ export const auditLogColumns: ColumnDef<AuditLogEntry>[] = [
|
|||
},
|
||||
},
|
||||
{
|
||||
header: "Action",
|
||||
header: i18n.t("viewLogs.columns.colAction"),
|
||||
accessorKey: "action",
|
||||
cell: (info: any) => <span>{getActionBadge(info.getValue())}</span>,
|
||||
},
|
||||
{
|
||||
header: "Changed By",
|
||||
header: i18n.t("viewLogs.columns.colChangedBy"),
|
||||
accessorKey: "changed_by",
|
||||
cell: (info: any) => {
|
||||
const changedBy = info.row.original.changed_by;
|
||||
|
|
@ -677,7 +679,7 @@ export const auditLogColumns: ColumnDef<AuditLogEntry>[] = [
|
|||
},
|
||||
},
|
||||
{
|
||||
header: "Affected Item ID",
|
||||
header: i18n.t("viewLogs.columns.colAffectedItemId"),
|
||||
accessorKey: "object_id",
|
||||
cell: (props) => {
|
||||
const ObjectIdDisplay = () => {
|
||||
|
|
@ -697,7 +699,7 @@ export const auditLogColumns: ColumnDef<AuditLogEntry>[] = [
|
|||
};
|
||||
|
||||
return (
|
||||
<Tooltip title={copied ? "Copied!" : String(objectId)}>
|
||||
<Tooltip title={copied ? i18n.t("viewLogs.columns.copied") : String(objectId)}>
|
||||
<span className="max-w-[20ch] truncate block cursor-pointer hover:text-blue-600" onClick={handleCopy}>
|
||||
{String(objectId)}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -2264,6 +2264,121 @@
|
|||
},
|
||||
"tokenFlow": {
|
||||
"breakdown": "{{total}} ({{prompt}} prompt tokens + {{completion}} completion tokens)"
|
||||
},
|
||||
"logsTableToolbar": {
|
||||
"searchPlaceholder": "Search by Request ID",
|
||||
"customRange": "Custom Range",
|
||||
"liveTail": "Live Tail",
|
||||
"fetchData": "Fetch data",
|
||||
"fetching": "Fetching",
|
||||
"fetch": "Fetch",
|
||||
"to": "to",
|
||||
"showing": "Showing {{from}} - {{to}} of {{total}} results",
|
||||
"page": "Page {{current}} of {{total}}",
|
||||
"autoRefreshing": "Auto-refreshing every 15 seconds",
|
||||
"stop": "Stop"
|
||||
},
|
||||
"requestResponsePanel": {
|
||||
"requestCopied": "Request copied to clipboard",
|
||||
"failedToCopyRequest": "Failed to copy request",
|
||||
"responseCopied": "Response copied to clipboard",
|
||||
"failedToCopyResponse": "Failed to copy response",
|
||||
"request": "Request",
|
||||
"response": "Response",
|
||||
"copyRequest": "Copy request",
|
||||
"copyResponse": "Copy response",
|
||||
"httpErrorCode": "• HTTP code {{code}}",
|
||||
"responseNotAvailable": "Response data not available"
|
||||
},
|
||||
"formattedToolView": {
|
||||
"colParameter": "Parameter",
|
||||
"colType": "Type",
|
||||
"colDescription": "Description",
|
||||
"parametersLabel": "Parameters",
|
||||
"calledWithLabel": "Called With"
|
||||
},
|
||||
"toolExpandedContent": {
|
||||
"descriptionLabel": "Description",
|
||||
"viewFormatted": "Formatted",
|
||||
"viewJson": "JSON"
|
||||
},
|
||||
"toolsSection": {
|
||||
"title": "Tools",
|
||||
"summary": "{{total}} provided, {{called}} called"
|
||||
},
|
||||
"vectorStoreViewer": {
|
||||
"title": "Vector Store Requests",
|
||||
"labelQuery": "Query:",
|
||||
"labelVectorStoreId": "Vector Store ID:",
|
||||
"labelProvider": "Provider:",
|
||||
"labelStartTime": "Start Time:",
|
||||
"labelEndTime": "End Time:",
|
||||
"labelDuration": "Duration:",
|
||||
"searchResults": "Search Results",
|
||||
"resultItem": "Result {{number}}",
|
||||
"score": "Score:"
|
||||
},
|
||||
"auditLogs": {
|
||||
"enterpriseTitle": "Enterprise Feature.",
|
||||
"enterpriseDesc": "This is a LiteLLM Enterprise feature, and requires a valid key to use.",
|
||||
"previewText": "Here's a preview of what Audit Logs offer:",
|
||||
"previewAlt": "Audit Logs Preview",
|
||||
"pageTitle": "Audit Logs",
|
||||
"placeholderObjectId": "Object ID",
|
||||
"placeholderChangedBy": "Changed By",
|
||||
"placeholderTeamId": "Team ID",
|
||||
"placeholderKeyHash": "Key Hash",
|
||||
"allActions": "All Actions",
|
||||
"allTables": "All Tables",
|
||||
"tableKeys": "Keys",
|
||||
"tableTeams": "Teams",
|
||||
"tableUsers": "Users",
|
||||
"tableOrganizations": "Organizations",
|
||||
"tableModels": "Models",
|
||||
"actionCreated": "Created",
|
||||
"actionUpdated": "Updated",
|
||||
"actionDeleted": "Deleted",
|
||||
"actionRotated": "Rotated",
|
||||
"showTotal": "{{total}} total",
|
||||
"colTimestamp": "Timestamp",
|
||||
"colAction": "Action",
|
||||
"colTable": "Table",
|
||||
"colObjectId": "Object ID",
|
||||
"colChangedBy": "Changed By",
|
||||
"colApiKeyHash": "API Key (Hash)"
|
||||
},
|
||||
"columns": {
|
||||
"colTime": "Time",
|
||||
"colType": "Type",
|
||||
"colStatus": "Status",
|
||||
"colSessionId": "Session ID",
|
||||
"colRequestId": "Request ID",
|
||||
"colCost": "Cost",
|
||||
"colDuration": "Duration (s)",
|
||||
"colTtft": "TTFT (s)",
|
||||
"colTeamName": "Team Name",
|
||||
"colKeyHash": "Key Hash",
|
||||
"colKeyName": "Key Name",
|
||||
"colModel": "Model",
|
||||
"colTokens": "Tokens",
|
||||
"colInternalUser": "Internal User",
|
||||
"colEndUser": "End User",
|
||||
"colTags": "Tags",
|
||||
"statusSuccess": "Success",
|
||||
"statusFailure": "Failure",
|
||||
"mcpCostNote": "incl. {{spend}} from {{count}} MCP",
|
||||
"colTableName": "Table Name",
|
||||
"colAffectedItemId": "Affected Item ID",
|
||||
"auditTableKeys": "Keys",
|
||||
"auditTableTeams": "Teams",
|
||||
"auditTableOrganizations": "Organizations",
|
||||
"auditTableUsers": "Users",
|
||||
"auditTableModels": "Models",
|
||||
"expandRow": "Expand row",
|
||||
"collapseRow": "Collapse row",
|
||||
"hideJson": "Hide JSON",
|
||||
"showJson": "Show JSON",
|
||||
"copied": "Copied!"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2264,6 +2264,121 @@
|
|||
},
|
||||
"tokenFlow": {
|
||||
"breakdown": "{{total}}({{prompt}} 提示词 Token + {{completion}} 补全 Token)"
|
||||
},
|
||||
"logsTableToolbar": {
|
||||
"searchPlaceholder": "按请求 ID 搜索",
|
||||
"customRange": "自定义范围",
|
||||
"liveTail": "实时追踪",
|
||||
"fetchData": "获取数据",
|
||||
"fetching": "获取中",
|
||||
"fetch": "获取",
|
||||
"to": "至",
|
||||
"showing": "显示第 {{from}} - {{to}} 条,共 {{total}} 条",
|
||||
"page": "第 {{current}} 页,共 {{total}} 页",
|
||||
"autoRefreshing": "每 15 秒自动刷新",
|
||||
"stop": "停止"
|
||||
},
|
||||
"requestResponsePanel": {
|
||||
"requestCopied": "请求已复制到剪贴板",
|
||||
"failedToCopyRequest": "复制请求失败",
|
||||
"responseCopied": "响应已复制到剪贴板",
|
||||
"failedToCopyResponse": "复制响应失败",
|
||||
"request": "请求",
|
||||
"response": "响应",
|
||||
"copyRequest": "复制请求",
|
||||
"copyResponse": "复制响应",
|
||||
"httpErrorCode": "• HTTP 错误码 {{code}}",
|
||||
"responseNotAvailable": "响应数据不可用"
|
||||
},
|
||||
"formattedToolView": {
|
||||
"colParameter": "参数",
|
||||
"colType": "类型",
|
||||
"colDescription": "描述",
|
||||
"parametersLabel": "参数",
|
||||
"calledWithLabel": "调用参数"
|
||||
},
|
||||
"toolExpandedContent": {
|
||||
"descriptionLabel": "描述",
|
||||
"viewFormatted": "格式化",
|
||||
"viewJson": "JSON"
|
||||
},
|
||||
"toolsSection": {
|
||||
"title": "工具",
|
||||
"summary": "共 {{total}} 个,已调用 {{called}} 个"
|
||||
},
|
||||
"vectorStoreViewer": {
|
||||
"title": "向量存储请求",
|
||||
"labelQuery": "查询:",
|
||||
"labelVectorStoreId": "向量存储 ID:",
|
||||
"labelProvider": "提供商:",
|
||||
"labelStartTime": "开始时间:",
|
||||
"labelEndTime": "结束时间:",
|
||||
"labelDuration": "耗时:",
|
||||
"searchResults": "搜索结果",
|
||||
"resultItem": "结果 {{number}}",
|
||||
"score": "得分:"
|
||||
},
|
||||
"auditLogs": {
|
||||
"enterpriseTitle": "企业版功能。",
|
||||
"enterpriseDesc": "这是 LiteLLM 企业版功能,需要有效的许可证密钥方可使用。",
|
||||
"previewText": "以下是审计日志功能的预览:",
|
||||
"previewAlt": "审计日志预览",
|
||||
"pageTitle": "审计日志",
|
||||
"placeholderObjectId": "对象 ID",
|
||||
"placeholderChangedBy": "操作人",
|
||||
"placeholderTeamId": "团队 ID",
|
||||
"placeholderKeyHash": "密钥哈希",
|
||||
"allActions": "所有操作",
|
||||
"allTables": "所有表",
|
||||
"tableKeys": "密钥",
|
||||
"tableTeams": "团队",
|
||||
"tableUsers": "用户",
|
||||
"tableOrganizations": "组织",
|
||||
"tableModels": "模型",
|
||||
"actionCreated": "创建",
|
||||
"actionUpdated": "更新",
|
||||
"actionDeleted": "删除",
|
||||
"actionRotated": "轮换",
|
||||
"showTotal": "共 {{total}} 条",
|
||||
"colTimestamp": "时间戳",
|
||||
"colAction": "操作",
|
||||
"colTable": "表",
|
||||
"colObjectId": "对象 ID",
|
||||
"colChangedBy": "操作人",
|
||||
"colApiKeyHash": "API 密钥(哈希)"
|
||||
},
|
||||
"columns": {
|
||||
"colTime": "时间",
|
||||
"colType": "类型",
|
||||
"colStatus": "状态",
|
||||
"colSessionId": "会话 ID",
|
||||
"colRequestId": "请求 ID",
|
||||
"colCost": "费用",
|
||||
"colDuration": "耗时(秒)",
|
||||
"colTtft": "TTFT(秒)",
|
||||
"colTeamName": "团队名称",
|
||||
"colKeyHash": "密钥哈希",
|
||||
"colKeyName": "密钥名称",
|
||||
"colModel": "模型",
|
||||
"colTokens": "Token",
|
||||
"colInternalUser": "内部用户",
|
||||
"colEndUser": "终端用户",
|
||||
"colTags": "标签",
|
||||
"statusSuccess": "成功",
|
||||
"statusFailure": "失败",
|
||||
"mcpCostNote": "含 {{count}} 个 MCP 花费 {{spend}}",
|
||||
"colTableName": "表名称",
|
||||
"colAffectedItemId": "受影响项目 ID",
|
||||
"auditTableKeys": "密钥",
|
||||
"auditTableTeams": "团队",
|
||||
"auditTableOrganizations": "组织",
|
||||
"auditTableUsers": "用户",
|
||||
"auditTableModels": "模型",
|
||||
"expandRow": "展开行",
|
||||
"collapseRow": "折叠行",
|
||||
"hideJson": "隐藏 JSON",
|
||||
"showJson": "显示 JSON",
|
||||
"copied": "已复制!"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue