feat(ui): audit log, alert history and Ansible runs load as you scroll (#5211)
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 51s
CI / unit (push) Successful in 1m13s
CI / publish (push) Successful in 1m7s

#5211 "Lists load as you scroll: audit, alert history, Ansible runs"
(milestone 477), per preference 172.

- core/paging.py: keyset paging over (time, id), newest first, with an
  opaque "<iso>|<id>" cursor; a page reads one row past itself to know
  whether another follows. No OFFSET, so rows added while someone scrolls
  never shift the pages after them.
- _macros.html scroll_more / scroll_end and static/scroll-more.js, written
  once: an IntersectionObserver with a 600px margin fetches the next page
  before the reader reaches the end; "Loading more…" while in flight,
  "End of the list." at the end, and a failed page says so with a Retry
  button instead of re-firing on every scroll.
- Audit log: no "Load more" link and no 500-row cap.
- Alert history: no 200-row stop with "pick a shorter range";
  alert_svc.history takes the cursor, so the MCP tool and the page share
  one query.
- Ansible runs: no silent cap of 50.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-07 00:10:53 -04:00
co-authored by Claude Opus 5.5
parent 8e890c6914
commit 7275fad767
18 changed files with 350 additions and 129 deletions
+3 -1
View File
@@ -50,7 +50,7 @@ async def test_the_runs_list_names_each_run_with_one_badge(app):
)]
async with app.test_request_context("/ansible/"):
session["user_role"] = "viewer"
page = await render_template("ansible/index.html", runs=runs)
page = await render_template("ansible/index.html", runs=runs, after="2026-10-07T01:00:00+00:00|x")
main = page[page.index("<main"):]
assert '<a href="/ansible/runs/aaaa1111" title="steward:target:t1">update.yml on web-1</a>' in main
@@ -58,6 +58,8 @@ async def test_the_runs_list_names_each_run_with_one_badge(app):
assert '<span class="badge badge-info">Running</span>' in main
assert '<span class="badge badge-crit">Failed</span>' in main
assert '<span class="badge badge-ok">Succeeded</span>' in main
# The list continues itself as the reader scrolls (#5211): no cap of 50.
assert 'data-scroll-more hx-get="/ansible/?before=2026-10-07T01%3A00%3A00%2B00%3A00%7Cx"' in main
assert "2m 5s" in main and "40s" in main and " so far" in main
assert "dry run" in main and "ago" in main
assert "RUNNING" not in main and "FAILED" not in main # the old shouted status text is gone