Files
unraid-docker-manager/n8n-confirmation.json
Lucas Berger 1c632d039a 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.
2026-02-08 18:56:44 -05:00

607 lines
20 KiB
JSON

{
"name": "Confirmation Dialogs",
"nodes": [
{
"parameters": {
"inputSource": "passthrough",
"schema": {
"schemaType": "fromFields",
"inputFieldName": "",
"fields": [
{
"fieldName": "chatId",
"fieldType": "number"
},
{
"fieldName": "messageId",
"fieldType": "number"
},
{
"fieldName": "action",
"fieldType": "string"
},
{
"fieldName": "containerId",
"fieldType": "string"
},
{
"fieldName": "containerName",
"fieldType": "string"
},
{
"fieldName": "confirmAction",
"fieldType": "string"
},
{
"fieldName": "confirmationToken",
"fieldType": "string"
},
{
"fieldName": "expired",
"fieldType": "boolean"
},
{
"fieldName": "responseMode",
"fieldType": "string"
},
{
"fieldName": "correlationId",
"fieldType": "string"
}
]
}
},
"id": "trigger-confirm-workflow",
"name": "When executed by another workflow",
"type": "n8n-nodes-base.executeWorkflowTrigger",
"typeVersion": 1.1,
"position": [
240,
300
]
},
{
"parameters": {
"rules": {
"values": [
{
"id": "show-stop",
"conditions": {
"options": {
"caseSensitive": true,
"typeValidation": "loose"
},
"conditions": [
{
"id": "is-show-stop",
"leftValue": "={{ $json.action }}",
"rightValue": "show_stop",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "show_stop"
},
{
"id": "show-update",
"conditions": {
"options": {
"caseSensitive": true,
"typeValidation": "loose"
},
"conditions": [
{
"id": "is-show-update",
"leftValue": "={{ $json.action }}",
"rightValue": "show_update",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "show_update"
},
{
"id": "confirm-stop",
"conditions": {
"options": {
"caseSensitive": true,
"typeValidation": "loose"
},
"conditions": [
{
"id": "is-confirm",
"leftValue": "={{ $json.action }}",
"rightValue": "confirm",
"operator": {
"type": "string",
"operation": "equals"
}
},
{
"id": "is-stop-action",
"leftValue": "={{ $json.confirmAction }}",
"rightValue": "stop",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "confirm_stop"
},
{
"id": "confirm-update",
"conditions": {
"options": {
"caseSensitive": true,
"typeValidation": "loose"
},
"conditions": [
{
"id": "is-confirm",
"leftValue": "={{ $json.action }}",
"rightValue": "confirm",
"operator": {
"type": "string",
"operation": "equals"
}
},
{
"id": "is-update-action",
"leftValue": "={{ $json.confirmAction }}",
"rightValue": "update",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "confirm_update"
},
{
"id": "cancel",
"conditions": {
"options": {
"caseSensitive": true,
"typeValidation": "loose"
},
"conditions": [
{
"id": "is-cancel",
"leftValue": "={{ $json.action }}",
"rightValue": "cancel",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "cancel"
},
{
"id": "expired",
"conditions": {
"options": {
"caseSensitive": true,
"typeValidation": "loose"
},
"conditions": [
{
"id": "is-expired",
"leftValue": "={{ $json.expired }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "expired"
}
]
},
"options": {
"fallbackOutput": "none"
}
},
"id": "switch-confirm-action",
"name": "Route Confirmation Action",
"type": "n8n-nodes-base.switch",
"typeVersion": 3.2,
"position": [
460,
300
]
},
{
"parameters": {
"jsCode": "// Build Stop Confirmation dialog with timestamp for 30-second timeout\nconst data = $('When executed by another workflow').item.json;\nconst containerName = data.containerName;\nconst chatId = data.chatId;\nconst messageId = data.messageId;\nconst timestamp = Math.floor(Date.now() / 1000); // Unix timestamp\n\nconst keyboard = {\n inline_keyboard: [\n [\n { text: '\\u2705 Yes, Stop', callback_data: `confirm:stop:${containerName}:${timestamp}` },\n { text: '\\u274C Cancel', callback_data: `cancel:${containerName}` }\n ]\n ]\n};\n\nconst text = `\\u26A0\\uFE0F <b>Stop ${containerName}?</b>\\n\\nThis will stop the container immediately.\\n\\n<i>Confirmation expires in 30 seconds.</i>`;\n\nreturn {\n json: {\n action: 'show_stop',\n chatId,\n messageId,\n text,\n reply_markup: keyboard\n }\n};"
},
"id": "code-build-stop-confirm",
"name": "Build Stop Confirmation",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
700,
100
]
},
{
"parameters": {
"jsCode": "// Build Update Confirmation dialog with timestamp for 30-second timeout\nconst data = $('When executed by another workflow').item.json;\nconst containerName = data.containerName;\nconst chatId = data.chatId;\nconst messageId = data.messageId;\nconst timestamp = Math.floor(Date.now() / 1000); // Unix timestamp\n\nconst keyboard = {\n inline_keyboard: [\n [\n { text: '\\u2705 Yes, Update', callback_data: `confirm:update:${containerName}:${timestamp}` },\n { text: '\\u274C Cancel', callback_data: `cancel:${containerName}` }\n ]\n ]\n};\n\nconst text = `\\u26A0\\uFE0F <b>Update ${containerName}?</b>\\n\\nThis will pull the latest image and recreate the container.\\n\\n<i>Confirmation expires in 30 seconds.</i>`;\n\nreturn {\n json: {\n action: 'show_update',\n chatId,\n messageId,\n text,\n reply_markup: keyboard\n }\n};"
},
"id": "code-build-update-confirm",
"name": "Build Update Confirmation",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
700,
200
]
},
{
"parameters": {
"jsCode": "// Confirmation expired - return expired response\nconst data = $('When executed by another workflow').item.json;\nconst containerName = data.containerName;\nconst chatId = data.chatId;\nconst messageId = data.messageId;\n\n// Build keyboard for expired message\nconst keyboard = {\n inline_keyboard: [\n [\n { text: '\\u25C0\\uFE0F Back to List', callback_data: 'list:0' }\n ]\n ]\n};\n\nreturn {\n json: {\n action: 'expired',\n chatId,\n messageId,\n text: `\\u23F0 Confirmation for <b>${containerName}</b> has expired.\\n\\nPlease try again.`,\n reply_markup: keyboard\n }\n};"
},
"id": "code-handle-expired",
"name": "Handle Expired",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
700,
600
]
},
{
"parameters": {
"jsCode": "// Cancel confirmation - return to container submenu\nconst data = $('When executed by another workflow').item.json;\nconst containerName = data.containerName;\nconst chatId = data.chatId;\nconst messageId = data.messageId;\n\nreturn {\n json: {\n action: 'cancel',\n containerName,\n chatId,\n messageId\n }\n};"
},
"id": "code-handle-cancel",
"name": "Handle Cancel",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
700,
500
]
},
{
"parameters": {
"jsCode": "// Check if expired BEFORE executing confirm action\nconst data = $('When executed by another workflow').item.json;\nconst expired = data.expired === true;\n\nif (expired) {\n // Return expired action - main workflow will handle display\n return {\n json: {\n ...data,\n action: 'expired',\n expired: true\n }\n };\n}\n\n// Not expired, proceed with stop confirmation\nreturn {\n json: {\n ...data,\n action: 'confirm_stop',\n expired: false\n }\n};"
},
"id": "code-check-stop-expired",
"name": "Check Stop Expired",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
700,
300
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"typeValidation": "strict"
},
"conditions": [
{
"id": "is-expired",
"leftValue": "={{ $json.expired }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "equals"
}
}
],
"combinator": "and"
}
},
"id": "if-stop-expired",
"name": "Is Stop Expired?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
920,
300
]
},
{
"parameters": {
"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",
"name": "Prepare Stop Action",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1140,
350
]
},
{
"parameters": {
"source": "database",
"workflowId": {
"__rl": true,
"mode": "list",
"value": "fYSZS5PkH0VSEaT5"
},
"mode": "once",
"options": {
"waitForSubWorkflow": true
}
},
"id": "execute-actions-stop",
"name": "Execute Stop Action",
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.2,
"position": [
1360,
350
]
},
{
"parameters": {
"jsCode": "// Format stop result with keyboard\nconst result = $input.item.json;\nconst success = result.success;\nconst message = result.message;\nconst containerName = result.containerName;\nconst chatId = result.chatId;\nconst messageId = result.messageId;\n\n// Build keyboard based on result\nlet keyboard;\nif (success) {\n // Success: only back button\n keyboard = {\n inline_keyboard: [\n [{ text: '\\u25C0\\uFE0F Back to Containers', callback_data: 'list:0' }]\n ]\n };\n} else {\n // Error: retry and back buttons\n const timestamp = Math.floor(Date.now() / 1000);\n keyboard = {\n inline_keyboard: [\n [{ text: '\\u{1F504} Try Again', callback_data: `confirm:stop:${containerName}:${timestamp}` }],\n [{ text: '\\u25C0\\uFE0F Back to Containers', callback_data: 'list:0' }]\n ]\n };\n}\n\nreturn {\n json: {\n action: 'confirm_stop_result',\n success,\n chatId,\n messageId,\n text: message,\n reply_markup: keyboard\n }\n};"
},
"id": "code-format-stop-result",
"name": "Format Stop Result",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1580,
350
]
},
{
"parameters": {
"jsCode": "// Build expired response for stop confirmation\nconst data = $('When executed by another workflow').item.json;\nconst containerName = data.containerName;\nconst chatId = data.chatId;\nconst messageId = data.messageId;\n\nconst keyboard = {\n inline_keyboard: [\n [\n { text: '\\u25C0\\uFE0F Back to List', callback_data: 'list:0' }\n ]\n ]\n};\n\nreturn {\n json: {\n action: 'expired',\n chatId,\n messageId,\n text: `\\u23F0 Confirmation for <b>${containerName}</b> has expired.\\n\\nPlease try again.`,\n reply_markup: keyboard\n }\n};"
},
"id": "code-stop-expired-response",
"name": "Stop Expired Response",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1140,
250
]
},
{
"parameters": {
"jsCode": "// Check if expired BEFORE executing update confirm action\nconst data = $('When executed by another workflow').item.json;\nconst expired = data.expired === true;\n\nif (expired) {\n // Return expired action - main workflow will handle display\n return {\n json: {\n ...data,\n action: 'expired',\n expired: true\n }\n };\n}\n\n// Not expired, proceed with update confirmation\nreturn {\n json: {\n ...data,\n action: 'confirm_update',\n expired: false\n }\n};"
},
"id": "code-check-update-expired",
"name": "Check Update Expired",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
700,
400
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"typeValidation": "strict"
},
"conditions": [
{
"id": "is-expired",
"leftValue": "={{ $json.expired }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "equals"
}
}
],
"combinator": "and"
}
},
"id": "if-update-expired",
"name": "Is Update Expired?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
920,
400
]
},
{
"parameters": {
"jsCode": "// Build expired response for update confirmation\nconst data = $('When executed by another workflow').item.json;\nconst containerName = data.containerName;\nconst chatId = data.chatId;\nconst messageId = data.messageId;\n\nconst keyboard = {\n inline_keyboard: [\n [\n { text: '\\u25C0\\uFE0F Back to List', callback_data: 'list:0' }\n ]\n ]\n};\n\nreturn {\n json: {\n action: 'expired',\n chatId,\n messageId,\n text: `\\u23F0 Confirmation for <b>${containerName}</b> has expired.\\n\\nPlease try again.`,\n reply_markup: keyboard\n }\n};"
},
"id": "code-update-expired-response",
"name": "Update Expired Response",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1140,
350
]
},
{
"parameters": {
"jsCode": "// Return confirm_update action for main workflow to handle\n// Main workflow will call the update sub-workflow\nconst data = $('When executed by another workflow').item.json;\n\nreturn {\n json: {\n action: 'confirm_update',\n containerId: data.containerId || '',\n containerName: data.containerName,\n chatId: data.chatId,\n messageId: data.messageId,\n responseMode: data.responseMode || 'inline'\n }\n};"
},
"id": "code-return-update-action",
"name": "Return Update Action",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1140,
450
]
}
],
"connections": {
"When executed by another workflow": {
"main": [
[
{
"node": "Route Confirmation Action",
"type": "main",
"index": 0
}
]
]
},
"Route Confirmation Action": {
"main": [
[
{
"node": "Build Stop Confirmation",
"type": "main",
"index": 0
}
],
[
{
"node": "Build Update Confirmation",
"type": "main",
"index": 0
}
],
[
{
"node": "Check Stop Expired",
"type": "main",
"index": 0
}
],
[
{
"node": "Check Update Expired",
"type": "main",
"index": 0
}
],
[
{
"node": "Handle Cancel",
"type": "main",
"index": 0
}
],
[
{
"node": "Handle Expired",
"type": "main",
"index": 0
}
]
]
},
"Check Stop Expired": {
"main": [
[
{
"node": "Is Stop Expired?",
"type": "main",
"index": 0
}
]
]
},
"Is Stop Expired?": {
"main": [
[
{
"node": "Stop Expired Response",
"type": "main",
"index": 0
}
],
[
{
"node": "Prepare Stop Action",
"type": "main",
"index": 0
}
]
]
},
"Prepare Stop Action": {
"main": [
[
{
"node": "Execute Stop Action",
"type": "main",
"index": 0
}
]
]
},
"Execute Stop Action": {
"main": [
[
{
"node": "Format Stop Result",
"type": "main",
"index": 0
}
]
]
},
"Check Update Expired": {
"main": [
[
{
"node": "Is Update Expired?",
"type": "main",
"index": 0
}
]
]
},
"Is Update Expired?": {
"main": [
[
{
"node": "Update Expired Response",
"type": "main",
"index": 0
}
],
[
{
"node": "Return Update Action",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1",
"callerPolicy": "any"
}
}