fix(batch): resolve container IDs in sub-workflows
When batch operations are triggered via keyboard callbacks, the main workflow only passes container names (not IDs). The sub-workflows now: - Check if containerId is empty - If empty, query Docker API to resolve name → ID - Then proceed with the action This fixes batch start/stop/restart/update operations failing with 404 "page not found" errors. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
+109
-10
@@ -44,6 +44,65 @@
|
|||||||
300
|
300
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"conditions": {
|
||||||
|
"options": {
|
||||||
|
"caseSensitive": true,
|
||||||
|
"typeValidation": "loose"
|
||||||
|
},
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"id": "has-container-id",
|
||||||
|
"leftValue": "={{ $json.containerId }}",
|
||||||
|
"rightValue": "",
|
||||||
|
"operator": {
|
||||||
|
"type": "string",
|
||||||
|
"operation": "notEquals"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"combinator": "and"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"id": "if-has-id",
|
||||||
|
"name": "Has Container ID?",
|
||||||
|
"type": "n8n-nodes-base.if",
|
||||||
|
"typeVersion": 2.2,
|
||||||
|
"position": [
|
||||||
|
420,
|
||||||
|
300
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"url": "http://docker-socket-proxy:2375/v1.47/containers/json?all=true",
|
||||||
|
"options": {
|
||||||
|
"timeout": 5000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"id": "http-get-containers",
|
||||||
|
"name": "Get All Containers",
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 4.2,
|
||||||
|
"position": [
|
||||||
|
600,
|
||||||
|
400
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "// Find container by name and resolve ID\nconst triggerData = $('When executed by another workflow').item.json;\nconst containerName = triggerData.containerName;\nconst containers = $input.all();\n\n// Normalize function to strip leading slash\nconst normalizeName = (name) => name.replace(/^\\//, '').toLowerCase();\nconst searchName = normalizeName(containerName);\n\n// Find matching container\nlet matched = null;\nfor (const item of containers) {\n const c = item.json;\n if (c.Names && c.Names.length > 0) {\n const cName = normalizeName(c.Names[0]);\n if (cName === searchName || cName.includes(searchName)) {\n matched = c;\n break;\n }\n }\n}\n\nif (!matched) {\n // Return error - container not found\n return {\n json: {\n ...triggerData,\n containerId: '',\n error: `Container '${containerName}' not found`\n }\n };\n}\n\nreturn {\n json: {\n ...triggerData,\n containerId: matched.Id\n }\n};"
|
||||||
|
},
|
||||||
|
"id": "code-resolve-id",
|
||||||
|
"name": "Resolve Container ID",
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
780,
|
||||||
|
400
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"rules": {
|
"rules": {
|
||||||
@@ -128,7 +187,7 @@
|
|||||||
"type": "n8n-nodes-base.switch",
|
"type": "n8n-nodes-base.switch",
|
||||||
"typeVersion": 3.2,
|
"typeVersion": 3.2,
|
||||||
"position": [
|
"position": [
|
||||||
460,
|
960,
|
||||||
300
|
300
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -145,7 +204,7 @@
|
|||||||
"type": "n8n-nodes-base.httpRequest",
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
"typeVersion": 4.2,
|
"typeVersion": 4.2,
|
||||||
"position": [
|
"position": [
|
||||||
680,
|
1160,
|
||||||
200
|
200
|
||||||
],
|
],
|
||||||
"onError": "continueRegularOutput"
|
"onError": "continueRegularOutput"
|
||||||
@@ -163,7 +222,7 @@
|
|||||||
"type": "n8n-nodes-base.httpRequest",
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
"typeVersion": 4.2,
|
"typeVersion": 4.2,
|
||||||
"position": [
|
"position": [
|
||||||
680,
|
1160,
|
||||||
300
|
300
|
||||||
],
|
],
|
||||||
"onError": "continueRegularOutput"
|
"onError": "continueRegularOutput"
|
||||||
@@ -181,53 +240,93 @@
|
|||||||
"type": "n8n-nodes-base.httpRequest",
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
"typeVersion": 4.2,
|
"typeVersion": 4.2,
|
||||||
"position": [
|
"position": [
|
||||||
680,
|
1160,
|
||||||
400
|
400
|
||||||
],
|
],
|
||||||
"onError": "continueRegularOutput"
|
"onError": "continueRegularOutput"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"jsCode": "// Format start action result\nconst triggerData = $('When executed by another workflow').item.json;\nconst containerId = triggerData.containerId;\nconst containerName = triggerData.containerName;\nconst action = triggerData.action;\nconst chatId = triggerData.chatId;\nconst messageId = triggerData.messageId;\nconst responseMode = triggerData.responseMode;\n\n// Docker API returns 204 No Content on success (empty response body)\n// Error responses contain 'message' field\nconst response = $input.item.json || {};\nconst hasError = response.message || response.error || false;\n\n// Success = no error message in response (empty {} means 204 success)\nconst success = !hasError;\n\nlet message;\nif (success) {\n message = `\\u25B6\\uFE0F <b>${containerName}</b> started successfully`;\n} else {\n message = `\\u274C Failed to start <b>${containerName}</b>`;\n}\n\nreturn {\n json: {\n success,\n message,\n action,\n containerName,\n containerId,\n chatId,\n messageId,\n responseMode\n }\n};"
|
"jsCode": "// Format start action result\nconst triggerData = $('When executed by another workflow').item.json;\nconst containerId = $json.containerId || triggerData.containerId;\nconst containerName = triggerData.containerName;\nconst action = triggerData.action;\nconst chatId = triggerData.chatId;\nconst messageId = triggerData.messageId;\nconst responseMode = triggerData.responseMode;\n\n// Docker API returns 204 No Content on success (empty response body)\n// Error responses contain 'message' field\nconst response = $input.item.json || {};\nconst hasError = response.message || response.error || false;\n\n// Success = no error message in response (empty {} means 204 success)\nconst success = !hasError;\n\nlet message;\nif (success) {\n message = `\\u25B6\\uFE0F <b>${containerName}</b> started successfully`;\n} else {\n message = `\\u274C Failed to start <b>${containerName}</b>`;\n}\n\nreturn {\n json: {\n success,\n message,\n action,\n containerName,\n containerId,\n chatId,\n messageId,\n responseMode\n }\n};"
|
||||||
},
|
},
|
||||||
"id": "code-format-start-result",
|
"id": "code-format-start-result",
|
||||||
"name": "Format Start Result",
|
"name": "Format Start Result",
|
||||||
"type": "n8n-nodes-base.code",
|
"type": "n8n-nodes-base.code",
|
||||||
"typeVersion": 2,
|
"typeVersion": 2,
|
||||||
"position": [
|
"position": [
|
||||||
900,
|
1380,
|
||||||
200
|
200
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"jsCode": "// Format stop action result\nconst triggerData = $('When executed by another workflow').item.json;\nconst containerId = triggerData.containerId;\nconst containerName = triggerData.containerName;\nconst action = triggerData.action;\nconst chatId = triggerData.chatId;\nconst messageId = triggerData.messageId;\nconst responseMode = triggerData.responseMode;\n\n// Docker API returns 204 No Content on success (empty response body)\n// Error responses contain 'message' field\nconst response = $input.item.json || {};\nconst hasError = response.message || response.error || false;\n\n// Success = no error message in response (empty {} means 204 success)\nconst success = !hasError;\n\nlet message;\nif (success) {\n message = `\\u23F9\\uFE0F <b>${containerName}</b> stopped`;\n} else {\n message = `\\u274C Failed to stop <b>${containerName}</b>`;\n}\n\nreturn {\n json: {\n success,\n message,\n action,\n containerName,\n containerId,\n chatId,\n messageId,\n responseMode\n }\n};"
|
"jsCode": "// Format stop action result\nconst triggerData = $('When executed by another workflow').item.json;\nconst containerId = $json.containerId || triggerData.containerId;\nconst containerName = triggerData.containerName;\nconst action = triggerData.action;\nconst chatId = triggerData.chatId;\nconst messageId = triggerData.messageId;\nconst responseMode = triggerData.responseMode;\n\n// Docker API returns 204 No Content on success (empty response body)\n// Error responses contain 'message' field\nconst response = $input.item.json || {};\nconst hasError = response.message || response.error || false;\n\n// Success = no error message in response (empty {} means 204 success)\nconst success = !hasError;\n\nlet message;\nif (success) {\n message = `\\u23F9\\uFE0F <b>${containerName}</b> stopped`;\n} else {\n message = `\\u274C Failed to stop <b>${containerName}</b>`;\n}\n\nreturn {\n json: {\n success,\n message,\n action,\n containerName,\n containerId,\n chatId,\n messageId,\n responseMode\n }\n};"
|
||||||
},
|
},
|
||||||
"id": "code-format-stop-result",
|
"id": "code-format-stop-result",
|
||||||
"name": "Format Stop Result",
|
"name": "Format Stop Result",
|
||||||
"type": "n8n-nodes-base.code",
|
"type": "n8n-nodes-base.code",
|
||||||
"typeVersion": 2,
|
"typeVersion": 2,
|
||||||
"position": [
|
"position": [
|
||||||
900,
|
1380,
|
||||||
300
|
300
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"jsCode": "// Format restart action result\nconst triggerData = $('When executed by another workflow').item.json;\nconst containerId = triggerData.containerId;\nconst containerName = triggerData.containerName;\nconst action = triggerData.action;\nconst chatId = triggerData.chatId;\nconst messageId = triggerData.messageId;\nconst responseMode = triggerData.responseMode;\n\n// Docker API returns 204 No Content on success (empty response body)\n// Error responses contain 'message' field\nconst response = $input.item.json || {};\nconst hasError = response.message || response.error || false;\n\n// Success = no error message in response (empty {} means 204 success)\nconst success = !hasError;\n\nlet message;\nif (success) {\n message = `\\u{1F504} <b>${containerName}</b> restarted`;\n} else {\n message = `\\u274C Failed to restart <b>${containerName}</b>`;\n}\n\nreturn {\n json: {\n success,\n message,\n action,\n containerName,\n containerId,\n chatId,\n messageId,\n responseMode\n }\n};"
|
"jsCode": "// Format restart action result\nconst triggerData = $('When executed by another workflow').item.json;\nconst containerId = $json.containerId || triggerData.containerId;\nconst containerName = triggerData.containerName;\nconst action = triggerData.action;\nconst chatId = triggerData.chatId;\nconst messageId = triggerData.messageId;\nconst responseMode = triggerData.responseMode;\n\n// Docker API returns 204 No Content on success (empty response body)\n// Error responses contain 'message' field\nconst response = $input.item.json || {};\nconst hasError = response.message || response.error || false;\n\n// Success = no error message in response (empty {} means 204 success)\nconst success = !hasError;\n\nlet message;\nif (success) {\n message = `\\u{1F504} <b>${containerName}</b> restarted`;\n} else {\n message = `\\u274C Failed to restart <b>${containerName}</b>`;\n}\n\nreturn {\n json: {\n success,\n message,\n action,\n containerName,\n containerId,\n chatId,\n messageId,\n responseMode\n }\n};"
|
||||||
},
|
},
|
||||||
"id": "code-format-restart-result",
|
"id": "code-format-restart-result",
|
||||||
"name": "Format Restart Result",
|
"name": "Format Restart Result",
|
||||||
"type": "n8n-nodes-base.code",
|
"type": "n8n-nodes-base.code",
|
||||||
"typeVersion": 2,
|
"typeVersion": 2,
|
||||||
"position": [
|
"position": [
|
||||||
900,
|
1380,
|
||||||
400
|
400
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"connections": {
|
"connections": {
|
||||||
"When executed by another workflow": {
|
"When executed by another workflow": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Has Container ID?",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Has Container ID?": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Route Action",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Get All Containers",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Get All Containers": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Resolve Container ID",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Resolve Container ID": {
|
||||||
"main": [
|
"main": [
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -39,6 +39,65 @@
|
|||||||
300
|
300
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"conditions": {
|
||||||
|
"options": {
|
||||||
|
"caseSensitive": true,
|
||||||
|
"typeValidation": "loose"
|
||||||
|
},
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"id": "has-container-id",
|
||||||
|
"leftValue": "={{ $json.containerId }}",
|
||||||
|
"rightValue": "",
|
||||||
|
"operator": {
|
||||||
|
"type": "string",
|
||||||
|
"operation": "notEquals"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"combinator": "and"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"id": "if-has-id",
|
||||||
|
"name": "Has Container ID?",
|
||||||
|
"type": "n8n-nodes-base.if",
|
||||||
|
"typeVersion": 2.2,
|
||||||
|
"position": [
|
||||||
|
400,
|
||||||
|
300
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"url": "http://docker-socket-proxy:2375/v1.47/containers/json?all=true",
|
||||||
|
"options": {
|
||||||
|
"timeout": 5000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"id": "http-get-containers",
|
||||||
|
"name": "Get All Containers",
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 4.2,
|
||||||
|
"position": [
|
||||||
|
560,
|
||||||
|
400
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "// Find container by name and resolve ID\nconst triggerData = $('When executed by another workflow').item.json;\nconst containerName = triggerData.containerName;\nconst containers = $input.all();\n\n// Normalize function to strip leading slash\nconst normalizeName = (name) => name.replace(/^\\//, '').toLowerCase();\nconst searchName = normalizeName(containerName);\n\n// Find matching container\nlet matched = null;\nfor (const item of containers) {\n const c = item.json;\n if (c.Names && c.Names.length > 0) {\n const cName = normalizeName(c.Names[0]);\n if (cName === searchName || cName.includes(searchName)) {\n matched = c;\n break;\n }\n }\n}\n\nif (!matched) {\n throw new Error(`Container '${containerName}' not found`);\n}\n\nreturn {\n json: {\n ...triggerData,\n containerId: matched.Id\n }\n};"
|
||||||
|
},
|
||||||
|
"id": "code-resolve-id",
|
||||||
|
"name": "Resolve Container ID",
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
720,
|
||||||
|
400
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
@@ -616,6 +675,46 @@
|
|||||||
],
|
],
|
||||||
"connections": {
|
"connections": {
|
||||||
"When executed by another workflow": {
|
"When executed by another workflow": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Has Container ID?",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Has Container ID?": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Inspect Container",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Get All Containers",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Get All Containers": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Resolve Container ID",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Resolve Container ID": {
|
||||||
"main": [
|
"main": [
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user