Skip to content

Commit a23d1cb

Browse files
authored
feat(error-reporting): CrashReport panel + Sentry-compatible client (#247)
* feat(error-reporting): add CrashReport panel + Sentry-compatible client Drops into the fileshare error boundary: on any uncaught render-time throw inside the encrypt flow, render a "Tell PostGuard" panel that captures the exception (with rich client context: URL, viewport, UA, locale, app version) and POSTs it to the configured GlitchTip / Sentry endpoint via @sentry/browser. Reset path wipes encryptState and resets the boundary so the user can keep going. - `src/lib/errorReporting.ts` — lazy SDK init, manual-capture only (no global error / unhandledrejection hooks, no breadcrumbs), 3 s flush budget. - `src/lib/components/filesharing/CrashReport.svelte` — two-button panel: primary "Tell PostGuard" (flips to "Sending…" → "Thank you!" in-place, width pinned), secondary "Try again". - `src/lib/env.ts` — `GLITCHTIP_DSN` runtime config flag; empty value disables reporting at module load. - `src/hooks.client.ts` — initialises the SDK on app boot. - `src/routes/(app)/fileshare/+page.svelte` — wraps the encrypt flow in `<svelte:boundary>` with CrashReport as the `failed` snippet. - Locale strings (`crash` section, en + nl). - `static/config.js` — local dev DSN matching the GlitchTip stack in docker-compose. Deployed environments override via the Terraform ConfigMap. - `docker-compose.yml` — local GlitchTip stack (postgres + redis + web + worker + idempotent init Job that seeds the admin user, the PostGuard org, the postguard-website project, and a pinned ProjectKey UUID so the DSN survives `docker compose down -v`). - `package.json` / `package-lock.json` — adds `@sentry/browser ^10` and bumps `svelte-preprocess` to `^6.0.5` (the older 6.0.3 declared `typescript@^5.0.0` only, which broke `npm ci` after the Sentry install pulled in a wider tsc range). Pairs with the postguard-ops GlitchTip deployment. * review: dobby fixes on the GlitchTip PR - docker-compose.yml glitchtip-init: invert the DEV_KEY rewrite condition. GlitchTip auto-creates a default ProjectKey on Project save, so get_or_create returns that auto-created key (key_created=False) and the `defaults={'public_key': DEV_KEY}` clause is ignored. The previous `if key_created and …` branch was unreachable, leaving the DSN pinned UUID never applied on fresh volumes. - errorReporting.ts: drop `navigator.platform` (deprecated; Firefox returns "" already). - CrashReport.svelte: - Wrap reportError() in try/catch so a synchronous SDK throw can't leave the button stuck on "Sending…". - Add `:focus-visible` outline on .crash-btn (WCAG 2.4.7). - Move the failed-state <p> into a stable role=status / aria-live live region so screen readers announce it (WCAG 4.1.3). - fileshare/+page.svelte: move <svelte:boundary> above <FileInput> so a render-time error in FileInput is caught by CrashReport instead of crashing the whole page.
1 parent d52ee5b commit a23d1cb

11 files changed

Lines changed: 530 additions & 57 deletions

File tree

docker-compose.yml

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,164 @@ services:
9797
networks:
9898
- postguard
9999

100+
# GlitchTip — open-source, Sentry-compatible error tracker.
101+
# Reports posted by the website's CrashReport button land here.
102+
# UI at http://localhost:8000 (override via GLITCHTIP_PORT if 8000 is taken).
103+
# On first boot, create an account and a project; the DSN goes into
104+
# APP_CONFIG.GLITCHTIP_DSN (see src/lib/env.ts).
105+
glitchtip-postgres:
106+
image: postgres:16-alpine
107+
environment:
108+
- POSTGRES_HOST_AUTH_METHOD=trust
109+
- POSTGRES_DB=glitchtip
110+
volumes:
111+
- glitchtip-pg:/var/lib/postgresql/data
112+
networks:
113+
- postguard
114+
115+
glitchtip-redis:
116+
image: valkey/valkey:8-alpine
117+
networks:
118+
- postguard
119+
120+
glitchtip-web:
121+
image: glitchtip/glitchtip:v5
122+
depends_on:
123+
- glitchtip-postgres
124+
- glitchtip-redis
125+
environment:
126+
- DATABASE_URL=postgres://postgres@glitchtip-postgres:5432/glitchtip
127+
- REDIS_URL=redis://glitchtip-redis:6379/0
128+
- SECRET_KEY=${GLITCHTIP_SECRET_KEY:-dev-only-change-me}
129+
- PORT=8001
130+
- DEFAULT_FROM_EMAIL=noreply@postguard.local
131+
- GLITCHTIP_DOMAIN=http://localhost:8001
132+
- ENABLE_OPEN_USER_REGISTRATION=true
133+
ports:
134+
- '8001:8001'
135+
volumes:
136+
- glitchtip-uploads:/code/uploads
137+
networks:
138+
- postguard
139+
140+
glitchtip-worker:
141+
image: glitchtip/glitchtip:v5
142+
command: ./bin/run-celery-with-beat.sh
143+
# `glitchtip-web` runs Django migrations on startup. Without
144+
# depending on it, the worker's celery-beat starts firing
145+
# scheduled jobs (uptime-dispatch-checks etc.) before the
146+
# tables exist and crashes with `relation "uptime_monitor"
147+
# does not exist`. The web container has no healthcheck, so
148+
# this is a `service_started` ordering rather than a true
149+
# readiness gate — good enough on a single host where the
150+
# web container reaches Postgres in a few seconds.
151+
depends_on:
152+
- glitchtip-postgres
153+
- glitchtip-redis
154+
- glitchtip-web
155+
- glitchtip-init
156+
environment:
157+
- DATABASE_URL=postgres://postgres@glitchtip-postgres:5432/glitchtip
158+
- REDIS_URL=redis://glitchtip-redis:6379/0
159+
- SECRET_KEY=${GLITCHTIP_SECRET_KEY:-dev-only-change-me}
160+
volumes:
161+
- glitchtip-uploads:/code/uploads
162+
networks:
163+
- postguard
164+
165+
# One-shot: applies migrations and creates the dev superuser if it
166+
# doesn't already exist. Runs to completion and exits. Idempotent —
167+
# `createsuperuser --noinput` errors on a duplicate, which we
168+
# swallow so subsequent `up`s remain no-ops. Override credentials
169+
# by exporting GLITCHTIP_ADMIN_EMAIL / GLITCHTIP_ADMIN_PASSWORD.
170+
glitchtip-init:
171+
image: glitchtip/glitchtip:v5
172+
depends_on:
173+
- glitchtip-postgres
174+
environment:
175+
- DATABASE_URL=postgres://postgres@glitchtip-postgres:5432/glitchtip
176+
- REDIS_URL=redis://glitchtip-redis:6379/0
177+
- SECRET_KEY=${GLITCHTIP_SECRET_KEY:-dev-only-change-me}
178+
# python-email-validator (used by GlitchTip's Pydantic response
179+
# schemas) rejects `local`, `localhost`, `test`, `invalid`,
180+
# `arpa`, `onion` TLDs as special-use — so `/api/0/users/me/`
181+
# 500s if the superuser email lands on one of those. `.dev` is
182+
# a real public TLD and not on that list.
183+
- DJANGO_SUPERUSER_EMAIL=${GLITCHTIP_ADMIN_EMAIL:-admin@postguard.dev}
184+
- DJANGO_SUPERUSER_PASSWORD=${GLITCHTIP_ADMIN_PASSWORD:-devpass1}
185+
# Bootstraps: migrations → superuser → PostGuard org → website
186+
# project → prints the DSN. Each step is idempotent so subsequent
187+
# `up`s remain no-ops. Paste the printed DSN into static/config.js
188+
# under `APP_CONFIG.GLITCHTIP_DSN` (or set `GLITCHTIP_DSN=…` in
189+
# .env.dev) — the DSN is stable as long as the postgres volume
190+
# survives, so this is a one-time copy.
191+
command:
192+
- sh
193+
- -c
194+
- |
195+
./manage.py migrate --noinput &&
196+
(./manage.py createsuperuser --noinput 2>/dev/null \
197+
&& echo 'glitchtip-init: superuser created' \
198+
|| echo 'glitchtip-init: superuser already exists') &&
199+
./manage.py shell -c "
200+
from django.apps import apps
201+
from django.contrib.auth import get_user_model
202+
Organization = apps.get_model('organizations_ext', 'Organization')
203+
Project = apps.get_model('projects', 'Project')
204+
ProjectKey = apps.get_model('projects', 'ProjectKey')
205+
206+
user = get_user_model().objects.get(email='$$DJANGO_SUPERUSER_EMAIL')
207+
208+
org, org_created = Organization.objects.get_or_create(
209+
slug='postguard',
210+
defaults={'name': 'PostGuard'},
211+
)
212+
if org_created:
213+
org.add_user(user)
214+
print('glitchtip-init: org PostGuard created')
215+
else:
216+
print('glitchtip-init: org PostGuard already exists')
217+
218+
project, proj_created = Project.objects.get_or_create(
219+
organization=org,
220+
slug='postguard-website',
221+
defaults={'name': 'postguard-website', 'platform': 'javascript'},
222+
)
223+
if proj_created:
224+
print('glitchtip-init: project postguard-website created')
225+
else:
226+
print('glitchtip-init: project postguard-website already exists')
227+
228+
# Pin a deterministic public_key so the DSN survives a
229+
# full volume wipe. Without this, a fresh postgres volume
230+
# regenerates the UUID and any hardcoded DSN in
231+
# static/config.js silently goes stale.
232+
#
233+
# GlitchTip auto-creates a default ProjectKey when a
234+
# Project is saved, so on a fresh volume the get_or_create
235+
# below returns that auto-created key (key_created=False)
236+
# and the defaults= clause is ignored. The `not key_created`
237+
# branch catches that case and rewrites public_key to
238+
# DEV_KEY.
239+
import uuid
240+
DEV_KEY = uuid.UUID('1d8ea2a49c904f079b116550780c0ece')
241+
key, key_created = ProjectKey.objects.get_or_create(
242+
project=project,
243+
defaults={'public_key': DEV_KEY},
244+
)
245+
if not key_created and key.public_key != DEV_KEY:
246+
key.public_key = DEV_KEY
247+
key.save()
248+
dsn = f'http://{key.public_key.hex}@localhost:8001/{project.id}'
249+
print('========================================')
250+
print('glitchtip-init: DSN (already set in static/config.js):')
251+
print(f' {dsn}')
252+
print('========================================')
253+
"
254+
restart: 'no'
255+
networks:
256+
- postguard
257+
100258
# Nginx reverse proxy
101259
nginx:
102260
image: nginx:alpine
@@ -120,3 +278,5 @@ volumes:
120278
cryptify-data:
121279
cryptify-target:
122280
postguard-pkg-target:
281+
glitchtip-pg:
282+
glitchtip-uploads:

0 commit comments

Comments
 (0)