From e3b453fd0ad6c66189eeab8dfcc5552005af544f Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Mon, 20 Jul 2026 22:23:54 -0700 Subject: [PATCH] refactor(ui): migrate audit logs table onto shared DataTable Move the Audit Logs table off the hand-rolled antd Table/Pagination onto the shared DataTable and cell library, matching the other migrated admin tables (Teams, Virtual Keys, Guardrails) The single audit_logs.tsx is split into three PascalCase files: AuditLogsPanel owns the data (server useQuery, pagination and filter state, the row-detail drawer, and the enterprise preview gate), AuditLogsTable is a thin DataTable consumer, and AuditLogsTableColumns exposes getAuditLogsTableColumns. The AuditLogEntry type moves out of the request-logs columns.tsx into the audit columns file, and AuditLogDrawer stays in the parent unchanged Server pagination is wired through paginationMode="server" with the shared footer replacing the standalone antd Pagination, keeping keepPreviousData semantics so page flips keep rows visible and only the initial load shows the skeleton. The six filters (Object ID, Changed By, Team ID, Key Hash, Action, Table) move into a DataTableFilterDrawer plus toolbar with active-filter chips, each resetting the page to the first. The Object ID cell is the clickable identity cell that opens the drawer; there is no whole-row navigation, no selection, and no per-row actions since the table is read-only The enterprise query is now also gated on premiumUser so the preview path no longer fires a doomed request for non-premium users --- .../AuditLogDrawer/AuditLogDrawer.tsx | 2 +- .../components/view_logs/AuditLogsPanel.tsx | 138 ++++++++ .../view_logs/AuditLogsTable.test.tsx | 146 ++++++++ .../components/view_logs/AuditLogsTable.tsx | 208 ++++++++++++ .../view_logs/AuditLogsTableColumns.tsx | 102 ++++++ .../src/components/view_logs/audit_logs.tsx | 315 ------------------ .../src/components/view_logs/columns.tsx | 12 - .../src/components/view_logs/index.tsx | 4 +- 8 files changed, 597 insertions(+), 330 deletions(-) create mode 100644 ui/litellm-dashboard/src/components/view_logs/AuditLogsPanel.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/AuditLogsTable.test.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/AuditLogsTable.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/AuditLogsTableColumns.tsx delete mode 100644 ui/litellm-dashboard/src/components/view_logs/audit_logs.tsx diff --git a/ui/litellm-dashboard/src/components/view_logs/AuditLogDrawer/AuditLogDrawer.tsx b/ui/litellm-dashboard/src/components/view_logs/AuditLogDrawer/AuditLogDrawer.tsx index aa690787f66..81759c80ab6 100644 --- a/ui/litellm-dashboard/src/components/view_logs/AuditLogDrawer/AuditLogDrawer.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/AuditLogDrawer/AuditLogDrawer.tsx @@ -2,7 +2,7 @@ import { Drawer, Tag, Typography } from "antd"; import { CloseOutlined, CopyOutlined, CheckOutlined } from "@ant-design/icons"; import { useState, useCallback } from "react"; import moment from "moment"; -import { AuditLogEntry } from "../columns"; +import { AuditLogEntry } from "../AuditLogsTableColumns"; import DefaultProxyAdminTag from "../../common_components/DefaultProxyAdminTag"; const { Text } = Typography; diff --git a/ui/litellm-dashboard/src/components/view_logs/AuditLogsPanel.tsx b/ui/litellm-dashboard/src/components/view_logs/AuditLogsPanel.tsx new file mode 100644 index 00000000000..81bd4a19f76 --- /dev/null +++ b/ui/litellm-dashboard/src/components/view_logs/AuditLogsPanel.tsx @@ -0,0 +1,138 @@ +import { useCallback, useState } from "react"; +import { useQuery, keepPreviousData } from "@tanstack/react-query"; +import { ColumnFiltersState, OnChangeFn, PaginationState } from "@tanstack/react-table"; +import { resolveLogoSrc } from "@/lib/assetPaths"; +import { uiAuditLogsCall } from "../networking"; +import { AuditLogEntry } from "./AuditLogsTableColumns"; +import { AuditLogsTable } from "./AuditLogsTable"; +import { AuditLogDrawer } from "./AuditLogDrawer/AuditLogDrawer"; + +interface AuditLogsProps { + accessToken: string | null; + token: string | null; + userRole: string | null; + userID: string | null; + isActive: boolean; + premiumUser: boolean; +} + +const asset_logos_folder = "/ui/assets/"; +const auditLogsPreviewImg = `${asset_logos_folder}audit-logs-preview.png`; + +const PAGE_SIZE = 50; + +interface AuditLogsResponse { + audit_logs: AuditLogEntry[]; + total: number; + page: number; + page_size: number; + total_pages: number; +} + +export default function AuditLogsPanel({ + userID, + userRole, + token, + accessToken, + isActive, + premiumUser, +}: AuditLogsProps) { + const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: PAGE_SIZE }); + const [columnFilters, setColumnFilters] = useState([]); + const [selectedLog, setSelectedLog] = useState(null); + const [drawerOpen, setDrawerOpen] = useState(false); + + const getFilterValue = (columnId: string): string | undefined => { + const entry = columnFilters.find((filter) => filter.id === columnId); + return typeof entry?.value === "string" && entry.value.trim() ? entry.value.trim() : undefined; + }; + + const canQueryAuditLogs = !!accessToken && !!token && !!userRole && !!userID && isActive && premiumUser; + + const query = useQuery({ + queryKey: ["audit_logs", pagination.pageIndex, pagination.pageSize, columnFilters], + queryFn: async () => { + if (!accessToken) { + return { audit_logs: [], total: 0, page: 1, page_size: pagination.pageSize, total_pages: 0 }; + } + return uiAuditLogsCall({ + accessToken, + page: pagination.pageIndex + 1, + page_size: pagination.pageSize, + params: { + object_id: getFilterValue("object_id"), + changed_by: getFilterValue("changed_by"), + object_key_hash: getFilterValue("key_hash"), + object_team_id: getFilterValue("team_id"), + action: getFilterValue("action"), + table_name: getFilterValue("table_name"), + sort_by: "updated_at", + sort_order: "desc", + }, + }); + }, + enabled: canQueryAuditLogs, + placeholderData: keepPreviousData, + }); + + const handleColumnFiltersChange = useCallback>((updaterOrValue) => { + setColumnFilters(updaterOrValue); + setPagination((prev) => ({ ...prev, pageIndex: 0 })); + }, []); + + const handleViewLog = useCallback((log: AuditLogEntry) => { + setSelectedLog(log); + setDrawerOpen(true); + }, []); + + if (!premiumUser) { + return ( +
+

✨ Enterprise Feature.

+

+ This is a LiteLLM Enterprise feature, and requires a valid key to use. +

+

+ Here's a preview of what Audit Logs offer: +

+ Audit Logs Preview { + (e.target as HTMLImageElement).style.display = "none"; + }} + /> +
+ ); + } + + return ( + <> +
+

Audit Logs

+
+ + query.refetch()} + onViewLog={handleViewLog} + /> + + setDrawerOpen(false)} log={selectedLog} /> + + ); +} diff --git a/ui/litellm-dashboard/src/components/view_logs/AuditLogsTable.test.tsx b/ui/litellm-dashboard/src/components/view_logs/AuditLogsTable.test.tsx new file mode 100644 index 00000000000..dbb0a39e2ee --- /dev/null +++ b/ui/litellm-dashboard/src/components/view_logs/AuditLogsTable.test.tsx @@ -0,0 +1,146 @@ +import type { ColumnFiltersState, PaginationState } from "@tanstack/react-table"; +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { describe, expect, it, vi } from "vitest"; + +import { AuditLogsTable } from "./AuditLogsTable"; +import type { AuditLogEntry } from "./AuditLogsTableColumns"; + +const ROWS: AuditLogEntry[] = [ + { + id: "log-1", + updated_at: "2026-07-20T12:00:00Z", + changed_by: "default_user_id", + changed_by_api_key: "sk-hash-abc", + action: "created", + table_name: "LiteLLM_TeamTable", + object_id: "team-obj-123", + before_value: {}, + updated_values: { foo: "bar" }, + }, + { + id: "log-2", + updated_at: "2026-07-20T11:00:00Z", + changed_by: "user-42", + changed_by_api_key: "sk-hash-def", + action: "deleted", + table_name: "LiteLLM_UserTable", + object_id: "user-obj-456", + before_value: { a: 1 }, + updated_values: {}, + }, +]; + +const FIRST_PAGE: PaginationState = { pageIndex: 0, pageSize: 50 }; + +function renderTable(overrides: Partial> = {}) { + const props: React.ComponentProps = { + data: ROWS, + rowCount: ROWS.length, + isLoading: false, + isRefreshing: false, + pagination: FIRST_PAGE, + onPaginationChange: vi.fn(), + columnFilters: [], + onColumnFiltersChange: vi.fn(), + onRefresh: vi.fn(), + onViewLog: vi.fn(), + ...overrides, + }; + render(); + return props; +} + +describe("AuditLogsTable", () => { + it("renders each audit column with the migrated shared cells", () => { + renderTable(); + + // Action -> StatusBadge with a capitalized label + expect(screen.getByText("Created")).toBeInTheDocument(); + expect(screen.getByText("Deleted")).toBeInTheDocument(); + // Table name -> display mapping + expect(screen.getByText("Teams")).toBeInTheDocument(); + expect(screen.getByText("Users")).toBeInTheDocument(); + // Changed By -> DefaultProxyAdminTag (default_user_id becomes a labeled tag; other ids stay raw) + expect(screen.getByText("Default Proxy Admin")).toBeInTheDocument(); + expect(screen.getByText("user-42")).toBeInTheDocument(); + // Object ID + API key hash + expect(screen.getByText("team-obj-123")).toBeInTheDocument(); + expect(screen.getByText("sk-hash-abc")).toBeInTheDocument(); + }); + + it("opens the detail drawer from the Object ID identity cell with the full row", async () => { + const user = userEvent.setup(); + const props = renderTable(); + + await user.click(screen.getByText("team-obj-123")); + + expect(props.onViewLog).toHaveBeenCalledTimes(1); + expect(props.onViewLog).toHaveBeenCalledWith(ROWS[0]); + }); + + it("drives the shared footer from the server rowCount and reports page changes", async () => { + const user = userEvent.setup(); + const onPaginationChange = vi.fn(); + renderTable({ rowCount: 120, onPaginationChange }); + + // ceil(120 / 50) = 3 pages, proving rowCount (not data length) feeds the footer + expect(screen.getByTestId("pagination-page")).toHaveTextContent("Page 1 of 3"); + + await user.click(screen.getByTestId("pagination-next")); + expect(onPaginationChange).toHaveBeenCalledTimes(1); + }); + + it("shows skeleton rows while loading and no data rows", () => { + renderTable({ isLoading: true, data: [] }); + + expect(screen.getAllByTestId("skeleton-row").length).toBeGreaterThan(0); + expect(screen.queryByText("No audit logs yet")).toBeNull(); + }); + + it("uses a distinct empty state for unfiltered vs filtered-empty results", () => { + const { unmount } = render( + , + ); + expect(screen.getByText("No audit logs yet")).toBeInTheDocument(); + unmount(); + + renderTable({ data: [], rowCount: 0, columnFilters: [{ id: "action", value: "created" }] }); + expect(screen.getByText("No matching audit logs")).toBeInTheDocument(); + }); + + it("renders active filter chips with human-readable labels", () => { + const filters: ColumnFiltersState = [{ id: "action", value: "created" }]; + renderTable({ columnFilters: filters }); + + const chip = screen.getByTestId("filter-chip-action"); + expect(chip).toHaveTextContent("Action:"); + expect(chip).toHaveTextContent("Created"); + }); + + it("commits a text filter through the filter drawer and reports it to the parent", async () => { + const user = userEvent.setup(); + const onColumnFiltersChange = vi.fn(); + renderTable({ onColumnFiltersChange }); + + await user.click(screen.getByTestId("datatable-filters-trigger")); + await user.type(await screen.findByPlaceholderText("Enter object ID…"), "obj-9"); + await user.click(screen.getByTestId("filter-drawer-apply")); + + expect(onColumnFiltersChange).toHaveBeenCalledTimes(1); + const arg = onColumnFiltersChange.mock.calls[0][0]; + const committed = typeof arg === "function" ? arg([]) : arg; + expect(committed).toEqual([{ id: "object_id", value: "obj-9" }]); + }); +}); diff --git a/ui/litellm-dashboard/src/components/view_logs/AuditLogsTable.tsx b/ui/litellm-dashboard/src/components/view_logs/AuditLogsTable.tsx new file mode 100644 index 00000000000..bcdce12fce8 --- /dev/null +++ b/ui/litellm-dashboard/src/components/view_logs/AuditLogsTable.tsx @@ -0,0 +1,208 @@ +"use client"; + +import { ColumnFiltersState, OnChangeFn, PaginationState } from "@tanstack/react-table"; +import { ScrollText } from "lucide-react"; +import { useMemo, useState } from "react"; + +import { + DataTable, + DataTableFilterDrawer, + DataTableFilterField, + DataTableToolbar, +} from "@/components/shared/DataTable"; +import { Input } from "@/components/ui/input"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; + +import { AUDIT_TABLE_NAME_DISPLAY, AuditLogEntry, getAuditLogsTableColumns } from "./AuditLogsTableColumns"; + +interface AuditLogsTableProps { + data: AuditLogEntry[]; + rowCount: number; + isLoading: boolean; + isRefreshing: boolean; + pagination: PaginationState; + onPaginationChange: OnChangeFn; + columnFilters: ColumnFiltersState; + onColumnFiltersChange: OnChangeFn; + onRefresh: () => void; + onViewLog: (log: AuditLogEntry) => void; +} + +const ALL_VALUE = "all"; + +const ACTION_OPTIONS = [ + { label: "Created", value: "created" }, + { label: "Updated", value: "updated" }, + { label: "Deleted", value: "deleted" }, + { label: "Rotated", value: "rotated" }, +] as const; + +const TABLE_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" }, +] as const; + +const FILTER_LABELS: Record = { + object_id: "Object ID", + changed_by: "Changed By", + team_id: "Team ID", + key_hash: "Key Hash", + action: "Action", + table_name: "Table", +}; + +const formatFilterValue = (columnId: string, value: unknown): string => { + const raw = String(value); + if (columnId === "action") { + return ACTION_OPTIONS.find((option) => option.value === raw)?.label ?? raw; + } + if (columnId === "table_name") { + return AUDIT_TABLE_NAME_DISPLAY[raw] ?? raw; + } + return raw; +}; + +function AuditLogsEmptyState({ filtered }: { filtered: boolean }) { + return ( +
+
+ +
+
+ {filtered ? "No matching audit logs" : "No audit logs yet"} +
+
+ {filtered + ? "No audit log entries match your filters." + : "Administrative changes to keys, teams, users, and models will appear here."} +
+
+ ); +} + +export function AuditLogsTable({ + data, + rowCount, + isLoading, + isRefreshing, + pagination, + onPaginationChange, + columnFilters, + onColumnFiltersChange, + onRefresh, + onViewLog, +}: AuditLogsTableProps) { + const [filtersOpen, setFiltersOpen] = useState(false); + const columns = useMemo(() => getAuditLogsTableColumns({ onViewLog }), [onViewLog]); + + return ( + row.id} + paginationMode="server" + pagination={pagination} + onPaginationChange={onPaginationChange} + rowCount={rowCount} + filterMode="server" + columnFilters={columnFilters} + onColumnFiltersChange={onColumnFiltersChange} + isLoading={isLoading} + loadingMessage="Loading audit logs…" + noDataMessage={ 0} />} + size="compact" + toolbar={(table) => ( + <> + setFiltersOpen(true)} + filterLabels={FILTER_LABELS} + formatFilterValue={formatFilterValue} + showViewOptions={false} + /> + + {({ get, set }) => ( + <> + + set("object_id", event.target.value)} + placeholder="Enter object ID…" + /> + + + set("changed_by", event.target.value)} + placeholder="Enter user ID…" + /> + + + set("team_id", event.target.value)} + placeholder="Enter team ID…" + /> + + + set("key_hash", event.target.value)} + placeholder="Enter key hash…" + /> + + + + + + + + + )} + + + )} + /> + ); +} diff --git a/ui/litellm-dashboard/src/components/view_logs/AuditLogsTableColumns.tsx b/ui/litellm-dashboard/src/components/view_logs/AuditLogsTableColumns.tsx new file mode 100644 index 00000000000..6910ca1c2f7 --- /dev/null +++ b/ui/litellm-dashboard/src/components/view_logs/AuditLogsTableColumns.tsx @@ -0,0 +1,102 @@ +"use client"; + +import { ColumnDef } from "@tanstack/react-table"; + +import { DateCell, IdCell, IdentityCell, StatusBadge, type StatusTone } from "@/components/shared/table_cells"; + +import DefaultProxyAdminTag from "../common_components/DefaultProxyAdminTag"; + +export type AuditLogEntry = { + id: string; + updated_at: string; + changed_by: string; + changed_by_api_key: string; + action: string; + table_name: string; + object_id: string; + before_value: Record; + updated_values: Record; +}; + +export const AUDIT_TABLE_NAME_DISPLAY: Record = { + LiteLLM_VerificationToken: "Keys", + LiteLLM_TeamTable: "Teams", + LiteLLM_UserTable: "Users", + LiteLLM_OrganizationTable: "Organizations", + LiteLLM_ProxyModelTable: "Models", +}; + +const ACTION_TONE: Record = { + created: "success", + updated: "info", + deleted: "error", + rotated: "warning", +}; + +const capitalize = (value: string): string => (value ? value.charAt(0).toUpperCase() + value.slice(1) : value); + +interface AuditLogsTableColumnsDeps { + onViewLog: (log: AuditLogEntry) => void; +} + +export const getAuditLogsTableColumns = ({ onViewLog }: AuditLogsTableColumnsDeps): ColumnDef[] => [ + { + id: "updated_at", + accessorKey: "updated_at", + header: "Timestamp", + size: 200, + enableSorting: false, + cell: ({ row }) => , + }, + { + id: "action", + accessorKey: "action", + header: "Action", + size: 110, + enableSorting: false, + cell: ({ row }) => ( + + ), + }, + { + id: "table_name", + accessorKey: "table_name", + header: "Table", + size: 130, + enableSorting: false, + cell: ({ row }) => ( + {AUDIT_TABLE_NAME_DISPLAY[row.original.table_name] ?? row.original.table_name} + ), + }, + { + id: "object_id", + accessorKey: "object_id", + header: "Object ID", + minSize: 220, + enableSorting: false, + cell: ({ row }) => ( + onViewLog(row.original)} + /> + ), + }, + { + id: "changed_by", + accessorKey: "changed_by", + header: "Changed By", + size: 200, + enableSorting: false, + cell: ({ row }) => , + }, + { + id: "changed_by_api_key", + accessorKey: "changed_by_api_key", + header: "API Key (Hash)", + size: 160, + enableSorting: false, + cell: ({ row }) => , + }, +]; diff --git a/ui/litellm-dashboard/src/components/view_logs/audit_logs.tsx b/ui/litellm-dashboard/src/components/view_logs/audit_logs.tsx deleted file mode 100644 index d811d3b9402..00000000000 --- a/ui/litellm-dashboard/src/components/view_logs/audit_logs.tsx +++ /dev/null @@ -1,315 +0,0 @@ -import { useState } from "react"; -import { useQuery, keepPreviousData } from "@tanstack/react-query"; -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 { resolveLogoSrc } from "@/lib/assetPaths"; -import { DateCell, IdCell } from "@/components/shared/table_cells"; -import { uiAuditLogsCall } from "../networking"; -import { AuditLogEntry } from "./columns"; -import { AuditLogDrawer } from "./AuditLogDrawer/AuditLogDrawer"; -import DefaultProxyAdminTag from "../common_components/DefaultProxyAdminTag"; - -const { Search } = Input; - -interface AuditLogsProps { - accessToken: string | null; - token: string | null; - userRole: string | null; - userID: string | null; - isActive: boolean; - premiumUser: boolean; -} - -const asset_logos_folder = "/ui/assets/"; -export const auditLogsPreviewImg = `${asset_logos_folder}audit-logs-preview.png`; - -const TABLE_NAME_DISPLAY: Record = { - LiteLLM_VerificationToken: "Keys", - LiteLLM_TeamTable: "Teams", - LiteLLM_UserTable: "Users", - LiteLLM_OrganizationTable: "Organizations", - LiteLLM_ProxyModelTable: "Models", -}; - -const ACTION_COLOR: Record = { - created: "green", - updated: "blue", - deleted: "red", - rotated: "orange", -}; - -const PAGE_SIZE = 50; - -export default function AuditLogs({ userID, userRole, token, accessToken, isActive, premiumUser }: AuditLogsProps) { - const [page, setPage] = useState(1); - - // Filter state - const [objectId, setObjectId] = useState(""); - const [changedBy, setChangedBy] = useState(""); - const [keyHash, setKeyHash] = useState(""); - const [teamId, setTeamId] = useState(""); - const [action, setAction] = useState(undefined); - const [tableName, setTableName] = useState(undefined); - - // Drawer state - const [selectedLog, setSelectedLog] = useState(null); - const [drawerOpen, setDrawerOpen] = useState(false); - - const query = useQuery({ - queryKey: ["audit_logs", page, PAGE_SIZE, objectId, changedBy, keyHash, teamId, action, tableName], - queryFn: async () => { - if (!accessToken || !token || !userRole || !userID) { - return { audit_logs: [], total: 0, page: 1, page_size: PAGE_SIZE, total_pages: 0 }; - } - return uiAuditLogsCall({ - accessToken, - page, - page_size: PAGE_SIZE, - params: { - object_id: objectId || undefined, - changed_by: changedBy || undefined, - object_key_hash: keyHash || undefined, - object_team_id: teamId || undefined, - action: action || undefined, - table_name: tableName || undefined, - sort_by: "updated_at", - sort_order: "desc", - }, - }); - }, - enabled: !!accessToken && !!token && !!userRole && !!userID && isActive, - placeholderData: keepPreviousData, - }); - - const resetPage = () => setPage(1); - - const handleRowClick = (log: AuditLogEntry) => { - setSelectedLog(log); - setDrawerOpen(true); - }; - - const columns: ColumnsType = [ - { - title: "Timestamp", - dataIndex: "updated_at", - key: "updated_at", - width: 200, - render: (val: string) => , - }, - { - title: "Action", - dataIndex: "action", - key: "action", - width: 100, - render: (val: string) => ( - - {val} - - ), - }, - { - title: "Table", - dataIndex: "table_name", - key: "table_name", - width: 130, - render: (val: string) => TABLE_NAME_DISPLAY[val] ?? val, - }, - { - title: "Object ID", - dataIndex: "object_id", - key: "object_id", - render: (val: string) => , - }, - { - title: "Changed By", - dataIndex: "changed_by", - key: "changed_by", - width: 200, - render: (val: string) => , - }, - { - title: "API Key (Hash)", - dataIndex: "changed_by_api_key", - key: "changed_by_api_key", - width: 140, - render: (val: string) => , - }, - ]; - - if (!premiumUser) { - return ( -
-

✨ Enterprise Feature.

-

- This is a LiteLLM Enterprise feature, and requires a valid key to use. -

-

- Here's a preview of what Audit Logs offer: -

- Audit Logs Preview { - (e.target as HTMLImageElement).style.display = "none"; - }} - /> -
- ); - } - - const auditLogs: AuditLogEntry[] = query.data?.audit_logs ?? []; - const total: number = query.data?.total ?? 0; - - return ( - <> -
- {/* Header */} -
-
-

Audit Logs

-
- - {/* Filters + pagination on same row */} -
- { - setObjectId(val); - resetPage(); - }} - onChange={(e) => { - if (!e.target.value) { - setObjectId(""); - resetPage(); - } - }} - /> - { - setChangedBy(val); - resetPage(); - }} - onChange={(e) => { - if (!e.target.value) { - setChangedBy(""); - resetPage(); - } - }} - /> - { - setTeamId(val); - resetPage(); - }} - onChange={(e) => { - if (!e.target.value) { - setTeamId(""); - resetPage(); - } - }} - /> - { - setKeyHash(val); - resetPage(); - }} - onChange={(e) => { - if (!e.target.value) { - setKeyHash(""); - resetPage(); - } - }} - /> - { - setTableName(val); - resetPage(); - }} - /> - - {/* Pagination + refresh pushed to the right */} -
-
-
-
- - {/* Table — pagination handled in header */} - - columns={columns} - dataSource={auditLogs} - rowKey="id" - loading={{ - spinning: query.isLoading, - indicator: } size="small" />, - }} - size="small" - pagination={false} - onRow={(record) => ({ - onClick: () => handleRowClick(record), - style: { cursor: "pointer" }, - })} - /> -
- - setDrawerOpen(false)} log={selectedLog} /> - - ); -} diff --git a/ui/litellm-dashboard/src/components/view_logs/columns.tsx b/ui/litellm-dashboard/src/components/view_logs/columns.tsx index 0ce5e8e4717..d3ba90d4e2d 100644 --- a/ui/litellm-dashboard/src/components/view_logs/columns.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/columns.tsx @@ -539,15 +539,3 @@ const CollapsibleJsonCell = ({ jsonData }: { jsonData: any }) => { ); }; - -export type AuditLogEntry = { - id: string; - updated_at: string; - changed_by: string; - changed_by_api_key: string; - action: string; - table_name: string; - object_id: string; - before_value: Record; - updated_values: Record; -}; diff --git a/ui/litellm-dashboard/src/components/view_logs/index.tsx b/ui/litellm-dashboard/src/components/view_logs/index.tsx index ee08712e56b..aa8077a02e9 100644 --- a/ui/litellm-dashboard/src/components/view_logs/index.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/index.tsx @@ -8,7 +8,7 @@ import { KeyResponse } from "../key_team_helpers/key_list"; import FilterComponent from "../molecules/filter"; import { keyInfoV1Call } from "../networking"; import KeyInfoView from "../templates/key_info_view"; -import AuditLogs from "./audit_logs"; +import AuditLogsPanel from "./AuditLogsPanel"; import { createColumns, LogEntry, type LogsSortField } from "./columns"; import { AGENT_CALL_TYPES, MCP_CALL_TYPES } from "./constants"; import { getLogFilterOptions } from "./filter_options"; @@ -296,7 +296,7 @@ export default function SpendLogsTable({ accessToken, token, userRole, userID, p )} -