--- phase: 16-api-migration plan: 04 subsystem: n8n-batch-ui tags: [api-migration, graphql, batch-operations, normalizer] dependency_graph: requires: - phase: 15 plan: 02 artifact: "GraphQL Response Normalizer pattern" provides: - artifact: "n8n-batch-ui.json with Unraid GraphQL API" consumers: ["Main workflow Batch UI callers"] affects: - "Batch container selection flow" - "All 5 batch action paths (mode, toggle, exec, nav, clear)" tech_stack: added: [] patterns: - "GraphQL API queries with normalizer transformation" - "5 identical normalizer nodes (one per query path)" - "Docker API contract compatibility layer" key_files: created: [] modified: - path: "n8n-batch-ui.json" lines_changed: 354 description: "Migrated all 5 container queries from Docker socket proxy to Unraid GraphQL API with normalizer nodes" decisions: - summary: "5 identical normalizer nodes instead of shared utility node" rationale: "n8n sub-workflows cannot share nodes across independent paths - each path needs its own node instance" alternatives: ["Single normalizer with complex routing (rejected: architectural constraint)"] - summary: "15-second timeout for GraphQL queries" rationale: "myunraid.net cloud relay adds 200-500ms latency, increased from 5s Docker socket proxy timeout for safety margin" alternatives: ["Keep 5s timeout (rejected: insufficient for cloud relay)", "30s timeout (rejected: too long for UI interaction)"] - summary: "Keep full PrefixedID in normalizer output" rationale: "Container ID Registry (Phase 15) handles translation downstream, normalizer preserves complete Unraid ID" alternatives: ["Truncate to 12-char in normalizer (rejected: breaks registry lookup)"] metrics: duration_minutes: 2 completed_date: "2026-02-09" tasks_completed: 1 files_modified: 1 nodes_added: 5 nodes_modified: 5 connections_rewired: 15 --- # Phase 16 Plan 04: Batch UI GraphQL Migration Summary **One-liner:** Migrated n8n-batch-ui.json from Docker socket proxy to Unraid GraphQL API with 5 normalizer nodes preserving zero-change contract for downstream consumers ## What Was Delivered ### Core Implementation **n8n-batch-ui.json transformation (nodes: 17 → 22):** All 5 container listing queries migrated from Docker socket proxy to Unraid GraphQL API: 1. **Fetch Containers For Mode** - Initial batch selection entry 2. **Fetch Containers For Update** - After toggling container selection 3. **Fetch Containers For Exec** - Before batch action execution 4. **Fetch Containers For Nav** - Page navigation 5. **Fetch Containers For Clear** - After clearing selection **For each query path:** ``` [upstream] → HTTP Request (GraphQL) → Normalizer (Code) → [existing downstream] ``` **HTTP Request nodes transformed:** - Method: `GET` → `POST` - URL: `http://docker-socket-proxy:2375/containers/json?all=true` → `={{ $env.UNRAID_HOST }}/graphql` - Query: `query { docker { containers { id names state image } } }` - Headers: `Content-Type: application/json`, `x-api-key: ={{ $env.UNRAID_API_KEY }}` - Timeout: 5000ms → 15000ms (cloud relay safety margin) - Error handling: `continueRegularOutput` **GraphQL Response Normalizer (5 identical nodes):** - Input: `{data: {docker: {containers: [{id, names, state, image}]}}}` - Output: `[{Id, Names, State, Status, Image, _unraidId}]` (Docker API contract) - State mapping: `RUNNING → running`, `STOPPED → exited`, `PAUSED → paused` - n8n multi-item output format: `[{json: container}, ...]` **Downstream Code nodes (UNCHANGED - verified):** - Build Batch Keyboard (bitmap encoding, pagination, keyboard building) - Handle Toggle (bitmap toggle logic) - Handle Exec (bitmap to names resolution, confirmation routing) - Rebuild Keyboard After Toggle (bitmap decoding, keyboard rebuild) - Rebuild Keyboard For Nav (page navigation, keyboard rebuild) - Rebuild Keyboard After Clear (reset to empty bitmap) - Handle Cancel (return to container list) All bitmap encoding, container sorting, pagination, and keyboard building logic preserved byte-for-byte. ### Zero-Change Migration Pattern **Docker API contract fields preserved:** - `Id` - Full Unraid PrefixedID (Container ID Registry handles translation) - `Names` - Array with `/` prefix (e.g., `["/plex"]`) - `State` - Lowercase state (`running`, `exited`, `paused`) - `Status` - Same as State (Docker API convention) - `Image` - Empty string (not queried, not used by batch UI) **Why this works:** - All downstream Code nodes reference `Names[0]`, `State`, `Id.substring(0, 12)` - Normalizer ensures these fields exist in the exact format expected - Bitmap encoding uses array indices, not IDs (migration transparent) - Container sorting uses state and name (both preserved) ## Deviations from Plan None - plan executed exactly as written. ## Authentication Gates None encountered. ## Testing & Verification **Automated verification (all passed):** 1. ✓ Zero HTTP Request nodes contain "docker-socket-proxy" 2. ✓ All 5 HTTP Request nodes use POST to `$env.UNRAID_HOST/graphql` 3. ✓ 5 GraphQL Response Normalizer Code nodes exist (one per query path) 4. ✓ All downstream Code nodes byte-for-byte identical to pre-migration 5. ✓ Node count: 22 (17 original + 5 normalizers) 6. ✓ All connection chains valid (15 connections verified) 7. ✓ Pushed to n8n successfully (HTTP 200, workflow ID `ZJhnGzJT26UUmW45`) **Connection chain validation:** - Route Batch UI Action → Fetch Containers For Mode → Normalizer → Build Batch Keyboard ✓ - Needs Keyboard Update? → Fetch Containers For Update → Normalizer → Rebuild Keyboard ✓ - Route Batch UI Action → Fetch Containers For Exec → Normalizer → Handle Exec ✓ - Handle Nav → Fetch Containers For Nav → Normalizer → Rebuild Keyboard For Nav ✓ - Handle Clear → Fetch Containers For Clear → Normalizer → Rebuild Keyboard After Clear ✓ **Manual testing required:** - Open Telegram bot, start batch selection (`/batch` command path) - Verify container list displays with correct names and states - Toggle container selection, verify checkmarks update correctly - Navigate between pages, verify pagination works - Execute batch start action, verify correct containers are started - Execute batch stop action, verify confirmation prompt appears - Clear selection, verify UI resets to empty state ## Impact Assessment **User-facing changes:** - None - UI and behavior identical to pre-migration **System changes:** - Removed dependency on docker-socket-proxy for batch container listing - Added dependency on Unraid GraphQL API + myunraid.net cloud relay - Increased query timeout from 5s to 15s (cloud relay latency) - Added 5 normalizer nodes (increased workflow complexity slightly) **Performance impact:** - Query latency: +200-500ms (cloud relay overhead vs local Docker socket) - User-perceivable: Minimal (batch selection already async) - Timeout safety: 15s provides 30x safety margin over typical 500ms latency **Risk mitigation:** - GraphQL error handling: normalizer throws on errors → captured by n8n error handling - Invalid response structure: explicit validation with descriptive errors - State mapping: comprehensive (RUNNING, STOPPED, PAUSED) + fallback to lowercase ## Known Limitations **Current state:** - Image field empty (not queried) - batch UI doesn't use it, no impact - No retry logic on GraphQL failures (relies on n8n default retry) - Cloud relay adds latency (200-500ms) - acceptable for batch operations **Future improvements:** - Could add retry logic with exponential backoff for cloud relay transient failures - Could query image field if future batch features need it - Could implement local caching if latency becomes problematic (unlikely for batch ops) ## Next Steps **Immediate:** - Phase 16 Plan 05: Migrate remaining workflows (Container Status, Confirmation, etc.) **Follow-up:** - Manual testing of batch selection end-to-end - Monitor cloud relay latency in production - Consider removing docker-socket-proxy container once all migrations complete ## Self-Check: PASSED **Files verified:** - ✓ FOUND: n8n-batch-ui.json (modified, 22 nodes) - ✓ FOUND: n8n-batch-ui.json pushed to n8n (HTTP 200) **Commits verified:** - ✓ FOUND: 73a01b6 (feat(16-04): migrate Batch UI to Unraid GraphQL API) **Claims verified:** - ✓ 5 GraphQL Response Normalizer nodes exist in workflow - ✓ All 5 HTTP Request nodes use GraphQL (verified in workflow JSON) - ✓ Zero docker-socket-proxy references (verified in workflow JSON) - ✓ Downstream Code nodes unchanged (verified byte-for-byte during transformation) All summary claims verified against actual implementation.