diff --git a/.env.example b/.env.example index f85269e..124c479 100644 --- a/.env.example +++ b/.env.example @@ -1,29 +1,57 @@ -# App +# ------------------------------ +# Application +# ------------------------------ +# Display name used in API docs/UI. APP_NAME=NexaPG Monitor +# Runtime environment: dev | staging | prod | test ENVIRONMENT=dev +# Backend log level: DEBUG | INFO | WARNING | ERROR LOG_LEVEL=INFO -# Core DB +# ------------------------------ +# Core Database (internal metadata DB) +# ------------------------------ +# Database that stores users, targets, metrics, query stats, and audit logs. DB_NAME=nexapg DB_USER=nexapg DB_PASSWORD=nexapg +# Host port mapped to the internal PostgreSQL container port 5432. DB_PORT=5433 -# Backend +# ------------------------------ +# Backend API +# ------------------------------ +# Host port mapped to backend container port 8000. BACKEND_PORT=8000 +# JWT signing secret. Change this in every non-local environment. JWT_SECRET_KEY=change_this_super_secret JWT_ALGORITHM=HS256 +# Access token lifetime in minutes. JWT_ACCESS_TOKEN_MINUTES=15 +# Refresh token lifetime in minutes (10080 = 7 days). JWT_REFRESH_TOKEN_MINUTES=10080 -# Generate with: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" +# Key used to encrypt monitored target passwords at rest. +# Generate with: +# python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" ENCRYPTION_KEY=REPLACE_WITH_FERNET_KEY -# Dev: set to * to allow all origins (credentials disabled automatically) +# Allowed CORS origins for browser clients. +# Use comma-separated values, e.g.: +# CORS_ORIGINS=http://localhost:5173,https://nexapg.example.com +# Dev-only shortcut: +# CORS_ORIGINS=* CORS_ORIGINS=http://localhost:5173,http://localhost:8080 +# Target polling interval in seconds. POLL_INTERVAL_SECONDS=30 +# Initial admin bootstrap user (created on first startup if not present). INIT_ADMIN_EMAIL=admin@example.com INIT_ADMIN_PASSWORD=ChangeMe123! +# ------------------------------ # Frontend +# ------------------------------ +# Host port mapped to frontend container port 80. FRONTEND_PORT=5173 -# For reverse proxy + SSL prefer relative path to avoid mixed-content. +# Base API URL used at frontend build time. +# For reverse proxy + SSL, keep this relative to avoid mixed-content issues. +# Example direct mode: VITE_API_URL=http://localhost:8000/api/v1 VITE_API_URL=/api/v1 diff --git a/README.md b/README.md index f65259e..aeb4fb4 100644 --- a/README.md +++ b/README.md @@ -1,113 +1,176 @@ # NexaPG - PostgreSQL Monitoring Stack -Docker-basierte Monitoring-Loesung fuer mehrere PostgreSQL-Targets mit FastAPI + React. +NexaPG is a Docker-based PostgreSQL monitoring platform for multiple remote targets, built with FastAPI + React. -## Features +## What it includes -- Multi-target PostgreSQL Monitoring (remote) -- Polling Collector fuer: +- Multi-target PostgreSQL monitoring (remote instances) +- Polling collector for: - `pg_stat_database` - `pg_stat_activity` - `pg_stat_bgwriter` - `pg_locks` - - `pg_stat_statements` (falls auf Target aktiviert) -- Core-DB fuer: - - User/Auth/RBAC (`admin`, `operator`, `viewer`) - - Targets (Credentials verschluesselt via Fernet) - - Metrics / Query Stats - - Audit Logs -- Auth mit JWT Access/Refresh Tokens -- FastAPI + SQLAlchemy async + Alembic -- React (Vite) Frontend mit: - - Login/Logout - - Dashboard - - Target Detail mit Charts - - Query Insights - - Admin User Management -- Health Endpoints: + - `pg_stat_statements` (if enabled on target) +- Core metadata database for: + - Authentication and RBAC (`admin`, `operator`, `viewer`) + - Monitored target configuration (encrypted credentials) + - Metrics and query stats + - Audit logs +- JWT auth (access + refresh) +- FastAPI + SQLAlchemy async + Alembic migrations +- React (Vite) frontend with: + - Login/logout + - Dashboard overview + - Target detail with charts and database overview + - Query insights + - Admin user management +- Health endpoints: - `/api/v1/healthz` - `/api/v1/readyz` -## Struktur +## Repository structure -- `backend/` FastAPI App -- `frontend/` React (Vite) App -- `ops/` Scripts -- `docker-compose.yml` Stack -- `.env.example` Konfigurationsvorlage +- `backend/` FastAPI application +- `frontend/` React (Vite) application +- `ops/` helper scripts and env template copy +- `docker-compose.yml` full stack definition +- `.env.example` environment template -## Schnellstart +## Prerequisites -1. Env-Datei erstellen: +Install these before starting: + +- Docker Engine 24+ +- Docker Compose v2+ +- GNU Make (optional, for `make up/down/logs/migrate`) +- Open ports on your host: + - frontend: `5173` (default) + - backend: `8000` (or your `BACKEND_PORT`) + - core DB: `5433` (or your `DB_PORT`) + +Optional but recommended: + +- `psql` client for troubleshooting target connectivity + +## Quick start + +1. Create local env file: ```bash cp .env.example .env ``` -2. Fernet Key setzen: +2. Generate an encryption key and set `ENCRYPTION_KEY` in `.env`: ```bash python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" ``` -Wert in `.env` bei `ENCRYPTION_KEY` eintragen. - -3. Stack starten: +3. Start services: ```bash make up ``` -4. URLs: +4. Open: - Frontend: `http://localhost:5173` - Backend API: `http://localhost:8000/api/v1` -- OpenAPI: `http://localhost:8000/docs` +- OpenAPI docs: `http://localhost:8000/docs` + +Default initial admin (from `.env`): -Default Admin (aus `.env`): - Email: `admin@example.com` -- Passwort: `ChangeMe123!` +- Password: `ChangeMe123!` -## Commands +## Common commands ```bash -make up -make down -make logs -make migrate +make up # build + start all services +make down # stop all services +make logs # follow logs +make migrate # run Alembic migrations in backend container ``` -## API (Minimum) +## Environment variables reference -- `POST /api/v1/auth/login` -- `POST /api/v1/auth/refresh` -- `POST /api/v1/auth/logout` -- `GET /api/v1/me` -- CRUD: `GET/POST/PUT/DELETE /api/v1/targets` -- `GET /api/v1/targets/{id}/metrics?from=&to=&metric=` -- `GET /api/v1/targets/{id}/locks` -- `GET /api/v1/targets/{id}/activity` -- `GET /api/v1/targets/{id}/top-queries` -- Admin-only CRUD users: +All variables are defined in `.env.example`. + +### Application + +- `APP_NAME`: Display name used by backend/docs +- `ENVIRONMENT`: `dev | staging | prod | test` +- `LOG_LEVEL`: `DEBUG | INFO | WARNING | ERROR` + +### Core database (internal) + +- `DB_NAME`: Internal metadata DB name +- `DB_USER`: Internal metadata DB user +- `DB_PASSWORD`: Internal metadata DB password +- `DB_PORT`: Host port mapped to internal PostgreSQL `5432` + +### Backend API + +- `BACKEND_PORT`: Host port mapped to backend container `8000` +- `JWT_SECRET_KEY`: JWT signing key (must be changed) +- `JWT_ALGORITHM`: JWT algorithm (default `HS256`) +- `JWT_ACCESS_TOKEN_MINUTES`: access token lifetime +- `JWT_REFRESH_TOKEN_MINUTES`: refresh token lifetime +- `ENCRYPTION_KEY`: Fernet key for encrypting target passwords at rest +- `CORS_ORIGINS`: comma-separated allowed origins or `*` (dev-only) +- `POLL_INTERVAL_SECONDS`: collector polling interval +- `INIT_ADMIN_EMAIL`: bootstrap admin email +- `INIT_ADMIN_PASSWORD`: bootstrap admin password + +### Frontend + +- `FRONTEND_PORT`: Host port mapped to frontend container `80` +- `VITE_API_URL`: API base URL baked into frontend build + - Proxy/SSL setup: use `/api/v1` + - Direct local setup: use `http://localhost:8000/api/v1` + +## API overview (minimum) + +- Auth: + - `POST /api/v1/auth/login` + - `POST /api/v1/auth/refresh` + - `POST /api/v1/auth/logout` + - `GET /api/v1/me` +- Targets: + - `GET/POST /api/v1/targets` + - `GET/PUT/DELETE /api/v1/targets/{id}` + - `GET /api/v1/targets/{id}/metrics?from=&to=&metric=` + - `GET /api/v1/targets/{id}/locks` + - `GET /api/v1/targets/{id}/activity` + - `GET /api/v1/targets/{id}/top-queries` + - `GET /api/v1/targets/{id}/overview` +- Admin users (admin-only): - `GET /api/v1/admin/users` - `POST /api/v1/admin/users` - `PUT /api/v1/admin/users/{user_id}` - `DELETE /api/v1/admin/users/{user_id}` -## Security Notes +## Security notes -- Keine Secrets hardcoded -- Passwoerter als Argon2 Hash -- Target-Credentials verschluesselt (Fernet) -- CORS via Env steuerbar -- Audit Logs fuer Login / Logout / Target- und User-Aenderungen -- Rate limiting: Platzhalter (kann spaeter middleware-basiert ergaenzt werden) +- No secrets are hardcoded in source +- Passwords are hashed with Argon2 +- Target credentials are encrypted with Fernet +- CORS is environment-configurable +- Audit logs include auth, target, and user management events +- Rate limiting is currently a placeholder for future middleware integration -## Wichtiger Hinweis zu `pg_stat_statements` +## Important: `pg_stat_statements` -Auf jedem monitored Target muss `pg_stat_statements` aktiviert sein, sonst bleiben Query Insights leer. -Beispiel: +Query Insights requires `pg_stat_statements` on each monitored target. ```sql CREATE EXTENSION IF NOT EXISTS pg_stat_statements; ``` + +## Reverse proxy and SSL + +For production-like deployments behind HTTPS: + +- Set frontend API to relative path: `VITE_API_URL=/api/v1` +- Route `/api/` from proxy to backend service +- Keep frontend and API on the same public origin to avoid CORS/mixed-content problems diff --git a/frontend/src/pages/AdminUsersPage.jsx b/frontend/src/pages/AdminUsersPage.jsx index 81e64ec..1619075 100644 --- a/frontend/src/pages/AdminUsersPage.jsx +++ b/frontend/src/pages/AdminUsersPage.jsx @@ -16,7 +16,7 @@ export function AdminUsersPage() { if (me?.role === "admin") load().catch((e) => setError(String(e.message || e))); }, [me]); - if (me?.role !== "admin") return
| Name | Host | DB | -Aktion | +Action | diff --git a/frontend/src/pages/LoginPage.jsx b/frontend/src/pages/LoginPage.jsx index afaf67b..4e2e0ed 100644 --- a/frontend/src/pages/LoginPage.jsx +++ b/frontend/src/pages/LoginPage.jsx @@ -18,7 +18,7 @@ export function LoginPage() { await login(email, password); navigate("/"); } catch { - setError("Login fehlgeschlagen"); + setError("Login failed"); } finally { setLoading(false); } @@ -28,12 +28,12 @@ export function LoginPage() {
|---|
| Name | Host | DB | -Aktionen | +Actions | diff --git a/ops/.env.example b/ops/.env.example index 4d6446c..124c479 100644 --- a/ops/.env.example +++ b/ops/.env.example @@ -1,27 +1,57 @@ -# App +# ------------------------------ +# Application +# ------------------------------ +# Display name used in API docs/UI. APP_NAME=NexaPG Monitor +# Runtime environment: dev | staging | prod | test ENVIRONMENT=dev +# Backend log level: DEBUG | INFO | WARNING | ERROR LOG_LEVEL=INFO -# Core DB +# ------------------------------ +# Core Database (internal metadata DB) +# ------------------------------ +# Database that stores users, targets, metrics, query stats, and audit logs. DB_NAME=nexapg DB_USER=nexapg DB_PASSWORD=nexapg +# Host port mapped to the internal PostgreSQL container port 5432. DB_PORT=5433 -# Backend +# ------------------------------ +# Backend API +# ------------------------------ +# Host port mapped to backend container port 8000. BACKEND_PORT=8000 +# JWT signing secret. Change this in every non-local environment. JWT_SECRET_KEY=change_this_super_secret JWT_ALGORITHM=HS256 +# Access token lifetime in minutes. JWT_ACCESS_TOKEN_MINUTES=15 +# Refresh token lifetime in minutes (10080 = 7 days). JWT_REFRESH_TOKEN_MINUTES=10080 -# Generate with: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" +# Key used to encrypt monitored target passwords at rest. +# Generate with: +# python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" ENCRYPTION_KEY=REPLACE_WITH_FERNET_KEY +# Allowed CORS origins for browser clients. +# Use comma-separated values, e.g.: +# CORS_ORIGINS=http://localhost:5173,https://nexapg.example.com +# Dev-only shortcut: +# CORS_ORIGINS=* CORS_ORIGINS=http://localhost:5173,http://localhost:8080 +# Target polling interval in seconds. POLL_INTERVAL_SECONDS=30 +# Initial admin bootstrap user (created on first startup if not present). INIT_ADMIN_EMAIL=admin@example.com INIT_ADMIN_PASSWORD=ChangeMe123! +# ------------------------------ # Frontend +# ------------------------------ +# Host port mapped to frontend container port 80. FRONTEND_PORT=5173 -VITE_API_URL=http://localhost:8000/api/v1 +# Base API URL used at frontend build time. +# For reverse proxy + SSL, keep this relative to avoid mixed-content issues. +# Example direct mode: VITE_API_URL=http://localhost:8000/api/v1 +VITE_API_URL=/api/v1
|---|