Single-binary self-hosted website analytics on Rust axum: collector API, dashboards, world map, and PDF reports.
analyticsaxumdockerrustself-hostedsqliteviteweb-analytics
1{% extends 'base.html' %}
2
3
4{% block extra_head %}
5{% include "includes/social.html" %}
6{% endblock %}
7
8
9{% block extra_css %}
10<link href="{{ vite_asset('static_src/pages/index.js', 'css') }}" rel="stylesheet">
11{% endblock %}
12
13
14{% block breadcrumbs %}
15<nav aria-label="breadcrumb">
16 <ol class="breadcrumb mb-0">
17 <li class="breadcrumb-item"><a href="/">Home</a></li>
18 <li class="breadcrumb-item active" aria-current="page">{{ page.title }}</li>
19 </ol>
20</nav>
21{% endblock %}
22
23
24{% block main %}
25<div class="container my-5">
26 <div class="row">
27 <div class="col-lg-8 offset-lg-2">
28 <div class="section-label mb-2">release log</div>
29 <h1 class="fw-bolder text-white" style="letter-spacing: -0.01em;">{{ page.title }}</h1>
30 <p class="text-muted">Shipped changes, schema tweaks, and the occasional tease for something coming.</p>
31 </div>
32 </div>
33
34 <div class="row mt-4">
35 <div class="col-lg-8 offset-lg-2">
36
37 <div class="changelog-entry">
38 <div class="changelog-date">2026-05-09</div>
39 <ul>
40 <li>Replace the dashboard PDF pipeline with the embedded Typst compiler (<code>typst</code> + <code>typst-pdf</code> + <code>typst-kit</code>). No chromium subprocess, no temp files; fonts are searched once at startup and shared across renders</li>
41 <li>Restructure the axum server into per-feature route modules under <code>src/routes/</code>, with <code>AppState</code>/router assembly split into <code>src/app.rs</code> and the standard render context centralized in <code>src/render.rs</code></li>
42 <li>Fix several dashboard issues exposed by the restructure (404 handling, breadcrumb edge cases)</li>
43 <li>Expand <code>make clean</code> to wipe <code>target/</code>, frontend deps, runtime data, and the cached topojson</li>
44 <li>Fix the mobile nav menu being clipped by the fixed navbar height</li>
45 </ul>
46 </div>
47
48 <div class="changelog-entry">
49 <div class="changelog-date">2026-05-07</div>
50 <ul>
51 <li>Rewrite the entire backend from Django to a single statically-linked axum binary in Rust. sqlx + SQLite directly, minijinja for templates, no more Gunicorn or Uvicorn or separate scheduler process. The collector endpoint and embed snippet are unchanged, so existing tracked sites keep reporting without edits</li>
52 <li>Drop the multi-user accounts model. Auth is now a single operator password from <code>.env</code> with signed-cookie sessions and <code>SameSite=Strict</code> instead of CSRF tokens</li>
53 <li>Route bot traffic into a dedicated <code>bot_events</code> table at write time so human dashboard queries never have to filter it</li>
54 <li>Auto-download the GeoIP database and ua-parser regexes on boot when missing or stale. The server still boots and serves dashboards if either download fails</li>
55 <li>Add an <code>analytics migrate</code> subcommand that imports an existing Django SQLite into the new schema while preserving property UUIDs, so embedded snippets keep working after the cutover</li>
56 <li>Add an in-memory dashboard cache (5-minute TTL, keyed by property + date range + filter), bypassed for PDF and markdown exports so they always reflect the live data</li>
57 <li>Add a stable <code>/static/collector.js</code> route so embed snippets don't break when Vite re-hashes the collector bundle</li>
58 </ul>
59 </div>
60
61 <div class="changelog-entry">
62 <div class="changelog-date">2026-04-29</div>
63 <ul>
64 <li>Replace the dashboard PDF report with a toner-friendly print template</li>
65 </ul>
66 </div>
67
68 <div class="changelog-entry">
69 <div class="changelog-date">2026-04-26</div>
70 <ul>
71 <li>Swap the US-only datamap for a Natural Earth world map with admin-1 drill-down on click</li>
72 <li>Switch IP geolocation from MaxMind to DB-IP City Lite (CC-BY-4.0, no signup, monthly auto-refresh)</li>
73 <li>Drop SQLite <code>mmap_size</code> to avoid a multi-process corruption hazard</li>
74 </ul>
75 </div>
76
77 <div class="changelog-entry">
78 <div class="changelog-date">2026-04-18</div>
79 <ul>
80 <li>Replace the Playwright PDF pipeline with a headless Chromium subprocess wrapper and move the Docker base image to Alpine: smaller image, faster cold starts</li>
81 <li>Centralize SQLite settings and expand PRAGMA tuning (WAL, synchronous, cache, mmap, temp_store)</li>
82 <li>Speed up the property dashboard, split the property view, and surface bot traffic separately</li>
83 <li>Swap the Exim relay for a Python direct-to-MX email backend</li>
84 </ul>
85 </div>
86
87 <div class="changelog-entry">
88 <div class="changelog-date">2026-04-17</div>
89 <ul>
90 <li>Redesign the whole dashboard with a warm-earth palette: mossy forest green and amber on deep charcoal, Monaspace Argon throughout, aligned to the status app</li>
91 <li>Rework the property dashboard with metric tiles, period-over-period deltas, chart panels, and compact ranked top-lists instead of Bootstrap list groups</li>
92 <li>Rework the properties listing page with search, live-signal tiles, and a dense row layout</li>
93 <li>Swap the bar-chart emoji favicon for a themed SVG mark matching the in-app logo</li>
94 <li>Add a <code>/documentation</code> link into the main nav so the collector snippet docs are easier to find</li>
95 <li>Restyle the collector-tag modal and accordion docs as terminal-style code blocks</li>
96 </ul>
97 </div>
98
99 <div class="changelog-entry">
100 <div class="changelog-date">2025-10-04</div>
101 <ul>
102 <li>Swap browser automation to Playwright with the bundled Chromium runtime</li>
103 <li>Switch Docker base image to Ubuntu 24.04 LTS</li>
104 </ul>
105 </div>
106
107 <div class="changelog-entry">
108 <div class="changelog-date">2025-10-03</div>
109 <ul>
110 <li>Migrate Python package management from Pipenv/pyenv to <code>uv</code></li>
111 <li>Switch frontend build from Yarn + Webpack to Bun + Vite</li>
112 <li>Fix d3/datamaps strict-mode crash under ESM so the US-state map renders again</li>
113 <li>Fix logout 405 by switching to a POST form + CSRF (Django 5 <code>LogoutView</code> is POST-only)</li>
114 </ul>
115 </div>
116
117 <div class="changelog-entry">
118 <div class="changelog-date">2022-07-26</div>
119 <ul>
120 <li>Add URL property filtering: click any top-page row to scope the whole dashboard</li>
121 </ul>
122 </div>
123
124 <div class="changelog-entry">
125 <div class="changelog-date">2022-07-06</div>
126 <ul>
127 <li>Change IPInfo to local MaxMind DB for IP → geo lookup (no more third-party API)</li>
128 </ul>
129 </div>
130
131 <div class="changelog-entry">
132 <div class="changelog-date">2022-06-18</div>
133 <ul>
134 <li>Rename all "tracking" terminology to "collecting" (we're not spyware)</li>
135 </ul>
136 </div>
137
138 <div class="changelog-entry">
139 <div class="changelog-date">2022-06-05</div>
140 <ul>
141 <li>Add PDF report generator: hit <code>?report</code> on any property page to export</li>
142 </ul>
143 </div>
144
145 <div class="changelog-entry">
146 <div class="changelog-date">2022-05-28</div>
147 <ul>
148 <li>Add headless Chromium screenshot utility</li>
149 <li>Add Open Graph / Twitter card metadata</li>
150 </ul>
151 </div>
152
153 <div class="changelog-entry">
154 <div class="changelog-date">2022-05-22</div>
155 <ul>
156 <li>Add previous-period comparisons on every metric card</li>
157 <li>Add a shareable public-link toggle for property pages</li>
158 <li>Add UTM query-param collection (source, medium, campaign)</li>
159 <li>Add platform doughnut graph</li>
160 <li>Add date-range selection with presets and a custom picker</li>
161 <li>Add custom event cards: pin any custom event as a headline metric</li>
162 </ul>
163 </div>
164
165 </div>
166 </div>
167</div>
168{% endblock %}