mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
Merge pull request #31426 from BerriAI/litellm_/cranky-hamilton-21b5d0
fix(ui): stop Request Logs page from overflowing horizontally and size its columns
This commit is contained in:
commit
f8a2ea7378
5 changed files with 74 additions and 4 deletions
|
|
@ -126,7 +126,7 @@ function DashboardShell({ children }: { children: React.ReactNode }) {
|
|||
<div className="mt-2">
|
||||
<SidebarProvider setPage={navigateToPage} defaultSelectedKey={page} sidebarCollapsed={sidebarCollapsed} />
|
||||
</div>
|
||||
<main className="flex-1">{children}</main>
|
||||
<main className="flex-1 min-w-0">{children}</main>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -119,11 +119,13 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
)
|
||||
: "Time",
|
||||
accessorKey: "startTime",
|
||||
size: 200,
|
||||
cell: (info: any) => <TimeCell utcTime={info.getValue()} />,
|
||||
},
|
||||
{
|
||||
header: "Type",
|
||||
id: "type",
|
||||
size: 90,
|
||||
cell: (info: any) => {
|
||||
const row = info.row.original;
|
||||
const sessionCount = row.session_total_count || 1;
|
||||
|
|
@ -168,6 +170,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
{
|
||||
header: "Status",
|
||||
accessorKey: "metadata.status",
|
||||
size: 100,
|
||||
cell: (info: any) => {
|
||||
const status = info.getValue() || "Success";
|
||||
const isSuccess = status.toLowerCase() !== "failure";
|
||||
|
|
@ -186,6 +189,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
{
|
||||
header: "Session ID",
|
||||
accessorKey: "session_id",
|
||||
size: 120,
|
||||
cell: (info: any) => {
|
||||
const value = String(info.getValue() || "");
|
||||
const onSessionClick = info.row.original.onSessionClick;
|
||||
|
|
@ -226,6 +230,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
)
|
||||
: "Cost",
|
||||
accessorKey: "spend",
|
||||
size: 110,
|
||||
cell: (info: any) => {
|
||||
const row = info.row.original;
|
||||
const mcpCount = row.mcp_tool_call_count || 0;
|
||||
|
|
@ -301,6 +306,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
{
|
||||
header: "Team Name",
|
||||
accessorKey: "metadata.user_api_key_team_alias",
|
||||
size: 150,
|
||||
cell: (info: any) => (
|
||||
<Tooltip title={String(info.getValue() || "-")}>
|
||||
<span className="max-w-[15ch] truncate block">{String(info.getValue() || "-")}</span>
|
||||
|
|
@ -310,6 +316,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
{
|
||||
header: "Key Hash",
|
||||
accessorKey: "metadata.user_api_key",
|
||||
size: 110,
|
||||
cell: (info: any) => {
|
||||
const value = String(info.getValue() || "-");
|
||||
const onKeyHashClick = info.row.original.onKeyHashClick;
|
||||
|
|
@ -329,6 +336,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
{
|
||||
header: "Key Alias",
|
||||
accessorKey: "metadata.user_api_key_alias",
|
||||
size: 150,
|
||||
cell: (info: any) => (
|
||||
<Tooltip title={String(info.getValue() || "-")}>
|
||||
<span className="max-w-[15ch] truncate block">{String(info.getValue() || "-")}</span>
|
||||
|
|
@ -348,6 +356,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
)
|
||||
: "Model",
|
||||
accessorKey: "model",
|
||||
size: 200,
|
||||
cell: (info: any) => {
|
||||
const row = info.row.original;
|
||||
const provider = row.custom_llm_provider;
|
||||
|
|
@ -385,6 +394,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
)
|
||||
: "Tokens",
|
||||
accessorKey: "total_tokens",
|
||||
size: 140,
|
||||
cell: (info: any) => {
|
||||
const row = info.row.original;
|
||||
return (
|
||||
|
|
@ -400,6 +410,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
{
|
||||
header: "Internal User",
|
||||
accessorKey: "user",
|
||||
size: 150,
|
||||
cell: (info: any) => (
|
||||
<Tooltip title={String(info.getValue() || "-")}>
|
||||
<span className="max-w-[15ch] truncate block">{String(info.getValue() || "-")}</span>
|
||||
|
|
@ -409,6 +420,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
{
|
||||
header: "End User",
|
||||
accessorKey: "end_user",
|
||||
size: 140,
|
||||
cell: (info: any) => (
|
||||
<Tooltip title={String(info.getValue() || "-")}>
|
||||
<span className="max-w-[15ch] truncate block">{String(info.getValue() || "-")}</span>
|
||||
|
|
@ -419,6 +431,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
{
|
||||
header: "Tags",
|
||||
accessorKey: "request_tags",
|
||||
size: 150,
|
||||
cell: (info: any) => {
|
||||
const tags = info.getValue();
|
||||
if (!tags || Object.keys(tags).length === 0) return "-";
|
||||
|
|
|
|||
|
|
@ -234,7 +234,7 @@ export default function SpendLogsTable({ accessToken, token, userRole, userID, p
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-screen p-6 overflow-x-hidden box-border">
|
||||
<div className="w-full p-6 overflow-x-hidden box-border">
|
||||
<TabGroup defaultIndex={0} onIndexChange={(index) => setActiveTab(index === 0 ? "request logs" : "audit logs")}>
|
||||
<TabList>
|
||||
<Tab>Request Logs</Tab>
|
||||
|
|
|
|||
46
ui/litellm-dashboard/src/components/view_logs/table.test.tsx
Normal file
46
ui/litellm-dashboard/src/components/view_logs/table.test.tsx
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import type { ColumnDef } from "@tanstack/react-table";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { DataTable } from "./table";
|
||||
|
||||
type Row = { request_id: string; a: string; b: string };
|
||||
|
||||
const data: Row[] = [{ request_id: "r1", a: "alpha", b: "beta" }];
|
||||
|
||||
const sizedColumns: ColumnDef<Row>[] = [
|
||||
{ header: "A", accessorKey: "a", size: 120 },
|
||||
{ header: "B", accessorKey: "b", size: 80 },
|
||||
];
|
||||
|
||||
const unsizedColumns: ColumnDef<Row>[] = [
|
||||
{ header: "A", accessorKey: "a" },
|
||||
{ header: "B", accessorKey: "b" },
|
||||
];
|
||||
|
||||
describe("DataTable column sizing", () => {
|
||||
it("min-widths the table to the column total and sizes every cell when columns declare sizes", () => {
|
||||
render(<DataTable data={data} columns={sizedColumns} />);
|
||||
|
||||
const table = screen.getByRole("table");
|
||||
expect(table.style.minWidth).toBe("200px");
|
||||
expect(table.style.width).toBe("");
|
||||
|
||||
const headers = screen.getAllByRole("columnheader");
|
||||
expect(headers.map((h) => h.style.width)).toEqual(["120px", "80px"]);
|
||||
|
||||
const cells = screen.getAllByRole("cell");
|
||||
expect(cells.map((c) => c.style.width)).toEqual(["120px", "80px"]);
|
||||
});
|
||||
|
||||
it("leaves cells unsized and keeps the fluid table when no column declares a size", () => {
|
||||
render(<DataTable data={data} columns={unsizedColumns} />);
|
||||
|
||||
const table = screen.getByRole("table");
|
||||
expect(table.style.width).toBe("");
|
||||
expect(table.style.minWidth).toBe("400px");
|
||||
|
||||
for (const cell of [...screen.getAllByRole("columnheader"), ...screen.getAllByRole("cell")]) {
|
||||
expect(cell.style.width).toBe("");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -41,6 +41,7 @@ export function DataTable<TData, TValue>({
|
|||
enableSorting = false,
|
||||
}: DataTableProps<TData, TValue>) {
|
||||
const supportsExpansion = !!(renderSubComponent || renderChildRows) && !!getRowCanExpand;
|
||||
const hasExplicitColumnSizes = columns.some((column) => column.size !== undefined);
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
|
||||
const table = useReactTable<TData>({
|
||||
|
|
@ -63,9 +64,14 @@ export function DataTable<TData, TValue>({
|
|||
...(supportsExpansion && { getExpandedRowModel: getExpandedRowModel() }),
|
||||
});
|
||||
|
||||
const tableClassName = hasExplicitColumnSizes
|
||||
? "[&_td]:py-0.5 [&_th]:py-1 [&_table]:table-fixed"
|
||||
: "[&_td]:py-0.5 [&_th]:py-1 table-fixed w-full box-border";
|
||||
const tableStyle = hasExplicitColumnSizes ? { minWidth: table.getCenterTotalSize() } : { minWidth: "400px" };
|
||||
|
||||
return (
|
||||
<div className="rounded-lg custom-border overflow-x-auto w-full max-w-full box-border">
|
||||
<Table className="[&_td]:py-0.5 [&_th]:py-1 table-fixed w-full box-border" style={{ minWidth: "400px" }}>
|
||||
<Table className={tableClassName} style={tableStyle}>
|
||||
<TableHead>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
|
|
@ -77,6 +83,7 @@ export function DataTable<TData, TValue>({
|
|||
<TableHeaderCell
|
||||
key={header.id}
|
||||
className={`py-1 h-8 ${canSort ? "cursor-pointer select-none hover:bg-gray-50" : ""}`}
|
||||
style={hasExplicitColumnSizes ? { width: header.getSize() } : undefined}
|
||||
onClick={canSort ? header.column.getToggleSortingHandler() : undefined}
|
||||
>
|
||||
{header.isPlaceholder ? null : (
|
||||
|
|
@ -112,7 +119,11 @@ export function DataTable<TData, TValue>({
|
|||
onClick={() => onRowClick?.(row.original)}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id} className="py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
<TableCell
|
||||
key={cell.id}
|
||||
className="py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap"
|
||||
style={hasExplicitColumnSizes ? { width: cell.column.getSize() } : undefined}
|
||||
>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</TableCell>
|
||||
))}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue