{ "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" } ] } }, "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 Stop ${containerName}?\\n\\nThis will stop the container immediately.\\n\\nConfirmation expires in 30 seconds.`;\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 Update ${containerName}?\\n\\nThis will pull the latest image and recreate the container.\\n\\nConfirmation expires in 30 seconds.`;\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 ${containerName} 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;\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};" }, "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 ${containerName} 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 ${containerName} 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" } }