mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-12 23:01:41 +00:00
feat(ui): give Request Logs columns explicit widths and tighten the dense ones
Now that the page-overflow bug is fixed by letting the main pane shrink, bring back per-column sizing purely to control widths. Columns declare explicit pixel sizes and the table derives its min-width from getCenterTotalSize(), so it stretches to fill a wide card but scrolls once the columns no longer fit. The shared DataTable applies this only when columns declare sizes, leaving the other consumers on their existing fluid layout Trim the columns that were eating horizontal space without earning it: Request ID and Key Hash drop ~30% (Key Hash now narrower than Key Alias, which is the more useful of the two), and Duration and TTFT shrink to fit their short numeric values
This commit is contained in:
parent
014754be94
commit
76be4461ca
3 changed files with 75 additions and 2 deletions
|
|
@ -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: 160,
|
||||
cell: (info: any) => {
|
||||
const value = String(info.getValue() || "");
|
||||
const onSessionClick = info.row.original.onSessionClick;
|
||||
|
|
@ -207,6 +211,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
{
|
||||
header: "Request ID",
|
||||
accessorKey: "request_id",
|
||||
size: 110,
|
||||
cell: (info: any) => (
|
||||
<Tooltip title={String(info.getValue() || "")}>
|
||||
<span className="font-mono text-xs max-w-[15ch] truncate block">{String(info.getValue() || "")}</span>
|
||||
|
|
@ -226,6 +231,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;
|
||||
|
|
@ -258,6 +264,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
)
|
||||
: "Duration (s)",
|
||||
accessorKey: "request_duration_ms",
|
||||
size: 90,
|
||||
cell: (info: any) => {
|
||||
const ms = info.getValue();
|
||||
if (ms == null) return <span>-</span>;
|
||||
|
|
@ -282,6 +289,7 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
)
|
||||
: "TTFT (s)",
|
||||
accessorKey: "completionStartTime",
|
||||
size: 80,
|
||||
cell: (info: any) => {
|
||||
const row = info.row.original;
|
||||
const completionStartTime = info.getValue();
|
||||
|
|
@ -301,6 +309,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 +319,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 +339,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 +359,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 +397,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 +413,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 +423,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 +434,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 "-";
|
||||
|
|
|
|||
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