🧙♂️ Transform LifeRPG into The Wizard's Grimoire - Production-Ready Application
✨ Major Features Added: - Complete magical theming and rebranding from LifeRPG to The Wizard's Grimoire - Production-grade React frontend with Tailwind CSS v4 and magical aesthetics - Comprehensive analytics dashboard with Recharts integration (ScryingPortal) - Push notifications system with PWA service worker support - Drag & drop functionality using @dnd-kit for habit reordering - Social features with friends system and leaderboards - Performance optimization tools and monitoring - Mobile app enhancement with PWA installation support 🏗️ Technical Infrastructure: - Advanced service worker with offline support and background sync - Zustand state management for scalable application state - Production-ready UI component system with enhanced Button, Card, Input - Progressive Web App (PWA) with manifest and app installation - FastAPI backend with comprehensive API endpoints - Docker containerization and CI/CD pipeline setup 📱 Progressive Web App Features: - Offline functionality with intelligent caching - Push notification support for habit reminders - App installation on mobile and desktop platforms - Background sync for offline data management - Performance monitoring and optimization tools 🎨 User Experience: - Magical wizard/grimoire theming throughout application - Responsive design optimized for all device sizes - Drag & drop habit management with smooth animations - Interactive analytics with multiple chart types - Social connectivity with friends and competitive features - Comprehensive notification and performance settings 🔧 Developer Experience: - Modern development stack with Vite and React - Comprehensive testing setup and CI/CD pipelines - Code quality tools with pre-commit hooks - Docker development environment - Detailed documentation and implementation guides This represents a complete transformation from prototype to production-ready application with enterprise-grade features and magical user experience.
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
# LifeRPG Ops Runbook
|
||||
|
||||
This runbook summarizes common operational signals and actions.
|
||||
|
||||
## Key metrics and dashboards
|
||||
- HTTP: request rate (`http_requests_total`), p95 latency (`http_request_duration_seconds`), in-progress gauge.
|
||||
- Jobs: `jobs_processed_total{status}`.
|
||||
- Integrations: `integration_sync_total{provider,result}`, `integration_sync_by_integration_total{integration_id,result}`.
|
||||
- Backpressure: `sync_enqueue_skips_total{reason}`, `sync_queue_depth{provider}`, `sync_inflight{provider}`.
|
||||
- Logs: structured JSON logs for requests and jobs; ship via Promtail to Loki.
|
||||
|
||||
Grafana dashboard: `ops/grafana-dashboard.json` (import into Grafana and configure `PROM_DS` and `LOKI_DS`).
|
||||
|
||||
## Common symptoms
|
||||
|
||||
1) High enqueue skips
|
||||
- Symptom: `sync_enqueue_skips_total` rate > 0.2 for >10m.
|
||||
- Likely causes: provider concurrency cap, duplicate enqueues (guard), or downstream slowness.
|
||||
- Actions:
|
||||
- Check `sync_inflight{provider}` vs cap (env `SYNC_MAX_CONCURRENCY_PER_PROVIDER`).
|
||||
- Temporarily raise the cap if safe, or reduce scheduler cadence (`sync_interval_seconds`).
|
||||
- Inspect job logs in Loki for adapter errors or rate limits.
|
||||
|
||||
2) Queue depth rising
|
||||
- Symptom: `increase(sync_queue_depth[15m]) > 50`.
|
||||
- Actions:
|
||||
- Scale workers or increase per-provider cap cautiously.
|
||||
- Pause non-critical providers by increasing intervals.
|
||||
- Check external API health/rate limits.
|
||||
|
||||
3) Elevated request latency
|
||||
- Symptom: p95 > 500ms sustained.
|
||||
- Actions:
|
||||
- Inspect recent deployments, DB CPU/IO, and external dependencies.
|
||||
- Enable sampling/profiling; consider caching.
|
||||
|
||||
## Configuration
|
||||
- Concurrency cap per provider: `SYNC_MAX_CONCURRENCY_PER_PROVIDER` (default 4).
|
||||
- Default scheduler interval: `DEFAULT_SYNC_INTERVAL_SECONDS` (default 900s). Per-integration override: `integration.config.sync_interval_seconds`.
|
||||
- Close mode: `INTEGRATION_CLOSE_MODE` (`archive` default; `delete` opt-in).
|
||||
|
||||
## On-call checklist
|
||||
- Confirm alerts and correlate with Grafana panels.
|
||||
- Review recent logs for `event=enqueued|start|success|fail` in Loki.
|
||||
- Take one mitigating action at a time; document in the incident log.
|
||||
|
||||
## Playbooks
|
||||
- Raise provider cap:
|
||||
- Set `SYNC_MAX_CONCURRENCY_PER_PROVIDER` and restart worker.
|
||||
- Slow the scheduler:
|
||||
- PATCH integration config `{"sync_interval_seconds": <value>}` for noisy integrations.
|
||||
- Toggle close policy:
|
||||
- POST `/api/v1/admin/settings` `{ "integration_close_mode": "archive|delete" }`.
|
||||
@@ -0,0 +1,336 @@
|
||||
{
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": {
|
||||
"type": "grafana",
|
||||
"uid": "-- Grafana --"
|
||||
},
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 0,
|
||||
"id": null,
|
||||
"links": [],
|
||||
"liveNow": false,
|
||||
"panels": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "PROM_DS"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 1,
|
||||
"options": {
|
||||
"legend": {
|
||||
"displayMode": "list"
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum by (method, path, status) (rate(http_requests_total[5m]))",
|
||||
"legendFormat": "{{method}} {{path}} {{status}}"
|
||||
}
|
||||
],
|
||||
"title": "HTTP Requests (rate)",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "PROM_DS"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 0
|
||||
},
|
||||
"id": 2,
|
||||
"options": {
|
||||
"legend": {
|
||||
"displayMode": "list"
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "histogram_quantile(0.95, sum by (le, method, path) (rate(http_request_duration_seconds_bucket[5m])))",
|
||||
"legendFormat": "p95 {{method}} {{path}}"
|
||||
}
|
||||
],
|
||||
"title": "Request Latency p95",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "PROM_DS"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 8
|
||||
},
|
||||
"id": 3,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(http_requests_in_progress)",
|
||||
"legendFormat": "in-progress"
|
||||
}
|
||||
],
|
||||
"title": "Requests In-Progress",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "PROM_DS"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 8
|
||||
},
|
||||
"id": 4,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum by (status) (rate(jobs_processed_total[5m]))",
|
||||
"legendFormat": "{{status}}"
|
||||
}
|
||||
],
|
||||
"title": "Jobs Processed (rate)",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "PROM_DS"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 16
|
||||
},
|
||||
"id": 5,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum by (provider, result) (rate(integration_sync_total[5m]))",
|
||||
"legendFormat": "{{provider}} {{result}}"
|
||||
}
|
||||
],
|
||||
"title": "Integration Syncs (rate)",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "PROM_DS"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 16
|
||||
},
|
||||
"id": 6,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum by (provider, verified) (rate(webhook_events_total[5m]))",
|
||||
"legendFormat": "{{provider}} {{verified}}"
|
||||
}
|
||||
],
|
||||
"title": "Webhook Events (rate)",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "PROM_DS"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 24
|
||||
},
|
||||
"id": 7,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum by (integration_id, result) (rate(integration_sync_by_integration_total[5m]))",
|
||||
"legendFormat": "id={{integration_id}} {{result}}"
|
||||
}
|
||||
],
|
||||
"title": "Integration Syncs by Integration (rate)",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "loki",
|
||||
"uid": "LOKI_DS"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 10,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 32
|
||||
},
|
||||
"id": 8,
|
||||
"options": {
|
||||
"dedupStrategy": "none",
|
||||
"enableLogDetails": true,
|
||||
"prettifyLogMessage": true,
|
||||
"showCommonLabels": false,
|
||||
"showLabels": true,
|
||||
"sortOrder": "Descending"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "{job=\"liferpg\"} |= `\"type\":\"job\"`",
|
||||
"queryType": "range"
|
||||
}
|
||||
],
|
||||
"title": "Job Logs (structured)",
|
||||
"type": "logs"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "PROM_DS"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 42
|
||||
},
|
||||
"id": 9,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum by (reason) (rate(sync_enqueue_skips_total[5m]))",
|
||||
"legendFormat": "skip {{reason}}"
|
||||
}
|
||||
],
|
||||
"title": "Sync Enqueue Skips (rate)",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "PROM_DS"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 42
|
||||
},
|
||||
"id": 10,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum by (provider) (sync_queue_depth)",
|
||||
"legendFormat": "depth {{provider}}"
|
||||
}
|
||||
],
|
||||
"title": "Sync Queue Depth",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "PROM_DS"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 48
|
||||
},
|
||||
"id": 11,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum by (provider) (sync_inflight)",
|
||||
"legendFormat": "inflight {{provider}}"
|
||||
}
|
||||
],
|
||||
"title": "Sync In-Flight",
|
||||
"type": "stat"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 38,
|
||||
"style": "dark",
|
||||
"tags": [
|
||||
"liferpg"
|
||||
],
|
||||
"templating": {
|
||||
"list": []
|
||||
},
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
},
|
||||
"timezone": "browser",
|
||||
"title": "LifeRPG Backend",
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
groups:
|
||||
- name: liferpg.rules
|
||||
interval: 1m
|
||||
rules:
|
||||
- alert: HighEnqueueSkips
|
||||
expr: sum(rate(sync_enqueue_skips_total[5m])) > 0.2
|
||||
for: 10m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: High rate of sync enqueue skips
|
||||
description: Enqueue skips ({{ $value }}) may indicate provider caps or guard contention.
|
||||
|
||||
- alert: ProviderAtConcurrencyCap
|
||||
expr: max_over_time(sync_inflight[15m]) >= on(provider) (sync_provider_cap)
|
||||
for: 10m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: Provider at concurrency cap
|
||||
description: In-flight syncs have been at the configured cap for 10m.
|
||||
|
||||
- alert: QueueDepthGrowing
|
||||
expr: increase(sync_queue_depth[15m]) > 50
|
||||
for: 10m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: Queue depth increasing
|
||||
description: Sync queue depth increased by >50 over 15m. Investigate worker capacity or provider health.
|
||||
|
||||
- alert: RQQueueBacklog
|
||||
expr: max_over_time(rq_queue_length[10m]) > 100
|
||||
for: 10m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: RQ queue backlog
|
||||
description: RQ queue length has exceeded 100 for 10 minutes.
|
||||
|
||||
- alert: SlowSyncsP95
|
||||
expr: |
|
||||
histogram_quantile(0.95, sum by (le, provider) (rate(sync_job_duration_seconds_bucket[5m]))) > 30
|
||||
for: 10m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: Slow syncs p95 over 30s
|
||||
description: The 95th percentile of sync job durations is above 30 seconds for 10 minutes.
|
||||
@@ -0,0 +1,11 @@
|
||||
global:
|
||||
scrape_interval: 15s
|
||||
|
||||
scrape_configs:
|
||||
- job_name: liferpg-backend
|
||||
static_configs:
|
||||
- targets: ["localhost:8000"]
|
||||
labels:
|
||||
service: backend
|
||||
env: dev
|
||||
metrics_path: /metrics
|
||||
@@ -0,0 +1,27 @@
|
||||
server:
|
||||
http_listen_port: 9080
|
||||
grpc_listen_port: 0
|
||||
|
||||
positions:
|
||||
filename: /tmp/positions.yaml
|
||||
|
||||
clients:
|
||||
- url: http://loki:3100/loki/api/v1/push
|
||||
|
||||
scrape_configs:
|
||||
- job_name: liferpg
|
||||
static_configs:
|
||||
- targets:
|
||||
- localhost
|
||||
labels:
|
||||
job: liferpg
|
||||
host: ${HOSTNAME}
|
||||
__path__: /var/log/liferpg/*.log
|
||||
- job_name: liferpg-stdout
|
||||
static_configs:
|
||||
- targets:
|
||||
- localhost
|
||||
labels:
|
||||
job: liferpg
|
||||
host: ${HOSTNAME}
|
||||
__path__: /var/lib/docker/containers/*/*-json.log
|
||||
Reference in New Issue
Block a user