feat: panel exposure policy — rich stats, opt-in per deployment #15
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/panels"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Implements the
panelsOpenSpec change — the exposure-policy layer, per operator decisions (opt-in default, policy-as-config, apex blocked on this).What's public is now policy, not capability: 9 panels implemented (summary, pages + 7 ranked: referrers, browsers, systems, sizes, locations, languages, campaigns — uniform
items:[{name,count}]shape, normalized names, empty-is-valid);GCSG_PANELS/modulepanelsoption selects what a deployment answers, defaulting to the minimal two. Disabled →404 {"error":"panel_disabled"}(distinguishable three ways: enabled / policy-disabled / nonexistent). Served/api/v1/openapi.jsonis filtered to the enabled set — per-deployment contract truth — while the committed snapshot remains the full capability document, still build-gated./api/v1/panels= cheap discovery.The widget got its superpower:
<goatcounter-stats>discovers the policy and renders every enabled panel — flip a panel on server-side, every embedding page follows with zero frontend deploys. Optionalpanelsattribute restricts page-side; empty panels skip silently; partial failures never kill the widget. Old gateways without/panelsfall back to the classic two.Lane compliance: hurl scenarios written red-first (7/7 green live and in the VM e2e with the canonical expanded policy); 28 rust + 27 bun tests; contract coupling now asserts the panel enum from both sides; startup info-logs the policy; disabled-panel demand is counted in http_requests_total; existing metric families extend via labels (no new signal types). Live run bonus: the dev GoatCounter's own 4/s limit produced a real 429 → classified
http_4xx, no retry, exactly as specced.Ships as v0.3.0 (separate bump PR after merge, as usual).
🤖 Generated with Claude Code
https://claude.ai/code/session_01T5j3dAJVcUjZv9k76sRTt9