feat(09-02): add per-container progress updates and action execution
- Add Build Progress Message node for per-container status display - Add Edit Progress Message to update Telegram with current progress - Add Route Batch Loop Action switch for action-specific execution - Add Build Batch Action Command with container lookup support - Add Execute Batch Container Action with error handling - Add Check Batch Action Result for lookup vs direct action - Add Needs Action Call IF node for two-phase execution - Add Execute Batch Action 2 for follow-up action calls - Add Parse Batch Action 2 for result handling - Add Handle Action Result to aggregate success/failure counts - Add Prepare Next Iteration for loop continuation - Add Prepare Batch Stop Exec for confirmed stop callbacks - Add Prepare Batch Exec for bexec callbacks - Connect Check Batch Stop Expired to execution flow - Connect Answer Batch Exec to execution flow
This commit is contained in:
+495
-1
@@ -4471,6 +4471,307 @@
|
||||
3100,
|
||||
-500
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Build progress message for current container\nconst data = $json;\nconst container = data.container;\nconst action = data.action;\nconst containerIndex = data.containerIndex;\nconst totalCount = data.totalCount;\nconst successCount = data.successCount || 0;\nconst failureCount = data.failureCount || 0;\n\nconst containerName = container.Name || container;\nconst current = containerIndex + 1;\n\nlet progressText = `<b>Batch ${action} in progress...</b>\\n\\n`;\nprogressText += `Current: <b>${containerName}</b>\\n`;\nprogressText += `Progress: ${current}/${totalCount}\\n\\n`;\n\nif (successCount > 0 || failureCount > 0) {\n progressText += `Completed: ${successCount} success`;\n if (failureCount > 0) {\n progressText += `, ${failureCount} failed`;\n }\n}\n\nreturn {\n json: {\n ...data,\n progressText: progressText,\n containerName: containerName\n }\n};"
|
||||
},
|
||||
"id": "code-build-progress",
|
||||
"name": "Build Progress Message",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
3320,
|
||||
-500
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/editMessageText",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify({ chat_id: $json.chatId, message_id: $json.progressMessageId, text: $json.progressText, parse_mode: 'HTML' }) }}",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"neverError": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": "http-edit-progress",
|
||||
"name": "Edit Progress Message",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
3540,
|
||||
-500
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"rules": {
|
||||
"values": [
|
||||
{
|
||||
"id": "loop-update",
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"typeValidation": "loose"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "is-update",
|
||||
"leftValue": "={{ $json.action }}",
|
||||
"rightValue": "update",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "equals"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"renameOutput": true,
|
||||
"outputKey": "update"
|
||||
},
|
||||
{
|
||||
"id": "loop-start",
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"typeValidation": "loose"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "is-start",
|
||||
"leftValue": "={{ $json.action }}",
|
||||
"rightValue": "start",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "equals"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"renameOutput": true,
|
||||
"outputKey": "start"
|
||||
},
|
||||
{
|
||||
"id": "loop-stop",
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"typeValidation": "loose"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "is-stop",
|
||||
"leftValue": "={{ $json.action }}",
|
||||
"rightValue": "stop",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "equals"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"renameOutput": true,
|
||||
"outputKey": "stop"
|
||||
},
|
||||
{
|
||||
"id": "loop-restart",
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"typeValidation": "loose"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "is-restart",
|
||||
"leftValue": "={{ $json.action }}",
|
||||
"rightValue": "restart",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "equals"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"renameOutput": true,
|
||||
"outputKey": "restart"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {
|
||||
"fallbackOutput": "none"
|
||||
}
|
||||
},
|
||||
"id": "switch-route-batch-loop-action",
|
||||
"name": "Route Batch Loop Action",
|
||||
"type": "n8n-nodes-base.switch",
|
||||
"typeVersion": 3.2,
|
||||
"position": [
|
||||
3760,
|
||||
-500
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Get container ID for the action\nconst data = $json;\nconst container = data.container;\nconst action = data.action;\n\n// Container might have Id directly or just Name\nconst containerId = container.Id || null;\nconst containerName = container.Name || container;\n\n// Build the curl command for start/stop/restart\nconst timeout = (action === 'stop' || action === 'restart') ? '?t=10' : '';\n\nlet cmd;\nif (containerId) {\n cmd = `curl -s -o /dev/null -w \"%{http_code}\" --max-time 30 -X POST 'http://docker-socket-proxy:2375/v1.47/containers/${containerId}/${action}${timeout}'`;\n} else {\n // Need to find container by name first - use filters\n cmd = `curl -s --max-time 5 'http://docker-socket-proxy:2375/v1.47/containers/json?all=true&filters=%7B%22name%22%3A%5B%22${containerName}%22%5D%7D'`;\n}\n\nreturn {\n json: {\n ...data,\n cmd: cmd,\n needsLookup: !containerId,\n containerId: containerId\n }\n};"
|
||||
},
|
||||
"id": "code-build-batch-action-cmd",
|
||||
"name": "Build Batch Action Command",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
3980,
|
||||
-400
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"command": "={{ $json.cmd }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "exec-batch-action",
|
||||
"name": "Execute Batch Container Action",
|
||||
"type": "n8n-nodes-base.executeCommand",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
4200,
|
||||
-400
|
||||
],
|
||||
"onError": "continueRegularOutput"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Parse action result and check if we need a second call\nconst data = $('Build Batch Action Command').item.json;\nconst stdout = $json.stdout || '';\nconst stderr = $json.stderr || '';\n\nif (data.needsLookup) {\n // First call was a lookup - parse result and make action call\n let containers;\n try {\n containers = JSON.parse(stdout);\n } catch (e) {\n return {\n json: {\n ...data,\n needsAction: false,\n status: 'error',\n reason: 'Container lookup failed'\n }\n };\n }\n \n if (!containers || containers.length === 0) {\n return {\n json: {\n ...data,\n needsAction: false,\n status: 'error',\n reason: 'Container not found'\n }\n };\n }\n \n const containerId = containers[0].Id;\n const action = data.action;\n const timeout = (action === 'stop' || action === 'restart') ? '?t=10' : '';\n const cmd = `curl -s -o /dev/null -w \"%{http_code}\" --max-time 30 -X POST 'http://docker-socket-proxy:2375/v1.47/containers/${containerId}/${action}${timeout}'`;\n \n return {\n json: {\n ...data,\n needsAction: true,\n containerId: containerId,\n cmd: cmd\n }\n };\n}\n\n// Direct action result\nconst statusCode = parseInt(stdout.trim());\nlet status, reason;\n\nif (statusCode === 204) {\n status = 'success';\n reason = null;\n} else if (statusCode === 304) {\n // Already in desired state\n status = 'warning';\n const action = data.action;\n reason = action === 'start' ? 'Already running' : \n action === 'stop' ? 'Already stopped' : 'No change needed';\n} else if (stderr || statusCode >= 400) {\n status = 'error';\n reason = stderr || `HTTP ${statusCode}`;\n} else {\n status = 'error';\n reason = 'Unknown error';\n}\n\nreturn {\n json: {\n ...data,\n needsAction: false,\n status: status,\n reason: reason\n }\n};"
|
||||
},
|
||||
"id": "code-check-batch-action-result",
|
||||
"name": "Check Batch Action Result",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
4420,
|
||||
-400
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "needs-action",
|
||||
"leftValue": "={{ $json.needsAction }}",
|
||||
"rightValue": true,
|
||||
"operator": {
|
||||
"type": "boolean",
|
||||
"operation": "equals"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "if-needs-batch-action",
|
||||
"name": "Needs Action Call",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
4640,
|
||||
-400
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"command": "={{ $json.cmd }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "exec-batch-action-2",
|
||||
"name": "Execute Batch Action 2",
|
||||
"type": "n8n-nodes-base.executeCommand",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
4860,
|
||||
-500
|
||||
],
|
||||
"onError": "continueRegularOutput"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Parse the final action result\nconst data = $('Check Batch Action Result').item.json;\nconst stdout = $json.stdout || '';\nconst stderr = $json.stderr || '';\n\nconst statusCode = parseInt(stdout.trim());\nlet status, reason;\n\nif (statusCode === 204) {\n status = 'success';\n reason = null;\n} else if (statusCode === 304) {\n status = 'warning';\n const action = data.action;\n reason = action === 'start' ? 'Already running' : \n action === 'stop' ? 'Already stopped' : 'No change needed';\n} else if (stderr || statusCode >= 400) {\n status = 'error';\n reason = stderr || `HTTP ${statusCode}`;\n} else {\n status = 'error';\n reason = 'Unknown error';\n}\n\nreturn {\n json: {\n ...data,\n status: status,\n reason: reason\n }\n};"
|
||||
},
|
||||
"id": "code-parse-batch-action-2",
|
||||
"name": "Parse Batch Action 2",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
5080,
|
||||
-500
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Merge both paths and update running totals\n// This node receives from either direct result or second action call\nconst data = $json;\n\nconst status = data.status;\nconst containerName = data.containerName;\nconst reason = data.reason;\nconst containerIndex = data.containerIndex;\n\n// Get previous results from workflow context or start fresh\nlet results = data.results || [];\nlet successCount = data.successCount || 0;\nlet failureCount = data.failureCount || 0;\nlet warningCount = data.warningCount || 0;\n\n// Add current result\nresults.push({\n name: containerName,\n status: status,\n reason: reason\n});\n\n// Update counters\nif (status === 'success') {\n successCount++;\n} else if (status === 'error') {\n failureCount++;\n} else if (status === 'warning') {\n warningCount++;\n}\n\nreturn {\n json: {\n ...data,\n results: results,\n successCount: successCount,\n failureCount: failureCount,\n warningCount: warningCount\n }\n};"
|
||||
},
|
||||
"id": "code-handle-batch-result",
|
||||
"name": "Handle Action Result",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
5300,
|
||||
-400
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Prepare data for next iteration or completion\nconst data = $json;\n\n// Check if more containers to process\nconst containerIndex = data.containerIndex;\nconst totalCount = data.totalCount;\nconst isLastContainer = (containerIndex + 1) >= totalCount;\n\nreturn {\n json: {\n ...data,\n isComplete: isLastContainer\n }\n};"
|
||||
},
|
||||
"id": "code-prepare-next-iteration",
|
||||
"name": "Prepare Next Iteration",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
5520,
|
||||
-400
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Prepare batch stop data for execution\n// Input from Check Batch Stop Expired (not expired)\nconst data = $json;\n\n// containerNames is a comma-separated array from callback\nconst containerNames = data.containerNames || [];\n\nreturn {\n json: {\n allMatched: containerNames.map(name => ({ Name: name, Id: null })),\n action: 'stop',\n chatId: data.chatId,\n messageId: data.messageId\n }\n};"
|
||||
},
|
||||
"id": "code-prepare-batch-stop-exec",
|
||||
"name": "Prepare Batch Stop Exec",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1780,
|
||||
700
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Prepare batch exec data for execution\n// Input from bexec callback (start/restart/update)\nconst data = $json;\n\n// containerNames is a comma-separated array from callback\nconst containerNames = data.containerNames || [];\nconst action = data.batchAction || 'start';\n\nreturn {\n json: {\n allMatched: containerNames.map(name => ({ Name: name, Id: null })),\n action: action,\n chatId: data.chatId,\n messageId: data.messageId\n }\n};"
|
||||
},
|
||||
"id": "code-prepare-batch-exec",
|
||||
"name": "Prepare Batch Exec",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1560,
|
||||
900
|
||||
]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
@@ -5845,7 +6146,13 @@
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[]
|
||||
[
|
||||
{
|
||||
"node": "Prepare Batch Stop Exec",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Build Batch Stop Expired": {
|
||||
@@ -6501,6 +6808,193 @@
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Batch Loop": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Build Progress Message",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Build Progress Message": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Edit Progress Message",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Edit Progress Message": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Route Batch Loop Action",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Route Batch Loop Action": {
|
||||
"main": [
|
||||
[],
|
||||
[
|
||||
{
|
||||
"node": "Build Batch Action Command",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Build Batch Action Command",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Build Batch Action Command",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Build Batch Action Command": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Execute Batch Container Action",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Execute Batch Container Action": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Check Batch Action Result",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Check Batch Action Result": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Needs Action Call",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Needs Action Call": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Execute Batch Action 2",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Handle Action Result",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Execute Batch Action 2": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Parse Batch Action 2",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Parse Batch Action 2": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Handle Action Result",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Handle Action Result": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Prepare Next Iteration",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Prepare Next Iteration": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Batch Loop",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Prepare Batch Stop Exec": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Initialize Batch State",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Answer Batch Exec": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Prepare Batch Exec",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Prepare Batch Exec": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Initialize Batch State",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"pinData": {},
|
||||
|
||||
Reference in New Issue
Block a user