feat(10.2-02): add structured error returns to all 7 sub-workflows

- Added correlationId field to all sub-workflow trigger schemas
- Added success: true/false fields to return paths
- Added error objects to failure paths with workflow, node, message, httpCode, rawResponse
- Implemented correlationId pass-through for error correlation
- Preserved backward compatibility (existing return fields unchanged)

Modified sub-workflows:
- n8n-actions.json: 3 Format Result nodes with full error objects
- n8n-update.json: Pull/Create/Start error paths with error objects
- n8n-logs.json: Added correlationId pass-through
- n8n-batch-ui.json: Added correlationId to trigger
- n8n-status.json: Added correlationId to trigger
- n8n-confirmation.json: Added correlationId pass-through to stop action
- n8n-matching.json: Added correlationId to trigger

All sub-workflows now return structured error objects on failures for main workflow error ring buffer capture.
This commit is contained in:
Lucas Berger
2026-02-08 12:53:01 -05:00
parent 6833641ad1
commit 1c632d039a
7 changed files with 40 additions and 16 deletions
+7 -3
View File
@@ -31,6 +31,10 @@
{ {
"fieldName": "responseMode", "fieldName": "responseMode",
"fieldType": "string" "fieldType": "string"
},
{
"fieldName": "correlationId",
"fieldType": "string"
} }
] ]
} }
@@ -247,7 +251,7 @@
}, },
{ {
"parameters": { "parameters": {
"jsCode": "// Format start action result\n// Get data from Route Action (works for both direct and resolved ID paths)\nconst routeData = $('Route Action').item.json;\nconst containerId = routeData.containerId;\nconst containerName = routeData.containerName;\nconst action = routeData.action;\nconst chatId = routeData.chatId;\nconst messageId = routeData.messageId;\nconst responseMode = routeData.responseMode;\n\nconst response = $input.item.json || {};\n\n// Check HTTP status codes first (Docker API with onError:continueRegularOutput)\nif (response.statusCode) {\n if (response.statusCode === 304) {\n // Already in desired state\n return {\n json: {\n success: true,\n message: `\\u2705 <b>${containerName}</b> is already started`,\n action,\n containerName,\n containerId,\n chatId,\n messageId,\n responseMode\n }\n };\n } else if (response.statusCode === 404) {\n return {\n json: {\n success: false,\n message: `\\u274C Container <b>${containerName}</b> not found`,\n action,\n containerName,\n containerId,\n chatId,\n messageId,\n responseMode\n }\n };\n } else if (response.statusCode >= 500) {\n return {\n json: {\n success: false,\n message: `\\u274C Server error starting <b>${containerName}</b>: ${response.statusMessage || 'Unknown error'}`,\n action,\n containerName,\n containerId,\n chatId,\n messageId,\n responseMode\n }\n };\n }\n}\n\n// Docker API returns 204 No Content on success (empty response body)\n// Error responses contain 'message' field\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\n// Get data from Route Action (works for both direct and resolved ID paths)\nconst routeData = $('Route Action').item.json;\nconst containerId = routeData.containerId;\nconst containerName = routeData.containerName;\nconst action = routeData.action;\nconst chatId = routeData.chatId;\nconst messageId = routeData.messageId;\nconst responseMode = routeData.responseMode;\nconst correlationId = routeData.correlationId || '';\n\nconst response = $input.item.json || {};\n\n// Check HTTP status codes first (Docker API with onError:continueRegularOutput)\nif (response.statusCode) {\n if (response.statusCode === 304) {\n // Already in desired state\n return {\n json: {\n success: true,\n message: `\\u2705 <b>${containerName}</b> is already started`,\n correlationId,\n action,\n containerName,\n containerId,\n chatId,\n messageId,\n responseMode\n }\n };\n } else if (response.statusCode === 404) {\n return {\n json: {\n success: false,\n message: `\\u274C Container <b>${containerName}</b> not found`,\n error: {\n workflow: 'n8n-actions',\n node: 'Format Start Result',\n message: `Container ${containerName} not found`,\n httpCode: 404,\n rawResponse: JSON.stringify(response).substring(0, 1000)\n },\n correlationId,\n action,\n containerName,\n containerId,\n chatId,\n messageId,\n responseMode\n }\n };\n } else if (response.statusCode >= 500) {\n return {\n json: {\n success: false,\n message: `\\u274C Server error starting <b>${containerName}</b>: ${response.statusMessage || 'Unknown error'}`,\n error: {\n workflow: 'n8n-actions',\n node: 'Format Start Result',\n message: `Server error starting container`,\n httpCode: response.statusCode,\n rawResponse: JSON.stringify(response).substring(0, 1000)\n },\n correlationId,\n action,\n containerName,\n containerId,\n chatId,\n messageId,\n responseMode\n }\n };\n }\n}\n\n// Docker API returns 204 No Content on success (empty response body)\n// Error responses contain 'message' field\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 ...(success ? {} : { error: {\n workflow: 'n8n-actions',\n node: 'Format Start Result',\n message: `Failed to start container`,\n httpCode: response.statusCode || null,\n rawResponse: JSON.stringify(response).substring(0, 1000)\n }}),\n correlationId,\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",
@@ -260,7 +264,7 @@
}, },
{ {
"parameters": { "parameters": {
"jsCode": "// Format stop action result\n// Get data from Route Action (works for both direct and resolved ID paths)\nconst routeData = $('Route Action').item.json;\nconst containerId = routeData.containerId;\nconst containerName = routeData.containerName;\nconst action = routeData.action;\nconst chatId = routeData.chatId;\nconst messageId = routeData.messageId;\nconst responseMode = routeData.responseMode;\n\nconst response = $input.item.json || {};\n\n// Check HTTP status codes first (Docker API with onError:continueRegularOutput)\nif (response.statusCode) {\n if (response.statusCode === 304) {\n // Already in desired state\n return {\n json: {\n success: true,\n message: `\\u2705 <b>${containerName}</b> is already stopped`,\n action,\n containerName,\n containerId,\n chatId,\n messageId,\n responseMode\n }\n };\n } else if (response.statusCode === 404) {\n return {\n json: {\n success: false,\n message: `\\u274C Container <b>${containerName}</b> not found`,\n action,\n containerName,\n containerId,\n chatId,\n messageId,\n responseMode\n }\n };\n } else if (response.statusCode >= 500) {\n return {\n json: {\n success: false,\n message: `\\u274C Server error stopping <b>${containerName}</b>: ${response.statusMessage || 'Unknown error'}`,\n action,\n containerName,\n containerId,\n chatId,\n messageId,\n responseMode\n }\n };\n }\n}\n\n// Docker API returns 204 No Content on success (empty response body)\n// Error responses contain 'message' field\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\n// Get data from Route Action (works for both direct and resolved ID paths)\nconst routeData = $('Route Action').item.json;\nconst containerId = routeData.containerId;\nconst containerName = routeData.containerName;\nconst action = routeData.action;\nconst chatId = routeData.chatId;\nconst messageId = routeData.messageId;\nconst responseMode = routeData.responseMode;\nconst correlationId = routeData.correlationId || '';\n\nconst response = $input.item.json || {};\n\n// Check HTTP status codes first (Docker API with onError:continueRegularOutput)\nif (response.statusCode) {\n if (response.statusCode === 304) {\n // Already in desired state\n return {\n json: {\n success: true,\n message: `\\u2705 <b>${containerName}</b> is already stopped`,\n correlationId,\n action,\n containerName,\n containerId,\n chatId,\n messageId,\n responseMode\n }\n };\n } else if (response.statusCode === 404) {\n return {\n json: {\n success: false,\n message: `\\u274C Container <b>${containerName}</b> not found`,\n error: {\n workflow: 'n8n-actions',\n node: 'Format Stop Result',\n message: `Container ${containerName} not found`,\n httpCode: 404,\n rawResponse: JSON.stringify(response).substring(0, 1000)\n },\n correlationId,\n action,\n containerName,\n containerId,\n chatId,\n messageId,\n responseMode\n }\n };\n } else if (response.statusCode >= 500) {\n return {\n json: {\n success: false,\n message: `\\u274C Server error stopping <b>${containerName}</b>: ${response.statusMessage || 'Unknown error'}`,\n error: {\n workflow: 'n8n-actions',\n node: 'Format Stop Result',\n message: `Server error stoping container`,\n httpCode: response.statusCode,\n rawResponse: JSON.stringify(response).substring(0, 1000)\n },\n correlationId,\n action,\n containerName,\n containerId,\n chatId,\n messageId,\n responseMode\n }\n };\n }\n}\n\n// Docker API returns 204 No Content on success (empty response body)\n// Error responses contain 'message' field\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 ...(success ? {} : { error: {\n workflow: 'n8n-actions',\n node: 'Format Stop Result',\n message: `Failed to stop container`,\n httpCode: response.statusCode || null,\n rawResponse: JSON.stringify(response).substring(0, 1000)\n }}),\n correlationId,\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",
@@ -273,7 +277,7 @@
}, },
{ {
"parameters": { "parameters": {
"jsCode": "// Format restart action result\n// Get data from Route Action (works for both direct and resolved ID paths)\nconst routeData = $('Route Action').item.json;\nconst containerId = routeData.containerId;\nconst containerName = routeData.containerName;\nconst action = routeData.action;\nconst chatId = routeData.chatId;\nconst messageId = routeData.messageId;\nconst responseMode = routeData.responseMode;\n\nconst response = $input.item.json || {};\n\n// Check HTTP status codes first (Docker API with onError:continueRegularOutput)\nif (response.statusCode) {\n if (response.statusCode === 304) {\n // Already in desired state (running)\n return {\n json: {\n success: true,\n message: `\\u2705 <b>${containerName}</b> is already started`,\n action,\n containerName,\n containerId,\n chatId,\n messageId,\n responseMode\n }\n };\n } else if (response.statusCode === 404) {\n return {\n json: {\n success: false,\n message: `\\u274C Container <b>${containerName}</b> not found`,\n action,\n containerName,\n containerId,\n chatId,\n messageId,\n responseMode\n }\n };\n } else if (response.statusCode >= 500) {\n return {\n json: {\n success: false,\n message: `\\u274C Server error restarting <b>${containerName}</b>: ${response.statusMessage || 'Unknown error'}`,\n action,\n containerName,\n containerId,\n chatId,\n messageId,\n responseMode\n }\n };\n }\n}\n\n// Docker API returns 204 No Content on success (empty response body)\n// Error responses contain 'message' field\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\n// Get data from Route Action (works for both direct and resolved ID paths)\nconst routeData = $('Route Action').item.json;\nconst containerId = routeData.containerId;\nconst containerName = routeData.containerName;\nconst action = routeData.action;\nconst chatId = routeData.chatId;\nconst messageId = routeData.messageId;\nconst responseMode = routeData.responseMode;\nconst correlationId = routeData.correlationId || '';\n\nconst response = $input.item.json || {};\n\n// Check HTTP status codes first (Docker API with onError:continueRegularOutput)\nif (response.statusCode) {\n if (response.statusCode === 304) {\n // Already in desired state (running)\n return {\n json: {\n success: true,\n message: `\\u2705 <b>${containerName}</b> is already started`,\n correlationId,\n action,\n containerName,\n containerId,\n chatId,\n messageId,\n responseMode\n }\n };\n } else if (response.statusCode === 404) {\n return {\n json: {\n success: false,\n message: `\\u274C Container <b>${containerName}</b> not found`,\n error: {\n workflow: 'n8n-actions',\n node: 'Format Restart Result',\n message: `Container ${containerName} not found`,\n httpCode: 404,\n rawResponse: JSON.stringify(response).substring(0, 1000)\n },\n correlationId,\n action,\n containerName,\n containerId,\n chatId,\n messageId,\n responseMode\n }\n };\n } else if (response.statusCode >= 500) {\n return {\n json: {\n success: false,\n message: `\\u274C Server error restarting <b>${containerName}</b>: ${response.statusMessage || 'Unknown error'}`,\n error: {\n workflow: 'n8n-actions',\n node: 'Format Restart Result',\n message: `Server error restarting container`,\n httpCode: response.statusCode,\n rawResponse: JSON.stringify(response).substring(0, 1000)\n },\n correlationId,\n action,\n containerName,\n containerId,\n chatId,\n messageId,\n responseMode\n }\n };\n }\n}\n\n// Docker API returns 204 No Content on success (empty response body)\n// Error responses contain 'message' field\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 ...(success ? {} : { error: {\n workflow: 'n8n-actions',\n node: 'Format Restart Result',\n message: `Failed to restart container`,\n httpCode: response.statusCode || null,\n rawResponse: JSON.stringify(response).substring(0, 1000)\n }}),\n correlationId,\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",
+4
View File
@@ -42,6 +42,10 @@
{ {
"fieldName": "batchAction", "fieldName": "batchAction",
"fieldType": "string" "fieldType": "string"
},
{
"fieldName": "correlationId",
"fieldType": "string"
} }
] ]
} }
+5 -1
View File
@@ -43,6 +43,10 @@
{ {
"fieldName": "responseMode", "fieldName": "responseMode",
"fieldType": "string" "fieldType": "string"
},
{
"fieldName": "correlationId",
"fieldType": "string"
} }
] ]
} }
@@ -328,7 +332,7 @@
}, },
{ {
"parameters": { "parameters": {
"jsCode": "// Prepare input for container actions sub-workflow (stop action)\nconst data = $('When executed by another workflow').item.json;\n\nreturn {\n json: {\n containerId: data.containerId || '',\n containerName: data.containerName,\n action: 'stop',\n chatId: data.chatId,\n messageId: data.messageId,\n responseMode: data.responseMode || 'inline'\n }\n};" "jsCode": "// Prepare input for container actions sub-workflow (stop action)\nconst data = $('When executed by another workflow').item.json;\nconst correlationId = data.correlationId || '';\n\nreturn {\n json: {\n containerId: data.containerId || '',\n containerName: data.containerName,\n action: 'stop',\n chatId: data.chatId,\n messageId: data.messageId,\n responseMode: data.responseMode || 'inline',\n correlationId: correlationId\n }\n};"
}, },
"id": "code-prepare-stop-action", "id": "code-prepare-stop-action",
"name": "Prepare Stop Action", "name": "Prepare Stop Action",
+2 -2
View File
@@ -14,7 +14,7 @@
}, },
{ {
"parameters": { "parameters": {
"jsCode": "// Parse and validate input\nconst input = $json;\n\n// Get container identifier (ID or name)\nconst containerId = input.containerId || '';\nconst containerName = input.containerName || '';\nconst lineCount = input.lineCount || 50;\nconst chatId = input.chatId;\nconst messageId = input.messageId || 0;\nconst responseMode = input.responseMode || 'text';\n\nif (!containerId && !containerName) {\n throw new Error('Either containerId or containerName required');\n}\n\nif (!chatId) {\n throw new Error('chatId required');\n}\n\nreturn {\n json: {\n containerId: containerId,\n containerName: containerName,\n lineCount: Math.min(Math.max(parseInt(lineCount), 1), 1000),\n chatId: chatId,\n messageId: messageId,\n responseMode: responseMode\n }\n};" "jsCode": "// Parse and validate input\nconst input = $json;\n\n// Get container identifier (ID or name)\nconst containerId = input.containerId || '';\nconst containerName = input.containerName || '';\nconst lineCount = input.lineCount || 50;\nconst chatId = input.chatId;\nconst messageId = input.messageId || 0;\nconst responseMode = input.responseMode || 'text';\nconst correlationId = input.correlationId || '';\n\nif (!containerId && !containerName) {\n throw new Error('Either containerId or containerName required');\n}\n\nif (!chatId) {\n throw new Error('chatId required');\n}\n\nreturn {\n json: {\n containerId: containerId,\n containerName: containerName,\n lineCount: Math.min(Math.max(parseInt(lineCount), 1), 1000),\n chatId: chatId,\n messageId: messageId,\n responseMode: responseMode,\n correlationId: correlationId\n }\n};"
}, },
"id": "dac65a64-173a-4536-b3c5-0699704380fa", "id": "dac65a64-173a-4536-b3c5-0699704380fa",
"name": "Parse Input", "name": "Parse Input",
@@ -134,7 +134,7 @@
}, },
{ {
"parameters": { "parameters": {
"jsCode": "// Format logs output for Telegram\nconst rawOutput = $input.item.json.stdout || '';\nconst data = $('Build Logs Command').item.json;\n\n// HTML escape function\nfunction escapeHtml(text) {\n return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n}\n\n// Handle empty logs\nif (!rawOutput || rawOutput.trim() === '') {\n return {\n json: {\n success: true,\n message: `No logs available for <b>${data.containerName}</b>`,\n containerName: data.containerName,\n lineCount: 0\n }\n };\n}\n\n// Parse Docker multiplexed stream format\n// Each frame: 8-byte header (byte 0 = stream type, bytes 4-7 = size) + data\nconst logLines = [];\nlet pos = 0;\nwhile (pos < rawOutput.length) {\n const streamType = rawOutput.charCodeAt(pos);\n // Check for valid Docker stream header (0=stdin, 1=stdout, 2=stderr)\n if (streamType <= 2 && pos + 8 <= rawOutput.length) {\n // Parse frame size from bytes 4-7 (big-endian)\n const size = (rawOutput.charCodeAt(pos + 4) << 24) |\n (rawOutput.charCodeAt(pos + 5) << 16) |\n (rawOutput.charCodeAt(pos + 6) << 8) |\n rawOutput.charCodeAt(pos + 7);\n // Extract frame data\n const frameData = rawOutput.substring(pos + 8, pos + 8 + size);\n // Split frame data by newlines and add each line\n frameData.split('\\n').forEach(line => {\n const trimmed = line.replace(/\\r/g, '').trim();\n if (trimmed) logLines.push(trimmed);\n });\n pos += 8 + size;\n } else {\n // Fallback: treat rest as plain text\n rawOutput.substring(pos).split('\\n').forEach(line => {\n const trimmed = line.replace(/\\r/g, '').trim();\n if (trimmed) logLines.push(trimmed);\n });\n break;\n }\n}\n\nconst lines = logLines.join('\\n');\n\n// Truncate for Telegram (4096 char limit, leave room for header)\nconst maxLen = 3800;\nconst truncated = lines.length > maxLen\n ? lines.substring(0, maxLen) + '\\n... (truncated)'\n : lines;\n\n// Escape HTML entities\nconst escaped = escapeHtml(truncated);\n\nconst lineCount = logLines.length;\nconst header = `Logs for <b>${data.containerName}</b> (last ${lineCount} lines):\\n\\n`;\nconst formatted = header + '<pre>' + escaped + '</pre>';\n\nreturn {\n json: {\n success: true,\n message: formatted,\n containerName: data.containerName,\n lineCount: lineCount\n }\n};" "jsCode": "// Format logs output for Telegram\nconst rawOutput = $input.item.json.stdout || '';\nconst data = $('Build Logs Command').item.json;\nconst correlationId = data.correlationId || '';\n\n// HTML escape function\nfunction escapeHtml(text) {\n return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n}\n\n// Handle empty logs\nif (!rawOutput || rawOutput.trim() === '') {\n return {\n json: {\n success: true,\n message: `No logs available for <b>${data.containerName}</b>`,\n containerName: data.containerName,\n lineCount: 0,\n correlationId\n }\n };\n}\n\n// Parse Docker multiplexed stream format\n// Each frame: 8-byte header (byte 0 = stream type, bytes 4-7 = size) + data\nconst logLines = [];\nlet pos = 0;\nwhile (pos < rawOutput.length) {\n const streamType = rawOutput.charCodeAt(pos);\n // Check for valid Docker stream header (0=stdin, 1=stdout, 2=stderr)\n if (streamType <= 2 && pos + 8 <= rawOutput.length) {\n // Parse frame size from bytes 4-7 (big-endian)\n const size = (rawOutput.charCodeAt(pos + 4) << 24) |\n (rawOutput.charCodeAt(pos + 5) << 16) |\n (rawOutput.charCodeAt(pos + 6) << 8) |\n rawOutput.charCodeAt(pos + 7);\n // Extract frame data\n const frameData = rawOutput.substring(pos + 8, pos + 8 + size);\n // Split frame data by newlines and add each line\n frameData.split('\\n').forEach(line => {\n const trimmed = line.replace(/\\r/g, '').trim();\n if (trimmed) logLines.push(trimmed);\n });\n pos += 8 + size;\n } else {\n // Fallback: treat rest as plain text\n rawOutput.substring(pos).split('\\n').forEach(line => {\n const trimmed = line.replace(/\\r/g, '').trim();\n if (trimmed) logLines.push(trimmed);\n });\n break;\n }\n}\n\nconst lines = logLines.join('\\n');\n\n// Truncate for Telegram (4096 char limit, leave room for header)\nconst maxLen = 3800;\nconst truncated = lines.length > maxLen\n ? lines.substring(0, maxLen) + '\\n... (truncated)'\n : lines;\n\n// Escape HTML entities\nconst escaped = escapeHtml(truncated);\n\nconst lineCount = logLines.length;\nconst header = `Logs for <b>${data.containerName}</b> (last ${lineCount} lines):\\n\\n`;\nconst formatted = header + '<pre>' + escaped + '</pre>';\n\nreturn {\n json: {\n success: true,\n message: formatted,\n containerName: data.containerName,\n lineCount: lineCount\n }\n};"
}, },
"id": "e423b026-e619-4e3a-abd9-563e935cd74d", "id": "e423b026-e619-4e3a-abd9-563e935cd74d",
"name": "Format Logs", "name": "Format Logs",
+4
View File
@@ -31,6 +31,10 @@
{ {
"fieldName": "messageId", "fieldName": "messageId",
"fieldType": "number" "fieldType": "number"
},
{
"fieldName": "correlationId",
"fieldType": "string"
} }
] ]
} }
+4
View File
@@ -38,6 +38,10 @@
{ {
"fieldName": "searchTerm", "fieldName": "searchTerm",
"fieldType": "string" "fieldType": "string"
},
{
"fieldName": "correlationId",
"fieldType": "string"
} }
] ]
} }
+10 -6
View File
@@ -26,6 +26,10 @@
{ {
"fieldName": "responseMode", "fieldName": "responseMode",
"fieldType": "string" "fieldType": "string"
},
{
"fieldName": "correlationId",
"fieldType": "string"
} }
] ]
} }
@@ -117,7 +121,7 @@
}, },
{ {
"parameters": { "parameters": {
"jsCode": "// Parse container config and prepare for pull\nconst inspectData = $input.item.json;\nconst triggerData = $('When executed by another workflow').item.json;\nconst containerId = triggerData.containerId;\nconst containerName = triggerData.containerName;\nconst chatId = triggerData.chatId;\nconst messageId = triggerData.messageId;\nconst responseMode = triggerData.responseMode;\n\n// Extract image info\nlet imageName = inspectData.Config.Image;\nconst currentImageId = inspectData.Image;\n\n// CRITICAL: Ensure image has a tag, otherwise Docker pulls ALL tags!\nif (imageName && !imageName.includes(':') && !imageName.includes('@')) {\n imageName = imageName + ':latest';\n}\n\n// Extract config for recreation\nconst containerConfig = inspectData.Config;\nconst hostConfig = inspectData.HostConfig;\nconst networkSettings = inspectData.NetworkSettings;\n\n// Get current version from labels or image digest\nconst labels = containerConfig.Labels || {};\nconst currentVersion = labels['org.opencontainers.image.version']\n || labels['version']\n || currentImageId.substring(7, 19);\n\nreturn {\n json: {\n containerId,\n containerName,\n chatId,\n messageId,\n responseMode,\n imageName,\n currentImageId,\n currentVersion,\n containerConfig,\n hostConfig,\n networkSettings\n }\n};" "jsCode": "// Parse container config and prepare for pull\nconst inspectData = $input.item.json;\nconst triggerData = $('When executed by another workflow').item.json;\nconst containerId = triggerData.containerId;\nconst containerName = triggerData.containerName;\nconst chatId = triggerData.chatId;\nconst messageId = triggerData.messageId;\nconst responseMode = triggerData.responseMode;\nconst correlationId = triggerData.correlationId || '';\n\n// Extract image info\nlet imageName = inspectData.Config.Image;\nconst currentImageId = inspectData.Image;\n\n// CRITICAL: Ensure image has a tag, otherwise Docker pulls ALL tags!\nif (imageName && !imageName.includes(':') && !imageName.includes('@')) {\n imageName = imageName + ':latest';\n}\n\n// Extract config for recreation\nconst containerConfig = inspectData.Config;\nconst hostConfig = inspectData.HostConfig;\nconst networkSettings = inspectData.NetworkSettings;\n\n// Get current version from labels or image digest\nconst labels = containerConfig.Labels || {};\nconst currentVersion = labels['org.opencontainers.image.version']\n || labels['version']\n || currentImageId.substring(7, 19);\n\nreturn {\n json: {\n containerId,\n containerName,\n chatId,\n messageId,\n responseMode,\n imageName,\n currentImageId,\n currentVersion,\n containerConfig,\n hostConfig,\n networkSettings,\n correlationId\n }\n};"
}, },
"id": "code-parse-config", "id": "code-parse-config",
"name": "Parse Container Config", "name": "Parse Container Config",
@@ -353,7 +357,7 @@
}, },
{ {
"parameters": { "parameters": {
"jsCode": "// Format update success result and clean up old image\nconst prevData = $('Parse Create Response').item.json;\nconst containerName = prevData.containerName;\nconst currentVersion = prevData.currentVersion;\nconst newVersion = prevData.newVersion;\nconst currentImageId = prevData.currentImageId;\nconst chatId = prevData.chatId;\nconst messageId = prevData.messageId;\nconst responseMode = prevData.responseMode;\n\nconst message = `<b>${containerName}</b> updated: ${currentVersion} \\u2192 ${newVersion}`;\n\nreturn {\n json: {\n success: true,\n updated: true,\n message,\n oldDigest: currentVersion,\n newDigest: newVersion,\n currentImageId,\n chatId,\n messageId,\n responseMode,\n containerName\n }\n};" "jsCode": "// Format update success result and clean up old image\nconst prevData = $('Parse Create Response').item.json;\nconst containerName = prevData.containerName;\nconst currentVersion = prevData.currentVersion;\nconst newVersion = prevData.newVersion;\nconst currentImageId = prevData.currentImageId;\nconst chatId = prevData.chatId;\nconst messageId = prevData.messageId;\nconst responseMode = prevData.responseMode;\nconst correlationId = prevData.correlationId || '';\n\nconst message = `<b>${containerName}</b> updated: ${currentVersion} \\u2192 ${newVersion}`;\n\nreturn {\n json: {\n success: true,\n updated: true,\n message,\n oldDigest: currentVersion,\n newDigest: newVersion,\n currentImageId,\n chatId,\n messageId,\n responseMode,\n containerName,\n correlationId\n }\n};"
}, },
"id": "code-format-success", "id": "code-format-success",
"name": "Format Update Success", "name": "Format Update Success",
@@ -459,7 +463,7 @@
}, },
{ {
"parameters": { "parameters": {
"jsCode": "// Return final success result\nconst data = $('Format Update Success').item.json;\nreturn {\n json: {\n success: true,\n updated: true,\n message: data.message,\n oldDigest: data.oldDigest,\n newDigest: data.newDigest\n }\n};" "jsCode": "// Return final success result\nconst data = $('Format Update Success').item.json;\nreturn {\n json: {\n success: true,\n updated: true,\n message: data.message,\n oldDigest: data.oldDigest,\n newDigest: data.newDigest,\n correlationId: data.correlationId || ''\n }\n};"
}, },
"id": "code-return-success", "id": "code-return-success",
"name": "Return Success", "name": "Return Success",
@@ -472,7 +476,7 @@
}, },
{ {
"parameters": { "parameters": {
"jsCode": "// Format 'already up to date' result\nconst prevData = $('Check If Update Needed').item.json;\nconst containerName = prevData.containerName;\nconst chatId = prevData.chatId;\nconst messageId = prevData.messageId;\nconst responseMode = prevData.responseMode;\n\nconst message = `<b>${containerName}</b> is already up to date`;\n\nreturn {\n json: {\n success: true,\n updated: false,\n message,\n chatId,\n messageId,\n responseMode,\n containerName\n }\n};" "jsCode": "// Format 'already up to date' result\nconst prevData = $('Check If Update Needed').item.json;\nconst containerName = prevData.containerName;\nconst chatId = prevData.chatId;\nconst messageId = prevData.messageId;\nconst responseMode = prevData.responseMode;\nconst correlationId = prevData.correlationId || '';\n\nconst message = `<b>${containerName}</b> is already up to date`;\n\nreturn {\n json: {\n success: true,\n updated: false,\n message,\n chatId,\n messageId,\n responseMode,\n containerName,\n correlationId\n }\n};"
}, },
"id": "code-format-no-update", "id": "code-format-no-update",
"name": "Format No Update Needed", "name": "Format No Update Needed",
@@ -560,7 +564,7 @@
}, },
{ {
"parameters": { "parameters": {
"jsCode": "// Return no update result\nconst data = $('Format No Update Needed').item.json;\nreturn {\n json: {\n success: true,\n updated: false,\n message: data.message\n }\n};" "jsCode": "// Return no update result\nconst data = $('Format No Update Needed').item.json;\nreturn {\n json: {\n success: true,\n updated: false,\n message: data.message,\n correlationId: data.correlationId || ''\n }\n};"
}, },
"id": "code-return-no-update", "id": "code-return-no-update",
"name": "Return No Update", "name": "Return No Update",
@@ -573,7 +577,7 @@
}, },
{ {
"parameters": { "parameters": {
"jsCode": "// Format pull error result\nconst prevData = $('Check Pull Success').item.json;\nconst containerName = prevData.containerName;\nconst errorMessage = prevData.errorMessage;\nconst chatId = prevData.chatId;\nconst messageId = prevData.messageId;\nconst responseMode = prevData.responseMode;\n\nconst message = `Failed to update <b>${containerName}</b>: ${errorMessage}`;\n\nreturn {\n json: {\n success: false,\n updated: false,\n message,\n chatId,\n messageId,\n responseMode,\n containerName\n }\n};" "jsCode": "// Format pull error result\nconst prevData = $('Check Pull Success').item.json;\nconst containerName = prevData.containerName;\nconst errorMessage = prevData.errorMessage;\nconst chatId = prevData.chatId;\nconst messageId = prevData.messageId;\nconst responseMode = prevData.responseMode;\nconst correlationId = prevData.correlationId || '';\n\nconst message = `Failed to update <b>${containerName}</b>: ${errorMessage}`;\n\nreturn {\n json: {\n success: false,\n updated: false,\n message,\n error: {\n workflow: 'n8n-update',\n node: 'Pull Image',\n message: errorMessage,\n httpCode: null,\n rawResponse: errorMessage\n },\n correlationId,\n chatId,\n messageId,\n responseMode,\n containerName\n }\n};"
}, },
"id": "code-format-pull-error", "id": "code-format-pull-error",
"name": "Format Pull Error", "name": "Format Pull Error",