mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-01-08 04:30:20 -06:00
Major Features: - Invoice Expenses: Allow linking billable expenses to invoices with automatic total calculations - Add expenses to invoices via "Generate from Time/Costs" workflow - Display expenses in invoice view, edit forms, and PDF exports - Track expense states (approved, invoiced, reimbursed) with automatic unlinking on invoice deletion - Update PDF generator and CSV exports to include expense line items - Enhanced PDF Invoice Editor: Complete redesign using Konva.js for visual drag-and-drop layout design - Add 40+ draggable elements (company info, invoice data, shapes, text, advanced elements) - Implement comprehensive properties panel for precise element customization (position, fonts, colors, opacity) - Add canvas toolbar with alignment tools, zoom controls, and layer management - Support keyboard shortcuts (copy/paste, duplicate, arrow key positioning) - Save designs as JSON for editing and generate clean HTML/CSS for rendering - Add real-time preview with live data - Uploads Persistence: Implement Docker volume persistence for user-uploaded files - Add app_uploads volume to all Docker Compose configurations - Ensure company logos and avatars persist across container rebuilds and restarts - Create migration script for existing installations - Update directory structure with proper permissions (755 for dirs, 644 for files) Database & Backend: - Add invoice_pdf_design_json column to settings table via Alembic migration - Extend Invoice model with expenses relationship - Update admin routes for PDF layout designer endpoints - Enhance invoice routes to handle expense linking/unlinking Frontend & UI: - Redesign PDF layout editor template with Konva.js canvas (2484 lines, major overhaul) - Update invoice edit/view templates to display and manage expenses - Add expense sections to invoice forms with unlink functionality - Enhance UI components with keyboard shortcuts support - Update multiple templates for consistency and accessibility Testing & Documentation: - Add comprehensive test suites for invoice expenses, PDF layouts, and uploads persistence - Create detailed documentation for all new features (5 new docs) - Include migration guides and troubleshooting sections Infrastructure: - Update docker-compose files (main, example, remote, remote-dev, local-test) with uploads volume - Configure pytest for new test modules - Add template filters for currency formatting and expense display This update significantly enhances TimeTracker's invoice management capabilities, improves the PDF customization experience, and ensures uploaded files persist reliably across deployments.
192 lines
6.5 KiB
YAML
192 lines
6.5 KiB
YAML
services:
|
|
# Certificate generator - runs once to create self-signed certs with SANs
|
|
certgen:
|
|
image: alpine:latest
|
|
container_name: timetracker-certgen
|
|
volumes:
|
|
- ./nginx/ssl:/certs
|
|
- ./scripts:/scripts:ro
|
|
command: sh /scripts/generate-certs.sh
|
|
restart: "no"
|
|
|
|
# HTTPS reverse proxy (TLS terminates here)
|
|
nginx:
|
|
image: nginx:alpine
|
|
container_name: timetracker-nginx
|
|
ports:
|
|
- "80:80"
|
|
- "443:443"
|
|
volumes:
|
|
- ./nginx/conf.d:/etc/nginx/conf.d:ro
|
|
- ./nginx/ssl:/etc/nginx/ssl:ro
|
|
depends_on:
|
|
certgen:
|
|
condition: service_completed_successfully
|
|
app:
|
|
condition: service_started
|
|
restart: unless-stopped
|
|
|
|
app:
|
|
build: .
|
|
container_name: timetracker-app
|
|
environment:
|
|
- TZ=${TZ:-Europe/Brussels}
|
|
- CURRENCY=${CURRENCY:-EUR}
|
|
- ROUNDING_MINUTES=${ROUNDING_MINUTES:-1}
|
|
- SINGLE_ACTIVE_TIMER=${SINGLE_ACTIVE_TIMER:-true}
|
|
- ALLOW_SELF_REGISTER=${ALLOW_SELF_REGISTER:-true}
|
|
- IDLE_TIMEOUT_MINUTES=${IDLE_TIMEOUT_MINUTES:-30}
|
|
- ADMIN_USERNAMES=${ADMIN_USERNAMES:-admin}
|
|
# IMPORTANT: Change SECRET_KEY in production! Used for sessions and CSRF tokens.
|
|
# Generate a secure key: python -c "import secrets; print(secrets.token_hex(32))"
|
|
#
|
|
# CSRF CONFIGURATION:
|
|
# - WTF_CSRF_SSL_STRICT: Set to 'false' for HTTP access (localhost or IP address)
|
|
# Set to 'true' only when using HTTPS in production
|
|
# - If accessing via IP address (e.g., 192.168.1.100), also set:
|
|
# SESSION_COOKIE_SECURE=false and CSRF_COOKIE_SECURE=false
|
|
#
|
|
# TROUBLESHOOTING: If forms fail with "CSRF token missing or invalid":
|
|
# 1. Verify SECRET_KEY is set and doesn't change between restarts
|
|
# 2. Check CSRF is enabled: WTF_CSRF_ENABLED=true
|
|
# 3. Ensure cookies are enabled in your browser
|
|
# 4. If behind a reverse proxy, ensure it forwards cookies correctly
|
|
# 5. Check the token hasn't expired (increase WTF_CSRF_TIME_LIMIT if needed)
|
|
# 6. If accessing via IP (not localhost): WTF_CSRF_SSL_STRICT=false
|
|
# For details: docs/CSRF_CONFIGURATION.md and docs/CSRF_IP_ACCESS_GUIDE.md
|
|
- SECRET_KEY=${SECRET_KEY:-your-secret-key-change-this}
|
|
# Disable strict Referer check by default to avoid privacy/port issues
|
|
- WTF_CSRF_SSL_STRICT=${WTF_CSRF_SSL_STRICT:-true}
|
|
- WTF_CSRF_ENABLED=${WTF_CSRF_ENABLED:-true}
|
|
- WTF_CSRF_TIME_LIMIT=${WTF_CSRF_TIME_LIMIT:-3600}
|
|
- SESSION_COOKIE_SECURE=${SESSION_COOKIE_SECURE:-true}
|
|
- SESSION_COOKIE_SAMESITE=${SESSION_COOKIE_SAMESITE:-Lax}
|
|
- REMEMBER_COOKIE_SECURE=${REMEMBER_COOKIE_SECURE:-true}
|
|
- CSRF_COOKIE_SECURE=${CSRF_COOKIE_SECURE:-true}
|
|
- CSRF_COOKIE_HTTPONLY=${CSRF_COOKIE_HTTPONLY:-false}
|
|
- CSRF_COOKIE_SAMESITE=${CSRF_COOKIE_SAMESITE:-Lax}
|
|
- CSRF_COOKIE_NAME=${CSRF_COOKIE_NAME:-XSRF-TOKEN}
|
|
- PREFERRED_URL_SCHEME=${PREFERRED_URL_SCHEME:-https}
|
|
- WTF_CSRF_TRUSTED_ORIGINS=${WTF_CSRF_TRUSTED_ORIGINS:-https://localhost}
|
|
- DATABASE_URL=postgresql+psycopg2://timetracker:timetracker@db:5432/timetracker
|
|
- LOG_FILE=/app/logs/timetracker.log
|
|
# Analytics & Monitoring (optional)
|
|
# See docs/analytics.md for configuration details
|
|
- SENTRY_DSN=${SENTRY_DSN:-}
|
|
- SENTRY_TRACES_RATE=${SENTRY_TRACES_RATE:-0.0}
|
|
- POSTHOG_API_KEY=${POSTHOG_API_KEY:-}
|
|
- POSTHOG_HOST=${POSTHOG_HOST:-https://app.posthog.com}
|
|
- ENABLE_TELEMETRY=${ENABLE_TELEMETRY:-false}
|
|
- TELE_URL=${TELE_URL:-}
|
|
- TELE_SALT=${TELE_SALT:-8f4a7b2e9c1d6f3a5e8b4c7d2a9f6e3b1c8d5a7f2e9b4c6d3a8f5e1b7c4d9a2f}
|
|
|
|
# Expose only internally; nginx publishes ports
|
|
ports: []
|
|
volumes:
|
|
- app_data:/data
|
|
- app_logs:/app/logs
|
|
- app_uploads:/app/app/static/uploads
|
|
depends_on:
|
|
db:
|
|
condition: service_healthy
|
|
restart: unless-stopped
|
|
healthcheck:
|
|
test: ["CMD", "curl", "-f", "http://localhost:8080/_health"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 40s
|
|
|
|
db:
|
|
image: postgres:16-alpine
|
|
container_name: timetracker-db
|
|
environment:
|
|
- POSTGRES_DB=${POSTGRES_DB:-timetracker}
|
|
- POSTGRES_USER=${POSTGRES_USER:-timetracker}
|
|
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-timetracker}
|
|
- TZ=${TZ:-Europe/Brussels}
|
|
volumes:
|
|
- db_data:/var/lib/postgresql/data
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 30s
|
|
restart: unless-stopped
|
|
|
|
# Analytics & Monitoring Services
|
|
# All services start by default for complete monitoring
|
|
# See docs/analytics.md and ANALYTICS_QUICK_START.md for details
|
|
|
|
# Prometheus - Metrics collection and storage
|
|
prometheus:
|
|
image: prom/prometheus:latest
|
|
container_name: timetracker-prometheus
|
|
volumes:
|
|
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
|
|
- prometheus_data:/prometheus
|
|
command:
|
|
- '--config.file=/etc/prometheus/prometheus.yml'
|
|
- '--storage.tsdb.path=/prometheus'
|
|
- '--storage.tsdb.retention.time=30d'
|
|
ports:
|
|
- "9090:9090"
|
|
restart: unless-stopped
|
|
|
|
# Grafana - Metrics visualization and dashboards
|
|
grafana:
|
|
image: grafana/grafana:latest
|
|
container_name: timetracker-grafana
|
|
environment:
|
|
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin}
|
|
- GF_USERS_ALLOW_SIGN_UP=false
|
|
- GF_SERVER_ROOT_URL=${GF_SERVER_ROOT_URL:-http://localhost:3000}
|
|
volumes:
|
|
- grafana_data:/var/lib/grafana
|
|
- ./grafana/provisioning:/etc/grafana/provisioning
|
|
ports:
|
|
- "3000:3000"
|
|
depends_on:
|
|
- prometheus
|
|
restart: unless-stopped
|
|
|
|
# Loki - Log aggregation
|
|
loki:
|
|
image: grafana/loki:latest
|
|
container_name: timetracker-loki
|
|
volumes:
|
|
- ./loki/loki-config.yml:/etc/loki/local-config.yaml
|
|
- loki_data:/loki
|
|
ports:
|
|
- "3100:3100"
|
|
command: -config.file=/etc/loki/local-config.yaml
|
|
restart: unless-stopped
|
|
|
|
# Promtail - Log shipping to Loki
|
|
promtail:
|
|
image: grafana/promtail:latest
|
|
container_name: timetracker-promtail
|
|
volumes:
|
|
- ./logs:/var/log/timetracker:ro
|
|
- ./promtail/promtail-config.yml:/etc/promtail/config.yml
|
|
command: -config.file=/etc/promtail/config.yml
|
|
depends_on:
|
|
- loki
|
|
restart: unless-stopped
|
|
|
|
volumes:
|
|
app_data:
|
|
driver: local
|
|
app_logs:
|
|
driver: local
|
|
app_uploads:
|
|
driver: local
|
|
db_data:
|
|
driver: local
|
|
prometheus_data:
|
|
driver: local
|
|
grafana_data:
|
|
driver: local
|
|
loki_data:
|
|
driver: local |