diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2e34d79..bd17398 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -40,12 +40,6 @@ jobs:
- name: Export OpenAPI spec
run: python scripts/export_openapi.py
- #- name: Set up Helm
- # uses: azure/setup-helm@v4
-
- #- name: Lint Helm chart
- # run: helm lint ./helm/graphql-meter --set secret.jwtSecret=ci-chart-secret-1234567890
-
test:
runs-on: ubuntu-latest
needs: quality
@@ -121,21 +115,80 @@ jobs:
python-version: "3.12"
cache: pip
- - name: Install dependencies (for lock generation)
+ - name: Install dependencies and generate lockfile
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
-
- - name: Generate pip freeze lock
- run: pip freeze > requirements.lock
+ pip freeze > requirements.txt
- name: Run Trivy vulnerability scan (filesystem)
uses: aquasecurity/trivy-action@v0.35.0
with:
scan-type: fs
scan-ref: .
- format: table
- exit-code: "1"
- severity: CRITICAL,HIGH
+ format: json
+ output: trivy-results.json
+ exit-code: "0" # Always return 0 here, let Python handle the failure logic
+ severity: UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL # Tell Trivy to report ALL severities
+ vuln-type: library
ignore-unfixed: true
scanners: vuln
+ env:
+ TRIVY_DETECTION_PRIORITY: comprehensive
+
+ - name: Parse Results and Enforce Policy
+ run: |
+ python - <<'PY'
+ import json, sys
+ from collections import Counter
+
+ with open("trivy-results.json", encoding="utf-8") as f:
+ results = json.load(f).get("Results") or []
+
+ scanned = [i for i in results if i.get("Target") not in ("", "-") and i.get("Type") not in ("", "-")]
+ if not scanned:
+ print("ERROR: Trivy scanned zero supported targets.")
+ sys.exit(1)
+
+ severity_counts = Counter({'UNKNOWN': 0, 'LOW': 0, 'MEDIUM': 0, 'HIGH': 0, 'CRITICAL': 0})
+ high_crit_count = 0
+
+ print("========================================")
+ print(" DETAILED VULNERABILITY LOG ")
+ print("========================================")
+
+ for i in results:
+ target = i.get("Target", "Unknown Target")
+ vulns = i.get("Vulnerabilities") or []
+
+ if vulns:
+ print(f"\nTarget: {target}")
+ for v in vulns:
+ pkg = v.get("PkgName", "Unknown")
+ sev = v.get("Severity", "UNKNOWN")
+ vid = v.get("VulnerabilityID", "UNKNOWN")
+ title = v.get("Title", "No title provided")
+
+ # Increment counters
+ severity_counts[sev] += 1
+ if sev in {"HIGH", "CRITICAL"}:
+ high_crit_count += 1
+
+ print(f" [{sev.ljust(8)}] {pkg} ({vid}): {title}")
+
+ print("\n========================================")
+ print(" TOTAL DETECTION SUMMARY ")
+ print("========================================")
+ for sev in ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW', 'UNKNOWN']:
+ print(f"{sev.ljust(10)}: {severity_counts[sev]}")
+
+ print("----------------------------------------")
+ print(f"Total Vulns : {sum(severity_counts.values())}")
+ print("========================================\n")
+
+ if high_crit_count > 0:
+ print(f"❌ ERROR: Pipeline failing due to {high_crit_count} HIGH/CRITICAL vulnerabilities.")
+ sys.exit(1)
+
+ print(f"✅ Success: No HIGH or CRITICAL vulnerabilities found. (Total scanned targets: {len(scanned)})")
+ PY
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 65bdcf4..9a9fb30 100644
--- a/.gitignore
+++ b/.gitignore
@@ -217,3 +217,7 @@ backend/data/
frontend/vendor/
learnings.md
.github/*.md
+.python-version
+notes/*
+alembic/
+alembic*
diff --git a/README.md b/README.md
index a14c2d6..6b1cf4c 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,10 @@
+
+
+
+
# GraphQL Meter
-**Schema-driven GraphQL performance testing — zero infrastructure, one container.**
+**Schema-driven GraphQL performance testing, GraphQL load testing, and GraphQL client workflows in one container.**
[](https://github.com/vanditsramblings/graphql-meter/actions/workflows/ci.yml)
[](https://github.com/vanditsramblings/graphql-meter/releases)
@@ -8,77 +12,113 @@
[](https://www.python.org/)
[](https://github.com/vanditsramblings/graphql-meter/pkgs/container/graphql-meter)
-[Quick Start](#getting-started) · [Features](#features) · [Installation](#installation) · [Configuration](#configuration) · [Architecture](#architecture)
+[Quick Start](#quick-start) · [Features](#features) · [Installation](#installation) · [Configuration](#configuration) · [Architecture](#architecture)
---
-## What is GraphQL Meter?
+## GraphQL Performance Testing
+
+GraphQL Meter turns a GraphQL schema into runnable performance tests, live dashboards, and repeatable comparisons. It is built for teams that want GraphQL performance testing without writing custom scripts or stitching together separate tools.
-GraphQL Meter is a self-hosted platform that transforms your GraphQL schema into fully configured performance tests. Paste a schema, select operations, set traffic distribution, and start load testing -- all from a single web interface. No YAML files, no test scripts to write, no external infrastructure to manage.
+It ships as a **single container** with Locust, k6, a built-in GraphQL client, and run history for regression analysis.
+
+| At a glance | Details |
+|:---|:---|
+| Primary use | GraphQL performance testing and GraphQL load testing |
+| Secondary use | GraphQL API testing, schema discovery, and client verification |
+| Delivery | Single container, no build step, no external orchestration |
+| Engines | Locust and k6, isolated from the FastAPI process |
+| Outputs | Live metrics, run comparisons, and trend views |
-It ships as a **single container** with everything included: two load-testing engines (Locust and k6), a real-time dashboard, run history, trend analysis, and a built-in GraphQL client for pre-test verification.
+## Why We Built It
-**The problem it solves:** Load testing GraphQL APIs typically requires writing custom scripts, managing test data, configuring authentication, and stitching together multiple tools. GraphQL Meter eliminates this setup cost by auto-discovering operations from your schema and generating everything needed to run, monitor, and compare tests.
+GraphQL teams usually need to solve the same problems before they can test at all:
-
-
+- Write custom load scripts for every API shape.
+- Manually manage test data and request variables.
+- Configure authentication, TLS, and client certificates.
+- Switch between multiple tools for schema discovery, execution, and comparison.
+
+GraphQL Meter removes that setup cost by auto-discovering operations from your schema and generating everything needed to run, monitor, and compare tests.
+
+
---
## Features
-| Capability | What it does |
-|:---|:---|
-| **Schema-Driven Tests** | Paste schema → auto-discover ops → generate typed test data |
-| **Dual Engine** | Locust (Python) + k6 (Go), subprocess-isolated, switchable per run |
-| **Live Monitoring** | p50/p90/p95/p99 charts updating every 2 seconds |
-| **Run Comparison** | Side-by-side delta with green/red regression highlights |
-| **Environments** | TLS/mTLS, client certs, 6 auth provider types with Fernet encryption |
-| **Built-in GQL Client** | Verify queries before running load tests |
-| **Runtime Config** | Adjust limits, toggle engines, enable debug — no restart needed |
+| Capability | What it covers | Why it matters |
+|:---|:---|:---|
+| **Schema-driven testing** | Discover queries and mutations from a GraphQL schema | Faster test setup with less manual wiring |
+| **Dual load engines** | Locust and k6, switchable per run | Compare engine behavior on the same workload |
+| **Live monitoring** | Throughput, error rate, and p50/p90/p95/p99 latency | See regressions while a test is running |
+| **Run comparison** | Side-by-side delta analysis across runs | Spot performance drift quickly |
+| **Environment profiles** | TLS/mTLS, certs, custom headers, and auth providers | Test realistic GraphQL API environments safely |
+| **Built-in GraphQL client** | Query execution before load testing | Validate schema, auth, and responses first |
+| **Runtime configuration** | Toggle limits and engines without restart | Adjust behavior from the UI when needed |
### Schema-Driven Test Generation
-Paste a GraphQL schema and GraphQL Meter will parse it (via AST with regex fallback), discover all queries and mutations, and generate type-aware test variables with smart defaults. No hand-authored test scripts required.
-
-
+Paste a GraphQL schema and GraphQL Meter will parse it, discover operations, and generate type-aware test variables with smart defaults. No hand-authored test scripts required.
+
+
+
+The wizard automatically discovers operations and allows you to configure traffic distribution:
+
+
### Dual Engine Support
-Choose between **Locust** (Python, greenlet-based) and **k6** (Go binary, scenarios-based) per test run. Both engines are subprocess-isolated from the main server -- they never share the FastAPI process. Compare results across engines to validate findings.
+
+Choose between **Locust** and **k6** per test run. Both engines are subprocess-isolated from the main server, so they never share the FastAPI process. Compare results across engines to validate findings.
### Real-Time Monitoring
-Watch throughput, response times (p50/p90/p95/p99), error rates, and per-operation breakdowns update live every 2 seconds. Interactive SVG charts with multi-series support render directly in the browser with no chart library dependency.
-
-
+Watch throughput, response times, error rates, and per-operation breakdowns update live every 2 seconds. Interactive SVG charts render directly in the browser with no chart library dependency.
+
+
+
+
### Test Configuration Wizard
-A 3-step wizard guides test setup: define global parameters, select operations with TPS percentage distribution (must sum to 100%), and review before starting. Saved configurations are reusable across runs.
+
+A 3-step wizard guides test setup: define global parameters, select operations with TPS percentage distribution, and review before starting. Saved configurations are reusable across runs.
### Run Comparison and Trend Analysis
-Compare any two runs side-by-side with delta highlighting (green = improved, red = regressed). View latency and throughput trends over the last N runs for any test configuration to catch regressions early.
-
-
+Compare any two runs side-by-side with delta highlighting. View per-operation deltas and overall metrics:
+
+
+
+View latency and throughput trends over the last N runs for any test configuration to catch regressions early:
+
+
### Environment Profiles
-Define multiple target environments with distinct base URLs, TLS/mTLS settings, client certificates (PEM, PFX, cert+key), custom headers, and linked authentication providers. Switch between dev, staging, and production targets without reconfiguring tests.
+
+Define multiple target environments with distinct base URLs, TLS/mTLS settings, client certificates, custom headers, and linked authentication providers. Switch between dev, staging, and production targets without reconfiguring tests:
+
+
### Encrypted Authentication
-Six auth provider types: Bearer Token, Basic Auth, API Key, OAuth2 Client Credentials, OAuth2 Password, and Custom JWT. All secrets encrypted at rest with Fernet AES. Thread-safe token caching with automatic refresh for OAuth2 flows.
+
+Six auth provider types: Bearer Token, Basic Auth, API Key, OAuth2 Client Credentials, OAuth2 Password, and Custom JWT. All secrets are encrypted at rest with Fernet. Token caching is thread-safe and automatically refreshes OAuth2 flows.
### Built-In GraphQL Client
-Verify queries against your target API before running load tests. Split-pane editor with variables/headers panels, environment and auth provider resolution, saved requests, and import from test configurations.
+
+Verify queries against your target API before running load tests. The split-pane editor supports variables, headers, environment resolution, auth provider resolution, saved requests, and import from test configurations:
+
+
### Runtime Configuration
-Adjust concurrency limits, enable/disable engines, toggle debug mode, and tune polling intervals from the Settings page without restarting the server. All changes take effect immediately for the current session.
-### Dark Professional UI
-Grafana/k6-inspired dark theme with CSS custom properties. No build step -- Preact + HTM served as vendored ES modules. Every page handles loading, empty, and error states.
+Adjust concurrency limits, enable or disable engines, toggle debug mode, and tune polling intervals from the Settings page without restarting the server. Changes take effect immediately for the current session:
+
+
+
---
-## Getting Started
+## Quick Start
The fastest path to a running instance:
@@ -106,7 +146,6 @@ docker run -p 8899:8899 ghcr.io/vanditsramblings/graphql-meter:latest
# Run with custom configuration
docker run -p 8899:8899 \
- -e JWT_SECRET=your-secret-key-here \
-e MAX_CONCURRENT_RUNS=5 \
-e ENABLE_K6=true \
-e ENABLE_LOCUST=true \
@@ -177,7 +216,6 @@ helm install graphql-meter ./helm/graphql-meter
# Install with custom values
helm install graphql-meter ./helm/graphql-meter \
- --set secret.jwtSecret=my-production-secret \
--set persistence.size=5Gi \
--set resources.limits.memory=2Gi
@@ -260,8 +298,6 @@ All settings are controlled via environment variables or a `.env` file. Copy `.e
| Variable | Default | Description |
|:---|:---|:---|
-| `JWT_SECRET` | (change me) | Secret key for JWT HS256 signing. **Must be changed in production.** |
-| `JWT_EXPIRY_HOURS` | `24` | Token expiration time |
| `ENCRYPTION_KEY` | (auto) | Fernet key for encrypting auth provider secrets. Auto-derived from `JWT_SECRET` if empty. |
### Load Testing
@@ -301,14 +337,13 @@ All settings are controlled via environment variables or a `.env` file. Copy `.e
**Environment variables** (highest priority):
```bash
-export JWT_SECRET=my-production-secret
export MAX_CONCURRENT_RUNS=5
graphql-meter
```
**Docker environment**:
```bash
-docker run -e JWT_SECRET=my-secret -e ENABLE_K6=false -p 8899:8899 ghcr.io/vanditsramblings/graphql-meter
+docker run -e ENABLE_K6=false -p 8899:8899 ghcr.io/vanditsramblings/graphql-meter
```
**.env file** (loaded automatically from working directory):
diff --git a/assets/00-dashboard.png b/assets/00-dashboard.png
new file mode 100644
index 0000000..28bf915
Binary files /dev/null and b/assets/00-dashboard.png differ
diff --git a/assets/01-login.png b/assets/01-login.png
new file mode 100644
index 0000000..5289d8b
Binary files /dev/null and b/assets/01-login.png differ
diff --git a/assets/02-dashboard.png b/assets/02-dashboard.png
new file mode 100644
index 0000000..c8a9894
Binary files /dev/null and b/assets/02-dashboard.png differ
diff --git a/assets/03-test-configs.png b/assets/03-test-configs.png
new file mode 100644
index 0000000..caeacb6
Binary files /dev/null and b/assets/03-test-configs.png differ
diff --git a/assets/04-schema-parsing.png b/assets/04-schema-parsing.png
new file mode 100644
index 0000000..17fcc7d
Binary files /dev/null and b/assets/04-schema-parsing.png differ
diff --git a/assets/05-operations.png b/assets/05-operations.png
new file mode 100644
index 0000000..f69edd4
Binary files /dev/null and b/assets/05-operations.png differ
diff --git a/assets/06-review-and-run.png b/assets/06-review-and-run.png
new file mode 100644
index 0000000..d9166a5
Binary files /dev/null and b/assets/06-review-and-run.png differ
diff --git a/assets/07-1-livemonitoring.png b/assets/07-1-livemonitoring.png
new file mode 100644
index 0000000..96d556f
Binary files /dev/null and b/assets/07-1-livemonitoring.png differ
diff --git a/assets/07-2-livemonitoring.png b/assets/07-2-livemonitoring.png
new file mode 100644
index 0000000..56751e3
Binary files /dev/null and b/assets/07-2-livemonitoring.png differ
diff --git a/assets/08-comparison-view.png b/assets/08-comparison-view.png
new file mode 100644
index 0000000..88f52d7
Binary files /dev/null and b/assets/08-comparison-view.png differ
diff --git a/assets/08-test-history.png b/assets/08-test-history.png
new file mode 100644
index 0000000..8ae9d79
Binary files /dev/null and b/assets/08-test-history.png differ
diff --git a/assets/09-comparison-view-data.png b/assets/09-comparison-view-data.png
new file mode 100644
index 0000000..1223263
Binary files /dev/null and b/assets/09-comparison-view-data.png differ
diff --git a/assets/10-trends-analysis.png b/assets/10-trends-analysis.png
new file mode 100644
index 0000000..efc6ea8
Binary files /dev/null and b/assets/10-trends-analysis.png differ
diff --git a/assets/11-environment-profiles.png b/assets/11-environment-profiles.png
new file mode 100644
index 0000000..6d6e5a1
Binary files /dev/null and b/assets/11-environment-profiles.png differ
diff --git a/assets/12-graphql-client.png b/assets/12-graphql-client.png
new file mode 100644
index 0000000..47ff506
Binary files /dev/null and b/assets/12-graphql-client.png differ
diff --git a/assets/13-runtime-configuration.png b/assets/13-runtime-configuration.png
new file mode 100644
index 0000000..191c1bb
Binary files /dev/null and b/assets/13-runtime-configuration.png differ
diff --git a/assets/graphql-meter-icon.svg b/assets/graphql-meter-icon.svg
new file mode 100644
index 0000000..d34c3cb
--- /dev/null
+++ b/assets/graphql-meter-icon.svg
@@ -0,0 +1,28 @@
+
\ No newline at end of file
diff --git a/assets/graphql-meter-readme.svg b/assets/graphql-meter-readme.svg
new file mode 100644
index 0000000..1439ff2
--- /dev/null
+++ b/assets/graphql-meter-readme.svg
@@ -0,0 +1,29 @@
+
\ No newline at end of file
diff --git a/frontend/components/Sidebar.js b/frontend/components/Sidebar.js
index 5cbfcc7..7a1b6eb 100644
--- a/frontend/components/Sidebar.js
+++ b/frontend/components/Sidebar.js
@@ -54,7 +54,7 @@ export function Sidebar({ currentPath }) {
return html`