Adds visitor dashboard and related features
This commit is contained in:
@@ -95,6 +95,8 @@ Open [http://localhost:4200](http://localhost:4200) and ensure the backend is ru
|
||||
├── Dockerfile # Python 3.12-slim + gunicorn/uvicorn
|
||||
├── requirements.txt
|
||||
├── seed.py # Initial data for all models
|
||||
├── inject_test_logs.py # Test data injector for visitor stats pipeline
|
||||
├── geo/ # MaxMind GeoLite2 City database
|
||||
└── app/
|
||||
├── main.py # FastAPI app, CORS, router registration
|
||||
├── config.py # ENV-based settings (KMTN_* prefix)
|
||||
@@ -104,7 +106,8 @@ Open [http://localhost:4200](http://localhost:4200) and ensure the backend is ru
|
||||
└── api/
|
||||
├── programs.py # CRUD /api/programs
|
||||
├── events.py # CRUD /api/events
|
||||
└── tiers.py # CRUD /api/tiers
|
||||
├── tiers.py # CRUD /api/tiers
|
||||
└── stats.py # Visitor stats endpoints (summary, timeseries, geo, parse)
|
||||
```
|
||||
|
||||
## Local Development
|
||||
@@ -197,6 +200,72 @@ ng test # Angular + Vitest
|
||||
|
||||
The backend API is self-documented at [http://localhost:8000/docs](http://localhost:8000/docs) (Swagger UI). The database auto-seeds on first startup — visit that URL to interactively test endpoints.
|
||||
|
||||
## Admin Dashboard — Visitor Stats
|
||||
|
||||
A visitor analytics dashboard built on a **log parsing pipeline**: Nginx writes access logs to a shared volume that the FastAPI backend reads and processes. No extra services required (no Logstash, Prometheus, or message queues).
|
||||
|
||||
### How it works
|
||||
|
||||
1. **Nginx** writes structured JSON access logs to **daily log files** on a shared mount: `/logs/access-YYYY-MM-DD.log`. A `map` directive extracts the date from the ISO timestamp, so Nginx rotates to a new file at midnight each day.
|
||||
2. **FastAPI** (via [log_parser.py](backend/app/log_parser.py)) scans `/logs/` for daily files that haven't been parsed yet, enriches each entry with IP geolocation (MaxMind GeoLite2 City), and stores aggregates in PostgreSQL.
|
||||
3. **PostgreSQL** tables (`visitor_stats_daily`, `visitor_stats_geo`, `log_parse_state`) hold the aggregated data and a "last parsed date" marker.
|
||||
4. **Angular admin dashboard** reads from the stats API endpoints to render charts and a world map.
|
||||
|
||||
### Why daily files
|
||||
|
||||
The currently-being-written file is never read — no partial lines, no race conditions, no file locking needed. Stats have a minimum one-day lag.
|
||||
|
||||
### Stats API endpoints
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|---|---|---|
|
||||
| `GET` | `/api/stats/summary` | Total unique visitors for a date range |
|
||||
| `GET` | `/api/stats/timeseries` | Daily unique visitor counts (chart data) |
|
||||
| `GET` | `/api/stats/geo` | Geographic breakdown by country (map data) |
|
||||
| `GET` | `/api/stats/parse` | Trigger log parsing (SSE progress stream) |
|
||||
|
||||
All endpoints require admin authentication (JWT token). Date range is optional; defaults to the last 30 days.
|
||||
|
||||
### GeoLite2 setup
|
||||
|
||||
The log parser requires a MaxMind GeoLite2 City database for IP geolocation:
|
||||
|
||||
1. Download from [https://dev.maxmind.com/geoip/geolite2-free-geolocation-data](https://dev.maxmind.com/geoip/geolite2-free-geolocation-data)
|
||||
2. Place the `.mmdb` file at `backend/geo/GeoLite2-City.mmdb`
|
||||
3. The Dockerfile copies it into the container at `/app/geo/GeoLite2-City.mmdb`
|
||||
|
||||
### Injecting test data
|
||||
|
||||
To verify the pipeline works (e.g., after deploying or before a demo), use the test log injector script at [backend/inject_test_logs.py](backend/inject_test_logs.py). It writes realistic traffic from 25 well-known public IPs across multiple past days — data is only aggregated a day in arrears, so all dates are strictly before today.
|
||||
|
||||
```bash
|
||||
# Run inside the api container (writes to shared /logs volume)
|
||||
docker compose exec api python /app/inject_test_logs.py /logs 14
|
||||
|
||||
# Or run locally (dev mode)
|
||||
cd backend
|
||||
python inject_test_logs.py /logs 14
|
||||
```
|
||||
|
||||
The second argument controls how many days of data to generate (default: 14). Each day gets a deterministic mix of visitors from the IP pool, so re-running produces the same output (idempotent — files are appended, so check first).
|
||||
|
||||
After injecting, trigger parsing via the admin dashboard or:
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8000/api/parse?token=YOUR_JWT"
|
||||
```
|
||||
|
||||
### Shared volume setup
|
||||
|
||||
For docker-compose, the `logs` named volume is mounted in both the `frontend` (Nginx) and `api` containers at `/logs`:
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
logs:/logs
|
||||
```
|
||||
|
||||
For Kubernetes (minikube), use a `hostPath` PersistentVolume with `ReadWriteMany` access mode, claimed by a PersistentVolumeClaim and mounted in both pods.
|
||||
|
||||
## API Reference
|
||||
|
||||
The backend exposes CRUD endpoints for three models:
|
||||
|
||||
Reference in New Issue
Block a user