diff --git a/n8n-batch-ui.json b/n8n-batch-ui.json index 02fd7d5..75b7bc9 100644 --- a/n8n-batch-ui.json +++ b/n8n-batch-ui.json @@ -218,9 +218,32 @@ }, { "parameters": { - "url": "http://docker-socket-proxy:2375/containers/json?all=true", + "method": "POST", + "url": "={{ $env.UNRAID_HOST }}/graphql", + "authentication": "none", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "{\"query\": \"query { docker { containers { id names state image } } }\"}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "x-api-key", + "value": "={{ $env.UNRAID_API_KEY }}" + } + ] + }, "options": { - "timeout": 5000 + "timeout": 15000, + "response": { + "response": { + "errorHandling": "continueRegularOutput" + } + } } }, "id": "http-fetch-containers-mode", @@ -291,9 +314,32 @@ }, { "parameters": { - "url": "http://docker-socket-proxy:2375/containers/json?all=true", + "method": "POST", + "url": "={{ $env.UNRAID_HOST }}/graphql", + "authentication": "none", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "{\"query\": \"query { docker { containers { id names state image } } }\"}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "x-api-key", + "value": "={{ $env.UNRAID_API_KEY }}" + } + ] + }, "options": { - "timeout": 5000 + "timeout": 15000, + "response": { + "response": { + "errorHandling": "continueRegularOutput" + } + } } }, "id": "http-fetch-containers-toggle", @@ -320,9 +366,32 @@ }, { "parameters": { - "url": "http://docker-socket-proxy:2375/containers/json?all=true", + "method": "POST", + "url": "={{ $env.UNRAID_HOST }}/graphql", + "authentication": "none", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "{\"query\": \"query { docker { containers { id names state image } } }\"}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "x-api-key", + "value": "={{ $env.UNRAID_API_KEY }}" + } + ] + }, "options": { - "timeout": 5000 + "timeout": 15000, + "response": { + "response": { + "errorHandling": "continueRegularOutput" + } + } } }, "id": "http-fetch-containers-exec", @@ -362,9 +431,32 @@ }, { "parameters": { - "url": "http://docker-socket-proxy:2375/containers/json?all=true", + "method": "POST", + "url": "={{ $env.UNRAID_HOST }}/graphql", + "authentication": "none", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "{\"query\": \"query { docker { containers { id names state image } } }\"}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "x-api-key", + "value": "={{ $env.UNRAID_API_KEY }}" + } + ] + }, "options": { - "timeout": 5000 + "timeout": 15000, + "response": { + "response": { + "errorHandling": "continueRegularOutput" + } + } } }, "id": "http-fetch-containers-nav", @@ -404,9 +496,32 @@ }, { "parameters": { - "url": "http://docker-socket-proxy:2375/containers/json?all=true", + "method": "POST", + "url": "={{ $env.UNRAID_HOST }}/graphql", + "authentication": "none", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "{\"query\": \"query { docker { containers { id names state image } } }\"}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "x-api-key", + "value": "={{ $env.UNRAID_API_KEY }}" + } + ] + }, "options": { - "timeout": 5000 + "timeout": 15000, + "response": { + "response": { + "errorHandling": "continueRegularOutput" + } + } } }, "id": "http-fetch-containers-clear", @@ -443,6 +558,71 @@ 680, 600 ] + }, + { + "parameters": { + "jsCode": "// GraphQL Response Normalizer - Transform Unraid GraphQL to Docker API contract\n// Input: $input.item.json = raw GraphQL response\n// Output: Array of normalized containers\n\nconst response = $input.item.json;\n\n// Validation: Check for GraphQL errors\nif (response.errors && response.errors.length > 0) {\n const messages = response.errors.map(e => e.message).join('; ');\n throw new Error(`GraphQL error: ${messages}`);\n}\n\n// Validation: Check response structure\nif (!response.data?.docker?.containers) {\n throw new Error('Invalid GraphQL response structure: missing data.docker.containers');\n}\n\n// State mapping: Unraid UPPERCASE -> Docker lowercase\nconst stateMap = {\n 'RUNNING': 'running',\n 'STOPPED': 'exited', // Docker convention: stopped = exited\n 'PAUSED': 'paused'\n};\n\nfunction normalizeState(unraidState) {\n return stateMap[unraidState] || unraidState.toLowerCase();\n}\n\n// Transform each container\nconst containers = response.data.docker.containers;\nconst normalized = containers.map(container => {\n const dockerState = normalizeState(container.state);\n \n return {\n // Core fields matching Docker API contract\n Id: container.id, // Keep full PrefixedID (registry handles translation)\n Names: container.names, // Already has '/' prefix (Phase 14 verified)\n State: dockerState, // Normalized lowercase state\n Status: dockerState, // Docker has separate Status field\n Image: '', // Not available in basic query\n \n // Debug field: preserve original Unraid ID\n _unraidId: container.id\n };\n});\n\n// Return as array of items (n8n multi-item output format)\nreturn normalized.map(container => ({ json: container }));" + }, + "id": "code-normalizer-mode", + "name": "Normalize GraphQL Response (Mode)", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 790, + 100 + ] + }, + { + "parameters": { + "jsCode": "// GraphQL Response Normalizer - Transform Unraid GraphQL to Docker API contract\n// Input: $input.item.json = raw GraphQL response\n// Output: Array of normalized containers\n\nconst response = $input.item.json;\n\n// Validation: Check for GraphQL errors\nif (response.errors && response.errors.length > 0) {\n const messages = response.errors.map(e => e.message).join('; ');\n throw new Error(`GraphQL error: ${messages}`);\n}\n\n// Validation: Check response structure\nif (!response.data?.docker?.containers) {\n throw new Error('Invalid GraphQL response structure: missing data.docker.containers');\n}\n\n// State mapping: Unraid UPPERCASE -> Docker lowercase\nconst stateMap = {\n 'RUNNING': 'running',\n 'STOPPED': 'exited', // Docker convention: stopped = exited\n 'PAUSED': 'paused'\n};\n\nfunction normalizeState(unraidState) {\n return stateMap[unraidState] || unraidState.toLowerCase();\n}\n\n// Transform each container\nconst containers = response.data.docker.containers;\nconst normalized = containers.map(container => {\n const dockerState = normalizeState(container.state);\n \n return {\n // Core fields matching Docker API contract\n Id: container.id, // Keep full PrefixedID (registry handles translation)\n Names: container.names, // Already has '/' prefix (Phase 14 verified)\n State: dockerState, // Normalized lowercase state\n Status: dockerState, // Docker has separate Status field\n Image: '', // Not available in basic query\n \n // Debug field: preserve original Unraid ID\n _unraidId: container.id\n };\n});\n\n// Return as array of items (n8n multi-item output format)\nreturn normalized.map(container => ({ json: container }));" + }, + "id": "code-normalizer-toggle", + "name": "Normalize GraphQL Response (Toggle)", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1230, + 100 + ] + }, + { + "parameters": { + "jsCode": "// GraphQL Response Normalizer - Transform Unraid GraphQL to Docker API contract\n// Input: $input.item.json = raw GraphQL response\n// Output: Array of normalized containers\n\nconst response = $input.item.json;\n\n// Validation: Check for GraphQL errors\nif (response.errors && response.errors.length > 0) {\n const messages = response.errors.map(e => e.message).join('; ');\n throw new Error(`GraphQL error: ${messages}`);\n}\n\n// Validation: Check response structure\nif (!response.data?.docker?.containers) {\n throw new Error('Invalid GraphQL response structure: missing data.docker.containers');\n}\n\n// State mapping: Unraid UPPERCASE -> Docker lowercase\nconst stateMap = {\n 'RUNNING': 'running',\n 'STOPPED': 'exited', // Docker convention: stopped = exited\n 'PAUSED': 'paused'\n};\n\nfunction normalizeState(unraidState) {\n return stateMap[unraidState] || unraidState.toLowerCase();\n}\n\n// Transform each container\nconst containers = response.data.docker.containers;\nconst normalized = containers.map(container => {\n const dockerState = normalizeState(container.state);\n \n return {\n // Core fields matching Docker API contract\n Id: container.id, // Keep full PrefixedID (registry handles translation)\n Names: container.names, // Already has '/' prefix (Phase 14 verified)\n State: dockerState, // Normalized lowercase state\n Status: dockerState, // Docker has separate Status field\n Image: '', // Not available in basic query\n \n // Debug field: preserve original Unraid ID\n _unraidId: container.id\n };\n});\n\n// Return as array of items (n8n multi-item output format)\nreturn normalized.map(container => ({ json: container }));" + }, + "id": "code-normalizer-exec", + "name": "Normalize GraphQL Response (Exec)", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 790, + 400 + ] + }, + { + "parameters": { + "jsCode": "// GraphQL Response Normalizer - Transform Unraid GraphQL to Docker API contract\n// Input: $input.item.json = raw GraphQL response\n// Output: Array of normalized containers\n\nconst response = $input.item.json;\n\n// Validation: Check for GraphQL errors\nif (response.errors && response.errors.length > 0) {\n const messages = response.errors.map(e => e.message).join('; ');\n throw new Error(`GraphQL error: ${messages}`);\n}\n\n// Validation: Check response structure\nif (!response.data?.docker?.containers) {\n throw new Error('Invalid GraphQL response structure: missing data.docker.containers');\n}\n\n// State mapping: Unraid UPPERCASE -> Docker lowercase\nconst stateMap = {\n 'RUNNING': 'running',\n 'STOPPED': 'exited', // Docker convention: stopped = exited\n 'PAUSED': 'paused'\n};\n\nfunction normalizeState(unraidState) {\n return stateMap[unraidState] || unraidState.toLowerCase();\n}\n\n// Transform each container\nconst containers = response.data.docker.containers;\nconst normalized = containers.map(container => {\n const dockerState = normalizeState(container.state);\n \n return {\n // Core fields matching Docker API contract\n Id: container.id, // Keep full PrefixedID (registry handles translation)\n Names: container.names, // Already has '/' prefix (Phase 14 verified)\n State: dockerState, // Normalized lowercase state\n Status: dockerState, // Docker has separate Status field\n Image: '', // Not available in basic query\n \n // Debug field: preserve original Unraid ID\n _unraidId: container.id\n };\n});\n\n// Return as array of items (n8n multi-item output format)\nreturn normalized.map(container => ({ json: container }));" + }, + "id": "code-normalizer-nav", + "name": "Normalize GraphQL Response (Nav)", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1010, + 300 + ] + }, + { + "parameters": { + "jsCode": "// GraphQL Response Normalizer - Transform Unraid GraphQL to Docker API contract\n// Input: $input.item.json = raw GraphQL response\n// Output: Array of normalized containers\n\nconst response = $input.item.json;\n\n// Validation: Check for GraphQL errors\nif (response.errors && response.errors.length > 0) {\n const messages = response.errors.map(e => e.message).join('; ');\n throw new Error(`GraphQL error: ${messages}`);\n}\n\n// Validation: Check response structure\nif (!response.data?.docker?.containers) {\n throw new Error('Invalid GraphQL response structure: missing data.docker.containers');\n}\n\n// State mapping: Unraid UPPERCASE -> Docker lowercase\nconst stateMap = {\n 'RUNNING': 'running',\n 'STOPPED': 'exited', // Docker convention: stopped = exited\n 'PAUSED': 'paused'\n};\n\nfunction normalizeState(unraidState) {\n return stateMap[unraidState] || unraidState.toLowerCase();\n}\n\n// Transform each container\nconst containers = response.data.docker.containers;\nconst normalized = containers.map(container => {\n const dockerState = normalizeState(container.state);\n \n return {\n // Core fields matching Docker API contract\n Id: container.id, // Keep full PrefixedID (registry handles translation)\n Names: container.names, // Already has '/' prefix (Phase 14 verified)\n State: dockerState, // Normalized lowercase state\n Status: dockerState, // Docker has separate Status field\n Image: '', // Not available in basic query\n \n // Debug field: preserve original Unraid ID\n _unraidId: container.id\n };\n});\n\n// Return as array of items (n8n multi-item output format)\nreturn normalized.map(container => ({ json: container }));" + }, + "id": "code-normalizer-clear", + "name": "Normalize GraphQL Response (Clear)", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1010, + 500 + ] } ], "connections": { @@ -601,10 +781,173 @@ } ] ] + }, + "http-fetch-containers-mode": { + "main": [ + [ + { + "node": "Normalize GraphQL Response (Mode)", + "type": "main", + "index": 0 + } + ] + ] + }, + "code-normalizer-mode": { + "main": [ + [ + { + "node": "Build Batch Keyboard", + "type": "main", + "index": 0 + } + ] + ] + }, + "http-fetch-containers-toggle": { + "main": [ + [ + { + "node": "Normalize GraphQL Response (Toggle)", + "type": "main", + "index": 0 + } + ] + ] + }, + "code-normalizer-toggle": { + "main": [ + [ + { + "node": "Rebuild Keyboard After Toggle", + "type": "main", + "index": 0 + } + ] + ] + }, + "http-fetch-containers-exec": { + "main": [ + [ + { + "node": "Normalize GraphQL Response (Exec)", + "type": "main", + "index": 0 + } + ] + ] + }, + "code-normalizer-exec": { + "main": [ + [ + { + "node": "Handle Exec", + "type": "main", + "index": 0 + } + ] + ] + }, + "http-fetch-containers-nav": { + "main": [ + [ + { + "node": "Normalize GraphQL Response (Nav)", + "type": "main", + "index": 0 + } + ] + ] + }, + "code-normalizer-nav": { + "main": [ + [ + { + "node": "Rebuild Keyboard For Nav", + "type": "main", + "index": 0 + } + ] + ] + }, + "http-fetch-containers-clear": { + "main": [ + [ + { + "node": "Normalize GraphQL Response (Clear)", + "type": "main", + "index": 0 + } + ] + ] + }, + "code-normalizer-clear": { + "main": [ + [ + { + "node": "Rebuild Keyboard After Clear", + "type": "main", + "index": 0 + } + ] + ] + }, + "switch-route-batch-action": { + "main": [ + [ + { + "node": "Fetch Containers For Mode", + "type": "main", + "index": 0 + } + ], + [], + [], + [ + { + "node": "Fetch Containers For Exec", + "type": "main", + "index": 0 + } + ] + ] + }, + "if-needs-keyboard-update": { + "main": [ + [ + { + "node": "Fetch Containers For Update", + "type": "main", + "index": 0 + } + ] + ] + }, + "code-handle-nav": { + "main": [ + [ + { + "node": "Fetch Containers For Nav", + "type": "main", + "index": 0 + } + ] + ] + }, + "code-handle-clear": { + "main": [ + [ + { + "node": "Fetch Containers For Clear", + "type": "main", + "index": 0 + } + ] + ] } }, "settings": { "executionOrder": "v1", "callerPolicy": "any" } -} +} \ No newline at end of file