fix(findings): server-side pagination — show all findings, not just first 100 - #324
Merged
Merged
Conversation
…irst 100 The findings list fetched a single page of per_page=100 and let the shared DataTable paginate those rows client-side (10/page -> 10 pages). A tenant with thousands of findings could therefore only ever reach the first 100; the tab count (from the stats endpoint) correctly showed the true total, making the capped table look broken. - DataTable: add opt-in manual (server) pagination — manualPagination, pageCount/ rowCount, controlled pagination + onPaginationChange, and getRowId for stable selection across page swaps. Default path (client-side) is byte-compatible, so the ~100 other callers are unaffected. - findings page: drive page/per_page from pagination state, use the response total for the pager, reset to page 1 on any filter change.
0xmanhnv
force-pushed
the
fix/findings-server-pagination
branch
from
July 24, 2026 07:37
08232a9 to
f147802
Compare
0xmanhnv
added a commit
that referenced
this pull request
Jul 27, 2026
…ved (#334) Found while studying OASM's console against ours; none of them are UX gaps. 1. Silent data loss. The Add buttons on attack-surface external/internal/cloud built an object, pushed it onto local state and toasted 'added successfully' with NO API call anywhere in the file — while the page READ from useAssets. The asset was gone on the next reload. Each handler now calls createAsset and refetches; cloud creates 'cloud_account' because that is the type its own list queries, so the new row appears in the list that just confirmed it. 2. Copying an API key failed silently over plain HTTP. The page called navigator.clipboard.writeText directly, which is undefined outside a secure context — so on a LAN deployment over http:// the key was never copied and nothing said so. We already ship lib/clipboard.ts with an execCommand fallback for exactly this; use it. 3. Sorting a server-paginated table silently sorted only the visible page. #324 added manualPagination without manualSorting/manualFiltering, so tanstack kept sorting and filtering client-side over the one page it had. Clicking a column header appeared to sort 6330 findings and actually reordered 20. My regression; the findings page had been working around it with showSearch={false}. Also: vitest's exclude used bare directory names, so a git worktree under .claude/ dragged its own node_modules — and a second copy of the app — into every local run: ~16,000 tests, 270s, and 274 phantom failures from Playwright specs vitest cannot execute. Globbed the patterns. The suite is now 768 tests in 11s, which is the difference between a signal and noise. Verified: tsc 0 errors, eslint clean, 768/768 pass, and all four affected routes compile and serve 200 on the live dev server. Co-authored-by: Nguyen Manh <0xmanhnv@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
On the Findings list, the severity tabs showed the true total (e.g. All (6330)) but the table was stuck at "Page 10 of 10 · of 100 results" — a tenant with thousands of findings could only ever see the first 100.
Cause:
findings/page.tsxfetched a single page ofper_page: 100(nopageparam) and handed those 100 rows to the sharedDataTable, which paginated them client-side (10/page → exactly 10 pages). The tab count comes from the separate stats endpoint (COUNT(*)), so it was correct — which made the capped table look broken.The fix — real server-side pagination
The API + types already supported
page/per_page/total_pages; only the UI wiring was missing.DataTable(shared, ~100 callers): add opt-in manual pagination —manualPagination,pageCount/rowCount, controlledpagination+onPaginationChangegetRowIdfor stable row selection when the page's data swapsgetPaginationRowModelis omitted (the parent already fetched one page) and the "Showing X of Y" total uses the server row countThe default (client-side) path is unchanged — every existing caller is byte-compatible.
findings/page.tsx: drivepage/per_pagefrompaginationstate (default 20/page), feed the responsetotalto the pager, and reset to page 1 on any filter change.Verify
tsc --noEmit= 0 errors; eslint clean on both files./findingscompiles + returns 200; the pager now walks the full result set, page size selector works, and filters reset to page 1.