mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-12 23:01:41 +00:00
fix(ui): let dashboard main pane shrink so wide tables scroll instead of overflowing
The Request Logs page pushed the whole page past the viewport horizontally. The cause was the app shell flex layout: <main className="flex-1"> is a flex item, and flex items default to min-width: auto, so they refuse to shrink below their content's intrinsic width. The logs table is intrinsically ~2300px across its 16 nowrap columns, so main grew to that width and dragged the page with it; the table's own overflow-x-auto wrapper never got the chance to scroll Add min-w-0 to main so it can shrink to the available width, at which point the existing overflow-x-auto wrapper engages and the table scrolls inside its card. This applies to every dashboard page, not just logs Also drop the dead max-w-screen class on the logs container (not a real Tailwind utility, so it was a no-op), and revert the earlier column-sizing attempt which targeted table-layout rather than the actual containment problem
This commit is contained in:
parent
93aca51251
commit
014754be94
5 changed files with 4 additions and 75 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,13 +119,11 @@ 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;
|
||||
|
|
@ -170,7 +168,6 @@ 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";
|
||||
|
|
@ -189,7 +186,6 @@ 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;
|
||||
|
|
@ -211,7 +207,6 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
{
|
||||
header: "Request ID",
|
||||
accessorKey: "request_id",
|
||||
size: 160,
|
||||
cell: (info: any) => (
|
||||
<Tooltip title={String(info.getValue() || "")}>
|
||||
<span className="font-mono text-xs max-w-[15ch] truncate block">{String(info.getValue() || "")}</span>
|
||||
|
|
@ -231,7 +226,6 @@ 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;
|
||||
|
|
@ -264,7 +258,6 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
)
|
||||
: "Duration (s)",
|
||||
accessorKey: "request_duration_ms",
|
||||
size: 120,
|
||||
cell: (info: any) => {
|
||||
const ms = info.getValue();
|
||||
if (ms == null) return <span>-</span>;
|
||||
|
|
@ -289,7 +282,6 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
)
|
||||
: "TTFT (s)",
|
||||
accessorKey: "completionStartTime",
|
||||
size: 110,
|
||||
cell: (info: any) => {
|
||||
const row = info.row.original;
|
||||
const completionStartTime = info.getValue();
|
||||
|
|
@ -309,7 +301,6 @@ 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>
|
||||
|
|
@ -319,7 +310,6 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
{
|
||||
header: "Key Hash",
|
||||
accessorKey: "metadata.user_api_key",
|
||||
size: 160,
|
||||
cell: (info: any) => {
|
||||
const value = String(info.getValue() || "-");
|
||||
const onKeyHashClick = info.row.original.onKeyHashClick;
|
||||
|
|
@ -339,7 +329,6 @@ 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>
|
||||
|
|
@ -359,7 +348,6 @@ 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;
|
||||
|
|
@ -397,7 +385,6 @@ export const createColumns = (sortProps?: LogsSortProps): ColumnDef<LogEntry>[]
|
|||
)
|
||||
: "Tokens",
|
||||
accessorKey: "total_tokens",
|
||||
size: 140,
|
||||
cell: (info: any) => {
|
||||
const row = info.row.original;
|
||||
return (
|
||||
|
|
@ -413,7 +400,6 @@ 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>
|
||||
|
|
@ -423,7 +409,6 @@ 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>
|
||||
|
|
@ -434,7 +419,6 @@ 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>
|
||||
|
|
|
|||
|
|
@ -1,44 +0,0 @@
|
|||
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("widths the table and every cell from column sizes when columns declare them", () => {
|
||||
render(<DataTable data={data} columns={sizedColumns} />);
|
||||
|
||||
expect(screen.getByRole("table").style.width).toBe("200px");
|
||||
|
||||
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,7 +41,6 @@ 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>({
|
||||
|
|
@ -64,14 +63,9 @@ 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 ? { width: table.getCenterTotalSize() } : { minWidth: "400px" };
|
||||
|
||||
return (
|
||||
<div className="rounded-lg custom-border overflow-x-auto w-full max-w-full box-border">
|
||||
<Table className={tableClassName} style={tableStyle}>
|
||||
<Table className="[&_td]:py-0.5 [&_th]:py-1 table-fixed w-full box-border" style={{ minWidth: "400px" }}>
|
||||
<TableHead>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
|
|
@ -83,7 +77,6 @@ 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 : (
|
||||
|
|
@ -119,11 +112,7 @@ 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"
|
||||
style={hasExplicitColumnSizes ? { width: cell.column.getSize() } : undefined}
|
||||
>
|
||||
<TableCell key={cell.id} className="py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</TableCell>
|
||||
))}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue