fix(diagnostics): honest live console after review

- Live stops polling on a failed read and marks the list as possibly
  stale (with Retry), instead of re-raising the error dialog every tick.
- While live, a refetch reads the newest page only, whatever the cache
  holds (a late "Load older", a revisited filter set).
- Copy is disabled and the list dimmed while a filter change loads.
- Dates follow the UI language; the pivot chip uses the shared .chip.
- Tests: query-string wire test, Copy, every facet's skip key.
This commit is contained in:
2026-09-19 03:42:26 +02:00
parent 1c49870a7a
commit 8f6f89832f
7 changed files with 213 additions and 17 deletions
+4 -2
View File
@@ -39,6 +39,8 @@ PAGE_MAX = 200
FACET_EVENTS_MAX = 100
Level = Literal["TRACE", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
# The filters a facet can leave out of its own count (`LogFilters.conditions`).
Facet = Literal["levels", "sources", "user", "event"]
# `none` selects the rows that have no account / no event code (a scheduler line, an anonymous
# request) — a real value to filter on, not the absence of a filter.
NONE = "none"
@@ -106,7 +108,7 @@ class LogFilters:
since: datetime | None
until: datetime | None
def conditions(self, skip: str | None = None) -> list:
def conditions(self, skip: Facet | None = None) -> list:
"""The WHERE conditions, ANDed by the caller. `skip` leaves one filter out: a facet counts
its own values under every OTHER filter, so picking a level does not zero the other
levels' counts (the way back), while a request pivot does narrow them (honest counts)."""
@@ -210,7 +212,7 @@ def log_facets(
most frequent events — each counted under every filter but its own (`LogFilters.conditions`).
`total` is the "all levels" count: every filter but the level one."""
def counts(col, skip: str, cap: int | None = None) -> list[dict]:
def counts(col, skip: Facet, cap: int | None = None) -> list[dict]:
n = func.count().label("n")
stmt = select(col, n).where(*filters.conditions(skip)).group_by(col).order_by(n.desc(), col)
if cap is not None: