Skip to content

feat(admin): paginate audit and request logs#155

Open
erwill2 wants to merge 1 commit into
icoretech:mainfrom
erwill2:feat/admin-log-pagination
Open

feat(admin): paginate audit and request logs#155
erwill2 wants to merge 1 commit into
icoretech:mainfrom
erwill2:feat/admin-log-pagination

Conversation

@erwill2

@erwill2 erwill2 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements item 11 by adding URL-driven pagination to both admin log views using one shared pagination component and helper:

  • /admin/audit-logs
  • /admin/request-logs

Both pages now render matching top and bottom controls, preserve active filters while navigating, omit page=1 from canonical URLs, show exact visible ranges, and clamp stale or overflowing page numbers.

This PR is based directly on current main (b628d7ea). The GitHub audit found no existing audit/request-log pagination PR or issue; the Jobs explorer is the closest existing UI precedent.

Context

The underlying readers already expose a stable offset-pagination contract:

  • items
  • exact filtered total
  • limit
  • offset

Audit events are ordered newest-first by their existing stable ordering, and request logs are ordered by admitted_at DESC, id DESC. The two LiveViews nevertheless always requested limit: 50 with the default offset, so records after the first 50 were inaccessible from the admin UI.

No accounting or audit query semantics needed to change. This PR wires the existing offset support into URL state and presentation.

Shared pagination design

CodexPoolerWeb.Admin.LogPagination owns the behavior shared by both pages:

  • parses and validates the page query parameter;
  • bounds pages to 1..10,000 before an SQL OFFSET can be constructed;
  • computes page offsets from the existing page size;
  • computes page count, previous/next state, and visible record range;
  • clamps requested pages to the last available page, including an empty result set;
  • builds deterministic, filter-preserving URLs;
  • removes page when navigating back to page 1;
  • renders the common accessible Previous/Next controls, page status, and range.

The audit and request-log components only supply route-specific IDs, labels, range roles, and border placement. This avoids maintaining two copies of the same pagination markup and arithmetic.

URL and state behavior

Situation Behavior
No page or blank page Load page 1
Valid page N Query with offset (N - 1) * 50
Invalid, structured, non-positive, or over-limit page Fall back to page 1 and expose validation feedback
Requested page exceeds the filtered result count Patch to the last available page while preserving filters
Requested page is greater than 1 and the result set is empty Patch to the canonical page-1 URL
Previous navigation reaches page 1 Remove the page query parameter
A request-log event refresh arrives while viewing page N Refresh page N; clamp only if the result count shrank below it
A filter changes Existing filter handlers produce a fresh filter URL and naturally reset to page 1

Request-log drawer state remains in current_params, so opening or closing a selected request continues to preserve the current page and filters.

Presentation

Both pages render pagination above and below their desktop/mobile log tables:

  • Page N of M
  • Showing X-Y of Z
  • disabled Previous/Next controls at the boundaries
  • filter-preserving LiveView patches
  • distinct accessible labels for top and bottom controls

Controls render whenever the filtered result has records, including a last page that becomes temporarily empty before LiveView applies its canonical clamp.

Realtime request-log refresh

Request logs can refresh from PostgreSQL-backed events. The LiveView now stores the current page separately from parsed filters and uses that page for lightweight event refreshes. If a new row arrives, the operator remains on the current page. If rows disappear and the page is no longer valid, the view patches to the new last page.

This keeps realtime behavior consistent with explicit pagination instead of unexpectedly returning operators to page 1.

Safety and access control

  • Pool and scope visibility still come from the existing Pools.list_log_filter_pools/1, audit readers, and request-log readers.
  • Selected-pool and all-visible-pools behavior is unchanged.
  • Pagination links preserve only the parameters already held by each LiveView.
  • Page input is bounded before calculating database offsets.
  • No raw log content, credentials, or sensitive metadata is added to URLs or pagination output.
  • Existing redaction and drawer access checks are unchanged.

Tests

The regression coverage verifies:

  • shared parsing for missing, blank, valid, malformed, structured, non-positive, and over-limit pages;
  • shared offset, clamp, metadata, navigation, range, and deterministic-path behavior;
  • audit-log page 1/page 2 navigation with filters preserved;
  • request-log page 1/page 2 navigation with filters preserved;
  • canonical removal of page=1;
  • exact Showing X-Y of Z ranges;
  • disabled controls at first/last-page boundaries;
  • overflow clamping to the last page;
  • empty-result clamping to page 1;
  • request-log event refresh while the operator remains on page 2;
  • existing audit/request rendering, filtering, authorization, redaction, drawer, and realtime tests.

Local verification:

  • mix compile --warnings-as-errors - passed in a clean build volume
  • complete shared-pagination, audit-log LiveView, and request-log LiveView suites - 48 passed
  • focused page-2 realtime refresh regression - 1 passed
  • mix credo --strict for all eight changed files - no issues
  • mix format --check-formatted for the new shared module and changed test files - passed
  • git diff --check - passed

Files changed

  • shared pagination component/helper
  • audit-log table component
  • request-log presentation component
  • audit-log LiveView
  • request-log LiveView
  • shared pagination unit tests
  • audit-log LiveView pagination tests
  • request-log LiveView pagination/realtime tests

Scope boundaries

This PR intentionally does not include:

  • the saved-reset quota fix from fix(upstreams): converge confirmed saved reset quota #153;
  • unrelated local admin UI changes;
  • a refactor of the existing Jobs explorer pagination;
  • changes to audit/accounting persistence or query contracts;
  • schema migrations or data backfills;
  • deployment or Portainer changes.

Risk and rollback

The pages use the repositories' existing offset pagination, so new records can shift later pages while an operator is browsing. That is the established reader contract and is preferable here to broadening this UI change into a cursor-pagination data-layer rewrite. Stable secondary ordering prevents nondeterministic ties within a snapshot.

Rollback is a single commit revert. There are no schema changes, backfills, or irreversible writes.

Add shared, filter-preserving pagination controls and bounded page parsing for both admin log views. Clamp empty and overflow pages, preserve request-log page state during event refresh, and cover the helper plus LiveView navigation regressions.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant