wip(08): paused at verification checkpoint - workflow fixes in progress
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
---
|
||||
phase: 08-inline-keyboard-infrastructure
|
||||
task: 3
|
||||
total_tasks: 3
|
||||
status: in_progress
|
||||
last_updated: 2026-02-03T22:05:00Z
|
||||
---
|
||||
|
||||
<current_state>
|
||||
Phase 8 Plan 03 Task 3 is a human verification checkpoint. All code work complete (Tasks 1-2 committed). During live testing, discovered multiple issues with n8n workflow deployment that required fixes. Currently iterating on bug fixes - user just tested `/status` and reported error in "Build Container Submenu" node.
|
||||
|
||||
The workflow has been pushed to n8n multiple times with fixes. Last push fixed array handling (`$input.all()` instead of `$input.item.json`). User was about to test again when pausing.
|
||||
</current_state>
|
||||
|
||||
<completed_work>
|
||||
|
||||
- Plan 08-01: Container List Keyboard - Done (commits f8d616e, 0148282, 393d368, 1a3feec)
|
||||
- Plan 08-02: Action Execution and Confirmation - Done (commits d158419, ab7ce88, a6548b3)
|
||||
- Plan 08-03 Task 1: Completion Messages for Quick Actions - Done (commit 3e11dea)
|
||||
- Plan 08-03 Task 2: Progress Feedback for Update Operations - Done (commit 127f176)
|
||||
- Plan 08-03 Task 3: Human Verification - IN PROGRESS (testing)
|
||||
</completed_work>
|
||||
|
||||
<remaining_work>
|
||||
|
||||
- Task 3: Human verification of all inline keyboard flows
|
||||
- User needs to test: /status, tap container, actions, confirmations, etc.
|
||||
- Several bugs found and fixed during testing
|
||||
- Need one more test cycle to verify latest fixes work
|
||||
</remaining_work>
|
||||
|
||||
<decisions_made>
|
||||
|
||||
- Changed from `$credentials.telegramApi.accessToken` to `$env.TELEGRAM_BOT_TOKEN` because n8n HTTP Request nodes can't interpolate Telegram credentials in URLs
|
||||
- User enabled `N8N_BLOCK_ENV_ACCESS_IN_NODE=false` on n8n container to allow env var access
|
||||
- Fixed nodes after HTTP Request callbacks to reference `$('Parse Callback Data').item.json` instead of `$input.item.json` because HTTP requests return their response, not pass-through data
|
||||
- Fixed array handling: Docker API returns arrays, but n8n HTTP Request splits arrays into multiple items, so Code nodes need `$input.all().map(item => item.json)` not `$input.item.json`
|
||||
- Restored webhookId `86b8d6bb-7c15-4aae-a749-fcaf2dfcb9e0` after API updates wiped it
|
||||
</decisions_made>
|
||||
|
||||
<blockers>
|
||||
None currently - last fix pushed, awaiting test results
|
||||
</blockers>
|
||||
|
||||
<bugs_fixed_this_session>
|
||||
|
||||
1. **Token interpolation**: `$credentials.telegramApi.accessToken` doesn't work in HTTP Request URLs → use `$env.TELEGRAM_BOT_TOKEN`
|
||||
2. **Data passthrough**: Nodes after "Answer * Callback" HTTP requests need to reference original data source → use `$('Parse Callback Data').item.json`
|
||||
- Fixed: Prepare Container Fetch, Prepare List Fetch, Prepare Cancel Return
|
||||
3. **webhookId null**: API updates wiped webhookId → restored to `86b8d6bb-7c15-4aae-a749-fcaf2dfcb9e0`
|
||||
4. **Array handling**: HTTP Request splits array responses into multiple items → use `$input.all().map(item => item.json)`
|
||||
- Fixed: Build Container Submenu, Build Paginated List, Build Cancel Return Submenu
|
||||
</bugs_fixed_this_session>
|
||||
|
||||
<context>
|
||||
We're in the final verification step of Phase 8. All the code/workflow changes are done. The challenge has been deploying to n8n correctly - the n8n API workflow updates have quirks (credentials get stripped, webhookIds get wiped, etc.).
|
||||
|
||||
The workflow in n8n should now be functional. User needs to:
|
||||
1. Test `/status` command
|
||||
2. If it works, go through all the verification flows from the checkpoint
|
||||
3. If approved, complete the phase
|
||||
|
||||
If more errors occur, the pattern is:
|
||||
- Check execution logs via n8n API
|
||||
- Identify which node failed
|
||||
- Look at the Code node's JavaScript
|
||||
- Fix data access patterns (usually $input vs $('NodeName') issues)
|
||||
- Push updated workflow to n8n
|
||||
</context>
|
||||
|
||||
<files_modified>
|
||||
- n8n-workflow.json (uncommitted - many fixes to Code nodes)
|
||||
</files_modified>
|
||||
|
||||
<next_action>
|
||||
1. User tests `/status` command
|
||||
2. If error: check n8n execution logs, fix the specific node
|
||||
3. If success: user completes full verification checklist from Task 3
|
||||
4. On "approved": spawn continuation agent to complete 08-03, create SUMMARY, run verifier
|
||||
5. Commit all workflow fixes with message like "fix(08): resolve n8n deployment issues"
|
||||
</next_action>
|
||||
|
||||
<n8n_api_reference>
|
||||
```bash
|
||||
# API credentials
|
||||
N8N_HOST="https://api.bergerhouse.net"
|
||||
N8N_API_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIzMjdlNWM0ZS1iZTQwLTQ4OGYtOGUyZS1kMzQzNzE5MmQ5N2IiLCJpc3MiOiJuOG4iLCJhdWQiOiJwdWJsaWMtYXBpIiwianRpIjoiNzFjZjBiYTAtZWIzMy00ZDU3LThiOWMtZjhhZjRlNjMxNWFhIiwiaWF0IjoxNzcwMTI0Mzc2fQ.PBPdCDoC_QqjiGX8bUTNUqXltmyyNA7UBaP511iJU3I"
|
||||
WORKFLOW_ID="HmiXBlJefBRPMS0m4iNYc"
|
||||
|
||||
# Push workflow (must extract fields, can't send full file)
|
||||
cat n8n-workflow.json | jq '{name: .name, nodes: .nodes, connections: .connections, settings: .settings}' > /tmp/workflow-fix.json
|
||||
curl -s -X PUT -H "X-N8N-API-KEY: $N8N_API_KEY" -H "Content-Type: application/json" -d @/tmp/workflow-fix.json "$N8N_HOST/api/v1/workflows/$WORKFLOW_ID"
|
||||
|
||||
# Check latest execution
|
||||
curl -s -H "X-N8N-API-KEY: $N8N_API_KEY" "$N8N_HOST/api/v1/executions?workflowId=$WORKFLOW_ID&limit=1&includeData=true" | jq '.data[0].data.resultData.runData["NodeName"][0].error'
|
||||
```
|
||||
</n8n_api_reference>
|
||||
+39
-49
@@ -21,7 +21,8 @@
|
||||
"id": "I0xTTiASl7C1NZhJ",
|
||||
"name": "Telegram account"
|
||||
}
|
||||
}
|
||||
},
|
||||
"webhookId": "86b8d6bb-7c15-4aae-a749-fcaf2dfcb9e0"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -670,7 +671,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/sendMessage",
|
||||
"url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/sendMessage",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify($json) }}",
|
||||
@@ -1044,7 +1045,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/answerCallbackQuery",
|
||||
"url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/answerCallbackQuery",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify({ callback_query_id: $json.queryId, text: $json.answerText }) }}",
|
||||
@@ -1068,7 +1069,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/deleteMessage",
|
||||
"url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/deleteMessage",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify({ chat_id: $('Handle Cancel').item.json.chatId, message_id: $('Handle Cancel').item.json.messageId }) }}",
|
||||
@@ -1105,7 +1106,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/answerCallbackQuery",
|
||||
"url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/answerCallbackQuery",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify({ callback_query_id: $json.queryId, text: $json.answerText, show_alert: true }) }}",
|
||||
@@ -1129,7 +1130,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/deleteMessage",
|
||||
"url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/deleteMessage",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify({ chat_id: $('Handle Expired').item.json.chatId, message_id: $('Handle Expired').item.json.messageId }) }}",
|
||||
@@ -1193,7 +1194,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/answerCallbackQuery",
|
||||
"url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/answerCallbackQuery",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify({ callback_query_id: $json.queryId, text: $json.answerText }) }}",
|
||||
@@ -1217,7 +1218,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/deleteMessage",
|
||||
"url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/deleteMessage",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify({ chat_id: $('Parse Callback Result').item.json.chatId, message_id: $('Parse Callback Result').item.json.messageId }) }}",
|
||||
@@ -1332,7 +1333,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/answerCallbackQuery",
|
||||
"url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/answerCallbackQuery",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify({ callback_query_id: $json.queryId }) }}",
|
||||
@@ -1356,7 +1357,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/deleteMessage",
|
||||
"url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/deleteMessage",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify({ chat_id: $('Format Batch Result').item.json.chatId, message_id: $('Format Batch Result').item.json.messageId }) }}",
|
||||
@@ -2416,7 +2417,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/sendMessage",
|
||||
"url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/sendMessage",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify({ chat_id: $json.chatId, text: $json.text, parse_mode: 'HTML', reply_markup: $json.reply_markup }) }}",
|
||||
@@ -2485,7 +2486,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/sendMessage",
|
||||
"url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/sendMessage",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify({ chat_id: $json.chatId, text: $json.text, parse_mode: 'HTML', reply_markup: $json.reply_markup }) }}",
|
||||
@@ -2509,7 +2510,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/answerCallbackQuery",
|
||||
"url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/answerCallbackQuery",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify({ callback_query_id: $json.queryId }) }}",
|
||||
@@ -2532,7 +2533,7 @@
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Prepare container fetch for submenu\nconst data = $input.item.json;\nreturn {\n json: {\n queryId: data.queryId,\n chatId: data.chatId,\n messageId: data.messageId,\n containerName: data.containerName\n }\n};"
|
||||
"jsCode": "// Prepare container fetch for submenu\nconst data = $(\"Parse Callback Data\").item.json;\nreturn {\n json: {\n queryId: data.queryId,\n chatId: data.chatId,\n messageId: data.messageId,\n containerName: data.containerName\n }\n};"
|
||||
},
|
||||
"id": "code-prepare-container-fetch",
|
||||
"name": "Prepare Container Fetch",
|
||||
@@ -2560,7 +2561,7 @@
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Build Container Submenu for callback selection\nconst containers = $input.item.json;\nconst prevData = $('Prepare Container Fetch').item.json;\nconst chatId = prevData.chatId;\nconst messageId = prevData.messageId;\nconst searchName = prevData.containerName.toLowerCase();\n\n// Function to normalize container names\nfunction normalizeName(name) {\n return name\n .replace(/^\\//, '')\n .replace(/^(linuxserver[-_]|binhex[-_])/i, '')\n .toLowerCase();\n}\n\n// Find the matching container\nconst container = containers.find(c => normalizeName(c.Names[0]) === searchName);\n\nif (!container) {\n return [{\n json: {\n chatId,\n messageId,\n text: `Container \"${searchName}\" not found`,\n reply_markup: { inline_keyboard: [[{ text: '\\u25C0\\uFE0F Back to List', callback_data: 'list:0' }]] }\n }\n }];\n}\n\nconst containerName = normalizeName(container.Names[0]);\nconst state = container.State;\nconst status = container.Status;\nconst image = container.Image;\n\n// Build action keyboard based on container state\nconst keyboard = [];\n\nif (state === 'running') {\n keyboard.push([\n { text: '\\u23F9\\uFE0F Stop', callback_data: `action:stop:${containerName}` },\n { text: '\\u{1F504} Restart', callback_data: `action:restart:${containerName}` }\n ]);\n} else {\n keyboard.push([\n { text: '\\u25B6\\uFE0F Start', callback_data: `action:start:${containerName}` }\n ]);\n}\n\nkeyboard.push([\n { text: '\\u{1F4CB} Logs', callback_data: `action:logs:${containerName}` },\n { text: '\\u2B06\\uFE0F Update', callback_data: `action:update:${containerName}` }\n]);\n\nkeyboard.push([\n { text: '\\u25C0\\uFE0F Back to List', callback_data: 'list:0' }\n]);\n\n// Build status text\nconst stateIcon = state === 'running' ? '\\u{1F7E2}' : '\\u26AA';\nlet text = `${stateIcon} <b>${containerName}</b>\\n\\n`;\ntext += `<b>State:</b> ${state}\\n`;\ntext += `<b>Status:</b> ${status}\\n`;\ntext += `<b>Image:</b> ${image}`;\n\nreturn [{\n json: {\n chatId,\n messageId,\n text,\n reply_markup: { inline_keyboard: keyboard }\n }\n}];"
|
||||
"jsCode": "// Build Container Submenu for callback selection\nconst containers = $input.all().map(item => item.json);\nconst prevData = $(\"Prepare Container Fetch\").item.json;\nconst chatId = prevData.chatId;\nconst messageId = prevData.messageId;\nconst searchName = prevData.containerName.toLowerCase();\n\n// Function to normalize container names\nfunction normalizeName(name) {\n return name\n .replace(/^\\//, \"\")\n .replace(/^(linuxserver[-_]|binhex[-_])/i, \"\")\n .toLowerCase();\n}\n\n// Find the matching container\nconst container = containers.find(c => normalizeName(c.Names[0]) === searchName);\n\nif (!container) {\n return [{\n json: {\n chatId,\n messageId,\n text: `Container \\\"${searchName}\\\" not found`,\n reply_markup: { inline_keyboard: [[{ text: \"\\u25C0\\uFE0F Back to List\", callback_data: \"list:0\" }]] }\n }\n }];\n}\n\nconst containerName = normalizeName(container.Names[0]);\nconst state = container.State;\nconst status = container.Status;\nconst image = container.Image;\n\n// Build action keyboard based on container state\nconst keyboard = [];\n\nif (state === \"running\") {\n keyboard.push([\n { text: \"\\u23F9\\uFE0F Stop\", callback_data: `action:stop:${containerName}` },\n { text: \"\\u{1F504} Restart\", callback_data: `action:restart:${containerName}` }\n ]);\n} else {\n keyboard.push([\n { text: \"\\u25B6\\uFE0F Start\", callback_data: `action:start:${containerName}` }\n ]);\n}\n\nkeyboard.push([\n { text: \"\\u{1F4CB} Logs\", callback_data: `action:logs:${containerName}` },\n { text: \"\\u2B06\\uFE0F Update\", callback_data: `action:update:${containerName}` }\n]);\n\nkeyboard.push([\n { text: \"\\u25C0\\uFE0F Back to List\", callback_data: \"list:0\" }\n]);\n\n// Build status text\nconst stateIcon = state === \"running\" ? \"\\u{1F7E2}\" : \"\\u26AA\";\nlet text = `${stateIcon} <b>${containerName}</b>\\n\\n`;\ntext += `<b>State:</b> ${state}\\n`;\ntext += `<b>Status:</b> ${status}\\n`;\ntext += `<b>Image:</b> ${image}`;\n\nreturn [{\n json: {\n chatId,\n messageId,\n text,\n reply_markup: { inline_keyboard: keyboard }\n }\n}];"
|
||||
},
|
||||
"id": "code-build-container-submenu",
|
||||
"name": "Build Container Submenu",
|
||||
@@ -2574,7 +2575,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/editMessageText",
|
||||
"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.messageId, text: $json.text, parse_mode: 'HTML', reply_markup: $json.reply_markup }) }}",
|
||||
@@ -2598,7 +2599,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/answerCallbackQuery",
|
||||
"url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/answerCallbackQuery",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify({ callback_query_id: $json.queryId }) }}",
|
||||
@@ -2622,7 +2623,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/answerCallbackQuery",
|
||||
"url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/answerCallbackQuery",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify({ callback_query_id: $json.queryId }) }}",
|
||||
@@ -2645,7 +2646,7 @@
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Prepare list pagination request\nconst data = $input.item.json;\nreturn {\n json: {\n queryId: data.queryId,\n chatId: data.chatId,\n messageId: data.messageId,\n page: data.page || 0\n }\n};"
|
||||
"jsCode": "// Prepare list pagination request\nconst data = $(\"Parse Callback Data\").item.json;\nreturn {\n json: {\n queryId: data.queryId,\n chatId: data.chatId,\n messageId: data.messageId,\n page: data.page || 0\n }\n};"
|
||||
},
|
||||
"id": "code-prepare-list-fetch",
|
||||
"name": "Prepare List Fetch",
|
||||
@@ -2673,7 +2674,7 @@
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Build Paginated Container List Keyboard for callback pagination\nconst containers = $input.item.json;\nconst prevData = $('Prepare List Fetch').item.json;\nconst chatId = prevData.chatId;\nconst messageId = prevData.messageId;\nconst page = prevData.page || 0;\n\n// Function to normalize container names\nfunction normalizeName(name) {\n return name\n .replace(/^\\//, '')\n .replace(/^(linuxserver[-_]|binhex[-_])/i, '')\n .toLowerCase();\n}\n\nconst containersPerPage = 6;\n\n// Group by state (running first)\nconst running = containers.filter(c => c.State === 'running');\nconst stopped = containers.filter(c => c.State !== 'running');\nconst sortedContainers = [...running, ...stopped];\n\nconst totalPages = Math.ceil(sortedContainers.length / containersPerPage);\nconst start = page * containersPerPage;\nconst pageContainers = sortedContainers.slice(start, start + containersPerPage);\n\n// Build keyboard rows\nconst keyboard = [];\n\npageContainers.forEach(container => {\n const name = normalizeName(container.Names[0]);\n const stateIcon = container.State === 'running' ? '\\u{1F7E2}' : '\\u26AA';\n const stateText = container.State === 'running' ? 'Running' : 'Stopped';\n keyboard.push([{\n text: `${stateIcon} ${name} - ${stateText}`,\n callback_data: `select:${name}`\n }]);\n});\n\n// Add navigation row if needed\nif (totalPages > 1) {\n const navRow = [];\n if (page > 0) {\n navRow.push({ text: '\\u25C0\\uFE0F Previous', callback_data: `list:${page - 1}` });\n }\n navRow.push({ text: `${page + 1}/${totalPages}`, callback_data: 'noop' });\n if (page < totalPages - 1) {\n navRow.push({ text: 'Next \\u25B6\\uFE0F', callback_data: `list:${page + 1}` });\n }\n keyboard.push(navRow);\n}\n\n// Build header text\nconst runningCount = running.length;\nconst totalCount = sortedContainers.length;\nlet headerText = `<b>\\u{1F5C2} Containers</b> (${runningCount}/${totalCount} running)`;\nif (totalPages > 1) {\n headerText += `\\n\\nPage ${page + 1} of ${totalPages}`;\n}\nheaderText += '\\n\\nTap a container to manage it:';\n\nreturn [{\n json: {\n chatId,\n messageId,\n text: headerText,\n reply_markup: { inline_keyboard: keyboard }\n }\n}];"
|
||||
"jsCode": "// Build Paginated Container List Keyboard for callback pagination\nconst containers = $input.all().map(item => item.json);\nconst prevData = $(\"Prepare List Fetch\").item.json;\nconst chatId = prevData.chatId;\nconst messageId = prevData.messageId;\nconst page = prevData.page || 0;\n\n// Function to normalize container names\nfunction normalizeName(name) {\n return name\n .replace(/^\\//, \"\")\n .replace(/^(linuxserver[-_]|binhex[-_])/i, \"\")\n .toLowerCase();\n}\n\nconst containersPerPage = 6;\n\n// Group by state (running first)\nconst running = containers.filter(c => c.State === \"running\");\nconst stopped = containers.filter(c => c.State !== \"running\");\nconst sortedContainers = [...running, ...stopped];\n\nconst totalPages = Math.ceil(sortedContainers.length / containersPerPage);\nconst start = page * containersPerPage;\nconst pageContainers = sortedContainers.slice(start, start + containersPerPage);\n\n// Build keyboard rows\nconst keyboard = [];\n\npageContainers.forEach(container => {\n const name = normalizeName(container.Names[0]);\n const stateIcon = container.State === \"running\" ? \"\\u{1F7E2}\" : \"\\u26AA\";\n const stateText = container.State === \"running\" ? \"Running\" : \"Stopped\";\n keyboard.push([{\n text: `${stateIcon} ${name} - ${stateText}`,\n callback_data: `select:${name}`\n }]);\n});\n\n// Add navigation row if needed\nif (totalPages > 1) {\n const navRow = [];\n if (page > 0) {\n navRow.push({ text: \"\\u25C0\\uFE0F Previous\", callback_data: `list:${page - 1}` });\n }\n navRow.push({ text: `${page + 1}/${totalPages}`, callback_data: \"noop\" });\n if (page < totalPages - 1) {\n navRow.push({ text: \"Next \\u25B6\\uFE0F\", callback_data: `list:${page + 1}` });\n }\n keyboard.push(navRow);\n}\n\n// Build header text\nconst runningCount = running.length;\nconst totalCount = sortedContainers.length;\nlet headerText = `<b>\\u{1F5C2} Containers</b> (${runningCount}/${totalCount} running)`;\nif (totalPages > 1) {\n headerText += `\\n\\nPage ${page + 1} of ${totalPages}`;\n}\nheaderText += \"\\n\\nTap a container to manage it:\";\n\nreturn [{\n json: {\n chatId,\n messageId,\n text: headerText,\n reply_markup: { inline_keyboard: keyboard }\n }\n}];"
|
||||
},
|
||||
"id": "code-build-paginated-list",
|
||||
"name": "Build Paginated List",
|
||||
@@ -2687,7 +2688,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/editMessageText",
|
||||
"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.messageId, text: $json.text, parse_mode: 'HTML', reply_markup: $json.reply_markup }) }}",
|
||||
@@ -2711,7 +2712,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/answerCallbackQuery",
|
||||
"url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/answerCallbackQuery",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify({ callback_query_id: $json.queryId }) }}",
|
||||
@@ -2937,7 +2938,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/editMessageText",
|
||||
"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.messageId, text: $json.text, parse_mode: 'HTML', reply_markup: $json.reply_markup }) }}",
|
||||
@@ -3029,7 +3030,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/editMessageText",
|
||||
"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.messageId, text: $json.text, parse_mode: 'HTML', reply_markup: $json.reply_markup }) }}",
|
||||
@@ -3066,7 +3067,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/editMessageText",
|
||||
"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.messageId, text: $json.text, parse_mode: 'HTML', reply_markup: $json.reply_markup }) }}",
|
||||
@@ -3103,7 +3104,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/editMessageText",
|
||||
"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.messageId, text: $json.text, parse_mode: 'HTML', reply_markup: $json.reply_markup }) }}",
|
||||
@@ -3127,7 +3128,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/answerCallbackQuery",
|
||||
"url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/answerCallbackQuery",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify({ callback_query_id: $json.queryId }) }}",
|
||||
@@ -3196,7 +3197,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/editMessageText",
|
||||
"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.messageId, text: $json.text, parse_mode: 'HTML', reply_markup: $json.reply_markup }) }}",
|
||||
@@ -3353,7 +3354,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/editMessageText",
|
||||
"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.messageId, text: $json.text, parse_mode: 'HTML', reply_markup: $json.reply_markup }) }}",
|
||||
@@ -3390,7 +3391,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/editMessageText",
|
||||
"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.messageId, text: $json.progressText, parse_mode: 'HTML', reply_markup: $json.reply_markup }) }}",
|
||||
@@ -3569,7 +3570,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/editMessageText",
|
||||
"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.messageId, text: $json.text, parse_mode: 'HTML', reply_markup: $json.reply_markup }) }}",
|
||||
@@ -3714,7 +3715,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/editMessageText",
|
||||
"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.messageId, text: $json.text, parse_mode: 'HTML', reply_markup: $json.reply_markup }) }}",
|
||||
@@ -3738,7 +3739,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/answerCallbackQuery",
|
||||
"url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/answerCallbackQuery",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify({ callback_query_id: $json.queryId }) }}",
|
||||
@@ -3761,7 +3762,7 @@
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Cancel confirmation - return to container submenu\nconst data = $input.item.json;\nreturn {\n json: {\n containerName: data.containerName,\n chatId: data.chatId,\n messageId: data.messageId\n }\n};"
|
||||
"jsCode": "// Cancel confirmation - return to container submenu\nconst data = $(\"Parse Callback Data\").item.json;\nreturn {\n json: {\n containerName: data.containerName,\n chatId: data.chatId,\n messageId: data.messageId\n }\n};"
|
||||
},
|
||||
"id": "code-prepare-cancel-return",
|
||||
"name": "Prepare Cancel Return",
|
||||
@@ -3789,7 +3790,7 @@
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Build submenu for return from cancel\nconst containers = $input.item.json;\nconst prevData = $('Prepare Cancel Return').item.json;\nconst searchName = prevData.containerName.toLowerCase();\nconst chatId = prevData.chatId;\nconst messageId = prevData.messageId;\n\n// Function to normalize container names\nfunction normalizeName(name) {\n return name\n .replace(/^\\//, '')\n .replace(/^(linuxserver[-_]|binhex[-_])/i, '')\n .toLowerCase();\n}\n\n// Find the matching container\nconst container = containers.find(c => normalizeName(c.Names[0]) === searchName);\n\nif (!container) {\n return [{\n json: {\n chatId,\n messageId,\n text: `Container \"${searchName}\" not found`,\n reply_markup: { inline_keyboard: [[{ text: '\\u25C0\\uFE0F Back to List', callback_data: 'list:0' }]] }\n }\n }];\n}\n\nconst containerName = normalizeName(container.Names[0]);\nconst state = container.State;\nconst status = container.Status;\nconst image = container.Image;\n\n// Build action keyboard based on container state\nconst keyboard = [];\n\nif (state === 'running') {\n keyboard.push([\n { text: '\\u23F9\\uFE0F Stop', callback_data: `action:stop:${containerName}` },\n { text: '\\u{1F504} Restart', callback_data: `action:restart:${containerName}` }\n ]);\n} else {\n keyboard.push([\n { text: '\\u25B6\\uFE0F Start', callback_data: `action:start:${containerName}` }\n ]);\n}\n\nkeyboard.push([\n { text: '\\u{1F4CB} Logs', callback_data: `action:logs:${containerName}` },\n { text: '\\u2B06\\uFE0F Update', callback_data: `action:update:${containerName}` }\n]);\n\nkeyboard.push([\n { text: '\\u25C0\\uFE0F Back to List', callback_data: 'list:0' }\n]);\n\n// Build status text\nconst stateIcon = state === 'running' ? '\\u{1F7E2}' : '\\u26AA';\nlet text = `${stateIcon} <b>${containerName}</b>\\n\\n`;\ntext += `<b>State:</b> ${state}\\n`;\ntext += `<b>Status:</b> ${status}\\n`;\ntext += `<b>Image:</b> ${image}`;\n\nreturn [{\n json: {\n chatId,\n messageId,\n text,\n reply_markup: { inline_keyboard: keyboard }\n }\n}];"
|
||||
"jsCode": "// Build submenu for return from cancel\nconst containers = $input.all().map(item => item.json);\nconst prevData = $(\"Prepare Cancel Return\").item.json;\nconst searchName = prevData.containerName.toLowerCase();\nconst chatId = prevData.chatId;\nconst messageId = prevData.messageId;\n\n// Function to normalize container names\nfunction normalizeName(name) {\n return name\n .replace(/^\\//, \"\")\n .replace(/^(linuxserver[-_]|binhex[-_])/i, \"\")\n .toLowerCase();\n}\n\n// Find the matching container\nconst container = containers.find(c => normalizeName(c.Names[0]) === searchName);\n\nif (!container) {\n return [{\n json: {\n chatId,\n messageId,\n text: `Container \\\"${searchName}\\\" not found`,\n reply_markup: { inline_keyboard: [[{ text: \"\\u25C0\\uFE0F Back to List\", callback_data: \"list:0\" }]] }\n }\n }];\n}\n\nconst containerName = normalizeName(container.Names[0]);\nconst state = container.State;\nconst status = container.Status;\nconst image = container.Image;\n\n// Build action keyboard based on container state\nconst keyboard = [];\n\nif (state === \"running\") {\n keyboard.push([\n { text: \"\\u23F9\\uFE0F Stop\", callback_data: `action:stop:${containerName}` },\n { text: \"\\u{1F504} Restart\", callback_data: `action:restart:${containerName}` }\n ]);\n} else {\n keyboard.push([\n { text: \"\\u25B6\\uFE0F Start\", callback_data: `action:start:${containerName}` }\n ]);\n}\n\nkeyboard.push([\n { text: \"\\u{1F4CB} Logs\", callback_data: `action:logs:${containerName}` },\n { text: \"\\u2B06\\uFE0F Update\", callback_data: `action:update:${containerName}` }\n]);\n\nkeyboard.push([\n { text: \"\\u25C0\\uFE0F Back to List\", callback_data: \"list:0\" }\n]);\n\n// Build status text\nconst stateIcon = state === \"running\" ? \"\\u{1F7E2}\" : \"\\u26AA\";\nlet text = `${stateIcon} <b>${containerName}</b>\\n\\n`;\ntext += `<b>State:</b> ${state}\\n`;\ntext += `<b>Status:</b> ${status}\\n`;\ntext += `<b>Image:</b> ${image}`;\n\nreturn [{\n json: {\n chatId,\n messageId,\n text,\n reply_markup: { inline_keyboard: keyboard }\n }\n}];"
|
||||
},
|
||||
"id": "code-build-cancel-return-submenu",
|
||||
"name": "Build Cancel Return Submenu",
|
||||
@@ -3803,7 +3804,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/editMessageText",
|
||||
"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.messageId, text: $json.text, parse_mode: 'HTML', reply_markup: $json.reply_markup }) }}",
|
||||
@@ -4627,7 +4628,7 @@
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Compare Digests",
|
||||
"node": "Compare Update Images",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
@@ -5391,17 +5392,6 @@
|
||||
]
|
||||
]
|
||||
},
|
||||
"Inspect New Image": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Compare Update Images",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Compare Update Images": {
|
||||
"main": [
|
||||
[
|
||||
@@ -5594,4 +5584,4 @@
|
||||
"tags": [],
|
||||
"triggerCount": 1,
|
||||
"active": false
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user