A web-based compliance tracking application for validating IPv6-only capabilities across network hardware platforms. Add vendors and models, then work through the 77-item compliance checklist — recording PASS / FAIL / PARTIAL / N/A results with supporting notes inline.
The test suite covers the full IPv6-only stack: core protocols, all major routing protocols (OSPFv3, IS-IS, MP-BGP), MPLS (6PE, 6VPE, LDP), Segment Routing (SRv6, SR-MPLS), overlays (VXLAN, EVPN, GENEVE), multicast (MLDv2, PIM-SM/SSM, mVPN), high availability (BFD, VRRPv3, LFA), security (MACsec, uRPF, CoPP), modern management APIs (NETCONF, RESTCONF, gNMI, OpenConfig), and VPN/tunneling (IKEv2, GRE, L2TPv3). Each test case carries an RFC reference, a severity level (MANDATORY / RECOMMENDED / OPTIONAL), and searchable tags.
Upload a device's running configuration and the app cross-references it against the compliance test cases, highlighting relevant config lines for each test.
Built with Next.js 16 (App Router), Prisma 7 ORM, SQLite, and Zod input validation. Ships with a full REST API, Vitest test suite, and a Docker Compose setup optimised for IPv6-only deployment targets.
- Requirements
- Quick Start (Local Dev)
- Docker Compose (Production / IPv6-only host)
- Seeding Test Cases
- Using the Web UI
- Running Tests
- npm Scripts Reference
- API Reference
- Database Schema
- Project Structure
- Extending the Application
| Tool | Minimum version | Notes |
|---|---|---|
| Node.js | 20.x LTS | |
| npm | 10.x (bundled with Node 20) | |
| Python 3 + make + g++ | — | Required to compile better-sqlite3 native addon during npm install |
| Docker + Compose | 26+ | For IPv6 Compose support; optional for local dev |
Linux / Alpine note: install
python3 make g++beforenpm install. macOS uses Xcode CLT (usually pre-installed). Windows usesnode-gypwith VS Build Tools.
# 1. Clone and enter the repo
git clone <repo-url>
cd 6verifiche
# 2. Install dependencies
npm install
# 3. Create a .env file for the local database URL
echo 'DATABASE_URL="file:./dev.db"' > .env
# 4. Push the Prisma schema to the local SQLite database and generate the client
npx prisma db push
npx prisma generate
# 5. Seed all 77 compliance test cases
npm run seed
# 6. Start the development server
npm run devThe app is now running at http://localhost:3000.
Note:
.envis listed in.gitignoreand will not be committed. TheDATABASE_URLmust be set (either via.envor the environment) forprisma db push,prisma generate, andnpm run seedto work.
The Compose stack runs Traefik v3 as a TLS-terminating reverse proxy in front of the Next.js app. Traefik listens on [::]:80 and [::]:443 (IPv6-only; dual-stack hosts also accept IPv4-mapped connections), redirects all HTTP to HTTPS, and obtains/renews Let's Encrypt certificates automatically. The app container no longer exposes a port directly — all ingress flows through Traefik over the shared v6net Docker network.
Complete these steps on the production host before starting the stack:
1. DNS — Create an AAAA record for your domain pointing at the host's public IPv6 address. Let's Encrypt's HTTP-01 challenge validates over IPv6 when the domain resolves to only an AAAA record.
2. Docker daemon IPv6 — Enable IPv6 forwarding and ip6tables so containers can make outbound connections (required for ACME requests to Let's Encrypt). Edit /etc/docker/daemon.json:
{
"ipv6": true,
"fixed-cidr-v6": "fd00:db8::/64",
"ip6tables": true
}Then restart the daemon: sudo systemctl restart docker.
3. Configure domain and email — two files need your real values:
# docker-compose.yml — update the app router rule label:
- "traefik.http.routers.app.rule=Host(`your.real.domain`)"# traefik/traefik.yml — set your Let's Encrypt notification address:
email: your-email@example.comUncomment the caServer line in traefik/traefik.yml to use the Let's Encrypt staging CA. This avoids the production rate limit (5 certificates per registered domain per week) while verifying that DNS and port 80 are reachable from the internet.
# traefik/traefik.yml
caServer: https://acme-staging-v02.api.letsencrypt.org/directoryRe-comment or remove the line once a staging certificate is issued successfully, then run docker compose down -v && docker compose up -d to obtain a real certificate (the staging cert is not browser-trusted).
# Build the image and start the full stack (Traefik + app)
docker compose up --build -d
# Seed test cases inside the running app container
docker compose exec app sh -c 'DATABASE_URL=file:/data/prod.db node scripts/seed.js'Once DNS is in place and Let's Encrypt has issued a certificate:
https://your.real.domain/
Traefik handles the HTTP → HTTPS redirect automatically. The app container is not reachable on any port directly.
The SQLite database is stored in the db-data named volume at /data/prod.db. Let's Encrypt certificates are stored in a separate traefik-certs volume at /letsencrypt/acme.json. Both survive container restarts and image rebuilds.
# Stop without removing volumes
docker compose down
# Remove everything including both data volumes (certificates will be re-issued on next start)
docker compose down -vThe Traefik dashboard is enabled in traefik/traefik.yml but not routed by default. To expose it, add these labels to the traefik service in docker-compose.yml:
labels:
- "traefik.enable=true"
- "traefik.http.routers.dashboard.rule=Host(`traefik.your.real.domain`)"
- "traefik.http.routers.dashboard.entrypoints=websecure"
- "traefik.http.routers.dashboard.tls.certresolver=letsencrypt"
- "traefik.http.routers.dashboard.service=api@internal"
- "traefik.http.routers.dashboard.middlewares=dash-auth"
# Generate the hash: echo $(htpasswd -nB admin) | sed 's/\$/\$\$/g'
- "traefik.http.middlewares.dash-auth.basicauth.users=admin:$$2y$$..."- Next.js
output: 'standalone'is enabled innext.config.ts— the Docker image runs the compiledserver.jsdirectly without a Node module install step at runtime. HOSTNAME=::tells Next.js's standalone server to bind on all IPv6 interfaces inside the container; Traefik reaches it via the sharedv6netinternal network.scripts/setup-db.jsruns automatically at container startup (idempotentCREATE TABLE IF NOT EXISTS), with no dependency on the Prisma CLI or any TypeScript loader.- The Docker network carries
name: v6netexplicitly sotraefik/traefik.ymlcan reference it directly regardless of the Compose project name. - ACME challenge method: HTTP-01 is configured by default. If port 80 is firewalled, switch to TLS-ALPN-01 or DNS-01 in
traefik/traefik.yml— see the inline comments in that file for all three options.
Test cases are defined in scripts/seed.ts and derived from IPv6-compliance-superset.md.
npm run seedThe seed script is idempotent — re-running it updates all metadata in place without creating duplicates or affecting existing test results. It upserts on the (category, name) unique key and syncs description, rfcReference, severity, and tags on every run.
77 test cases across 13 categories:
| Category | Count | Highlights |
|---|---|---|
| Network Management & Telemetry | 9 | SSH, RADIUS, TACACS+, SNMP, syslog, NTP, DNS, NetFlow/IPFIX |
| Core IPv6 Protocols & Features | 10 | ICMPv6/ND, SLAAC/DHCPv6, extension headers, PMTUD, flow label, DHCPv6 relay, DHCPv6-PD relay, RA suppression, RDNSS/DNSSL/route-info RA options, RFC 8781 PREF64 |
| IPv6 Routing & Forwarding | 7 | RIPng, OSPFv3, IS-IS, MP-BGP, BGP ADD-PATH, BGP-LU, RFC 8950 (IPv4 NLRI/IPv6 next hop) |
| Security, Transition & Hardware | 7 | ACLs/FHS, simultaneous IPv4+IPv6 ACLs, ingress/egress ACL restrictions, ASIC datapath, NAT64, CoPP, uRPF |
| Connectivity & Validation | 3 | E2E traffic, log/monitor, certifications |
| MPLS & Label Switching | 5 | 6PE, 6VPE, LDPoIPv6, MPLS BFD, RSVP-TE |
| Segment Routing | 9 | SRv6 encap, endpoint behaviors, L3VPN, TE policy, IS-IS/OSPFv3 extensions, uSID, OAM, SR-MPLS |
| Overlay Networks & EVPN | 5 | VXLANv6, VXLAN EVPN, GENEVE, EVPN IRB, EVPN multi-homing |
| IPv6 Multicast | 5 | MLDv2, PIM-SM, PIM-SSM, mVPN/NG-mVPN, mLDP |
| High Availability & Resiliency | 5 | BFD, VRRPv3, NSF/graceful restart, ECMP, IP-FRR/LFA |
| Link Security (MACsec) | 3 | 802.1AE encryption, MKA key agreement, SRv6/VXLAN interop |
| Modern Management APIs & Telemetry | 5 | NETCONF, RESTCONF, gNMI/gRPC streaming, OpenConfig, gRIBI |
| VPN & Tunneling | 4 | IPsec/IKEv2, GREv6, L2TPv3, LDP pseudowires |
Each test case includes:
rfcReference— the governing RFC(s) or IEEE standard (e.g."RFC 8754","IEEE 802.1AE")severity—MANDATORY,RECOMMENDED, orOPTIONALtags— searchable labels (e.g.["srv6", "segment-routing", "data-plane"])
Overview of all platforms with pass-rate statistics. Per-category breakdown shows cumulative PASS / FAIL / PARTIAL / N/A counts across all platforms.
- Add a platform — fill in Vendor, Model Name, and OS/Firmware Version. The form validates input client-side and server-side (Zod).
- Delete a platform — cascades and removes all associated test results and config snapshots.
- Compliance Matrix → — link to the per-platform result entry page.
The main data-entry screen. Displays all 77 test cases grouped by category. For each row:
- Status dropdown — select PASS / FAIL / PARTIAL / N/A (or leave unset).
- Notes / Evidence text field — free-text supporting notes.
- Results auto-save immediately on status change, and 800 ms after the last keystroke in the notes field. A "✓ Saved" indicator confirms successful writes.
- The header shows a live pass-rate summary.
The Export PDF button in the page header opens /platforms/[id]/print in a new tab. The print page is a standalone static HTML report (no nav, no form elements) that renders every test case with its full status and untruncated notes. Use the browser's Save as PDF option or the Save as PDF / Print button on the report page itself. The report includes a summary bar (pass rate, PASS / FAIL / PARTIAL / N/A counts) and is formatted with @media print styles for clean A4/Letter output.
Below the compliance matrix is the Configuration Snapshot panel, which links config file content to test cases:
- Upload — drag-and-drop or click to browse for a plain-text config file (
.cfg,.conf,.txt; max 5 MB). Multiple snapshots per platform are supported. - Select a snapshot — click any listed snapshot to load it in the viewer on the right.
- Find in Config — once a snapshot is loaded, each compliance row shows a Find button. Clicking it highlights all lines in the config that are relevant to that test case (e.g. clicking the OSPFv3 row highlights lines containing
ospfv3,ospf3,ipv6 ospf, etc.). The keyword sets for all test cases are defined insrc/lib/config.keywords.ts. - Search — free-text search within the loaded file; matches highlighted in blue alongside the keyword highlights.
- Matching only toggle — collapses the viewer to show only matched lines, filtering out irrelevant config.
- Delete — removes a snapshot from the database (does not affect compliance results).
Read-only view of all seeded test cases grouped by category, showing RFC references, severity levels, and tags alongside result counts.
The admin section provides a full management UI for test cases and categories. Access it via the Admin link in the navigation bar.
- List — all test cases grouped by category with severity badge, RFC reference, and tags. Edit or delete any row.
- New Test Case —
/admin/testcases/new— form with category autocomplete (datalist from existing categories), all fields, severity select, comma-separated tags. - Edit —
/admin/testcases/:id/edit— pre-populated form using the same component. - Delete — removes the test case. Results referencing it are restricted (Prisma
onDelete: Restrict) — delete associated results first.
- List — all categories with their test-case count.
- Rename — click Rename on any row, edit inline, press Enter or click Save. The rename is applied atomically to all test cases in that category via
updateMany.
Auth note: When
AUTH_MODE=none(the default), a yellow warning banner is displayed at the top of every admin page as a reminder that the section is publicly accessible. SetAUTH_MODE=cookieorAUTH_MODE=basicto restrict access.
Access control is configured via the AUTH_MODE environment variable. All three modes share the same AUTH_USER / AUTH_PASS credentials; AUTH_SECRET is only required for cookie mode.
AUTH_MODE |
Description | Extra deps |
|---|---|---|
none (default) |
Open access — suitable for trusted networks or local dev | — |
basic |
HTTP Basic Auth on every request — works with browsers and curl -u user:pass |
— |
cookie |
Login page at /login, signed 12-hour httpOnly session cookie |
jose |
| Variable | Required for | Description |
|---|---|---|
AUTH_MODE |
all | none | basic | cookie |
AUTH_USER |
basic, cookie |
Username |
AUTH_PASS |
basic, cookie |
Password |
AUTH_SECRET |
cookie |
JWT signing secret — minimum 32 random characters |
# .env
AUTH_MODE=cookie
AUTH_USER=admin
AUTH_PASS=hunter2
AUTH_SECRET=dev-secret-not-for-production-use-32charsEdit the environment block in docker-compose.yml:
AUTH_MODE: "cookie"
AUTH_USER: "admin"
AUTH_PASS: "changeme"
AUTH_SECRET: "replace-with-a-long-random-string-32-chars-min"Generate a strong secret:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"The nextauth stub is wired in src/middleware.ts. To activate it:
npm install next-auth@5- Add
User,Session,Accounttables toprisma/schema.prisma(see Auth.js Prisma adapter docs) - Create
src/auth.tswith yourNextAuth({...})config - Replace the stub comment in
src/middleware.tswithexport { auth as middleware } from '@/auth' - Set
AUTH_MODE=nextauth— the dispatch block in middleware will be bypassed by the Auth.js handler
# Run all tests once
npm test
# Watch mode (re-runs on file change)
npm run test:watch
# With coverage report
npm run test:coverageTests use a separate prisma/test.db database that is created and destroyed automatically by the global setup/teardown hooks. The main dev.db is never touched during tests.
Test coverage:
| File | What it covers |
|---|---|
src/__tests__/validation.test.ts |
Zod schema boundary tests — no DB required |
src/__tests__/platform.service.test.ts |
Platform CRUD, pagination |
src/__tests__/testcase.service.test.ts |
TestCase CRUD + upsert, tags serialization |
src/__tests__/result.service.test.ts |
Result upsert, matrix query, update |
src/__tests__/config.service.test.ts |
ConfigSnapshot CRUD |
src/__tests__/config.keywords.test.ts |
Keyword matching, 1-based line numbers — no DB |
src/__tests__/auth.test.ts |
signToken/verifyToken round-trip, wrong secret, tampered token, missing AUTH_SECRET |
src/__tests__/middleware.test.ts |
All three middleware modes (none, basic, cookie) — public path bypass, redirect, WWW-Authenticate |
src/__tests__/auth.routes.test.ts |
Login endpoint (correct/wrong creds, cookie set), logout endpoint (cookie cleared) |
| Script | Description |
|---|---|
npm run dev |
Start Next.js development server at http://localhost:3000 |
npm run build |
Production build (outputs Next.js standalone bundle) |
npm start |
Start the production Next.js server |
npm run lint |
Run ESLint across src/ |
npm test |
Run all Vitest tests once |
npm run test:watch |
Vitest in interactive watch mode |
npm run test:coverage |
Vitest with V8 coverage report |
npm run seed |
Seed/update all 77 compliance test cases from scripts/seed.ts |
npm run db:reset |
Delete dev.db, re-push schema, and re-seed (destructive) |
All endpoints accept and return application/json unless noted. List endpoints support ?page=N&limit=N query parameters (default: page 1, limit 50, max 200).
Base URL: local dev (
npm run dev) →http://localhost:3000; Docker/production →https://your.real.domain. The examples below usehttp://[::1]:3000for local dev. Substitute your domain andhttps://for the Docker stack.
| Method | Route | Description |
|---|---|---|
GET |
/api/platforms |
List platforms (paginated) |
POST |
/api/platforms |
Create a platform |
GET |
/api/platforms/:id |
Get platform + all results |
PUT |
/api/platforms/:id |
Update platform fields |
DELETE |
/api/platforms/:id |
Delete platform (cascades results + config snapshots) |
GET |
/api/platforms/:id/results |
Full compliance matrix for one platform |
POST /api/platforms body:
{
"vendor": "Arista",
"modelName": "7050X3-48YC12",
"osVersion": "EOS 4.30.2F"
}| Method | Route | Description |
|---|---|---|
GET |
/api/testcases |
List test cases (paginated) |
POST |
/api/testcases |
Create a test case |
GET |
/api/testcases/:id |
Get a test case |
PUT |
/api/testcases/:id |
Update a test case |
DELETE |
/api/testcases/:id |
Delete a test case |
GET |
/api/testcases/categories |
List categories with test-case counts |
PATCH |
/api/testcases/categories |
Rename a category (body: {oldName, newName}) |
POST /api/testcases body:
{
"category": "Segment Routing",
"name": "SRv6 Encapsulation (H.Encaps / H.Insert)",
"description": "Verify hardware-accelerated SRH imposition at line rate.",
"rfcReference": "RFC 8754",
"severity": "MANDATORY",
"tags": ["srv6", "segment-routing", "data-plane"]
}severity must be one of: MANDATORY | RECOMMENDED | OPTIONAL (defaults to MANDATORY if omitted).
tags is an array of strings (max 20 items, each max 50 chars). Defaults to [].
rfcReference is optional free text (max 500 chars).
| Method | Route | Description |
|---|---|---|
GET |
/api/results |
List results (paginated) |
POST |
/api/results |
Create or update a result (upsert) |
GET |
/api/results/:id |
Get a result |
PUT |
/api/results/:id |
Update status / detail / metadata |
DELETE |
/api/results/:id |
Delete a result |
POST /api/results body:
{
"platformId": "<cuid>",
"testCaseId": "<cuid>",
"status": "PASS",
"detail": "SRv6 H.Encaps verified in hardware at 400 Gbps line rate.",
"testedAt": "2026-04-22T13:00:00Z",
"testedBy": "lab-automation-v2",
"firmwareBuild": "EOS 4.32.1F-12345678"
}status must be one of: PASS | FAIL | PARTIAL | N/A
testedAt, testedBy, and firmwareBuild are optional. They let you record when the test was run, who or what ran it, and which exact firmware build was under test — useful for tracking regressions across firmware upgrades.
The POST endpoint is an upsert — posting the same (platformId, testCaseId) pair updates the existing record.
Scripted result ingestion example:
curl -6 -X POST http://[::1]:3000/api/results \
-H "Content-Type: application/json" \
-d '{
"platformId": "<id>",
"testCaseId": "<id>",
"status": "PASS",
"detail": "Automated test passed",
"testedAt": "2026-04-22T13:00:00Z",
"testedBy": "ci-pipeline",
"firmwareBuild": "17.9.3a"
}'| Method | Route | Description |
|---|---|---|
GET |
/api/platforms/:id/config |
List snapshots for a platform (metadata only — no content) |
POST |
/api/platforms/:id/config |
Upload a config file (multipart/form-data, field config) |
GET |
/api/platforms/:id/config/:snapshotId |
Get full snapshot including file content |
DELETE |
/api/platforms/:id/config/:snapshotId |
Delete a snapshot |
POST /api/platforms/:id/config — multipart/form-data, field name config:
curl -6 -X POST http://[::1]:3000/api/platforms/<id>/config \
-F "config=@/path/to/router.cfg"Constraints: plain text, max 5 MB. Content is stored verbatim in the SQLite database.
Only active when AUTH_MODE is basic or cookie. In none mode these endpoints still exist but always succeed.
| Method | Route | Description |
|---|---|---|
POST |
/api/auth/login |
Validate credentials; sets auth-session cookie on success |
POST |
/api/auth/logout |
Clears the session cookie |
POST /api/auth/login body:
{ "username": "admin", "password": "changeme" }Returns 200 on success with an httpOnly session cookie (12-hour TTL, HS256 JWT). Returns 401 on bad credentials.
| Method | Route | Description |
|---|---|---|
GET |
/platforms/:id/print |
Standalone HTML compliance report — open in browser and save as PDF |
This is a page route (not a JSON API). All test results and notes are rendered as static text; no form elements. Accepts ?autoprint=1 to trigger window.print() automatically on load.
Four models managed by Prisma. Schema lives in prisma/schema.prisma.
Platform
id String @id @default(cuid())
vendor String
modelName String
osVersion String
createdAt DateTime @default(now())
results TestResult[]
configSnapshots ConfigSnapshot[]
TestCase
id String @id @default(cuid())
category String
name String
description String
rfcReference String? -- governing RFC(s) / IEEE standard
severity String @default("MANDATORY") -- MANDATORY | RECOMMENDED | OPTIONAL
tags String @default("[]") -- JSON-encoded string[]
results TestResult[]
@@unique([category, name])
@@index([category])
@@index([severity])
TestResult
id String @id @default(cuid())
platformId String
testCaseId String
status String -- PASS | FAIL | PARTIAL | N/A
detail String?
testedAt DateTime? -- when the test was executed
testedBy String? -- engineer name or automation system
firmwareBuild String? -- exact image tested (e.g. "17.9.3a")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
platform Platform @relation(...) -- onDelete: Cascade
testCase TestCase @relation(...) -- onDelete: Restrict
@@unique([platformId, testCaseId])
@@index([platformId])
@@index([testCaseId])
ConfigSnapshot
id String @id @default(cuid())
platformId String
filename String
content String -- full plain-text file (max 5 MB)
uploadedAt DateTime @default(now())
platform Platform @relation(...) -- onDelete: Cascade
@@index([platformId])
Tags storage: tags is stored as a JSON string in SQLite (e.g. '["srv6","data-plane"]') and deserialized to string[] transparently by the service layer. The REST API always returns and accepts tags as a JSON array.
To modify the schema: edit prisma/schema.prisma, then run:
npx prisma db push
npx prisma generate6verifiche/
├── src/
│ ├── app/
│ │ ├── api/
│ │ │ ├── platforms/
│ │ │ │ ├── route.ts # GET /api/platforms, POST
│ │ │ │ └── [id]/
│ │ │ │ ├── route.ts # GET/PUT/DELETE /api/platforms/:id
│ │ │ │ ├── results/route.ts # GET /api/platforms/:id/results
│ │ │ │ └── config/
│ │ │ │ ├── route.ts # GET/POST /api/platforms/:id/config
│ │ │ │ └── [snapshotId]/route.ts # GET/DELETE snapshot
│ │ │ ├── testcases/
│ │ │ │ ├── route.ts # GET/POST /api/testcases
│ │ │ │ ├── [id]/route.ts # GET/PUT/DELETE /api/testcases/:id
│ │ │ │ └── categories/route.ts # GET category counts / PATCH rename
│ │ │ └── results/
│ │ │ ├── route.ts # GET/POST /api/results (upsert)
│ │ │ └── [id]/route.ts # GET/PUT/DELETE /api/results/:id
│ │ ├── platforms/
│ │ │ ├── page.tsx # Platform list + add form
│ │ │ └── [id]/
│ │ │ ├── page.tsx # Compliance matrix + config panel (77 tests)
│ │ │ └── print/route.ts # GET — PDF-ready static compliance report
│ │ ├── testcases/page.tsx # Test case browser
│ │ ├── admin/
│ │ │ ├── layout.tsx # Admin sub-nav + AUTH_MODE warning
│ │ │ ├── page.tsx # Redirects to /admin/testcases
│ │ │ ├── testcases/
│ │ │ │ ├── page.tsx # List all test cases (edit / delete)
│ │ │ │ ├── new/page.tsx # Create test case form
│ │ │ │ └── [id]/edit/page.tsx # Edit test case form
│ │ │ └── categories/page.tsx # List + inline rename categories
│ │ ├── login/page.tsx # Login page (AUTH_MODE=cookie only)
│ │ ├── page.tsx # Dashboard
│ │ ├── layout.tsx # Root layout with Nav
│ │ └── globals.css # Application-wide styles
│ ├── components/
│ │ ├── Nav.tsx # Sticky navigation bar (Dashboard / Platforms / Test Cases / Admin)
│ │ ├── PlatformForm.tsx # Add-platform client form
│ │ ├── ResultCell.tsx # Auto-saving result row (+ Find in Config)
│ │ ├── DeleteButton.tsx # Confirm-then-delete client button
│ │ ├── TestCaseForm.tsx # Create/edit test case (category datalist, tags)
│ │ ├── CategoryRenameRow.tsx # Inline category rename row
│ │ ├── LoginForm.tsx # Cookie-mode login form
│ │ ├── PlatformComplianceSection.tsx # Client wrapper — lifts config/test-case state
│ │ ├── ConfigUpload.tsx # Upload + manage config snapshots
│ │ └── ConfigViewer.tsx # Config file viewer with keyword highlights
│ ├── lib/
│ │ ├── prisma.ts # Singleton PrismaClient (Prisma 7 + adapter)
│ │ ├── auth.ts # signToken / verifyToken (jose / HS256)
│ │ ├── validation.ts # Zod schemas for all API inputs
│ │ ├── platform.service.ts # Platform CRUD
│ │ ├── testcase.service.ts # TestCase CRUD + tags + category helpers
│ │ ├── result.service.ts # Result CRUD + matrix query
│ │ ├── config.service.ts # ConfigSnapshot CRUD
│ │ └── config.keywords.ts # Keyword map + findMatchingLines()
│ ├── middleware.ts # AUTH_MODE dispatch (none / basic / cookie)
│ └── __tests__/
│ ├── globalSetup.ts # Create/destroy prisma/test.db
│ ├── setup.ts # Clear all tables before each test
│ ├── validation.test.ts
│ ├── platform.service.test.ts
│ ├── testcase.service.test.ts
│ ├── result.service.test.ts
│ ├── config.service.test.ts
│ ├── config.keywords.test.ts
│ ├── auth.test.ts # signToken/verifyToken unit tests
│ ├── middleware.test.ts # Middleware mode tests (none/basic/cookie)
│ └── auth.routes.test.ts # Login + logout API route tests
├── prisma/
│ └── schema.prisma
├── scripts/
│ └── seed.ts # 77 compliance test cases across 13 categories
├── traefik/
│ └── traefik.yml # Traefik v3 static config (entrypoints, ACME, Docker provider)
├── .env # DATABASE_URL (git-ignored)
├── Dockerfile # Multi-stage build (node:20-alpine)
├── docker-compose.yml # Traefik + app stack for IPv6-only deployment
├── .dockerignore
├── .github/workflows/ci.yml # lint → test → build CI pipeline
├── vitest.config.ts
├── next.config.ts # standalone output + CORS headers
└── IPv6-compliance-superset.md # Source of truth for test case definitions
Edit scripts/seed.ts, add entries to the TEST_CASES array with category, name, description, rfcReference, severity, and tags, then run:
npm run seedThe upsert logic means existing test results are never affected — only test case metadata is updated. Also add a matching entry to TEST_CASE_KEYWORDS in src/lib/config.keywords.ts — the key must match the name field exactly.
Edit src/lib/config.keywords.ts. Each key is a test case name (must match scripts/seed.ts exactly); the value is an array of case-insensitive keyword strings tested against each config line. Vendor-specific terms (e.g. crypto pki, set protocols ospf3) can be added alongside the vendor-agnostic defaults.
curl -6 -X POST http://[::1]:3000/api/testcases \
-H "Content-Type: application/json" \
-d '{
"category": "Segment Routing",
"name": "SRv6 FlexAlgo",
"description": "Verify Flexible Algorithm (RFC 9350) with SRv6 data plane.",
"rfcReference": "RFC 9350",
"severity": "OPTIONAL",
"tags": ["srv6", "flex-algo", "traffic-engineering"]
}'Change provider = "sqlite" to provider = "postgresql" in prisma/schema.prisma, update DATABASE_URL in .env, and run npx prisma db push. No application code changes required. The tags field (stored as a JSON string for SQLite compatibility) can be migrated to a native Json column type in PostgreSQL by changing the field type in the schema.
PDF report: Click the Export PDF button on any platform's compliance page (/platforms/[id]). This opens a standalone print page at /platforms/[id]/print with all test cases, statuses, and full untruncated notes rendered as static text. Use the browser's print dialog or the on-page Save as PDF / Print button.
JSON export: GET /api/platforms/:id/results returns the full compliance matrix as JSON — the platform record plus every test case with its result (null if untested). Suitable for piping into jq or feeding a reporting script:
curl -6 -s http://[::1]:3000/api/platforms/<id>/results | \
jq '.matrix[] | select(.result.status == "FAIL") | .testCase.name'Each result record accepts testedAt, testedBy, and firmwareBuild fields. Automate ingestion from a CI pipeline and use the detail field to record test tool output. To track history across firmware versions, query results filtered by firmwareBuild or script against the API to compare two platform records representing the same hardware at different firmware versions.