Voeg self-hosted Umami analytics + UptimeRobot uptime dashboard toe

Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
2026-07-25 19:59:40 +02:00
co-authored by Junie
parent da2a6e03ea
commit 08b489e305
15 changed files with 405 additions and 29 deletions
@@ -5,8 +5,8 @@
- **Feature Slug**: react-frontend
- **Project Type**: Greenfield
- **Start Date**: 2026-07-18T00:37:00Z
- **Current Stage**: OPERATIONS - Monitoring Setup (in progress)
- **Branch**: master
- **Current Stage**: Complete (post-completion follow-ups tracked in `audit.md` / `operations/production-readiness-checklist.md`)
- **Branch**: feature/analytics_uptime_dashboard
## Workspace State
- **Existing Code**: No
@@ -432,3 +432,15 @@
**Context**: Post-workflow follow-up (feature already marked Complete); reverts the previous change and documents remaining open questions about the PR triggering issue that require checking the Gitea instance directly. Committed on `feature/updates_gitea_workflow`.
---
## Post-Completion Follow-up: Analytics + Uptime Dashboard Tools Decided (Umami self-hosted + UptimeRobot)
**Timestamp**: 2026-07-25T16:10:00Z
**User Input**: "Analytics/uptime dashboard" open item chosen next, after closing the Sentry logging follow-up on its own branch. Clarified: Umami (self-hosted, needs Podman help), UptimeRobot, hosted on the webserver Pi (Pi Main), dashboard reachable via `analytics.slpsoftware.nl`, Podman already available on that Pi.
**AI Response**: "Created feature branch `feature/analytics_uptime_dashboard` from `master` (independent of the not-yet-merged `feature/sentry_error_logging` branch). Added `src/components/UmamiAnalytics.tsx`, which injects the Umami tracking script client-side, gated on `VITE_UMAMI_SCRIPT_URL`/`VITE_UMAMI_WEBSITE_ID` (typed in a new `src/vite-env.d.ts`) and skipped entirely during local development, mirroring the existing Sentry-DSN pattern; mounted it in `RootLayout.tsx`, with unit tests (`UmamiAnalytics.test.tsx`, 4 tests). Wired both variables into `continuous_integration.yaml`'s `Build` step as Gitea repository variables (not secrets). Added a self-hosted Umami example setup: `operations/deployment/umami/podman-compose.yml.example` + `.env.example` (Umami + Postgres, bound to the Pi's internal IP only) and `operations/deployment/nginx/analytics-nginx.conf.example` (reverse-proxy Pi forwarding `analytics.slpsoftware.nl` to Pi Main's Umami port, with certbot SSL, same pattern as the test-environment config). Wrote a new step-by-step `operations/monitoring/umami-setup.md` covering container setup, systemd auto-start (rootless Podman + `loginctl enable-linger`), reverse proxy/SSL, website registration in the Umami UI, and wiring the resulting website ID into the build. Updated `monitoring-plan.md`, `monitoring-setup.md`, and `production-readiness-checklist.md` to mark the analytics/uptime tool-selection open item as resolved, listing the remaining manual deployment steps."
**Context**: Post-workflow follow-up (feature already marked Complete); addresses the "Analytics/uptime dashboard" open item from `production-readiness-checklist.md`. Work done on `feature/analytics_uptime_dashboard`. `pnpm run build`, `pnpm run lint`, and `pnpm run test` to be verified before committing.
---
@@ -0,0 +1,57 @@
# Voorbeeldconfiguratie voor de nginx reverse proxy op de reverse-proxy-Pi
# (dezelfde Pi als reverse-proxy-nginx.conf.example) om het zelf-gehoste
# Umami-analyticsdashboard bereikbaar te maken via analytics.slpsoftware.nl.
#
# Umami zelf draait als podman-container op de webserver-Pi (Pi Main,
# 192.168.1.103:3000 — zie ../umami/podman-compose.yml.example) en heeft dus
# GEEN eigen nginx-configuratie op die Pi nodig; Umami serveert zijn eigen
# dashboard + tracking-script rechtstreeks via die poort.
#
# Kopieer dit bestand handmatig naar bijvoorbeeld
# /etc/nginx/sites-available/slpsoftware-analytics.conf op de reverse-proxy-Pi,
# maak een symlink in sites-enabled, en herlaad nginx.
#
# SSL-certificaat: net als voor test.slpsoftware.nl wordt dit aangevraagd met
# certbot (Let's Encrypt), bijvoorbeeld:
# sudo certbot --nginx -d analytics.slpsoftware.nl
# Zorg dat het DNS-record voor analytics.slpsoftware.nl al naar het publieke
# IP van deze Pi wijst voordat je certbot draait.
# HTTP: alleen gebruikt voor de Let's Encrypt ACME-challenge en om door te
# verwijzen naar HTTPS. Na het draaien van certbot ziet dit block er zo uit.
server {
listen 80;
listen [::]:80;
server_name analytics.slpsoftware.nl;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
# HTTPS: het daadwerkelijke reverse-proxy-verkeer naar Umami op de webserver-Pi.
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name analytics.slpsoftware.nl;
ssl_certificate /etc/letsencrypt/live/analytics.slpsoftware.nl/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/analytics.slpsoftware.nl/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
proxy_pass http://192.168.1.103:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
@@ -0,0 +1,10 @@
# Kopieer dit bestand naar `.env` naast podman-compose.yml op de Pi (NIET committen —
# bevat straks echte wachtwoorden/secrets).
# Willekeurig, sterk wachtwoord voor de Umami PostgreSQL-database.
# Genereren met bv.: openssl rand -base64 32
POSTGRES_PASSWORD=
# Willekeurige, sterke random string die Umami gebruikt om sessies te ondertekenen.
# Genereren met bv.: openssl rand -base64 32
APP_SECRET=
@@ -0,0 +1,55 @@
# Voorbeeld podman-compose bestand om Umami (self-hosted analytics) te draaien
# op de webserver-Pi (Pi Main, 192.168.1.103), naast de al draaiende nginx die
# de website zelf serveert.
#
# Vereist: podman + podman-compose (`pip3 install --user podman-compose`) op de Pi.
#
# Gebruik:
# 1. Kopieer dit bestand naar bv. ~/umami/podman-compose.yml op de Pi.
# 2. Kopieer .env.example naast dit bestand naar .env en vul een echte
# POSTGRES_PASSWORD en APP_SECRET in (zie .env.example voor hoe je die genereert).
# 3. cd ~/umami && podman-compose up -d
# 4. Zie umami-setup.md voor het instellen van auto-start na reboot (systemd)
# en het aanmaken van een website in de Umami-UI om het website-ID te krijgen.
services:
umami:
image: docker.umami.is/umami-software/umami:postgresql-latest
# Alleen op het interne (LAN) IP van deze Pi binden, niet op 0.0.0.0 — zelfde
# aanpak als webserver-nginx.conf.example, zodat deze poort niet per ongeluk
# vanaf buiten het netwerk bereikbaar is. De reverse-proxy-Pi stuurt
# analytics.slpsoftware.nl door naar 192.168.1.103:3000 (zie
# ../nginx/analytics-nginx.conf.example).
ports:
- "192.168.1.103:3000:3000"
environment:
DATABASE_URL: postgresql://umami:${POSTGRES_PASSWORD}@db:5432/umami
DATABASE_TYPE: postgresql
APP_SECRET: ${APP_SECRET}
depends_on:
db:
condition: service_healthy
restart: always
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:3000/api/heartbeat"]
interval: 10s
timeout: 5s
retries: 5
db:
image: docker.io/postgres:15-alpine
environment:
POSTGRES_DB: umami
POSTGRES_USER: umami
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- umami-db-data:/var/lib/postgresql/data
restart: always
healthcheck:
test: ["CMD-SHELL", "pg_isready -U umami -d umami"]
interval: 10s
timeout: 5s
retries: 5
volumes:
umami-db-data:
@@ -17,8 +17,8 @@ Client-side errors (JavaScript crashes, broken links) should be logged, but the
### Dashboards
A combination of:
- **Website analytics** (visitors, page views, basic engagement) — e.g. a simple/free tool such as Plausible, Umami, or Google Analytics/Search Console.
- **Uptime dashboard** (site reachability) — e.g. an external monitoring service such as UptimeRobot or Better Uptime.
- **Website analytics**: **decided** — self-hosted Umami (Podman on the webserver Pi, see `operations/monitoring/umami-setup.md`), chosen over GA4/hosted Plausible for privacy-friendliness and no recurring cost.
- **Uptime dashboard**: **decided** — UptimeRobot, chosen over Better Uptime for its established free tier.
(Original Question 6 = C, "Both (analytics + uptime dashboard)".)
@@ -28,7 +28,7 @@ A combination of:
## Open Action Items
1. **Decide logging destination**: choose between "browser console only" (no central storage, manual debugging) or a free/low-cost external error-tracking service (e.g. Sentry free tier) once this becomes a priority. Until decided, `monitoring-setup.md` documents both options so either can be adopted without re-doing this stage.
2. **Pick concrete analytics + uptime tools**: `monitoring-setup.md` lists candidate free-tier tools; final tool selection/account creation is a manual follow-up outside this workflow (no code changes required to swap providers, since neither is wired into the codebase yet beyond an optional embed snippet).
2. ~~Pick concrete analytics + uptime tools~~**Resolved**: self-hosted Umami + UptimeRobot (see `monitoring-setup.md` and `umami-setup.md`). Remaining manual follow-ups: actually deploying the Umami containers on the Pi, registering DNS/SSL for `analytics.slpsoftware.nl`, creating the UptimeRobot monitor, and setting the `VITE_UMAMI_SCRIPT_URL`/`VITE_UMAMI_WEBSITE_ID` Gitea repository variables.
## Rationale
Given this is a simple static marketing site with no backend and no existing monitoring, the aim is lightweight, low/no-cost observability: enough to know if the site is down (uptime) and how it's being used (analytics), plus a documented (if not yet finalized) path for capturing client-side errors. Alerting was deliberately left out to avoid over-engineering a notification pipeline before there's a concrete trigger/audience for it.
@@ -25,38 +25,49 @@ The destination was not finalized (original Question 3 = C). Two supported optio
## Dashboards
### Website analytics
Pick one (all have generous free tiers suitable for a small marketing site):
### Website analytics — decided: self-hosted Umami
**Decided**: self-hosted Umami, running via Podman on the webserver Pi (Pi Main,
`192.168.1.103:3000`), reachable for the dashboard itself via `analytics.slpsoftware.nl`
(reverse-proxied + SSL via certbot, same pattern as `test.slpsoftware.nl`). Chosen over
GA4 for privacy-friendliness (typically no cookie banner needed) and full self-hosted
control, and over a hosted Umami/Plausible plan to avoid recurring cost.
| Option | Notes |
|---|---|
| Plausible / Umami | Privacy-friendly, lightweight, no cookie banner typically required; self-hosted or low-cost hosted tier |
| Google Analytics (GA4) / Search Console | Free, widely known, but heavier script and involves third-party data sharing (cookie/consent implications) |
- Full step-by-step setup: `operations/monitoring/umami-setup.md` (Podman/compose files,
systemd auto-start, reverse proxy + SSL, website registration).
- Example config: `operations/deployment/umami/podman-compose.yml.example` +
`.env.example`; `operations/deployment/nginx/analytics-nginx.conf.example`.
- The tracking script is injected client-side by `src/components/UmamiAnalytics.tsx`,
gated on two build-time variables (`VITE_UMAMI_SCRIPT_URL`, `VITE_UMAMI_WEBSITE_ID`),
wired into `continuous_integration.yaml`'s `Build` step as Gitea repository variables,
not secrets (same pattern as `VITE_SENTRY_DSN`). It never loads during local
development (`pnpm dev`), even if those variables happen to be set, so local testing
never pollutes visitor analytics.
- Key metrics to surface: unique visitors, page views per route (Home, Packages, etc. —
see `frontend-components.md`), and referral sources.
**Setup (once a tool is picked)**:
1. Create an account/site entry with the chosen provider and obtain the tracking snippet or `<script>` tag.
2. Add the snippet to `index.html` (or load it conditionally in `src/main.tsx`) — this is a documentation/config task, not something the current codebase needs restructuring for.
3. Key metrics to surface: unique visitors, page views per route (Home, Packages, etc. — see `frontend-components.md`), and referral sources.
### Uptime dashboard — decided: UptimeRobot
**Decided**: UptimeRobot (free tier: up to 50 monitors, 5-minute check interval, optional
e-mail notification on downtime — opportunistic, not a designed alerting feature per
`monitoring-plan.md`). Chosen over Better Uptime for its long-standing free tier and
simplicity for a single low-traffic site.
### Uptime dashboard
Pick one:
**Setup**:
1. Create a free UptimeRobot account (https://uptimerobot.com).
2. Register `test.slpsoftware.nl` as an HTTP(S) monitor now (checking for a `200`
response); add the production URL once hosting is finalized (see
`operations/deployment/deployment-plan.md` "Open Item") — still an open follow-up.
3. Optional: publish a public status page if desired for transparency to visitors.
4. Key metric to surface: uptime percentage / current status.
| Option | Notes |
|---|---|
| UptimeRobot | Free tier: up to 50 monitors, 5-minute check interval, optional e-mail notification on downtime (opportunistic, not a designed alerting feature per `monitoring-plan.md`) |
| Better Uptime | Free tier available; similar capability, nicer public status page option |
**Setup (once a tool is picked)**:
1. Register the production URL (once hosting is finalized — see `operations/deployment/deployment-plan.md` "Open Item") as an HTTP(S) monitor, checking for a `200` response.
2. Optional: publish a public status page if desired for transparency to visitors.
3. Key metric to surface: uptime percentage / current status.
No application code changes are needed for UptimeRobot — it works purely by polling the
public URL from the outside, entirely independent of the site's own codebase.
## Summary Table
| Concern | Approach | Status |
|---|---|---|
| Client-side errors | Logging (console today; Sentry free tier optional later) | Destination open item |
| Visitor/usage insight | Analytics dashboard (Plausible/Umami/GA4) | Tool selection open item |
| Site reachability | Uptime dashboard (UptimeRobot/Better Uptime) | Tool selection + production URL open item |
| Visitor/usage insight | Analytics dashboard (self-hosted Umami) | Tool decided; deployment (Podman on Pi Main + `analytics.slpsoftware.nl`) is a manual follow-up, see `umami-setup.md` |
| Site reachability | Uptime dashboard (UptimeRobot) | Tool decided; account creation + production URL are manual follow-ups |
| Alerting | Out of scope | Not configured |
| Shared infrastructure reuse | Out of scope | None exists yet |
@@ -0,0 +1,110 @@
# Umami Self-Hosted Setup (Analytics)
Stapsgewijze handleiding om Umami (self-hosted website-analytics) op te zetten op de
webserver-Pi (Pi Main, `192.168.1.103`) met Podman, en bereikbaar te maken via
`analytics.slpsoftware.nl`. Dit vult het "Analytics/uptime dashboard" open item uit
`operations/production-readiness-checklist.md` in voor het analytics-gedeelte.
## Vereisten
- Podman is al aanwezig op de Pi (bevestigd door de gebruiker).
- `podman-compose` geïnstalleerd: `pip3 install --user podman-compose` (of via de package
manager van je distro, indien beschikbaar).
- DNS: een A-record voor `analytics.slpsoftware.nl` dat naar het publieke IP van de
reverse-proxy-Pi wijst (dezelfde Pi die ook `test.slpsoftware.nl` afhandelt).
## 1. Umami + database opzetten (op de webserver-Pi)
1. Maak een map aan, bv. `~/umami/`, en kopieer daarin:
- `operations/deployment/umami/podman-compose.yml.example``~/umami/podman-compose.yml`
- `operations/deployment/umami/.env.example``~/umami/.env`
2. Vul in `~/umami/.env` een echte `POSTGRES_PASSWORD` en `APP_SECRET` in (bv. via
`openssl rand -base64 32` voor beide — gebruik twee verschillende waarden).
3. Start de containers:
```bash
cd ~/umami
podman-compose up -d
```
4. Controleer dat Umami draait en bereikbaar is op het interne netwerk:
```bash
curl http://192.168.1.103:3000/api/heartbeat
```
Dit zou een JSON-antwoord met `"ok"` moeten teruggeven.
## 2. Automatisch starten na reboot (systemd user service)
Podman-compose start niet vanzelf op na een herstart van de Pi, tenzij je dit expliciet
instelt. Omdat rootless Podman hier al gebruikt wordt, is een systemd **user**-service de
eenvoudigste aanpak:
1. Zorg dat de gebruiker-sessie blijft "linger-en" na uitloggen/reboot:
```bash
sudo loginctl enable-linger $(whoami)
```
2. Maak `~/.config/systemd/user/umami.service` aan:
```ini
[Unit]
Description=Umami analytics (podman-compose)
After=network-online.target
[Service]
WorkingDirectory=%h/umami
ExecStart=/usr/bin/podman-compose up
ExecStop=/usr/bin/podman-compose down
Restart=on-failure
[Install]
WantedBy=default.target
```
3. Activeer en start de service:
```bash
systemctl --user daemon-reload
systemctl --user enable --now umami.service
```
## 3. Reverse proxy + SSL (op de reverse-proxy-Pi)
1. Kopieer `operations/deployment/nginx/analytics-nginx.conf.example` naar bv.
`/etc/nginx/sites-available/slpsoftware-analytics.conf` op de reverse-proxy-Pi, en maak
een symlink in `sites-enabled`.
2. Zorg dat het DNS-record voor `analytics.slpsoftware.nl` al actief is, en draai dan:
```bash
sudo certbot --nginx -d analytics.slpsoftware.nl
```
3. Herlaad nginx: `sudo nginx -t && sudo systemctl reload nginx`.
4. Test: open `https://analytics.slpsoftware.nl` in de browser — je zou het
Umami-inlogscherm moeten zien (standaard inloggegevens: `admin` / `umami`, **direct
wijzigen na eerste login**).
## 4. Website registreren in Umami en het website-ID ophalen
1. Log in op `https://analytics.slpsoftware.nl` en wijzig direct het standaardwachtwoord.
2. Ga naar **Settings → Websites → Add website** en vul in:
- Name: `SLP Software` (of naar keuze)
- Domain: het domein van de daadwerkelijke website (bv. `slpsoftware.nl` of
`test.slpsoftware.nl`, afhankelijk van welke omgeving je eerst wilt meten)
3. Na het opslaan toont Umami een **Website ID** (een UUID) — dit heb je nodig voor de
volgende stap.
## 5. Tracking script koppelen aan de website (build-configuratie)
De React-app (`src/components/UmamiAnalytics.tsx`) injecteert het Umami tracking-script
automatisch, mits de volgende twee build-time variabelen zijn ingesteld — beide zijn
**geen secrets** (client-side zichtbaar), dus als Gitea Actions **repository variables**
(niet secrets), net als `VITE_SENTRY_DSN`:
| Variabele | Waarde |
|---|---|
| `VITE_UMAMI_SCRIPT_URL` | `https://analytics.slpsoftware.nl/script.js` |
| `VITE_UMAMI_WEBSITE_ID` | het Website ID uit stap 4 |
Stel deze in via **Gitea → Repository Settings → Actions → Variables**. Zodra beide
bestaan, pakt de eerstvolgende build ze automatisch op; zonder deze variabelen slaat de
app het inladen van het script gewoon over (geen crash, geen tracking).
**Lokaal (`pnpm dev`)**: het tracking-script wordt hier bewust nooit geladen (zie
`UmamiAnalytics.tsx`), zodat lokaal testen de bezoekersstatistieken niet vervuilt. Wil je
dit toch lokaal testen, zet dan tijdelijk beide waarden in `.env.local` (zie
`.env.example`) én verwijder tijdelijk de `import.meta.env.DEV`-check.
## Openstaande handmatige stappen
- [ ] `podman-compose up -d` daadwerkelijk draaien op Pi Main.
- [ ] Systemd user-service instellen voor auto-start na reboot.
- [ ] DNS-record + certbot voor `analytics.slpsoftware.nl` op de reverse-proxy-Pi.
- [ ] Standaard Umami-wachtwoord direct wijzigen na eerste login.
- [ ] Website aanmaken in Umami en het Website ID overnemen.
- [ ] `VITE_UMAMI_SCRIPT_URL` en `VITE_UMAMI_WEBSITE_ID` als Gitea repository variables instellen.
@@ -20,5 +20,5 @@
- **Open Follow-ups**:
- Finalize the hosting/domain setup and extend `.gitea/workflows/deploy.yml`'s `deploy` job to actually upload `dist/` to the host, instead of only packaging it as a downloadable artifact (see `deployment-plan.md` "Open Item")
- Decide the client-side error logging destination (browser console only vs. an external service such as Sentry free tier) — see `monitoring-setup.md`
- Pick and configure the concrete analytics tool (e.g. Plausible/Umami/GA4) and uptime dashboard tool (e.g. UptimeRobot/Better Uptime), including registering the final production URL once hosting is finalized
- ~~Pick and configure the concrete analytics tool and uptime dashboard tool~~**Resolved**: self-hosted Umami (Podman on Pi Main) + UptimeRobot decided; see `monitoring-setup.md` and the new `umami-setup.md`. Remaining manual follow-ups: actually deploy the Umami containers, set up `analytics.slpsoftware.nl` DNS/SSL, create the UptimeRobot monitor, and register the final production URL once hosting is finalized
- Run a formal Lighthouse performance check before the first real production deployment (flagged as not yet run in `build-and-test-summary.md`)