From deb5419c478a6c044077c86848e48d630e063fd9 Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Fri, 21 Aug 2026 00:23:07 +0000 Subject: [PATCH] =?UTF-8?q?analytics=20improvements:=20-=20keep=20visitor?= =?UTF-8?q?=20charts=20y-axis=20minimum=20range=20at=2010=20-=20keep=20'al?= =?UTF-8?q?l'=20chart=20x-axis=20minimum=20span=20at=2030=20days=20-=20gro?= =?UTF-8?q?up=20crawler=20hits=20by=20(ip,=20ua)=20and=20list=20top=20page?= =?UTF-8?q?s=20visited,=20show=20crawler=20page=20load=20counts=20as=20N?= =?UTF-8?q?=C3=97=20prefix=20-=20store=20and=20display=20geoip=20city,=20k?= =?UTF-8?q?eep=20geoip=20country=20overwrite=20-=20stream=20live=20updates?= =?UTF-8?q?=20over=20WebSocket=20/=5Fapi/ws/analytics=20-=20include=20fami?= =?UTF-8?q?ly=20ring=20arcs=20in=20transition=20map=20crop=20bounds=20-=20?= =?UTF-8?q?remove=20top=20UA=20summary,=20limit=20crawlers=20to=2010=20and?= =?UTF-8?q?=20visits=20to=2020=20-=20human-readable=20relative=20timestamp?= =?UTF-8?q?s=20with=20UTC=20tooltip?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/analytics.md | 33 ++++--- frontend/src/AnalyticsView.vue | 89 ++++++++++++------ frontend/src/analytics/chart.js | 12 +-- frontend/src/analytics/format.js | 125 ++++++++++++++++++++++---- frontend/src/analytics/time.js | 9 +- frontend/src/analytics/transitions.js | 35 +++++++- pagerite/analytics.py | 28 ++++++ pagerite/app.py | 78 ++++++++++++++-- 8 files changed, 333 insertions(+), 76 deletions(-) diff --git a/docs/analytics.md b/docs/analytics.md index 6ed3db3..efd9358 100644 --- a/docs/analytics.md +++ b/docs/analytics.md @@ -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). diff --git a/frontend/src/AnalyticsView.vue b/frontend/src/AnalyticsView.vue index 0d8ea57..607382d 100644 --- a/frontend/src/AnalyticsView.vue +++ b/frontend/src/AnalyticsView.vue @@ -1,16 +1,14 @@