diff --git a/n8n-workflow.json b/n8n-workflow.json index 916bee0..3002acf 100644 --- a/n8n-workflow.json +++ b/n8n-workflow.json @@ -1802,9 +1802,34 @@ }, { "parameters": { - "method": "GET", - "url": "=http://docker-socket-proxy:2375/containers/json?all=true", - "options": {} + "method": "POST", + "url": "={{ $env.UNRAID_HOST }}/graphql", + "authentication": "none", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "x-api-key", + "value": "={{ $env.UNRAID_API_KEY }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ {\"query\": \"query { docker { containers { id names state image } } }\"} }}", + "options": { + "timeout": 15000, + "response": { + "response": { + "errorRedirection": "continue", + "fullResponse": false + } + } + } }, "id": "http-get-container-for-action", "name": "Get Container For Action", @@ -1918,9 +1943,34 @@ }, { "parameters": { - "method": "GET", - "url": "=http://docker-socket-proxy:2375/containers/json?all=true", - "options": {} + "method": "POST", + "url": "={{ $env.UNRAID_HOST }}/graphql", + "authentication": "none", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "x-api-key", + "value": "={{ $env.UNRAID_API_KEY }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ {\"query\": \"query { docker { containers { id names state image } } }\"} }}", + "options": { + "timeout": 15000, + "response": { + "response": { + "errorRedirection": "continue", + "fullResponse": false + } + } + } }, "id": "http-get-container-for-cancel", "name": "Get Container For Cancel", @@ -2683,8 +2733,34 @@ }, { "parameters": { - "url": "http://docker-socket-proxy:2375/containers/json?all=false", - "options": {} + "method": "POST", + "url": "={{ $env.UNRAID_HOST }}/graphql", + "authentication": "none", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "x-api-key", + "value": "={{ $env.UNRAID_API_KEY }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ {\"query\": \"query { docker { containers { id names state image imageId } } }\"} }}", + "options": { + "timeout": 15000, + "response": { + "response": { + "errorRedirection": "continue", + "fullResponse": false + } + } + } }, "id": "http-get-all-containers-update-all", "name": "Get All Containers For Update All", @@ -2974,8 +3050,34 @@ }, { "parameters": { - "url": "http://docker-socket-proxy:2375/containers/json?all=false", - "options": {} + "method": "POST", + "url": "={{ $env.UNRAID_HOST }}/graphql", + "authentication": "none", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "x-api-key", + "value": "={{ $env.UNRAID_API_KEY }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ {\"query\": \"query { docker { containers { id names state image imageId } } }\"} }}", + "options": { + "timeout": 15000, + "response": { + "response": { + "errorRedirection": "continue", + "fullResponse": false + } + } + } }, "id": "http-fetch-containers-update-all-exec", "name": "Fetch Containers For Update All Exec", @@ -3080,9 +3182,34 @@ }, { "parameters": { - "method": "GET", - "url": "=http://docker-socket-proxy:2375/containers/json?all=true", - "options": {} + "method": "POST", + "url": "={{ $env.UNRAID_HOST }}/graphql", + "authentication": "none", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "x-api-key", + "value": "={{ $env.UNRAID_API_KEY }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ {\"query\": \"query { docker { containers { id names state image } } }\"} }}", + "options": { + "timeout": 15000, + "response": { + "response": { + "errorRedirection": "continue", + "fullResponse": false + } + } + } }, "id": "http-get-container-callback", "name": "Get Container For Callback Update", @@ -4751,9 +4878,33 @@ }, { "parameters": { - "url": "http://docker-socket-proxy:2375/containers/json?all=true", + "method": "POST", + "url": "={{ $env.UNRAID_HOST }}/graphql", + "authentication": "none", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "x-api-key", + "value": "={{ $env.UNRAID_API_KEY }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ {\"query\": \"query { docker { containers { id names state image } } }\"} }}", "options": { - "timeout": 5000 + "timeout": 15000, + "response": { + "response": { + "errorRedirection": "continue", + "fullResponse": false + } + } } }, "id": "http-fetch-containers-bitmap-stop", @@ -4984,6 +5135,162 @@ 1000, 2400 ] + }, + { + "parameters": { + "jsCode": "// GraphQL Response Normalizer - Transform Unraid API to Docker API contract\nconst response = $input.item.json;\n\n// Validate GraphQL response\nif (response.errors) {\n throw new Error(`GraphQL Error: ${response.errors[0].message}`);\n}\n\nif (!response.data?.docker?.containers) {\n throw new Error('Invalid GraphQL response structure');\n}\n\nconst containers = response.data.docker.containers;\n\n// Transform to Docker API format\nconst normalized = containers.map(container => {\n // State mapping: RUNNING\u2192running, STOPPED\u2192exited, PAUSED\u2192paused\n const stateLower = container.state ? container.state.toLowerCase() : 'unknown';\n \n return {\n Id: container.id, // Full 129-char PrefixedID\n Names: container.names || [container.name ? `/${container.name}` : '/unknown'],\n State: stateLower === 'stopped' ? 'exited' : stateLower,\n Status: container.status || stateLower,\n Image: container.image || '',\n ImageId: container.imageId || ''\n };\n});\n\nreturn normalized.map(container => ({ json: container }));\n" + }, + "id": "normalize-graphql-action", + "name": "Normalize GraphQL Response (Action)", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 2300, + 1100 + ] + }, + { + "parameters": { + "jsCode": "// Update Container ID Registry - Store name\u2192PrefixedID mappings\nconst containers = $input.all();\nconst staticData = $getWorkflowStaticData('global');\n\n// Parse existing registry\nconst registry = JSON.parse(staticData._containerIdRegistry || '{}');\n\n// Update registry with current containers\ncontainers.forEach(item => {\n const container = item.json;\n const name = container.Names?.[0]?.substring(1) || 'unknown'; // Remove leading /\n \n registry[name] = {\n name: name,\n unraidId: container.Id, // Full PrefixedID\n prefixedId: container.Id, // Alias for consistency\n lastSeen: Date.now()\n };\n});\n\n// Write back to static data (top-level assignment for persistence)\nstaticData._containerIdRegistry = JSON.stringify(registry);\nstaticData._containerRegistryLastUpdate = Date.now();\n\n// Pass through all container data\nreturn $input.all();\n" + }, + "id": "registry-update-action", + "name": "Update Container Registry (Action)", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 2600, + 1100 + ] + }, + { + "parameters": { + "jsCode": "// GraphQL Response Normalizer - Transform Unraid API to Docker API contract\nconst response = $input.item.json;\n\n// Validate GraphQL response\nif (response.errors) {\n throw new Error(`GraphQL Error: ${response.errors[0].message}`);\n}\n\nif (!response.data?.docker?.containers) {\n throw new Error('Invalid GraphQL response structure');\n}\n\nconst containers = response.data.docker.containers;\n\n// Transform to Docker API format\nconst normalized = containers.map(container => {\n // State mapping: RUNNING\u2192running, STOPPED\u2192exited, PAUSED\u2192paused\n const stateLower = container.state ? container.state.toLowerCase() : 'unknown';\n \n return {\n Id: container.id, // Full 129-char PrefixedID\n Names: container.names || [container.name ? `/${container.name}` : '/unknown'],\n State: stateLower === 'stopped' ? 'exited' : stateLower,\n Status: container.status || stateLower,\n Image: container.image || '',\n ImageId: container.imageId || ''\n };\n});\n\nreturn normalized.map(container => ({ json: container }));\n" + }, + "id": "normalize-graphql-cancel", + "name": "Normalize GraphQL Response (Cancel)", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 2080, + 1800 + ] + }, + { + "parameters": { + "jsCode": "// Update Container ID Registry - Store name\u2192PrefixedID mappings\nconst containers = $input.all();\nconst staticData = $getWorkflowStaticData('global');\n\n// Parse existing registry\nconst registry = JSON.parse(staticData._containerIdRegistry || '{}');\n\n// Update registry with current containers\ncontainers.forEach(item => {\n const container = item.json;\n const name = container.Names?.[0]?.substring(1) || 'unknown'; // Remove leading /\n \n registry[name] = {\n name: name,\n unraidId: container.Id, // Full PrefixedID\n prefixedId: container.Id, // Alias for consistency\n lastSeen: Date.now()\n };\n});\n\n// Write back to static data (top-level assignment for persistence)\nstaticData._containerIdRegistry = JSON.stringify(registry);\nstaticData._containerRegistryLastUpdate = Date.now();\n\n// Pass through all container data\nreturn $input.all();\n" + }, + "id": "registry-update-cancel", + "name": "Update Container Registry (Cancel)", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 2380, + 1800 + ] + }, + { + "parameters": { + "jsCode": "// GraphQL Response Normalizer - Transform Unraid API to Docker API contract\nconst response = $input.item.json;\n\n// Validate GraphQL response\nif (response.errors) {\n throw new Error(`GraphQL Error: ${response.errors[0].message}`);\n}\n\nif (!response.data?.docker?.containers) {\n throw new Error('Invalid GraphQL response structure');\n}\n\nconst containers = response.data.docker.containers;\n\n// Transform to Docker API format\nconst normalized = containers.map(container => {\n // State mapping: RUNNING\u2192running, STOPPED\u2192exited, PAUSED\u2192paused\n const stateLower = container.state ? container.state.toLowerCase() : 'unknown';\n \n return {\n Id: container.id, // Full 129-char PrefixedID\n Names: container.names || [container.name ? `/${container.name}` : '/unknown'],\n State: stateLower === 'stopped' ? 'exited' : stateLower,\n Status: container.status || stateLower,\n Image: container.image || '',\n ImageId: container.imageId || ''\n };\n});\n\nreturn normalized.map(container => ({ json: container }));\n" + }, + "id": "normalize-graphql-update-all", + "name": "Normalize GraphQL Response (Update All)", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1500, + 2200 + ] + }, + { + "parameters": { + "jsCode": "// Update Container ID Registry - Store name\u2192PrefixedID mappings\nconst containers = $input.all();\nconst staticData = $getWorkflowStaticData('global');\n\n// Parse existing registry\nconst registry = JSON.parse(staticData._containerIdRegistry || '{}');\n\n// Update registry with current containers\ncontainers.forEach(item => {\n const container = item.json;\n const name = container.Names?.[0]?.substring(1) || 'unknown'; // Remove leading /\n \n registry[name] = {\n name: name,\n unraidId: container.Id, // Full PrefixedID\n prefixedId: container.Id, // Alias for consistency\n lastSeen: Date.now()\n };\n});\n\n// Write back to static data (top-level assignment for persistence)\nstaticData._containerIdRegistry = JSON.stringify(registry);\nstaticData._containerRegistryLastUpdate = Date.now();\n\n// Pass through all container data\nreturn $input.all();\n" + }, + "id": "registry-update-update-all", + "name": "Update Container Registry (Update All)", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1800, + 2200 + ] + }, + { + "parameters": { + "jsCode": "// GraphQL Response Normalizer - Transform Unraid API to Docker API contract\nconst response = $input.item.json;\n\n// Validate GraphQL response\nif (response.errors) {\n throw new Error(`GraphQL Error: ${response.errors[0].message}`);\n}\n\nif (!response.data?.docker?.containers) {\n throw new Error('Invalid GraphQL response structure');\n}\n\nconst containers = response.data.docker.containers;\n\n// Transform to Docker API format\nconst normalized = containers.map(container => {\n // State mapping: RUNNING\u2192running, STOPPED\u2192exited, PAUSED\u2192paused\n const stateLower = container.state ? container.state.toLowerCase() : 'unknown';\n \n return {\n Id: container.id, // Full 129-char PrefixedID\n Names: container.names || [container.name ? `/${container.name}` : '/unknown'],\n State: stateLower === 'stopped' ? 'exited' : stateLower,\n Status: container.status || stateLower,\n Image: container.image || '',\n ImageId: container.imageId || ''\n };\n});\n\nreturn normalized.map(container => ({ json: container }));\n" + }, + "id": "normalize-graphql-update-all-exec", + "name": "Normalize GraphQL Response (Update All Exec)", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 2500, + 2600 + ] + }, + { + "parameters": { + "jsCode": "// Update Container ID Registry - Store name\u2192PrefixedID mappings\nconst containers = $input.all();\nconst staticData = $getWorkflowStaticData('global');\n\n// Parse existing registry\nconst registry = JSON.parse(staticData._containerIdRegistry || '{}');\n\n// Update registry with current containers\ncontainers.forEach(item => {\n const container = item.json;\n const name = container.Names?.[0]?.substring(1) || 'unknown'; // Remove leading /\n \n registry[name] = {\n name: name,\n unraidId: container.Id, // Full PrefixedID\n prefixedId: container.Id, // Alias for consistency\n lastSeen: Date.now()\n };\n});\n\n// Write back to static data (top-level assignment for persistence)\nstaticData._containerIdRegistry = JSON.stringify(registry);\nstaticData._containerRegistryLastUpdate = Date.now();\n\n// Pass through all container data\nreturn $input.all();\n" + }, + "id": "registry-update-update-all-exec", + "name": "Update Container Registry (Update All Exec)", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 2800, + 2600 + ] + }, + { + "parameters": { + "jsCode": "// GraphQL Response Normalizer - Transform Unraid API to Docker API contract\nconst response = $input.item.json;\n\n// Validate GraphQL response\nif (response.errors) {\n throw new Error(`GraphQL Error: ${response.errors[0].message}`);\n}\n\nif (!response.data?.docker?.containers) {\n throw new Error('Invalid GraphQL response structure');\n}\n\nconst containers = response.data.docker.containers;\n\n// Transform to Docker API format\nconst normalized = containers.map(container => {\n // State mapping: RUNNING\u2192running, STOPPED\u2192exited, PAUSED\u2192paused\n const stateLower = container.state ? container.state.toLowerCase() : 'unknown';\n \n return {\n Id: container.id, // Full 129-char PrefixedID\n Names: container.names || [container.name ? `/${container.name}` : '/unknown'],\n State: stateLower === 'stopped' ? 'exited' : stateLower,\n Status: container.status || stateLower,\n Image: container.image || '',\n ImageId: container.imageId || ''\n };\n});\n\nreturn normalized.map(container => ({ json: container }));\n" + }, + "id": "normalize-graphql-callback", + "name": "Normalize GraphQL Response (Callback)", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 2740, + 1650 + ] + }, + { + "parameters": { + "jsCode": "// Update Container ID Registry - Store name\u2192PrefixedID mappings\nconst containers = $input.all();\nconst staticData = $getWorkflowStaticData('global');\n\n// Parse existing registry\nconst registry = JSON.parse(staticData._containerIdRegistry || '{}');\n\n// Update registry with current containers\ncontainers.forEach(item => {\n const container = item.json;\n const name = container.Names?.[0]?.substring(1) || 'unknown'; // Remove leading /\n \n registry[name] = {\n name: name,\n unraidId: container.Id, // Full PrefixedID\n prefixedId: container.Id, // Alias for consistency\n lastSeen: Date.now()\n };\n});\n\n// Write back to static data (top-level assignment for persistence)\nstaticData._containerIdRegistry = JSON.stringify(registry);\nstaticData._containerRegistryLastUpdate = Date.now();\n\n// Pass through all container data\nreturn $input.all();\n" + }, + "id": "registry-update-callback", + "name": "Update Container Registry (Callback)", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 3040, + 1650 + ] + }, + { + "parameters": { + "jsCode": "// GraphQL Response Normalizer - Transform Unraid API to Docker API contract\nconst response = $input.item.json;\n\n// Validate GraphQL response\nif (response.errors) {\n throw new Error(`GraphQL Error: ${response.errors[0].message}`);\n}\n\nif (!response.data?.docker?.containers) {\n throw new Error('Invalid GraphQL response structure');\n}\n\nconst containers = response.data.docker.containers;\n\n// Transform to Docker API format\nconst normalized = containers.map(container => {\n // State mapping: RUNNING\u2192running, STOPPED\u2192exited, PAUSED\u2192paused\n const stateLower = container.state ? container.state.toLowerCase() : 'unknown';\n \n return {\n Id: container.id, // Full 129-char PrefixedID\n Names: container.names || [container.name ? `/${container.name}` : '/unknown'],\n State: stateLower === 'stopped' ? 'exited' : stateLower,\n Status: container.status || stateLower,\n Image: container.image || '',\n ImageId: container.imageId || ''\n };\n});\n\nreturn normalized.map(container => ({ json: container }));\n" + }, + "id": "normalize-graphql-bitmap-stop", + "name": "Normalize GraphQL Response (Bitmap Stop)", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 2300, + 700 + ] + }, + { + "parameters": { + "jsCode": "// Update Container ID Registry - Store name\u2192PrefixedID mappings\nconst containers = $input.all();\nconst staticData = $getWorkflowStaticData('global');\n\n// Parse existing registry\nconst registry = JSON.parse(staticData._containerIdRegistry || '{}');\n\n// Update registry with current containers\ncontainers.forEach(item => {\n const container = item.json;\n const name = container.Names?.[0]?.substring(1) || 'unknown'; // Remove leading /\n \n registry[name] = {\n name: name,\n unraidId: container.Id, // Full PrefixedID\n prefixedId: container.Id, // Alias for consistency\n lastSeen: Date.now()\n };\n});\n\n// Write back to static data (top-level assignment for persistence)\nstaticData._containerIdRegistry = JSON.stringify(registry);\nstaticData._containerRegistryLastUpdate = Date.now();\n\n// Pass through all container data\nreturn $input.all();\n" + }, + "id": "registry-update-bitmap-stop", + "name": "Update Container Registry (Bitmap Stop)", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 2600, + 700 + ] } ], "connections": { @@ -6887,7 +7194,145 @@ } ] ] - } + }, + "http-get-container-for-action": { + "main": [ + [ + { + "node": "normalize-graphql-action", + "type": "main", + "index": 0 + } + ] + ] + }, + "normalize-graphql-action": { + "main": [ + [ + { + "node": "registry-update-action", + "type": "main", + "index": 0 + } + ] + ] + }, + "registry-update-action": {}, + "http-get-container-for-cancel": { + "main": [ + [ + { + "node": "normalize-graphql-cancel", + "type": "main", + "index": 0 + } + ] + ] + }, + "normalize-graphql-cancel": { + "main": [ + [ + { + "node": "registry-update-cancel", + "type": "main", + "index": 0 + } + ] + ] + }, + "registry-update-cancel": {}, + "http-get-all-containers-update-all": { + "main": [ + [ + { + "node": "normalize-graphql-update-all", + "type": "main", + "index": 0 + } + ] + ] + }, + "normalize-graphql-update-all": { + "main": [ + [ + { + "node": "registry-update-update-all", + "type": "main", + "index": 0 + } + ] + ] + }, + "registry-update-update-all": {}, + "http-fetch-containers-update-all-exec": { + "main": [ + [ + { + "node": "normalize-graphql-update-all-exec", + "type": "main", + "index": 0 + } + ] + ] + }, + "normalize-graphql-update-all-exec": { + "main": [ + [ + { + "node": "registry-update-update-all-exec", + "type": "main", + "index": 0 + } + ] + ] + }, + "registry-update-update-all-exec": {}, + "http-get-container-callback": { + "main": [ + [ + { + "node": "normalize-graphql-callback", + "type": "main", + "index": 0 + } + ] + ] + }, + "normalize-graphql-callback": { + "main": [ + [ + { + "node": "registry-update-callback", + "type": "main", + "index": 0 + } + ] + ] + }, + "registry-update-callback": {}, + "http-fetch-containers-bitmap-stop": { + "main": [ + [ + { + "node": "normalize-graphql-bitmap-stop", + "type": "main", + "index": 0 + } + ] + ] + }, + "normalize-graphql-bitmap-stop": { + "main": [ + [ + { + "node": "registry-update-bitmap-stop", + "type": "main", + "index": 0 + } + ] + ] + }, + "registry-update-bitmap-stop": {} }, "pinData": {}, "settings": {