diff --git a/n8n-confirmation.json b/n8n-confirmation.json
new file mode 100644
index 0000000..811851b
--- /dev/null
+++ b/n8n-confirmation.json
@@ -0,0 +1,603 @@
+{
+ "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"
+ }
+}