admin: activity history filters become a live-apply toolbar
Gates / frontend (push) Failing after 1m4s
Gates / package (push) Skipped
Gates / container (push) Skipped
Gates / test (push) Successful in 2m33s
Gates / test-aarch64 (push) Successful in 7m33s
CI / gates (push) Failing after 10m6s

the five-field form with its apply button is gone. one row holds a search-shaped domain field and a client ip field that debounce into the url with enter flushing at once, result segments and a time menu that commit instantly, and a clear that appears only when a filter is active without reflowing the row. presets freeze both absolute bounds at click time so a bookmark describes the same investigation later, custom ranges apply atomically through set range with inline validation, and echo queues keep in-flight commits from clobbering newer typing or newer picks. live mode renders no toolbar, the availability banner became a footer note, previous rows stay visible during refetch, and every control carries a 44px hit target.
This commit is contained in:
2026-08-29 14:56:40 +02:00
parent c65d92d8f8
commit 039eeeda96
7 changed files with 1107 additions and 233 deletions
@@ -521,27 +521,18 @@ test("an open streamed detail survives Freeze and Resume", async () => {
expect(detailDialog().textContent).toBe(snapshot);
});
test("the filter row stays visible, keeps its values, and is out of the tab order", async () => {
test("live renders no filter toolbar, not even a disabled one", async () => {
await openLive("/activity?mode=live&domain=ads&client=192.0.2.10&blocked=true");
const domain = screen.getByLabelText("Domain contains") as HTMLInputElement;
expect(domain.value).toBe("ads");
expect(domain.disabled).toBe(true);
expect((screen.getByLabelText("Client (exact)") as HTMLInputElement).disabled).toBe(true);
expect((screen.getByLabelText("Since") as HTMLInputElement).disabled).toBe(true);
expect((screen.getByLabelText("Until") as HTMLInputElement).disabled).toBe(true);
const form = domain.closest("form")!;
const controls = [...form.querySelectorAll("input, button, select, textarea, a[href], [tabindex]")];
expect(controls.length).toBeGreaterThan(0);
for (const control of controls) {
// A disabled form control is skipped by the browser's tab order, and RAC
// pins its own trigger out of it as well. Nothing in the row may
// reintroduce itself with a reachable tabindex.
expect(control.hasAttribute("disabled")).toBe(true);
const tabindex = control.getAttribute("tabindex");
expect(tabindex === null || tabindex === "-1").toBe(true);
}
// The stream is unfiltered — the server sends every query — so a row of
// controls here would promise filtering that is not happening. The filters
// are not lost: they are in the URL, and History applies them on the way back.
expect(screen.queryByLabelText("Filter domains")).toBeNull();
expect(screen.queryByLabelText("Client IP (exact match)")).toBeNull();
expect(screen.queryByRole("radio", { name: "Blocked" })).toBeNull();
expect(screen.queryByRole("button", { name: /^Time: / })).toBeNull();
expect(screen.queryByText("Clear")).toBeNull();
expect(screen.getByText(/the History filters apply to history only/)).toBeTruthy();
});
test("live mode asks for no query pages, whatever filters the url retained", async () => {
@@ -555,13 +546,12 @@ test("leaving live closes the stream, and coming back opens exactly one fresh on
await openLive("/activity?mode=live&domain=ads");
fireEvent.click(screen.getByRole("tab", { name: "History" }));
await screen.findByRole("button", { name: "Apply filters" });
await screen.findByLabelText("Filter domains");
expect(sources).toHaveLength(1);
expect(sources[0]!.closed).toBe(true);
// The filters came along, which is the point of switching rather than
// navigating: the reader keeps the question they were asking.
expect((screen.getByLabelText("Domain contains") as HTMLInputElement).value).toBe("ads");
expect((screen.getByLabelText("Domain contains") as HTMLInputElement).disabled).toBe(false);
expect((screen.getByLabelText("Filter domains") as HTMLInputElement).value).toBe("ads");
fireEvent.click(screen.getByRole("tab", { name: "Live" }));
await screen.findByRole("button", { name: "Freeze" });