diff --git a/ui/actions/invitations/invitation.ts b/ui/actions/invitations/invitation.ts index 6211ed5a06..44cbd1a455 100644 --- a/ui/actions/invitations/invitation.ts +++ b/ui/actions/invitations/invitation.ts @@ -15,6 +15,7 @@ export const getInvitations = async ({ query = "", sort = "", filters = {}, + pageSize = 10, }) => { const headers = await getAuthHeaders({ contentType: false }); @@ -23,6 +24,7 @@ export const getInvitations = async ({ const url = new URL(`${apiBaseUrl}/tenants/invitations`); if (page) url.searchParams.append("page[number]", page.toString()); + if (pageSize) url.searchParams.append("page[size]", pageSize.toString()); if (query) url.searchParams.append("filter[search]", query); if (sort) url.searchParams.append("sort", sort); diff --git a/ui/actions/manage-groups/manage-groups.ts b/ui/actions/manage-groups/manage-groups.ts index 118ab1704e..f2b1ca7289 100644 --- a/ui/actions/manage-groups/manage-groups.ts +++ b/ui/actions/manage-groups/manage-groups.ts @@ -16,11 +16,13 @@ export const getProviderGroups = async ({ query = "", sort = "", filters = {}, + pageSize = 10, }: { page?: number; query?: string; sort?: string; filters?: Record; + pageSize?: number; }): Promise => { const headers = await getAuthHeaders({ contentType: false }); @@ -29,6 +31,7 @@ export const getProviderGroups = async ({ const url = new URL(`${apiBaseUrl}/provider-groups`); if (page) url.searchParams.append("page[number]", page.toString()); + if (pageSize) url.searchParams.append("page[size]", pageSize.toString()); if (query) url.searchParams.append("filter[search]", query); if (sort) url.searchParams.append("sort", sort); diff --git a/ui/actions/providers/providers.ts b/ui/actions/providers/providers.ts index 6dbb3a0aee..2a36f7ca05 100644 --- a/ui/actions/providers/providers.ts +++ b/ui/actions/providers/providers.ts @@ -16,6 +16,7 @@ export const getProviders = async ({ query = "", sort = "", filters = {}, + pageSize = 10, }) => { const headers = await getAuthHeaders({ contentType: false }); @@ -24,6 +25,7 @@ export const getProviders = async ({ const url = new URL(`${apiBaseUrl}/providers?include=provider_groups`); if (page) url.searchParams.append("page[number]", page.toString()); + if (pageSize) url.searchParams.append("page[size]", pageSize.toString()); if (query) url.searchParams.append("filter[search]", query); if (sort) url.searchParams.append("sort", sort); diff --git a/ui/actions/roles/roles.ts b/ui/actions/roles/roles.ts index 94dd92d270..e63558d1b9 100644 --- a/ui/actions/roles/roles.ts +++ b/ui/actions/roles/roles.ts @@ -15,6 +15,7 @@ export const getRoles = async ({ query = "", sort = "", filters = {}, + pageSize = 10, }) => { const headers = await getAuthHeaders({ contentType: false }); @@ -23,6 +24,7 @@ export const getRoles = async ({ const url = new URL(`${apiBaseUrl}/roles`); if (page) url.searchParams.append("page[number]", page.toString()); + if (pageSize) url.searchParams.append("page[size]", pageSize.toString()); if (query) url.searchParams.append("filter[search]", query); if (sort) url.searchParams.append("sort", sort); diff --git a/ui/actions/scans/scans.ts b/ui/actions/scans/scans.ts index 0871bded69..81ded7e469 100644 --- a/ui/actions/scans/scans.ts +++ b/ui/actions/scans/scans.ts @@ -15,6 +15,7 @@ export const getScans = async ({ query = "", sort = "", filters = {}, + pageSize = 10, }) => { const headers = await getAuthHeaders({ contentType: false }); @@ -23,6 +24,7 @@ export const getScans = async ({ const url = new URL(`${apiBaseUrl}/scans`); if (page) url.searchParams.append("page[number]", page.toString()); + if (pageSize) url.searchParams.append("page[size]", pageSize.toString()); if (query) url.searchParams.append("filter[search]", query); if (sort) url.searchParams.append("sort", sort); diff --git a/ui/actions/users/users.ts b/ui/actions/users/users.ts index af688877bd..b826c5fcc7 100644 --- a/ui/actions/users/users.ts +++ b/ui/actions/users/users.ts @@ -15,6 +15,7 @@ export const getUsers = async ({ query = "", sort = "", filters = {}, + pageSize = 10, }) => { const headers = await getAuthHeaders({ contentType: false }); @@ -23,6 +24,7 @@ export const getUsers = async ({ const url = new URL(`${apiBaseUrl}/users?include=roles`); if (page) url.searchParams.append("page[number]", page.toString()); + if (pageSize) url.searchParams.append("page[size]", pageSize.toString()); if (query) url.searchParams.append("filter[search]", query); if (sort) url.searchParams.append("sort", sort); diff --git a/ui/app/(prowler)/findings/page.tsx b/ui/app/(prowler)/findings/page.tsx index 9fa50fc115..6166ed4f94 100644 --- a/ui/app/(prowler)/findings/page.tsx +++ b/ui/app/(prowler)/findings/page.tsx @@ -149,6 +149,7 @@ const SSRDataTable = async ({ searchParams: SearchParamsProps; }) => { const page = parseInt(searchParams.page?.toString() || "1", 10); + const pageSize = parseInt(searchParams.pageSize?.toString() || "10", 10); const defaultSort = "severity,status,-inserted_at"; const sort = searchParams.sort?.toString() || defaultSort; @@ -186,7 +187,7 @@ const SSRDataTable = async ({ page, sort: encodedSort, filters, - pageSize: 10, + pageSize, }); // Create dictionaries for resources, scans, and providers diff --git a/ui/app/(prowler)/invitations/page.tsx b/ui/app/(prowler)/invitations/page.tsx index b51a315d15..89a7776b13 100644 --- a/ui/app/(prowler)/invitations/page.tsx +++ b/ui/app/(prowler)/invitations/page.tsx @@ -44,6 +44,7 @@ const SSRDataTable = async ({ }) => { const page = parseInt(searchParams.page?.toString() || "1", 10); const sort = searchParams.sort?.toString(); + const pageSize = parseInt(searchParams.pageSize?.toString() || "10", 10); // Extract all filter parameters const filters = Object.fromEntries( @@ -54,7 +55,13 @@ const SSRDataTable = async ({ const query = (filters["filter[search]"] as string) || ""; // Fetch invitations and roles - const invitationsData = await getInvitations({ query, page, sort, filters }); + const invitationsData = await getInvitations({ + query, + page, + sort, + filters, + pageSize, + }); const rolesData = await getRoles({}); // Create a dictionary for roles by invitation ID diff --git a/ui/app/(prowler)/manage-groups/page.tsx b/ui/app/(prowler)/manage-groups/page.tsx index 62b3c83342..178baf320e 100644 --- a/ui/app/(prowler)/manage-groups/page.tsx +++ b/ui/app/(prowler)/manage-groups/page.tsx @@ -163,6 +163,7 @@ const SSRDataTable = async ({ }) => { const page = parseInt(searchParams.page?.toString() || "1", 10); const sort = searchParams.sort?.toString(); + const pageSize = parseInt(searchParams.pageSize?.toString() || "10", 10); // Convert filters to the correct type const filters: Record = {}; @@ -178,6 +179,7 @@ const SSRDataTable = async ({ page, sort, filters, + pageSize, }); return ( { const page = parseInt(searchParams.page?.toString() || "1", 10); const sort = searchParams.sort?.toString(); + const pageSize = parseInt(searchParams.pageSize?.toString() || "10", 10); // Extract all filter parameters const filters = Object.fromEntries( @@ -59,7 +60,13 @@ const SSRDataTable = async ({ // Extract query from filters const query = (filters["filter[search]"] as string) || ""; - const providersData = await getProviders({ query, page, sort, filters }); + const providersData = await getProviders({ + query, + page, + sort, + filters, + pageSize, + }); const providerGroupDict = providersData?.included diff --git a/ui/app/(prowler)/roles/page.tsx b/ui/app/(prowler)/roles/page.tsx index f0515ea546..d613990d98 100644 --- a/ui/app/(prowler)/roles/page.tsx +++ b/ui/app/(prowler)/roles/page.tsx @@ -41,6 +41,7 @@ const SSRDataTable = async ({ }) => { const page = parseInt(searchParams.page?.toString() || "1", 10); const sort = searchParams.sort?.toString(); + const pageSize = parseInt(searchParams.pageSize?.toString() || "10", 10); // Extract all filter parameters const filters = Object.fromEntries( @@ -50,7 +51,7 @@ const SSRDataTable = async ({ // Extract query from filters const query = (filters["filter[search]"] as string) || ""; - const rolesData = await getRoles({ query, page, sort, filters }); + const rolesData = await getRoles({ query, page, sort, filters, pageSize }); return ( { const page = parseInt(searchParams.page?.toString() || "1", 10); + const pageSize = parseInt(searchParams.pageSize?.toString() || "10", 10); const sort = searchParams.sort?.toString(); // Extract all filter parameters, excluding scanId @@ -120,7 +121,7 @@ const SSRDataTableScans = async ({ const query = (filters["filter[search]"] as string) || ""; // Fetch scans data - const scansData = await getScans({ query, page, sort, filters }); + const scansData = await getScans({ query, page, sort, filters, pageSize }); // Handle expanded scans data const expandedScansData = await Promise.all( diff --git a/ui/app/(prowler)/users/page.tsx b/ui/app/(prowler)/users/page.tsx index b4a19ac5fc..d6bbe6152f 100644 --- a/ui/app/(prowler)/users/page.tsx +++ b/ui/app/(prowler)/users/page.tsx @@ -41,6 +41,7 @@ const SSRDataTable = async ({ }) => { const page = parseInt(searchParams.page?.toString() || "1", 10); const sort = searchParams.sort?.toString(); + const pageSize = parseInt(searchParams.pageSize?.toString() || "10", 10); // Extract all filter parameters const filters = Object.fromEntries( @@ -50,7 +51,7 @@ const SSRDataTable = async ({ // Extract query from filters const query = (filters["filter[search]"] as string) || ""; - const usersData = await getUsers({ query, page, sort, filters }); + const usersData = await getUsers({ query, page, sort, filters, pageSize }); const rolesData = await getRoles({}); // Create a dictionary for roles by user ID diff --git a/ui/components/ui/table/data-table-pagination.tsx b/ui/components/ui/table/data-table-pagination.tsx index c81e272100..a7773925e9 100644 --- a/ui/components/ui/table/data-table-pagination.tsx +++ b/ui/components/ui/table/data-table-pagination.tsx @@ -7,23 +7,36 @@ import { DoubleArrowRightIcon, } from "@radix-ui/react-icons"; import Link from "next/link"; -import { usePathname, useSearchParams } from "next/navigation"; +import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { useState } from "react"; import { getPaginationInfo } from "@/lib"; import { MetaDataProps } from "@/types"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "../select/Select"; + interface DataTablePaginationProps { - pageSizeOptions?: number[]; metadata?: MetaDataProps; } export function DataTablePagination({ metadata }: DataTablePaginationProps) { const pathname = usePathname(); const searchParams = useSearchParams(); + const router = useRouter(); + const initialPageSize = searchParams.get("pageSize") ?? "10"; + + const [selectedPageSize, setSelectedPageSize] = useState(initialPageSize); if (!metadata) return null; - const { currentPage, totalPages, totalEntries } = getPaginationInfo(metadata); + const { currentPage, totalPages, totalEntries, itemsPerPageOptions } = + getPaginationInfo(metadata); const createPageUrl = (pageNumber: number | string) => { const params = new URLSearchParams(searchParams); @@ -44,6 +57,38 @@ export function DataTablePagination({ metadata }: DataTablePaginationProps) { {totalEntries} entries in Total.
+ {/* Rows per page selector */} +
+

Rows per page

+ +
Page {currentPage} of {totalPages}
diff --git a/ui/components/ui/table/data-table.tsx b/ui/components/ui/table/data-table.tsx index 4577f1bf08..b759e4316d 100644 --- a/ui/components/ui/table/data-table.tsx +++ b/ui/components/ui/table/data-table.tsx @@ -48,6 +48,7 @@ export function DataTable({ getSortedRowModel: getSortedRowModel(), onColumnFiltersChange: setColumnFilters, getFilteredRowModel: getFilteredRowModel(), + manualPagination: true, state: { sorting, columnFilters, diff --git a/ui/lib/helper.ts b/ui/lib/helper.ts index e8e7a9e71c..94da6e948e 100644 --- a/ui/lib/helper.ts +++ b/ui/lib/helper.ts @@ -161,8 +161,11 @@ export const getPaginationInfo = (metadata: MetaDataProps) => { const currentPage = metadata?.pagination.page ?? "1"; const totalPages = metadata?.pagination.pages; const totalEntries = metadata?.pagination.count; + const itemsPerPageOptions = metadata?.pagination.itemsPerPage ?? [ + 10, 20, 30, 50, 100, + ]; - return { currentPage, totalPages, totalEntries }; + return { currentPage, totalPages, totalEntries, itemsPerPageOptions }; }; export function encryptKey(passkey: string) { diff --git a/ui/types/components.ts b/ui/types/components.ts index b9a9453690..606c0fe0ba 100644 --- a/ui/types/components.ts +++ b/ui/types/components.ts @@ -755,6 +755,7 @@ export interface MetaDataProps { page: number; pages: number; count: number; + itemsPerPage?: Array; }; version: string; }