diff --git a/doc/designs/main-pages/02-structure-and-anatomy.md b/doc/designs/main-pages/02-structure-and-anatomy.md index 3b2f99d22..605bd34fd 100644 --- a/doc/designs/main-pages/02-structure-and-anatomy.md +++ b/doc/designs/main-pages/02-structure-and-anatomy.md @@ -24,7 +24,7 @@ Every main page follows the same structural pattern, in this order: 2. **URL-synced pagination/search state** (`useListPageSearchParams`) 3. **API version retrieval** (Redux selector) 4. **Data fetching** (RTK Query hook for initial load) -5. **Search mutation** (RTK Query mutation for explicit search) +5. **Search mutation** (RTK Query mutation for submit-only search) 6. **Selection management** (selected items, bulk selector logic) 7. **Button state management** (delete, enable, disable disabled states) 8. **Data wrappers** (prop bundles for child components) diff --git a/doc/designs/main-pages/03-walkthrough-init-fetch.md b/doc/designs/main-pages/03-walkthrough-init-fetch.md index 4640c4849..ab4b6347b 100644 --- a/doc/designs/main-pages/03-walkthrough-init-fetch.md +++ b/doc/designs/main-pages/03-walkthrough-init-fetch.md @@ -31,7 +31,7 @@ The `pathname` must be registered in `AppRoutes.tsx` and `NavRoutes.ts`. const lastIdx = page * perPage; const dataResponse = useGettingMyEntitiesQuery({ - searchValue: "", + searchValue: searchValue, sizeLimit: 0, apiVersion: apiVersion || API_VERSION_BACKUP, startIdx: firstIdx, @@ -41,6 +41,11 @@ The `pathname` must be registered in `AppRoutes.tsx` and `NavRoutes.ts`. const { data: batchResponse, isLoading, isFetching, error } = dataResponse; ``` +> **Important:** Always pass `searchValue` (not `""`) to the query hook. RTK Query +> auto-refetches whenever its parameters change (`searchValue`, `startIdx`, `stopIdx`), +> so both pagination and filtering are handled automatically. Using a hardcoded `""` +> causes a race condition where the unfiltered query response overwrites search results. + ## Step 4: Derive State with useMemo (Recommended) Use `useMemo` to derive `elementsList` and `totalCount` from the query response — **do not** use `useEffect` + `useState` to sync state. @@ -64,10 +69,14 @@ Use it to type the search data state: // Derive elementsList and totalCount const { elementsList, totalCount } = useMemo(() => { + // Search results are fetched with stopIdx: 100 (all matches at once), + // so paginate them client-side using page/perPage. if (isSearchActive && searchData) { + const start = (page - 1) * perPage; + const end = start + perPage; return { - elementsList: searchData.elementsList, - totalCount: searchData.totalCount, + elementsList: searchData.elementsList.slice(start, end), + totalCount: searchData.elementsList.length, }; } @@ -81,7 +90,7 @@ Use it to type the search data state: } return { elementsList: [], totalCount: 0 }; - }, [batchResponse, isSearchActive, searchData]); + }, [batchResponse, isSearchActive, searchData, page, perPage]); // Derive showTableRows from loading states const showTableRows = useMemo(() => { @@ -124,22 +133,50 @@ This pattern avoids eslint warnings about calling `setState` in `useEffect`. > **Note:** No manual `useEffect` for pagination is needed. RTK Query automatically > re-fetches when `startIdx`/`stopIdx` change (derived from `page`/`perPage`). -## Step 7: Search Handler +## Step 7: Search Value Update Handler + +`SearchInputLayout` buffers keystrokes locally — it does **not** call `updateSearchValue` +on every keystroke. Instead, it calls `updateSearchValue(value)` and +`submitSearchValue(value)` only when the user presses Enter or clicks the search button. +This avoids firing an API request on every keystroke. + +`updateSearchValue` resets pagination to page 1 and updates the committed search value +(which drives the RTK Query parameter and URL sync): + +```tsx + const updateSearchValue = (value: string) => { + setPage(1); + setSearchValue(value); + }; +``` + +> **Do not** add search-as-you-type behavior by calling `setSearchValue` inside +> `SearchInputLayout`'s `onChange`. The component deliberately buffers input locally +> and only propagates on submit or clear. + +## Step 8: Search Submit Handler + +`submitSearchValue` is called by `SearchInputLayout` when the user presses Enter or +clicks the search button. It receives the **current input value** as an argument to +avoid stale closures (since `updateSearchValue` and `submitSearchValue` are called in +the same event handler, the React state from `updateSearchValue` has not yet committed). ```tsx const [searchEntities, searchResult] = useSearchMyEntitiesEntriesMutation({}); const [searchDisabled, setSearchIsDisabled] = useState(false); - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; + setPage(1); setSearchIsDisabled(true); setIsSearchActive(true); searchEntities({ - searchValue, + searchValue: search, sizeLimit: 0, apiVersion: apiVersion || API_VERSION_BACKUP, - startIdx: firstIdx, - stopIdx: lastIdx, + startIdx: 0, + stopIdx: 100, }).then((result) => { if ("data" in result) { const searchError = result.data?.error; @@ -170,6 +207,17 @@ This pattern avoids eslint warnings about calling `setState` in `useEffect`. }; ``` +> **Important — `value` parameter:** Always use the `value` argument (falling back to +> `searchValue` with `??`) instead of reading `searchValue` directly from the closure. +> Both `updateSearchValue` and `submitSearchValue` are called in the same React event +> handler, so the state set by `updateSearchValue` is not yet available when +> `submitSearchValue` runs. + +> **Important — `stopIdx: 100`:** Use a fixed upper bound (100) instead of `perPage`. +> The LDAP backend has a size limit close to this value, and using `perPage` (e.g. 10) +> would miss entries beyond the first page of results when searching from a page other +> than page 1. + ## Legacy Pattern (Avoid) The older pattern using `useEffect` + `setState` triggers eslint warnings: diff --git a/doc/designs/main-pages/04-walkthrough-selection-toolbar.md b/doc/designs/main-pages/04-walkthrough-selection-toolbar.md index fbca989f3..89223abcf 100644 --- a/doc/designs/main-pages/04-walkthrough-selection-toolbar.md +++ b/doc/designs/main-pages/04-walkthrough-selection-toolbar.md @@ -110,7 +110,7 @@ These objects group related props for child components: const searchValueData = { searchValue, - updateSearchValue: setSearchValue, + updateSearchValue, submitSearchValue, }; diff --git a/doc/designs/sub-pages/06a-membership-custom.md b/doc/designs/sub-pages/06a-membership-custom.md index 7e4cac77f..245b725f9 100644 --- a/doc/designs/sub-pages/06a-membership-custom.md +++ b/doc/designs/sub-pages/06a-membership-custom.md @@ -135,6 +135,54 @@ return ( - Modals should be **outside** `TabLayout` to avoid z-index issues - Inner `Tab` title can include a `Badge` showing member count +## Search Behavior in Membership Tabs + +`SearchInputLayout` buffers keystrokes locally — the `searchValue` state is only updated +when the user presses Enter, clicks the search button, or clears the input. This avoids +unnecessary re-renders and API calls on every keystroke. + +### Wiring the MemberOfToolbar + +```tsx + {}} + searchPlaceholder="Search members" + searchAriaLabel="Search members" + // ... other props +/> +``` + +- **`onSearchTextChange`** is called by `SearchInputLayout` only on submit or clear + (not on every keystroke). It updates `searchValue` which drives the client-side filter. +- **`onSearch`** can remain `() => {}` — membership tabs use client-side filtering + via `searchValue`, so no mutation-based search is needed. + +### Client-Side Filtering Pattern + +Use `searchValue` to filter the membership list. Since `searchValue` only changes on +submit, the filter is applied only when the user explicitly searches: + +```tsx +const filteredMembers = React.useMemo(() => { + let toLoad = [...memberList].sort(); + if (searchValue) { + const q = searchValue.toLowerCase(); + toLoad = toLoad.filter((name) => name.toLowerCase().includes(q)); + } + return toLoad; +}, [memberList, searchValue]); + +const namesToLoad = React.useMemo( + () => paginate(filteredMembers, page, perPage), + [filteredMembers, page, perPage] +); +``` + +> **Do not** wire `onSearchTextChange` to trigger API calls or heavy computations. +> It is called once per search submit, not per keystroke. + ## Handling API Array Responses The IPA API often returns single values as arrays: diff --git a/doc/designs/sub-pages/06c-membership-creating-new.md b/doc/designs/sub-pages/06c-membership-creating-new.md index 1368ec2ad..f6102297f 100644 --- a/doc/designs/sub-pages/06c-membership-creating-new.md +++ b/doc/designs/sub-pages/06c-membership-creating-new.md @@ -183,7 +183,7 @@ After gathering information, the component MUST include: | Required Feature | Description | |------------------|-------------| -| **Toolbar** | `MemberOfToolbar` with search, pagination, Add/Delete buttons | +| **Toolbar** | `MemberOfToolbar` with search (submit-only, not per-keystroke), pagination, Add/Delete buttons | | **Table** | `MemberTable` displaying the member list | | **Pagination** | Bottom pagination component | | **Add button** | Enabled, opens add modal | diff --git a/src/components/MemberOf/MemberOfToolbar.tsx b/src/components/MemberOf/MemberOfToolbar.tsx index 5446451a0..5f27fc2d3 100644 --- a/src/components/MemberOf/MemberOfToolbar.tsx +++ b/src/components/MemberOf/MemberOfToolbar.tsx @@ -22,7 +22,7 @@ interface MemberOfToolbarProps { // search searchText: string; onSearchTextChange: (value: string) => void; - onSearch: () => void; + onSearch: (value?: string) => void; searchPlaceholder: string; searchAriaLabel: string; diff --git a/src/components/layouts/DualListLayout/DualListLayout.tsx b/src/components/layouts/DualListLayout/DualListLayout.tsx index 1f51113c1..e3ae36aa6 100644 --- a/src/components/layouts/DualListLayout/DualListLayout.tsx +++ b/src/components/layouts/DualListLayout/DualListLayout.tsx @@ -162,11 +162,12 @@ const DualListTableLayoutInner = (props: DualListProps) => { // Issue a search using a specific search value const [retrieveIDs] = useGetIDListMutation({}); - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; setSearchIsDisabled(true); if (props.availableOptions === undefined) { retrieveIDs({ - searchValue: props.availableOptions || searchValue, + searchValue: props.availableOptions || search, sizeLimit: 200, startIdx: 0, stopIdx: 200, @@ -189,9 +190,9 @@ const DualListTableLayoutInner = (props: DualListProps) => { } }; - function doSearch() { + function doSearch(value?: string) { setStatus("searching"); - submitSearchValue(); + submitSearchValue(value); } const searchValueData = { diff --git a/src/components/layouts/SearchInputLayout/SearchInputLayout.test.tsx b/src/components/layouts/SearchInputLayout/SearchInputLayout.test.tsx index 3288c37a9..fb9206b7f 100644 --- a/src/components/layouts/SearchInputLayout/SearchInputLayout.test.tsx +++ b/src/components/layouts/SearchInputLayout/SearchInputLayout.test.tsx @@ -61,7 +61,7 @@ describe("SearchInputLayout Component", () => { expect(searchInput).toHaveValue("current search value"); }); - it("calls updateSearchValue when input changes", () => { + it("does not call updateSearchValue on keystroke (buffers locally)", () => { render( { const searchInput = screen.getByRole("textbox"); fireEvent.change(searchInput, { target: { value: "new search value" } }); - expect(mockUpdateSearchValue).toHaveBeenCalledWith("new search value"); + expect(mockUpdateSearchValue).not.toHaveBeenCalled(); + expect(searchInput).toHaveValue("new search value"); }); it("calls updateSearchValue with empty string when reset button is clicked", () => { @@ -94,7 +95,7 @@ describe("SearchInputLayout Component", () => { expect(mockUpdateSearchValue).toHaveBeenCalledWith(""); }); - it("calls submitSearchValue when search button is clicked", () => { + it("calls updateSearchValue and submitSearchValue with input value on search submit", () => { render( { /> ); + const searchInput = screen.getByRole("textbox"); + fireEvent.change(searchInput, { target: { value: "typed value" } }); + const searchButton = screen.getByRole("button", { name: /search/i }); fireEvent.click(searchButton); - expect(mockSubmitSearchValue).toHaveBeenCalled(); + expect(mockUpdateSearchValue).toHaveBeenCalledWith("typed value"); + expect(mockSubmitSearchValue).toHaveBeenCalledWith("typed value"); }); }); diff --git a/src/components/layouts/SearchInputLayout/SearchInputLayout.tsx b/src/components/layouts/SearchInputLayout/SearchInputLayout.tsx index 0efaea12d..bddddefc7 100644 --- a/src/components/layouts/SearchInputLayout/SearchInputLayout.tsx +++ b/src/components/layouts/SearchInputLayout/SearchInputLayout.tsx @@ -1,12 +1,11 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import React, { FormEvent, SyntheticEvent } from "react"; +import React from "react"; // PatternFly import { SearchInput } from "@patternfly/react-core"; interface SearchValueData { searchValue: string; updateSearchValue: (value: string) => void; - submitSearchValue?: () => void; + submitSearchValue?: (value?: string) => void; } interface PropsToSearchInput { @@ -19,15 +18,19 @@ interface PropsToSearchInput { } const SearchInputLayout = (props: PropsToSearchInput) => { - const onSearchChange = ( - _event: FormEvent, - value: string - ) => { - props.searchValueData.updateSearchValue(value); - }; + const [inputValue, setInputValue] = React.useState( + props.searchValueData.searchValue + ); + + const prevSearchValue = React.useRef(props.searchValueData.searchValue); + if (prevSearchValue.current !== props.searchValueData.searchValue) { + prevSearchValue.current = props.searchValueData.searchValue; + setInputValue(props.searchValueData.searchValue); + } - const onSearchClear = (_event: SyntheticEvent) => { - props.searchValueData.updateSearchValue(""); + const onSearchSubmit = () => { + props.searchValueData.updateSearchValue(inputValue); + props.searchValueData.submitSearchValue?.(inputValue); }; return ( @@ -36,14 +39,13 @@ const SearchInputLayout = (props: PropsToSearchInput) => { name={props.name} aria-label={props.ariaLabel} placeholder={props.placeholder} - value={props.searchValueData.searchValue} - onSearch={ - props.searchValueData.submitSearchValue - ? props.searchValueData.submitSearchValue - : () => void undefined - } - onChange={onSearchChange} - onClear={onSearchClear} + value={inputValue} + onSearch={onSearchSubmit} + onChange={(_event, value: string) => setInputValue(value)} + onClear={() => { + setInputValue(""); + props.searchValueData.updateSearchValue(""); + }} isDisabled={props.isDisabled} /> ); diff --git a/src/pages/ActiveUsers/ActiveUsers.tsx b/src/pages/ActiveUsers/ActiveUsers.tsx index 7ae5377c5..39c53d6c9 100644 --- a/src/pages/ActiveUsers/ActiveUsers.tsx +++ b/src/pages/ActiveUsers/ActiveUsers.tsx @@ -232,6 +232,7 @@ const ActiveUsers = () => { // Update search input valie const updateSearchValue = (value: string) => { + setPage(1); setSearchValue(value); }; @@ -245,18 +246,20 @@ const ActiveUsers = () => { const [retrieveUser] = useSearchEntriesMutation({}); // Issue a search using a specific search value - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; + setPage(1); setShowTableRows(false); setSearchIsDisabled(true); setUsersTotalCount(0); // Make search via API call retrieveUser({ - searchValue: searchValue, + searchValue: search, sizeLimit: 0, apiVersion: apiVersion || API_VERSION_BACKUP, - startIdx: firstUserIdx, - stopIdx: lastUserIdx, + startIdx: 0, + stopIdx: 100, entryType: "user", } as GenericPayload).then((result) => { // Manage new response here @@ -292,7 +295,7 @@ const ActiveUsers = () => { } setUsersTotalCount(totalCount); - setActiveUsersList(usersList); + setActiveUsersList(usersList.slice(0, perPage)); // Show table elements setShowTableRows(true); } diff --git a/src/pages/AutoMemHostRules/AutoMemHostRules.tsx b/src/pages/AutoMemHostRules/AutoMemHostRules.tsx index 37ffbf0f5..866ea7ca6 100644 --- a/src/pages/AutoMemHostRules/AutoMemHostRules.tsx +++ b/src/pages/AutoMemHostRules/AutoMemHostRules.tsx @@ -268,6 +268,7 @@ const AutoMemHostRules = () => { // Update search input valie const updateSearchValue = (value: string) => { + setPage(1); setSearchValue(value); }; @@ -291,21 +292,23 @@ const AutoMemHostRules = () => { const [retrieveAutomembers] = useSearchHostGroupRulesEntriesMutation({}); // Issue a search using a specific search value - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; + setPage(1); setShowTableRows(false); setSearchIsDisabled(true); setTotalCount(0); retrieveAutomembers({ - searchValue: searchValue, + searchValue: search, sizeLimit: 0, apiVersion: apiVersion || API_VERSION_BACKUP, - startIdx: firstIdx, - stopIdx: lastIdx, + startIdx: 0, + stopIdx: 100, } as GenericPayload).then((result) => { if ("data" in result) { const automembersListResult = result.data; setTotalCount(result.data?.length ?? 0); - setAutomemberRules(automembersListResult || []); + setAutomemberRules((automembersListResult || []).slice(0, perPage)); // Show table elements setShowTableRows(true); setSearchIsDisabled(false); diff --git a/src/pages/AutoMemUserRules/AutoMemUserRules.tsx b/src/pages/AutoMemUserRules/AutoMemUserRules.tsx index b6b201cb9..972b2e9c4 100644 --- a/src/pages/AutoMemUserRules/AutoMemUserRules.tsx +++ b/src/pages/AutoMemUserRules/AutoMemUserRules.tsx @@ -266,6 +266,7 @@ const AutoMemUserRules = () => { // Update search input valie const updateSearchValue = (value: string) => { + setPage(1); setSearchValue(value); }; @@ -289,21 +290,23 @@ const AutoMemUserRules = () => { const [retrieveAutomembers] = useSearchUserGroupRulesEntriesMutation({}); // Issue a search using a specific search value - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; + setPage(1); setShowTableRows(false); setSearchIsDisabled(true); setTotalCount(0); retrieveAutomembers({ - searchValue: searchValue, + searchValue: search, sizeLimit: 0, apiVersion: apiVersion || API_VERSION_BACKUP, - startIdx: firstIdx, - stopIdx: lastIdx, + startIdx: 0, + stopIdx: 100, } as GenericPayload).then((result) => { if ("data" in result) { const automembersListResult = result.data; setTotalCount((result.data ?? []).length); - setAutomemberRules(automembersListResult ?? []); + setAutomemberRules((automembersListResult ?? []).slice(0, perPage)); // Show table elements setShowTableRows(true); setSearchIsDisabled(false); diff --git a/src/pages/AutomountLocations/AutomountLocations.tsx b/src/pages/AutomountLocations/AutomountLocations.tsx index 2372e1c80..5618be3e4 100644 --- a/src/pages/AutomountLocations/AutomountLocations.tsx +++ b/src/pages/AutomountLocations/AutomountLocations.tsx @@ -195,9 +195,14 @@ const AutomountLocations = () => { totalCount, }; + const updateSearchValue = (value: string) => { + setPage(1); + setSearchValue(value); + }; + const searchValueData = { searchValue, - updateSearchValue: setSearchValue, + updateSearchValue, }; const bulkSelectorData = { diff --git a/src/pages/CertificateMapping/CertificateMapping.tsx b/src/pages/CertificateMapping/CertificateMapping.tsx index c9d5c70b4..98432877a 100644 --- a/src/pages/CertificateMapping/CertificateMapping.tsx +++ b/src/pages/CertificateMapping/CertificateMapping.tsx @@ -248,9 +248,11 @@ const CertificateMappingPage = () => { // Search API call const [searchEntry] = useSearchCertMapRuleEntriesMutation(); - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; + setPage(1); searchEntry({ - searchValue: searchValue, + searchValue: search, apiVersion, sizelimit: 100, startIdx: 0, @@ -288,7 +290,7 @@ const CertificateMappingPage = () => { } setTotalCount(totalCount); - setCertMapRules(elementsList); + setCertMapRules(elementsList.slice(0, perPage)); setShowTableRows(true); } setIsSearchDisabled(false); @@ -309,10 +311,15 @@ const CertificateMappingPage = () => { totalCount, }; + const updateSearchValue = (value: string) => { + setPage(1); + setSearchValue(value); + }; + // SearchInputLayout const searchValueData = { searchValue, - updateSearchValue: setSearchValue, + updateSearchValue, submitSearchValue, }; diff --git a/src/pages/DNSZones/DnsForwardZones.tsx b/src/pages/DNSZones/DnsForwardZones.tsx index 5445beffa..533582b7e 100644 --- a/src/pages/DNSZones/DnsForwardZones.tsx +++ b/src/pages/DNSZones/DnsForwardZones.tsx @@ -197,9 +197,11 @@ const DnsForwardZones = () => { // Search API call const [searchEntry] = useSearchDnsForwardZonesEntriesMutation(); - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; + setPage(1); searchEntry({ - searchValue: searchValue, + searchValue: search, apiVersion, startIdx: 0, stopIdx: 200, // Search will consider a max. of elements @@ -224,7 +226,7 @@ const DnsForwardZones = () => { const dnsForwardZones = result.data?.result || []; setTotalCount(dnsForwardZones.length); - setDnsForwardZones(dnsForwardZones); + setDnsForwardZones(dnsForwardZones.slice(0, perPage)); setShowTableRows(true); } setIsSearchDisabled(false); @@ -244,10 +246,15 @@ const DnsForwardZones = () => { totalCount, }; + const updateSearchValue = (value: string) => { + setPage(1); + setSearchValue(value); + }; + // SearchInputLayout const searchValueData = { searchValue, - updateSearchValue: setSearchValue, + updateSearchValue, submitSearchValue, }; diff --git a/src/pages/DNSZones/DnsResourceRecords.tsx b/src/pages/DNSZones/DnsResourceRecords.tsx index afbcb5d01..6207ceed1 100644 --- a/src/pages/DNSZones/DnsResourceRecords.tsx +++ b/src/pages/DNSZones/DnsResourceRecords.tsx @@ -221,13 +221,15 @@ const DnsResourceRecords = (props: DnsResourceRecordsProps) => { // Search DNS records const [searchDnsRecords] = useSearchDnsRecordsEntriesMutation(); - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; + setPage(1); const payload: FindDnsRecordPayload = { dnsZoneId: props.dnsZoneId, - recordName: searchValue, - sizeLimit: perPage, + recordName: search, + sizeLimit: 100, startIdx: 0, // Reset to first page for search - stopIdx: perPage, + stopIdx: 100, }; setIsSearchDisabled(true); @@ -256,11 +258,9 @@ const DnsResourceRecords = (props: DnsResourceRecordsProps) => { } else { // Success const records = result.data?.result || []; - setDnsRecords(records); + setDnsRecords(records.slice(0, perPage)); setTotalCount(records.length); setShowTableRows(true); - // Reset to first page - setPage(1); } setIsSearchDisabled(false); } @@ -280,10 +280,15 @@ const DnsResourceRecords = (props: DnsResourceRecordsProps) => { totalCount, }; + const updateSearchValue = (value: string) => { + setPage(1); + setSearchValue(value); + }; + // SearchInputLayout const searchValueData = { searchValue, - updateSearchValue: setSearchValue, + updateSearchValue, submitSearchValue, }; diff --git a/src/pages/DNSZones/DnsServers.tsx b/src/pages/DNSZones/DnsServers.tsx index f3e40b417..807f265e1 100644 --- a/src/pages/DNSZones/DnsServers.tsx +++ b/src/pages/DNSZones/DnsServers.tsx @@ -139,9 +139,11 @@ const DnsServers = () => { // Search API call const [searchEntry] = useSearchDnsServersEntriesMutation(); - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; + setPage(1); searchEntry({ - searchValue: searchValue, + searchValue: search, pkeyOnly: true, sizeLimit: 100, version: apiVersion, @@ -169,9 +171,7 @@ const DnsServers = () => { const dnsServers = result.data || []; setTotalCount(totalCount); - setDnsServersId( - dnsServers.data.slice(firstUserIdx, lastUserIdx) || [] - ); + setDnsServersId(dnsServers.data.slice(0, perPage) || []); setShowTableRows(true); } setIsSearchDisabled(false); @@ -192,10 +192,15 @@ const DnsServers = () => { totalCount, }; + const updateSearchValue = (value: string) => { + setPage(1); + setSearchValue(value); + }; + // SearchInputLayout const searchValueData = { searchValue, - updateSearchValue: setSearchValue, + updateSearchValue, submitSearchValue, }; diff --git a/src/pages/DNSZones/DnsZones.tsx b/src/pages/DNSZones/DnsZones.tsx index 94361ddff..0f68d10c9 100644 --- a/src/pages/DNSZones/DnsZones.tsx +++ b/src/pages/DNSZones/DnsZones.tsx @@ -224,9 +224,11 @@ const DnsZones = () => { // Search API call const [searchEntry] = useSearchDnsZonesEntriesMutation(); - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; + setPage(1); searchEntry({ - searchValue: searchValue, + searchValue: search, apiVersion, sizelimit: 100, startIdx: 0, @@ -257,7 +259,7 @@ const DnsZones = () => { const dnsZones = result.data?.result || []; setTotalCount(totalCount); - setDnsZones(dnsZones); + setDnsZones(dnsZones.slice(0, perPage)); setShowTableRows(true); } setIsSearchDisabled(false); @@ -278,10 +280,15 @@ const DnsZones = () => { totalCount, }; + const updateSearchValue = (value: string) => { + setPage(1); + setSearchValue(value); + }; + // SearchInputLayout const searchValueData = { searchValue, - updateSearchValue: setSearchValue, + updateSearchValue, submitSearchValue, }; diff --git a/src/pages/HBACRules/HBACRules.tsx b/src/pages/HBACRules/HBACRules.tsx index 227c8ba35..5f710b856 100644 --- a/src/pages/HBACRules/HBACRules.tsx +++ b/src/pages/HBACRules/HBACRules.tsx @@ -83,7 +83,7 @@ const HBACRules = () => { // Derived states - what we get from API const rulesDataResponse = useGettingHbacRulesQuery({ - searchValue: "", + searchValue: searchValue, sizeLimit: 0, apiVersion: apiVersion || API_VERSION_BACKUP, startIdx: firstUserIdx, @@ -156,7 +156,7 @@ const HBACRules = () => { // This ensures the data is always up-to-date. React.useEffect(() => { rulesDataResponse.refetch(); - }, [page, perPage]); + }, []); // 'Delete' button state const [isDeleteButtonDisabled, setIsDeleteButtonDisabled] = @@ -220,6 +220,7 @@ const HBACRules = () => { // Update search input valie const updateSearchValue = (value: string) => { + setPage(1); setSearchValue(value); }; @@ -233,16 +234,18 @@ const HBACRules = () => { const [retrieveRules] = useSearchEntriesMutation({}); // Issue a search using a specific search value - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; + setPage(1); setShowTableRows(false); setSearchIsDisabled(true); setRulesTotalCount(0); retrieveRules({ - searchValue: searchValue, + searchValue: search, sizeLimit: 0, apiVersion: apiVersion || API_VERSION_BACKUP, - startIdx: firstUserIdx, - stopIdx: lastUserIdx, + startIdx: 0, + stopIdx: 100, entryType: "hbacrule", } as GenericPayload).then((result) => { // Manage new response here @@ -278,7 +281,7 @@ const HBACRules = () => { } setRulesTotalCount(totalCount); - setRulesList(rulesList); + setRulesList(rulesList.slice(0, perPage)); // Show table elements setShowTableRows(true); } diff --git a/src/pages/HBACServiceGroups/HBACServiceGroups.tsx b/src/pages/HBACServiceGroups/HBACServiceGroups.tsx index eb29b7fc4..b186a7346 100644 --- a/src/pages/HBACServiceGroups/HBACServiceGroups.tsx +++ b/src/pages/HBACServiceGroups/HBACServiceGroups.tsx @@ -85,7 +85,7 @@ const HBACServiceGroups = () => { // Derived states - what we get from API const servicesDataResponse = useGettingHbacServiceGroupQuery({ - searchValue: "", + searchValue: searchValue, sizeLimit: 0, apiVersion: apiVersion || API_VERSION_BACKUP, startIdx: firstIdx, @@ -153,7 +153,7 @@ const HBACServiceGroups = () => { // This ensures the data is always up-to-date. React.useEffect(() => { servicesDataResponse.refetch(); - }, [page, perPage]); + }, []); // 'Delete' button state const [isDeleteButtonDisabled, setIsDeleteButtonDisabled] = @@ -196,6 +196,7 @@ const HBACServiceGroups = () => { // Update search input value const updateSearchValue = (value: string) => { + setPage(1); setSearchValue(value); }; @@ -211,16 +212,18 @@ const HBACServiceGroups = () => { const [retrieveServices] = useSearchEntriesMutation({}); // Issue a search using a specific search value - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; + setPage(1); setShowTableRows(false); setSearchIsDisabled(true); setServicesTotalCount(0); retrieveServices({ - searchValue: searchValue, + searchValue: search, sizeLimit: 0, apiVersion: apiVersion || API_VERSION_BACKUP, - startIdx: firstIdx, - stopIdx: lastIdx, + startIdx: 0, + stopIdx: 100, entryType: "hbacsvcgroup", } as GenericPayload).then((result) => { // Manage new response here @@ -255,7 +258,7 @@ const HBACServiceGroups = () => { servicesList.push(servicesListResult[i].result); } setServicesTotalCount(totalCount); - setServicesList(servicesList); + setServicesList(servicesList.slice(0, perPage)); setShowTableRows(true); } setSearchIsDisabled(false); diff --git a/src/pages/HBACServices/HBACServices.tsx b/src/pages/HBACServices/HBACServices.tsx index e339796bf..83ac71bf6 100644 --- a/src/pages/HBACServices/HBACServices.tsx +++ b/src/pages/HBACServices/HBACServices.tsx @@ -82,7 +82,7 @@ const HBACServices = () => { // Derived states - what we get from API const servicesDataResponse = useGettingHbacServicesQuery({ - searchValue: "", + searchValue: searchValue, sizeLimit: 0, apiVersion: apiVersion || API_VERSION_BACKUP, startIdx: firstIdx, @@ -155,7 +155,7 @@ const HBACServices = () => { // This ensures the data is always up-to-date. React.useEffect(() => { servicesDataResponse.refetch(); - }, [page, perPage]); + }, []); // 'Delete' button state const [isDeleteButtonDisabled, setIsDeleteButtonDisabled] = @@ -196,6 +196,7 @@ const HBACServices = () => { // Update search input valie const updateSearchValue = (value: string) => { + setPage(1); setSearchValue(value); }; @@ -209,16 +210,18 @@ const HBACServices = () => { const [retrieveServices] = useSearchEntriesMutation({}); // Issue a search using a specific search value - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; + setPage(1); setShowTableRows(false); setSearchIsDisabled(true); setServicesTotalCount(0); retrieveServices({ - searchValue: searchValue, + searchValue: search, sizeLimit: 0, apiVersion: apiVersion || API_VERSION_BACKUP, - startIdx: firstIdx, - stopIdx: lastIdx, + startIdx: 0, + stopIdx: 100, entryType: "hbacsvc", } as GenericPayload).then((result) => { // Manage new response here @@ -254,7 +257,7 @@ const HBACServices = () => { } setServicesTotalCount(totalCount); - setServicesList(servicesList); + setServicesList(servicesList.slice(0, perPage)); // Show table elements setShowTableRows(true); } diff --git a/src/pages/HostGroups/HostGroups.tsx b/src/pages/HostGroups/HostGroups.tsx index dcd9a98c3..46a9fb90a 100644 --- a/src/pages/HostGroups/HostGroups.tsx +++ b/src/pages/HostGroups/HostGroups.tsx @@ -200,6 +200,7 @@ const HostGroups = () => { }; const updateSearchValue = (value: string) => { + setPage(1); setSearchValue(value); }; @@ -212,16 +213,18 @@ const HostGroups = () => { const [retrieveGroup] = useSearchEntriesMutation({}); // Issue a search using a specific search value - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; + setPage(1); setShowTableRows(false); setGroupsTotalCount(0); setSearchIsDisabled(true); retrieveGroup({ - searchValue: searchValue, + searchValue: search, sizeLimit: 0, apiVersion: apiVersion || API_VERSION_BACKUP, - startIdx: firstIdx, - stopIdx: lastIdx, + startIdx: 0, + stopIdx: 100, entryType: "hostgroup", } as GenericPayload).then((result) => { // Manage new response here @@ -256,8 +259,7 @@ const HostGroups = () => { groupsList.push(groupsListResult[i].result); } - setPage(1); - setGroupsList(groupsList); + setGroupsList(groupsList.slice(0, perPage)); setGroupsTotalCount(totalCount); // Show table elements setShowTableRows(true); diff --git a/src/pages/Hosts/Hosts.tsx b/src/pages/Hosts/Hosts.tsx index d4846d064..686ac4516 100644 --- a/src/pages/Hosts/Hosts.tsx +++ b/src/pages/Hosts/Hosts.tsx @@ -203,6 +203,7 @@ const Hosts = () => { }; const updateSearchValue = (value: string) => { + setPage(1); setSearchValue(value); }; @@ -215,16 +216,18 @@ const Hosts = () => { const [retrieveHost] = useSearchEntriesMutation({}); // Issue a search using a specific search value - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; + setPage(1); setShowTableRows(false); setHostsTotalCount(0); setSearchIsDisabled(true); retrieveHost({ - searchValue: searchValue, + searchValue: search, sizeLimit: 0, apiVersion: apiVersion || API_VERSION_BACKUP, - startIdx: firstHostIdx, - stopIdx: lastHostIdx, + startIdx: 0, + stopIdx: 100, entryType: "host", } as GenericPayload).then((result) => { // Manage new response here @@ -259,7 +262,7 @@ const Hosts = () => { hostsList.push(hostsListResult[i].result); } - setHostsList(hostsList); + setHostsList(hostsList.slice(0, perPage)); setHostsTotalCount(totalCount); // Show table elements setShowTableRows(true); diff --git a/src/pages/IDViews/IDViews.tsx b/src/pages/IDViews/IDViews.tsx index 0827605f4..6eeec21f3 100644 --- a/src/pages/IDViews/IDViews.tsx +++ b/src/pages/IDViews/IDViews.tsx @@ -130,7 +130,7 @@ const IDViews = () => { // Derived states - what we get from API const viewsDataResponse = useGettingIDViewsQuery({ - searchValue: "", + searchValue: searchValue, sizeLimit: 0, apiVersion: apiVersion || API_VERSION_BACKUP, startIdx: firstIdx, @@ -191,7 +191,7 @@ const IDViews = () => { useEffect(() => { viewsDataResponse.refetch(); setIsKebabOpen(false); - }, [page, perPage]); + }, []); // Refresh button handling const refreshViewsData = () => { @@ -206,6 +206,7 @@ const IDViews = () => { }; const updateSearchValue = (value: string) => { + setPage(1); setSearchValue(value); }; @@ -218,16 +219,18 @@ const IDViews = () => { const [retrieveViews] = useSearchEntriesMutation({}); // Issue a search using a specific search value - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; + setPage(1); setShowTableRows(false); setViewsTotalCount(0); setSearchIsDisabled(true); retrieveViews({ - searchValue: searchValue, + searchValue: search, sizeLimit: 0, apiVersion: apiVersion || API_VERSION_BACKUP, - startIdx: firstIdx, - stopIdx: lastIdx, + startIdx: 0, + stopIdx: 100, entryType: "idview", } as GenericPayload).then((result) => { // Manage new response here @@ -262,8 +265,7 @@ const IDViews = () => { idViewsList.push(viewsListResult[i].result); } - setPage(1); - setViewsList(idViewsList); + setViewsList(idViewsList.slice(0, perPage)); setViewsTotalCount(totalCount); setIsKebabOpen(false); setShowTableRows(true); diff --git a/src/pages/IDViews/IDViewsAppliedTo.tsx b/src/pages/IDViews/IDViewsAppliedTo.tsx index 51dbee68b..f65cd43a7 100644 --- a/src/pages/IDViews/IDViewsAppliedTo.tsx +++ b/src/pages/IDViews/IDViewsAppliedTo.tsx @@ -160,6 +160,7 @@ const IDViewsAppliedTo = (props: AppliesToProps) => { }; const updateSearchValue = (value: string) => { + setPage(1); setSearchValue(value); }; diff --git a/src/pages/IDViews/IDViewsOverrideGroups.tsx b/src/pages/IDViews/IDViewsOverrideGroups.tsx index 7f2434544..90eedd364 100644 --- a/src/pages/IDViews/IDViewsOverrideGroups.tsx +++ b/src/pages/IDViews/IDViewsOverrideGroups.tsx @@ -71,10 +71,6 @@ const IDViewsOverrideGroups = (props: PropsToOverrides) => { clearSelectedGroups, }; - // Page indexes - const firstIdx = (page - 1) * perPage; - const lastIdx = page * perPage; - // 'Delete' button state const [isDeleteButtonDisabled, setIsDeleteButtonDisabled] = useState(true); @@ -133,21 +129,24 @@ const IDViewsOverrideGroups = (props: PropsToOverrides) => { // Update search input valie const updateSearchValue = (value: string) => { + setPage(1); setSearchValue(value); }; const [retrieveEntries] = useSearchOverrideEntriesMutation({}); - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; + setPage(1); setShowTableRows(false); setTotalCount(0); setSearchIsDisabled(true); retrieveEntries({ idView: props.idview, - searchValue: searchValue, + searchValue: search, sizeLimit: 0, - startIdx: firstIdx, - stopIdx: lastIdx, + startIdx: 0, + stopIdx: 100, entryType: "idoverridegroup", } as IDOverridePayload).then((result) => { // Manage new response here @@ -181,7 +180,7 @@ const IDViewsOverrideGroups = (props: PropsToOverrides) => { for (let i = 0; i < groupsListSize; i++) { groupList.push(groupsListResult[i].result); } - setGroupsList(groupList); + setGroupsList(groupList.slice(0, perPage)); setTotalCount(totalCount); // Show table elements setShowTableRows(true); diff --git a/src/pages/IDViews/IDViewsOverrideUsers.tsx b/src/pages/IDViews/IDViewsOverrideUsers.tsx index b13597d97..96457a6ce 100644 --- a/src/pages/IDViews/IDViewsOverrideUsers.tsx +++ b/src/pages/IDViews/IDViewsOverrideUsers.tsx @@ -68,10 +68,6 @@ const IDViewsOverrideUsers = (props: PropsToOverrides) => { clearSelectedUsers, }; - // Page indexes - const firstIdx = (page - 1) * perPage; - const lastIdx = page * perPage; - // 'Delete' button state const [isDeleteButtonDisabled, setIsDeleteButtonDisabled] = useState(true); @@ -130,21 +126,24 @@ const IDViewsOverrideUsers = (props: PropsToOverrides) => { // Update search input valie const updateSearchValue = (value: string) => { + setPage(1); setSearchValue(value); }; const [retrieveEntries] = useSearchOverrideEntriesMutation({}); - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; + setPage(1); setShowTableRows(false); setTotalCount(0); setSearchIsDisabled(true); retrieveEntries({ idView: props.idview, - searchValue: searchValue, + searchValue: search, sizeLimit: 0, - startIdx: firstIdx, - stopIdx: lastIdx, + startIdx: 0, + stopIdx: 100, entryType: "idoverrideuser", } as IDOverridePayload).then((result) => { // Manage new response here @@ -178,7 +177,7 @@ const IDViewsOverrideUsers = (props: PropsToOverrides) => { for (let i = 0; i < usersListSize; i++) { userList.push(usersListResult[i].result); } - setUsersList(userList); + setUsersList(userList.slice(0, perPage)); setTotalCount(totalCount); // Show table elements setShowTableRows(true); diff --git a/src/pages/IdPReferences/IdpReferences.tsx b/src/pages/IdPReferences/IdpReferences.tsx index eefbcffe1..928cd9255 100644 --- a/src/pages/IdPReferences/IdpReferences.tsx +++ b/src/pages/IdPReferences/IdpReferences.tsx @@ -228,9 +228,11 @@ const IdpReferences = () => { // Search API call const [searchEntry] = useSearchIdpEntriesMutation(); - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; + setPage(1); searchEntry({ - searchValue: searchValue, + searchValue: search, apiVersion, sizelimit: 100, startIdx: 0, @@ -268,7 +270,7 @@ const IdpReferences = () => { } setTotalCount(totalCount); - setIdpReferences(elementsList); + setIdpReferences(elementsList.slice(0, perPage)); setShowTableRows(true); } setIsSearchDisabled(false); @@ -289,10 +291,15 @@ const IdpReferences = () => { totalCount, }; + const updateSearchValue = (value: string) => { + setPage(1); + setSearchValue(value); + }; + // SearchInputLayout const searchValueData = { searchValue, - updateSearchValue: setSearchValue, + updateSearchValue, submitSearchValue, }; diff --git a/src/pages/IdRanges/IdRanges.tsx b/src/pages/IdRanges/IdRanges.tsx index db4ca8179..5d1b5ad6b 100644 --- a/src/pages/IdRanges/IdRanges.tsx +++ b/src/pages/IdRanges/IdRanges.tsx @@ -182,10 +182,12 @@ const IdRanges = () => { const [isSearchDisabled, setIsSearchDisabled] = React.useState(false); - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; + setPage(1); setSearchOverride(null); searchEntry({ - searchValue: searchValue, + searchValue: search, apiVersion, sizelimit: 100, startIdx: 0, @@ -220,7 +222,7 @@ const IdRanges = () => { elementsList.push(listResult[i].result as IdRange); } - setSearchOverride({ list: elementsList, total }); + setSearchOverride({ list: elementsList.slice(0, perPage), total }); } setIsSearchDisabled(false); } @@ -261,6 +263,7 @@ const IdRanges = () => { const searchValueData = { searchValue, updateSearchValue: (v: string) => { + setPage(1); setSearchOverride(null); setSearchValue(v); }, diff --git a/src/pages/Netgroups/Netgroups.tsx b/src/pages/Netgroups/Netgroups.tsx index bf8c0ca68..1637d1ea4 100644 --- a/src/pages/Netgroups/Netgroups.tsx +++ b/src/pages/Netgroups/Netgroups.tsx @@ -190,6 +190,7 @@ const Netgroups = () => { }; const updateSearchValue = (value: string) => { + setPage(1); setSearchValue(value); }; @@ -202,16 +203,18 @@ const Netgroups = () => { const [retrieveGroup] = useSearchEntriesMutation({}); // Issue a search using a specific search value - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; + setPage(1); setShowTableRows(false); setGroupsTotalCount(0); setSearchIsDisabled(true); retrieveGroup({ - searchValue: searchValue, + searchValue: search, sizeLimit: 0, apiVersion: apiVersion || API_VERSION_BACKUP, - startIdx: firstIdx, - stopIdx: lastIdx, + startIdx: 0, + stopIdx: 100, entryType: "netgroup", } as GenericPayload).then((result) => { // Manage new response here @@ -246,8 +249,7 @@ const Netgroups = () => { groupsList.push(groupsListResult[i].result); } - setPage(1); - setGroupsList(groupsList); + setGroupsList(groupsList.slice(0, perPage)); setGroupsTotalCount(totalCount); // Show table elements setShowTableRows(true); diff --git a/src/pages/OtpTokens/OtpTokens.tsx b/src/pages/OtpTokens/OtpTokens.tsx index 996909bfb..afaa9c6f0 100644 --- a/src/pages/OtpTokens/OtpTokens.tsx +++ b/src/pages/OtpTokens/OtpTokens.tsx @@ -238,12 +238,13 @@ const OtpTokens = () => { // Search API call const [searchEntry] = useSearchOtpTokensEntriesMutation(); - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; setPage(1); setSearchValue(searchValue); setIsSearchDisabled(true); searchEntry({ - searchValue: searchValue, + searchValue: search, apiVersion, sizelimit: 100, startIdx: 0, @@ -273,7 +274,7 @@ const OtpTokens = () => { // Success const otpTokens = result.data?.result || []; setTotalCount(result.data?.totalCount || otpTokens.length); - setOtpTokens(otpTokens); + setOtpTokens(otpTokens.slice(0, perPage)); setShowTableRows(true); } setIsSearchDisabled(false); @@ -294,10 +295,15 @@ const OtpTokens = () => { totalCount, }; + const updateSearchValue = (value: string) => { + setPage(1); + setSearchValue(value); + }; + // - 'SearchInputLayout' const searchValueData = { searchValue, - updateSearchValue: setSearchValue, + updateSearchValue, submitSearchValue, }; diff --git a/src/pages/PasswordPolicies/PasswordPolicies.tsx b/src/pages/PasswordPolicies/PasswordPolicies.tsx index 75cd80e08..3376ea45c 100644 --- a/src/pages/PasswordPolicies/PasswordPolicies.tsx +++ b/src/pages/PasswordPolicies/PasswordPolicies.tsx @@ -255,15 +255,18 @@ const PasswordPolicies = () => { // Update search input valie const updateSearchValue = (value: string) => { + setPage(1); setSearchValue(value); }; // Search API call const [searchEntry] = useSearchPwdPolicyEntriesMutation(); - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; + setPage(1); searchEntry({ - searchValue: searchValue, + searchValue: search, apiVersion, sizelimit: 100, startIdx: 0, @@ -301,7 +304,7 @@ const PasswordPolicies = () => { } setTotalCount(totalCount); - setPwPolicies(elementsList); + setPwPolicies(elementsList.slice(0, perPage)); setShowTableRows(true); } setSearchIsDisabled(false); diff --git a/src/pages/PreservedUsers/PreservedUsers.tsx b/src/pages/PreservedUsers/PreservedUsers.tsx index 559ff47ea..a9258ca1e 100644 --- a/src/pages/PreservedUsers/PreservedUsers.tsx +++ b/src/pages/PreservedUsers/PreservedUsers.tsx @@ -197,6 +197,7 @@ const PreservedUsers = () => { // Filter (Input search) const updateSearchValue = (value: string) => { + setPage(1); setSearchValue(value); }; @@ -204,17 +205,19 @@ const PreservedUsers = () => { const [retrieveUser] = useSearchEntriesMutation({}); // Issue a search using a specific search value - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; + setPage(1); setShowTableRows(false); setUsersTotalCount(0); setSearchIsDisabled(true); retrieveUser({ - searchValue: searchValue, + searchValue: search, sizeLimit: 0, apiVersion: apiVersion || API_VERSION_BACKUP, - startIdx: firstUserIdx, - stopIdx: lastUserIdx, + startIdx: 0, + stopIdx: 100, entryType: "preserved", } as GenericPayload).then((result) => { // Manage new response here @@ -249,7 +252,7 @@ const PreservedUsers = () => { usersList.push(usersListResult[i].result); } - setPreservedUsersList(usersList); + setPreservedUsersList(usersList.slice(0, perPage)); setUsersTotalCount(totalCount); // Show table elements setShowTableRows(true); diff --git a/src/pages/Privileges/Privileges.tsx b/src/pages/Privileges/Privileges.tsx index 0bc3499c5..3c834f039 100644 --- a/src/pages/Privileges/Privileges.tsx +++ b/src/pages/Privileges/Privileges.tsx @@ -98,11 +98,13 @@ const Privileges = () => { // Derive privilegesList and totalCount from query response or search results const { elementsList, totalCount } = useMemo(() => { - // If search is active and has results, use search data + // If search is active and has results, paginate client-side if (isSearchActive && searchData) { + const start = (page - 1) * perPage; + const end = start + perPage; return { - elementsList: searchData.elementsList, - totalCount: searchData.totalCount, + elementsList: searchData.elementsList.slice(start, end), + totalCount: searchData.elementsList.length, }; } @@ -123,7 +125,7 @@ const Privileges = () => { } return { elementsList: [], totalCount: 0 }; - }, [batchResponse, isSearchActive, searchData]); + }, [batchResponse, isSearchActive, searchData, page, perPage]); // Derive showTableRows from loading states const showTableRows = useMemo(() => { @@ -178,17 +180,18 @@ const Privileges = () => { const [searchDisabled, setSearchIsDisabled] = useState(false); - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; setPage(1); setSearchIsDisabled(true); setIsSearchActive(true); searchPrivileges({ - searchValue: searchValue, + searchValue: search, sizeLimit: 0, apiVersion: apiVersion || API_VERSION_BACKUP, startIdx: 0, - stopIdx: perPage, + stopIdx: 100, }).then((result) => { if ("data" in result) { const searchError = result.data?.error as @@ -307,9 +310,14 @@ const Privileges = () => { updateSelectedPerPage: setSelectedPerPage, }; + const updateSearchValue = (value: string) => { + setPage(1); + setSearchValue(value); + }; + const searchValueData = { searchValue, - updateSearchValue: setSearchValue, + updateSearchValue, submitSearchValue, }; diff --git a/src/pages/Roles/Roles.tsx b/src/pages/Roles/Roles.tsx index 9fd64cff8..3b75c46f6 100644 --- a/src/pages/Roles/Roles.tsx +++ b/src/pages/Roles/Roles.tsx @@ -75,7 +75,7 @@ const Roles = () => { const lastIdx = page * perPage; const rolesDataResponse = useGettingRolesQuery({ - searchValue: "", + searchValue: searchValue, sizeLimit: 0, apiVersion: apiVersion || API_VERSION_BACKUP, startIdx: firstIdx, @@ -97,11 +97,13 @@ const Roles = () => { // Derive rolesList and totalCount from query response or search results const { elementsList, totalCount } = useMemo(() => { - // If search is active and has results, use search data + // If search is active and has results, paginate client-side if (isSearchActive && searchData) { + const start = (page - 1) * perPage; + const end = start + perPage; return { - elementsList: searchData.elementsList, - totalCount: searchData.totalCount, + elementsList: searchData.elementsList.slice(start, end), + totalCount: searchData.elementsList.length, }; } @@ -122,7 +124,7 @@ const Roles = () => { } return { elementsList: [], totalCount: 0 }; - }, [batchResponse, isSearchActive, searchData]); + }, [batchResponse, isSearchActive, searchData, page, perPage]); // Derive showTableRows from loading states const showTableRows = useMemo(() => { @@ -159,10 +161,8 @@ const Roles = () => { }; React.useEffect(() => { - if (!isSearchActive) { - rolesDataResponse.refetch(); - } - }, [page, perPage]); + rolesDataResponse.refetch(); + }, []); const [isDeleteButtonDisabled, setIsDeleteButtonDisabled] = useState(true); @@ -179,16 +179,18 @@ const Roles = () => { const [searchDisabled, setSearchIsDisabled] = useState(false); - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; + setPage(1); setSearchIsDisabled(true); setIsSearchActive(true); searchRoles({ - searchValue: searchValue, + searchValue: search, sizeLimit: 0, apiVersion: apiVersion || API_VERSION_BACKUP, - startIdx: firstIdx, - stopIdx: lastIdx, + startIdx: 0, + stopIdx: 100, }).then((result) => { if ("data" in result) { const searchError = result.data?.error as @@ -304,9 +306,14 @@ const Roles = () => { updateSelectedPerPage: setSelectedPerPage, }; + const updateSearchValue = (value: string) => { + setPage(1); + setSearchValue(value); + }; + const searchValueData = { searchValue, - updateSearchValue: setSearchValue, + updateSearchValue, submitSearchValue, }; diff --git a/src/pages/SELinuxUserMaps/SELinuxUserMaps.tsx b/src/pages/SELinuxUserMaps/SELinuxUserMaps.tsx index 4af646cc9..a515597c8 100644 --- a/src/pages/SELinuxUserMaps/SELinuxUserMaps.tsx +++ b/src/pages/SELinuxUserMaps/SELinuxUserMaps.tsx @@ -194,9 +194,11 @@ const SELinuxUserMaps = () => { const [searchEntry] = useSearchSelinuxUserMapsEntriesMutation(); - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; + setPage(1); searchEntry({ - searchValue, + searchValue: search, apiVersion, sizelimit: 100, startIdx: 0, @@ -236,9 +238,14 @@ const SELinuxUserMaps = () => { totalCount, }; + const updateSearchValue = (value: string) => { + setPage(1); + setSearchValue(value); + }; + const searchValueData = { searchValue, - updateSearchValue: setSearchValue, + updateSearchValue, submitSearchValue, }; diff --git a/src/pages/Services/Services.tsx b/src/pages/Services/Services.tsx index 00135eef8..428e781d4 100644 --- a/src/pages/Services/Services.tsx +++ b/src/pages/Services/Services.tsx @@ -111,6 +111,7 @@ const Services = () => { const [searchDisabled, setSearchIsDisabled] = useState(false); const updateSearchValue = (value: string) => { + setPage(1); setSearchValue(value); }; @@ -123,16 +124,18 @@ const Services = () => { const [retrieveServices] = useSearchEntriesMutation({}); // Issue search with filter - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; + setPage(1); setShowTableRows(false); setServicesTotalCount(0); setSearchIsDisabled(true); retrieveServices({ - searchValue: searchValue, + searchValue: search, sizeLimit: 0, apiVersion: apiVersion || API_VERSION_BACKUP, - startIdx: firstServiceIdx, - stopIdx: lastServiceIdx, + startIdx: 0, + stopIdx: 100, entryType: "service", } as GenericPayload).then((result) => { // Manage new response here @@ -167,7 +170,7 @@ const Services = () => { serviceList.push(serviceListResult[i].result); } - setServicesList(serviceList); + setServicesList(serviceList.slice(0, perPage)); setServicesTotalCount(totalCount); // Show table elements setShowTableRows(true); @@ -290,7 +293,7 @@ const Services = () => { // Derived states - what we get from API const servicesDataResponse = useGettingServicesQuery({ - searchValue: "", + searchValue: searchValue, sizeLimit: 0, apiVersion: apiVersion || API_VERSION_BACKUP, startIdx: firstServiceIdx, diff --git a/src/pages/StageUsers/StageUsers.tsx b/src/pages/StageUsers/StageUsers.tsx index 70e236a68..e585fc692 100644 --- a/src/pages/StageUsers/StageUsers.tsx +++ b/src/pages/StageUsers/StageUsers.tsx @@ -198,6 +198,7 @@ const StageUsers = () => { // Filter (Input search) const updateSearchValue = (value: string) => { + setPage(1); setSearchValue(value); }; @@ -205,17 +206,19 @@ const StageUsers = () => { const [retrieveUser] = useSearchEntriesMutation({}); // Issue a search using a specific search value - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; + setPage(1); setShowTableRows(false); setUsersTotalCount(0); setSearchIsDisabled(true); retrieveUser({ - searchValue: searchValue, + searchValue: search, sizeLimit: 0, apiVersion: apiVersion || API_VERSION_BACKUP, - startIdx: firstUserIdx, - stopIdx: lastUserIdx, + startIdx: 0, + stopIdx: 100, entryType: "stage", } as GenericPayload).then((result) => { // Manage new response here @@ -250,7 +253,7 @@ const StageUsers = () => { usersList.push(usersListResult[i].result); } - setStageUsersList(usersList); + setStageUsersList(usersList.slice(0, perPage)); setUsersTotalCount(totalCount); // Show table elements setShowTableRows(true); diff --git a/src/pages/SubordinateIDs/SubordinateIDs.tsx b/src/pages/SubordinateIDs/SubordinateIDs.tsx index 247771993..c9b759eb7 100644 --- a/src/pages/SubordinateIDs/SubordinateIDs.tsx +++ b/src/pages/SubordinateIDs/SubordinateIDs.tsx @@ -180,15 +180,18 @@ const SubordinateIDs = () => { // Update search input valie const updateSearchValue = (value: string) => { + setPage(1); setSearchValue(value); }; // Search API call const [searchEntry] = useSearchSubIdEntriesMutation(); - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; + setPage(1); searchEntry({ - searchValue: searchValue, + searchValue: search, apiVersion, sizelimit: 100, startIdx: 0, @@ -226,7 +229,7 @@ const SubordinateIDs = () => { } setTotalCount(totalCount); - setSubIds(elementsList); + setSubIds(elementsList.slice(0, perPage)); setShowTableRows(true); } setSearchIsDisabled(false); diff --git a/src/pages/SudoCmdGroups/SudoCmdGroups.tsx b/src/pages/SudoCmdGroups/SudoCmdGroups.tsx index c52ec916d..9c841f344 100644 --- a/src/pages/SudoCmdGroups/SudoCmdGroups.tsx +++ b/src/pages/SudoCmdGroups/SudoCmdGroups.tsx @@ -82,7 +82,7 @@ const SudoCmds = () => { // Derived states - what we get from API const cmdGroupsDataResponse = useGettingSudoCmdGroupsQuery({ - searchValue: "", + searchValue: searchValue, sizeLimit: 0, apiVersion: apiVersion || API_VERSION_BACKUP, startIdx: firstIdx, @@ -151,7 +151,7 @@ const SudoCmds = () => { // This ensures the data is always up-to-date. React.useEffect(() => { cmdGroupsDataResponse.refetch(); - }, [page, perPage]); + }, []); // 'Delete' button state const [isDeleteButtonDisabled, setIsDeleteButtonDisabled] = @@ -192,6 +192,7 @@ const SudoCmds = () => { // Update search input valie const updateSearchValue = (value: string) => { + setPage(1); setSearchValue(value); }; @@ -205,16 +206,18 @@ const SudoCmds = () => { const [retrieveCmds] = useSearchEntriesMutation({}); // Issue a search using a specific search value - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; + setPage(1); setShowTableRows(false); setSearchIsDisabled(true); setCmdGroupsTotalCount(0); retrieveCmds({ - searchValue: searchValue, + searchValue: search, sizeLimit: 0, apiVersion: apiVersion || API_VERSION_BACKUP, - startIdx: firstIdx, - stopIdx: lastIdx, + startIdx: 0, + stopIdx: 100, entryType: "sudocmdgroup", } as GenericPayload).then((result) => { // Manage new response here @@ -250,7 +253,7 @@ const SudoCmds = () => { } setCmdGroupsTotalCount(totalCount); - setCmdGroupsList(cmdsList); + setCmdGroupsList(cmdsList.slice(0, perPage)); // Show table elements setShowTableRows(true); } diff --git a/src/pages/SudoCmds/SudoCmds.tsx b/src/pages/SudoCmds/SudoCmds.tsx index 3d16d8201..bb5f502dd 100644 --- a/src/pages/SudoCmds/SudoCmds.tsx +++ b/src/pages/SudoCmds/SudoCmds.tsx @@ -82,7 +82,7 @@ const SudoCmds = () => { // Derived states - what we get from API const cmdsDataResponse = useGettingSudoCmdsQuery({ - searchValue: "", + searchValue: searchValue, sizeLimit: 0, apiVersion: apiVersion || API_VERSION_BACKUP, startIdx: firstIdx, @@ -153,7 +153,7 @@ const SudoCmds = () => { // This ensures the data is always up-to-date. React.useEffect(() => { cmdsDataResponse.refetch(); - }, [page, perPage]); + }, []); // 'Delete' button state const [isDeleteButtonDisabled, setIsDeleteButtonDisabled] = @@ -194,6 +194,7 @@ const SudoCmds = () => { // Update search input valie const updateSearchValue = (value: string) => { + setPage(1); setSearchValue(value); }; @@ -207,16 +208,18 @@ const SudoCmds = () => { const [retrieveCmds] = useSearchEntriesMutation({}); // Issue a search using a specific search value - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; + setPage(1); setShowTableRows(false); setSearchIsDisabled(true); setCmdsTotalCount(0); retrieveCmds({ - searchValue: searchValue, + searchValue: search, sizeLimit: 0, apiVersion: apiVersion || API_VERSION_BACKUP, - startIdx: firstIdx, - stopIdx: lastIdx, + startIdx: 0, + stopIdx: 100, entryType: "sudocmd", } as GenericPayload).then((result) => { // Manage new response here @@ -252,7 +255,7 @@ const SudoCmds = () => { } setCmdsTotalCount(totalCount); - setCmdsList(cmdsList); + setCmdsList(cmdsList.slice(0, perPage)); // Show table elements setShowTableRows(true); } diff --git a/src/pages/SudoRules/SudoRules.tsx b/src/pages/SudoRules/SudoRules.tsx index 2bd4de233..04dcd7e9f 100644 --- a/src/pages/SudoRules/SudoRules.tsx +++ b/src/pages/SudoRules/SudoRules.tsx @@ -84,7 +84,7 @@ const SudoRules = () => { // Derived states - what we get from API const rulesDataResponse = useGettingSudoRulesQuery({ - searchValue: "", + searchValue: searchValue, sizeLimit: 0, apiVersion: apiVersion || API_VERSION_BACKUP, startIdx: firstIdx, @@ -153,7 +153,7 @@ const SudoRules = () => { // This ensures the data is always up-to-date. React.useEffect(() => { rulesDataResponse.refetch(); - }, [page, perPage]); + }, []); // 'Delete' button state const [isDeleteButtonDisabled, setIsDeleteButtonDisabled] = @@ -215,6 +215,7 @@ const SudoRules = () => { // Update search input valie const updateSearchValue = (value: string) => { + setPage(1); setSearchValue(value); }; @@ -228,16 +229,18 @@ const SudoRules = () => { const [retrieveRules] = useSearchEntriesMutation({}); // Issue a search using a specific search value - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; + setPage(1); setShowTableRows(false); setSearchIsDisabled(true); setRulesTotalCount(0); retrieveRules({ - searchValue: searchValue, + searchValue: search, sizeLimit: 0, apiVersion: apiVersion || API_VERSION_BACKUP, - startIdx: firstIdx, - stopIdx: lastIdx, + startIdx: 0, + stopIdx: 100, entryType: "sudorule", } as GenericPayload).then((result) => { // Manage new response here @@ -273,7 +276,7 @@ const SudoRules = () => { } setRulesTotalCount(totalCount); - setRulesList(rulesList); + setRulesList(rulesList.slice(0, perPage)); // Show table elements setShowTableRows(true); } diff --git a/src/pages/Trusts/TrustedDomains.tsx b/src/pages/Trusts/TrustedDomains.tsx index f3b1af8d3..53139866e 100644 --- a/src/pages/Trusts/TrustedDomains.tsx +++ b/src/pages/Trusts/TrustedDomains.tsx @@ -225,10 +225,12 @@ const TrustedDomains = (props: TrustedDomainsProps) => { // Search API call const [searchEntry] = useSearchTrustDomainsEntriesMutation(); - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; + setPage(1); const payload: TrustDomainFindPayload = { trustId: props.trustId, - searchValue: searchValue, + searchValue: search, sizelimit: 0, }; @@ -253,9 +255,8 @@ const TrustedDomains = (props: TrustedDomainsProps) => { const trustDomains = ( result.data?.result.result as unknown as Record[] ).map((item) => apiToTrustDomain(item)); - const paginatedData = trustDomains.slice(startIdx, stopIdx); - setTrustDomains(paginatedData); - setTotalCount(paginatedData.length); + setTrustDomains(trustDomains.slice(0, perPage)); + setTotalCount(trustDomains.length); setShowTableRows(true); } } else { @@ -319,10 +320,15 @@ const TrustedDomains = (props: TrustedDomainsProps) => { totalCount, }; + const updateSearchValue = (value: string) => { + setPage(1); + setSearchValue(value); + }; + // SearchInputLayout const searchValueData = { searchValue, - updateSearchValue: setSearchValue, + updateSearchValue, submitSearchValue, }; diff --git a/src/pages/Trusts/Trusts.tsx b/src/pages/Trusts/Trusts.tsx index 10443dc26..3a4a8598d 100644 --- a/src/pages/Trusts/Trusts.tsx +++ b/src/pages/Trusts/Trusts.tsx @@ -218,9 +218,11 @@ const Trusts = () => { // Search API call const [searchEntry] = useSearchTrustsEntriesMutation(); - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; + setPage(1); searchEntry({ - searchValue, + searchValue: search, apiVersion, sizelimit: 100, startIdx: 0, @@ -267,10 +269,15 @@ const Trusts = () => { totalCount, }; + const updateSearchValue = (value: string) => { + setPage(1); + setSearchValue(value); + }; + // SearchInputLayout const searchValueData = { searchValue, - updateSearchValue: setSearchValue, + updateSearchValue, submitSearchValue, }; diff --git a/src/pages/UserGroups/UserGroups.tsx b/src/pages/UserGroups/UserGroups.tsx index 841015b6f..9974841c7 100644 --- a/src/pages/UserGroups/UserGroups.tsx +++ b/src/pages/UserGroups/UserGroups.tsx @@ -190,6 +190,7 @@ const UserGroups = () => { }; const updateSearchValue = (value: string) => { + setPage(1); setSearchValue(value); }; @@ -202,16 +203,18 @@ const UserGroups = () => { const [retrieveGroup] = useSearchEntriesMutation({}); // Issue a search using a specific search value - const submitSearchValue = () => { + const submitSearchValue = (value?: string) => { + const search = value ?? searchValue; + setPage(1); setShowTableRows(false); setGroupsTotalCount(0); setSearchIsDisabled(true); retrieveGroup({ - searchValue: searchValue, + searchValue: search, sizeLimit: 0, apiVersion: apiVersion || API_VERSION_BACKUP, - startIdx: firstIdx, - stopIdx: lastIdx, + startIdx: 0, + stopIdx: 100, entryType: "usergroup", } as GenericPayload).then((result) => { // Manage new response here @@ -247,8 +250,7 @@ const UserGroups = () => { } // Update 'user groups' slice data - setPage(1); - setGroupsList(groupsList); + setGroupsList(groupsList.slice(0, perPage)); setGroupsTotalCount(totalCount); // Show table elements setShowTableRows(true);