analytics improvements:

- keep visitor charts y-axis minimum range at 10
- keep 'all' chart x-axis minimum span at 30 days
- group crawler hits by (ip, ua) and list top pages visited, show crawler page load counts as N× prefix
- store and display geoip city, keep geoip country overwrite
- stream live updates over WebSocket /_api/ws/analytics
- include family ring arcs in transition map crop bounds
- remove top UA summary, limit crawlers to 10 and visits to 20
- human-readable relative timestamps with UTC tooltip
This commit is contained in:
2026-08-21 01:36:58 +00:00
parent 242b62784c
commit deb5419c47
8 changed files with 333 additions and 76 deletions
+21 -12
View File
@@ -8,8 +8,8 @@ Struct dumped to disk — separate from the kanta content database, path from
- `pagerite/analytics.py` — data model (`Analytics`, `Visit`) and the `Store`
(in-memory data + session map, atomic JSON persistence).
- `pagerite/app.py` — entry-referer stashing in `show_page` (`_track_entry`),
the `POST /_a` ping endpoint, and `GET /_api/analytics` (admin-gated like
every `/_api` endpoint).
the `POST /_a` ping endpoint, and `WebSocket /_api/ws/analytics`
(admin-gated like every `/_api` endpoint).
- `frontend/src/pagerite.js` — client navigation pings and the 📊 pen.
- `frontend/src/AnalyticsView.vue` — viewer component rendered inside the
normal site layout on the `/_a` analytics page.
@@ -59,7 +59,10 @@ The client (`pagerite.js`) POSTs fire-and-forget pings to `/_a` with
- **Crawler hits**: every document GET is queued in RAM as a pending crawler
hit. If a ping from the same (IP, User-Agent) pair arrives within 10
seconds the hit is discarded; otherwise it is written to `crawlers`.
Crawlers do not count as visits or views.
Crawlers do not count as visits or views. In the analytics viewer, crawler
hits are grouped by the same (IP, User-Agent) pair and shown as a trail of
internal pages that crawler visited; the crawler table lists the most active
crawlers first rather than the most recent hits.
## Visits and sessions
@@ -86,6 +89,7 @@ Each `Visit` record:
- `country` — two-letter country code. Initially derived from the
`Accept-Language` region subtag, but overwritten by the DB-IP MMDB result
when a database is available,
- `city` — city name from the DB-IP MMDB lookup, when available,
- `ua` — raw `User-Agent` string from the initial ping,
- `ua_pretty` — compact display form of the UA (browser/OS/device) when
parsable, otherwise the raw string,
@@ -131,9 +135,9 @@ The 📊 pen in the banner corner (admins only, injected by pagerite.js next to
the edit pens) links to `/_a`, the analytics page. It is a normal site page:
the standard banner, navigation and footer stay in place, and the analytics
content is rendered inside `#main`. The page itself is public, but the data
still comes from `GET /_api/analytics`, which remains admin-gated like the
rest of the management API; visitors without access see the viewer with a
"could not be loaded" message.
stream comes from `WebSocket /_api/ws/analytics`, which remains admin-gated
like the rest of the management API; visitors without access see the viewer
with a "could not be loaded" message.
Because it is a real page, fetch-navigation handles it like any other internal
link: clicking the 📊 pen (or any link to `/_a`) fetches the server-rendered
@@ -156,16 +160,19 @@ the smoothing time scale follows the unit: the month+ sigmas are 24× the
hourly ones. The y max is derived from the smoothed curves so single-bucket
spikes don't blow up the scale, and raw spikes are clamped into the plot.
Axes always start at 0 and end at a multiple of a 1-2-5 major step (max 5
labeled intervals, minor lines at fifths when integral; the floor is 1/h).
labeled intervals, minor lines at fifths when integral; the minimum y-axis
range is 10 so tiny values such as a single visit are not stretched to a
fractional scale).
The week range is aligned to Monday 00:00 UTC and overlays up to 8 previous
weeks in the same accent color at decreasing opacity (the current week is
truncated at the current bucket, never drawing fake zeroes for the future);
its x labels are weekday names centered at midday UTC, without vertical grid
lines (day boundaries would be misleading in the viewer's timezone). The
month view labels days the same lineless way — day numbers at noon UTC,
with the month name substituted for the 1st. Year and all are rolling
windows ending at now, re-bucketed to daily points, with boundary lines at
months/years. Below the charts: a radial **transition map** (all pages from
with the month name substituted for the 1st. Year is a rolling 365-day window ending at now, re-bucketed to daily points,
with boundary lines at months/years. All uses the full data reach, but keeps
at least the past 30 days so the chart never collapses to a tiny sliver when
the site is young. Below the charts: a radial **transition map** (all pages from
`/_api/pages` — front page at the center, each slug level on its own ring,
siblings clockwise in navigation order from the top, radial gap equal to
the arc spacing — opposite transition directions joined into organic
@@ -178,5 +185,7 @@ to the directional count with no in-flight limit, opposing directions
offset onto parallel lanes. External referers show as a node row above the
map, external exits as small nodes fanned outwards from their source
page), per-page view
counts, the top transitions and the 50 most recent visit trails. Data comes from `GET /_api/analytics`, which
returns the raw JSON file contents.
counts, the top transitions and the 50 most recent visit trails. Data is
streamed live over `WebSocket /_api/ws/analytics`, which pushes the latest
JSON snapshot on connect and again whenever the analytics file is updated
(with a small server-side debounce to avoid flooding under high traffic).