From 409dde22f6292510d17c02f9e70ea0405f23747e Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Thu, 17 Apr 2025 18:12:20 -0700 Subject: [PATCH] UI: Make columns resizable/hideable in Models table (#10119) * Make columns resizable in Models table * Make edit and delete buttons sticky on right side * Add Columns dropdown to control which columns are shown * Remove unnecessary dependencies * Fix title of visibility checkboxes for Input Cost and Output Cost * Make the Columns dropdown close if the user clicks anywhere outside of it --- .../components/model_dashboard/columns.tsx | 34 +-- .../src/components/model_dashboard/table.tsx | 261 ++++++++++++------ 2 files changed, 198 insertions(+), 97 deletions(-) diff --git a/ui/litellm-dashboard/src/components/model_dashboard/columns.tsx b/ui/litellm-dashboard/src/components/model_dashboard/columns.tsx index 2602761a5b..18b1f97410 100644 --- a/ui/litellm-dashboard/src/components/model_dashboard/columns.tsx +++ b/ui/litellm-dashboard/src/components/model_dashboard/columns.tsx @@ -23,18 +23,14 @@ export const columns = ( cell: ({ row }) => { const model = row.original; return ( -
- - - -
+ +
setSelectedModelId(model.model_info.id)} + > + {model.model_info.id} +
+
); }, }, @@ -45,9 +41,9 @@ export const columns = ( const displayName = getDisplayModelName(row.original) || "-"; return ( -

- {displayName.length > 20 ? displayName.slice(0, 20) + "..." : displayName} -

+
+ {displayName} +
); }, @@ -88,11 +84,9 @@ export const columns = ( const model = row.original; return ( -
-            {model.litellm_model_name
-              ? model.litellm_model_name.slice(0, 20) + (model.litellm_model_name.length > 20 ? "..." : "")
-              : "-"}
-          
+
+ {model.litellm_model_name || "-"} +
); }, diff --git a/ui/litellm-dashboard/src/components/model_dashboard/table.tsx b/ui/litellm-dashboard/src/components/model_dashboard/table.tsx index 7499c9094c..48661ad15e 100644 --- a/ui/litellm-dashboard/src/components/model_dashboard/table.tsx +++ b/ui/litellm-dashboard/src/components/model_dashboard/table.tsx @@ -6,6 +6,8 @@ import { getSortedRowModel, SortingState, useReactTable, + ColumnResizeMode, + VisibilityState, } from "@tanstack/react-table"; import React from "react"; import { @@ -16,7 +18,7 @@ import { TableRow, TableCell, } from "@tremor/react"; -import { SwitchVerticalIcon, ChevronUpIcon, ChevronDownIcon } from "@heroicons/react/outline"; +import { SwitchVerticalIcon, ChevronUpIcon, ChevronDownIcon, TableIcon } from "@heroicons/react/outline"; interface ModelDataTableProps { data: TData[]; @@ -32,100 +34,205 @@ export function ModelDataTable({ const [sorting, setSorting] = React.useState([ { id: "model_info.created_at", desc: true } ]); + const [columnResizeMode] = React.useState("onChange"); + const [columnSizing, setColumnSizing] = React.useState({}); + const [columnVisibility, setColumnVisibility] = React.useState({}); + const [isDropdownOpen, setIsDropdownOpen] = React.useState(false); + const dropdownRef = React.useRef(null); + + React.useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { + setIsDropdownOpen(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, []); const table = useReactTable({ data, columns, state: { sorting, + columnSizing, + columnVisibility, }, + columnResizeMode, onSortingChange: setSorting, + onColumnSizingChange: setColumnSizing, + onColumnVisibilityChange: setColumnVisibility, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), enableSorting: true, + enableColumnResizing: true, + defaultColumn: { + minSize: 40, + maxSize: 500, + }, }); + const getHeaderText = (header: any): string => { + if (typeof header === 'string') { + return header; + } + if (typeof header === 'function') { + const headerElement = header(); + if (headerElement && headerElement.props && headerElement.props.children) { + const children = headerElement.props.children; + if (typeof children === 'string') { + return children; + } + if (children.props && children.props.children) { + return children.props.children; + } + } + } + return ''; + }; + return ( -
-
- - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => ( - -
-
- {header.isPlaceholder ? null : ( - flexRender( - header.column.columnDef.header, - header.getContext() - ) - )} -
- {header.id !== 'actions' && ( -
- {header.column.getIsSorted() ? ( - { - asc: , - desc: - }[header.column.getIsSorted() as string] - ) : ( - +
+
+
+ + {isDropdownOpen && ( +
+
+ {table.getAllLeafColumns().map((column) => { + if (column.id === 'actions') return null; + return ( +
column.toggleVisibility()} + > + column.toggleVisibility()} + className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" + /> + {getHeaderText(column.columnDef.header)} +
+ ); + })} +
+
+ )} +
+
+
+
+
+
+ + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + +
+
+ {header.isPlaceholder ? null : ( + flexRender( + header.column.columnDef.header, + header.getContext() + ) + )} +
+ {header.id !== 'actions' && ( +
+ {header.column.getIsSorted() ? ( + { + asc: , + desc: + }[header.column.getIsSorted() as string] + ) : ( + + )} +
)}
- )} - -
+ {header.column.getCanResize() && ( +
+ )} + + ))} + ))} - - ))} - - - {isLoading ? ( - - -
-

🚅 Loading models...

-
-
-
- ) : table.getRowModel().rows.length > 0 ? ( - table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - {flexRender(cell.column.columnDef.cell, cell.getContext())} + + + {isLoading ? ( + + +
+

🚅 Loading models...

+
- ))} -
- )) - ) : ( - - -
-

No models found

-
-
-
- )} -
-
+ + ) : table.getRowModel().rows.length > 0 ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + )) + ) : ( + + +
+

No models found

+
+
+
+ )} + + +
+
);