({
+ pageIndex: 0,
+ pageSize: DEFAULT_PAGE_SIZE_OPTIONS[0],
+ });
+ const { data: teamsData, isLoading } = useDeletedTeams(pagination.pageIndex + 1, pagination.pageSize);
return (
@@ -20,7 +27,13 @@ export default function DeletedTeamsPage() {
)}
-
+
);
}
diff --git a/ui/litellm-dashboard/src/components/DeletedTeamsPage/DeletedTeamsTable/DeletedTeamsTable.test.tsx b/ui/litellm-dashboard/src/components/DeletedTeamsPage/DeletedTeamsTable/DeletedTeamsTable.test.tsx
index c0cc5a342a8..e166f6b0d1b 100644
--- a/ui/litellm-dashboard/src/components/DeletedTeamsPage/DeletedTeamsTable/DeletedTeamsTable.test.tsx
+++ b/ui/litellm-dashboard/src/components/DeletedTeamsPage/DeletedTeamsTable/DeletedTeamsTable.test.tsx
@@ -22,12 +22,19 @@ const makeDeletedTeam = (overrides: Partial = {}): DeletedTeam => (
...overrides,
});
+const paginationProps = {
+ pagination: { pageIndex: 0, pageSize: 25 },
+ onPaginationChange: vi.fn(),
+};
+
beforeEach(() => {
vi.clearAllMocks();
});
it("should display team information", () => {
- renderWithProviders();
+ renderWithProviders(
+ ,
+ );
expect(screen.getByText("Test Team")).toBeInTheDocument();
expect(screen.getByText("team-1")).toBeInTheDocument();
@@ -39,7 +46,7 @@ it("should sort teams by deleted_at descending by default", () => {
makeDeletedTeam({ team_id: "team-old", team_alias: "older-team", deleted_at: "2024-01-01T10:00:00Z" }),
makeDeletedTeam({ team_id: "team-new", team_alias: "newer-team", deleted_at: "2024-06-01T10:00:00Z" }),
];
- renderWithProviders();
+ renderWithProviders();
const rows = screen.getAllByRole("row").slice(1);
expect(within(rows[0]).getByText("newer-team")).toBeInTheDocument();
@@ -47,13 +54,30 @@ it("should sort teams by deleted_at descending by default", () => {
});
it("should show skeleton rows when loading", () => {
- renderWithProviders();
+ renderWithProviders();
expect(screen.getAllByTestId("skeleton-row").length).toBeGreaterThan(0);
});
it("should show the empty state when there are no deleted teams", () => {
- renderWithProviders();
+ renderWithProviders();
expect(screen.getByText("No deleted teams found")).toBeInTheDocument();
});
+
+it("renders the shared pagination footer with the server row count", () => {
+ renderWithProviders(
+ ,
+ );
+
+ expect(screen.getByTestId("pagination-range")).toHaveTextContent("Showing 101-137 of 137");
+ expect(screen.getByTestId("pagination-page-size")).toHaveTextContent("50");
+ expect(screen.getByTestId("pagination-prev")).toBeEnabled();
+ expect(screen.getByTestId("pagination-next")).toBeDisabled();
+});
diff --git a/ui/litellm-dashboard/src/components/DeletedTeamsPage/DeletedTeamsTable/DeletedTeamsTable.tsx b/ui/litellm-dashboard/src/components/DeletedTeamsPage/DeletedTeamsTable/DeletedTeamsTable.tsx
index 9578a52453f..c7e759754b8 100644
--- a/ui/litellm-dashboard/src/components/DeletedTeamsPage/DeletedTeamsTable/DeletedTeamsTable.tsx
+++ b/ui/litellm-dashboard/src/components/DeletedTeamsPage/DeletedTeamsTable/DeletedTeamsTable.tsx
@@ -1,6 +1,6 @@
"use client";
-import { SortingState } from "@tanstack/react-table";
+import { OnChangeFn, PaginationState, SortingState } from "@tanstack/react-table";
import { Inbox } from "lucide-react";
import { useMemo, useState } from "react";
@@ -12,6 +12,9 @@ import { getDeletedTeamsTableColumns } from "./DeletedTeamsTableColumns";
interface DeletedTeamsTableProps {
teams: DeletedTeam[];
isLoading: boolean;
+ pagination: PaginationState;
+ onPaginationChange: OnChangeFn;
+ rowCount: number;
}
const DEFAULT_SORTING: SortingState = [{ id: "deleted_at", desc: true }];
@@ -28,7 +31,13 @@ function EmptyState() {
);
}
-export function DeletedTeamsTable({ teams, isLoading }: DeletedTeamsTableProps) {
+export function DeletedTeamsTable({
+ teams,
+ isLoading,
+ pagination,
+ onPaginationChange,
+ rowCount,
+}: DeletedTeamsTableProps) {
const [sorting, setSorting] = useState(DEFAULT_SORTING);
const columns = useMemo(() => getDeletedTeamsTableColumns(), []);
@@ -41,6 +50,10 @@ export function DeletedTeamsTable({ teams, isLoading }: DeletedTeamsTableProps)
sortingMode="client"
sorting={sorting}
onSortingChange={setSorting}
+ paginationMode="server"
+ pagination={pagination}
+ onPaginationChange={onPaginationChange}
+ rowCount={rowCount}
isLoading={isLoading}
loadingMessage="Loading deleted teams…"
noDataMessage={}
diff --git a/ui/litellm-dashboard/src/components/PassThroughSettings/PassThroughEndpointsTable.tsx b/ui/litellm-dashboard/src/components/PassThroughSettings/PassThroughEndpointsTable.tsx
index 754e7ff68dd..35f0bd4bc62 100644
--- a/ui/litellm-dashboard/src/components/PassThroughSettings/PassThroughEndpointsTable.tsx
+++ b/ui/litellm-dashboard/src/components/PassThroughSettings/PassThroughEndpointsTable.tsx
@@ -41,6 +41,7 @@ export function PassThroughEndpointsTable({
return (
endpoint.id || endpoint.path || String(index)}
isLoading={isLoading}
diff --git a/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.test.ts b/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.test.ts
index 17c1a83fd67..baf04822e4f 100644
--- a/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.test.ts
+++ b/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.test.ts
@@ -1170,7 +1170,7 @@ describe("buildComplexityRouterConfig stall escalation", () => {
});
it("emits the toggle and both knobs when it is on", () => {
- const params = {
+ const params: BuildComplexityRouterConfigParams = {
...baseParams,
stallEscalationEnabled: true,
stallEscalationWindow: 8,
diff --git a/ui/litellm-dashboard/src/components/model_add/CredentialsTable.tsx b/ui/litellm-dashboard/src/components/model_add/CredentialsTable.tsx
index 33d63e87a5b..835cd57ae92 100644
--- a/ui/litellm-dashboard/src/components/model_add/CredentialsTable.tsx
+++ b/ui/litellm-dashboard/src/components/model_add/CredentialsTable.tsx
@@ -48,6 +48,7 @@ const CredentialsTable: React.FC = ({
return (
credential.credential_name || String(index)}
sortingMode="client"
diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx
index e06d457cfa9..9edb0bf8e9e 100644
--- a/ui/litellm-dashboard/src/components/networking.tsx
+++ b/ui/litellm-dashboard/src/components/networking.tsx
@@ -1724,6 +1724,8 @@ export const modelInfoCall = async (
sortOrder?: string,
excludeAutoRouters?: boolean,
modelName?: string,
+ accessGroup?: string,
+ wildcardOnly?: boolean,
) => {
/**
* Get all models on proxy
@@ -1755,6 +1757,12 @@ export const modelInfoCall = async (
if (excludeAutoRouters) {
params.append("exclude_auto_routers", "true");
}
+ if (accessGroup && accessGroup.trim()) {
+ params.append("access_group", accessGroup.trim());
+ }
+ if (wildcardOnly) {
+ params.append("wildcard_only", "true");
+ }
if (params.toString()) {
url += `?${params.toString()}`;
}
diff --git a/ui/litellm-dashboard/src/components/public_model_hub.tsx b/ui/litellm-dashboard/src/components/public_model_hub.tsx
index 8bd47e47a84..29a84175d75 100644
--- a/ui/litellm-dashboard/src/components/public_model_hub.tsx
+++ b/ui/litellm-dashboard/src/components/public_model_hub.tsx
@@ -554,6 +554,7 @@ const PublicModelHub: React.FC = ({ accessToken, isEmbedded
agent.name || String(index)}
sortingMode="client"
@@ -620,6 +621,7 @@ const PublicModelHub: React.FC = ({ accessToken, isEmbedded
server.server_id || String(index)}
sortingMode="client"
diff --git a/ui/litellm-dashboard/src/components/routing_groups/RoutingGroupsTable.tsx b/ui/litellm-dashboard/src/components/routing_groups/RoutingGroupsTable.tsx
index fce887fc63b..1d2ef75361f 100644
--- a/ui/litellm-dashboard/src/components/routing_groups/RoutingGroupsTable.tsx
+++ b/ui/litellm-dashboard/src/components/routing_groups/RoutingGroupsTable.tsx
@@ -64,6 +64,7 @@ const RoutingGroupsTable: React.FC = ({
return (
group.group_name}
sortingMode="client"
diff --git a/ui/litellm-dashboard/src/components/team/AvailableTeamsTable.tsx b/ui/litellm-dashboard/src/components/team/AvailableTeamsTable.tsx
index 6719cc09780..11c430d05d8 100644
--- a/ui/litellm-dashboard/src/components/team/AvailableTeamsTable.tsx
+++ b/ui/litellm-dashboard/src/components/team/AvailableTeamsTable.tsx
@@ -46,6 +46,7 @@ const AvailableTeamsTable: React.FC = ({ teams, isLoad
return (
team.team_id || String(index)}
sortingMode="client"
diff --git a/ui/litellm-dashboard/src/components/view_logs/RequestLogsPanel.test.tsx b/ui/litellm-dashboard/src/components/view_logs/RequestLogsPanel.test.tsx
index be0d0049c13..295446186b1 100644
--- a/ui/litellm-dashboard/src/components/view_logs/RequestLogsPanel.test.tsx
+++ b/ui/litellm-dashboard/src/components/view_logs/RequestLogsPanel.test.tsx
@@ -148,13 +148,61 @@ describe("RequestLogsPanel", () => {
});
describe("server-grouped session pagination (#38060)", () => {
- it("requests session-grouped pages of 10 rows by default without a cursor", async () => {
+ it("requests session-grouped pages of 25 rows by default without a cursor", async () => {
renderPanel();
await waitFor(() => expect(uiSpendLogsCall).toHaveBeenCalled());
expect(lastCall()?.params?.group_by_session).toBe(true);
expect(lastCall()?.params?.session_cursor).toBeUndefined();
- expect(lastCall()?.page_size).toBe(10);
+ expect(lastCall()?.page_size).toBe(25);
+ });
+
+ it("offers the same page sizes as the other tables", async () => {
+ const user = userEvent.setup();
+ respondWith([logEntry({ request_id: "req-a" })]);
+ renderPanel();
+
+ await waitFor(() => expect(row("req-a")).not.toBeNull());
+ await user.click(screen.getByTestId("pagination-page-size"));
+
+ const options = await screen.findAllByRole("option");
+ expect(options.map((option) => option.textContent)).toEqual(["25", "50", "100"]);
+ });
+
+ it("counts the rendered rows in the footer instead of the server's session total", async () => {
+ const lastPage = {
+ data: [logEntry({ request_id: "req-a" }), logEntry({ request_id: "req-b" }), logEntry({ request_id: "req-c" })],
+ total: 40,
+ page: 1,
+ page_size: 25,
+ total_pages: 2,
+ next_session_cursor: null,
+ has_more: false,
+ };
+ vi.mocked(uiSpendLogsCall).mockResolvedValue(lastPage);
+ renderPanel();
+
+ await waitFor(() => expect(row("req-a")).not.toBeNull());
+ expect(screen.getByTestId("pagination-range")).toHaveTextContent("Showing 1-3 of 3");
+ expect(screen.getByTestId("pagination-next")).toBeDisabled();
+ });
+
+ it("keeps Next enabled from the server total while more session pages remain", async () => {
+ const firstPage = {
+ data: Array.from({ length: 25 }, (_, index) => logEntry({ request_id: `req-${index}` })),
+ total: 80,
+ page: 1,
+ page_size: 25,
+ total_pages: 4,
+ next_session_cursor: "2026-07-07 09:50:13|key-1|sess-1",
+ has_more: true,
+ };
+ vi.mocked(uiSpendLogsCall).mockResolvedValue(firstPage);
+ renderPanel();
+
+ await waitFor(() => expect(row("req-0")).not.toBeNull());
+ expect(screen.getByTestId("pagination-range")).toHaveTextContent("Showing 1-25 of 80");
+ expect(screen.getByTestId("pagination-next")).toBeEnabled();
});
it("renders every row the server returns without client-side collapsing", async () => {
diff --git a/ui/litellm-dashboard/src/components/view_logs/RequestLogsPanel.tsx b/ui/litellm-dashboard/src/components/view_logs/RequestLogsPanel.tsx
index 9b99c6af923..6e984297bf2 100644
--- a/ui/litellm-dashboard/src/components/view_logs/RequestLogsPanel.tsx
+++ b/ui/litellm-dashboard/src/components/view_logs/RequestLogsPanel.tsx
@@ -6,13 +6,13 @@ import type { ColumnFiltersState, OnChangeFn, PaginationState, SortingState } fr
import moment from "moment";
import { useCallback, useEffect, useMemo, useState } from "react";
+import { DEFAULT_PAGE_SIZE_OPTIONS } from "@/components/shared/DataTable";
import { AutoRouterModelGroupsProvider } from "@/components/shared/table_cells";
import { DEBOUNCE_WAIT_MS } from "@/utils/debounceConstants";
import type { KeyResponse } from "../key_team_helpers/key_list";
import { keyInfoV1Call, uiSpendLogsCall } from "../networking";
import KeyInfoView from "../templates/key_info_view";
import type { LogEntry } from "./columns";
-import { LOGS_PAGE_SIZE_OPTIONS } from "./constants";
import {
DEFAULT_LOGS_SORTING,
formatLogsWindow,
@@ -26,7 +26,7 @@ import { LogDetailsDrawer } from "./LogDetailsDrawer";
import { LiveTailBanner, LogsTableToolbar } from "./LogsTableToolbar";
import { RequestLogsTable } from "./RequestLogsTable";
-const PAGE_SIZE = LOGS_PAGE_SIZE_OPTIONS[0];
+const PAGE_SIZE = DEFAULT_PAGE_SIZE_OPTIONS[0];
const DEFAULT_INTERVAL = { value: 24, unit: "hours" };
interface RequestLogsPanelProps {
@@ -166,6 +166,10 @@ export default function RequestLogsPanel({ accessToken, token, userRole, userID,
const isDrawerOpen = displayLog !== null || displaySessionId !== null;
const rows: LogEntry[] = filteredLogs.data;
+ const rowsThroughThisPage = pagination.pageIndex * pagination.pageSize + rows.length;
+ const isLastPage =
+ filteredLogs.has_more === false || (filteredLogs.has_more === undefined && rows.length < pagination.pageSize);
+ const rowCount = isLastPage ? rowsThroughThisPage : Math.max(filteredLogs.total, rowsThroughThisPage);
const handleSearchChange = useCallback((value: string) => {
setColumnFilters((previous) => {
@@ -290,7 +294,7 @@ export default function RequestLogsPanel({ accessToken, token, userRole, userID,